TabHub Devlog (v1.0.2 + v1.0.3)

TabHub Devlog (v1.0.2 + v1.0.3)

This blog post mainly talk about the technical side behinds TabHub directory system. There are a lot of new features implemented in TabHub but I can't cover everything in this single blog post. If you guys want to learn more about those features, you can follow: https://tabhub.medium.com/ or read the CHANGELOG below.

✅ v1.0.3 - TabHub Docs
Hotfix bugs in v1.0.3. Introduce new UI and Time Tracker widget

New features introduction

Brand new UI/UX

There's nothing to talk much about this new UI/UX. It's obvious. I put a lot of effort to improve the browsing experience of TabHub users.

It's beautiful! Right?

Quick Action Mode

So after receiving a few feedback from some folks on the Internet and my co-founder, I decided to add this new feature to replace OneTab main functionality completely. As a past user of OneTab extension, what I did notice is how annoying when you mistakenly click on the extension and it automatically collapse all your active tabs. I hate that! A lot! So I add a new mechanism called "✅ Remember action". One click on TabHub icon only take action immediately if the check box is checked.

I also have a source code for this little piece component if you want to use it somewhere or in your own apps.

const TabCardsStack = ({ tabs }: Props) => {
  return (
    <div>
      {tabs.map((tab, index) => (
        <div
          style={{
            position: 'absolute',
            marginLeft: (index % 2 === 0 ? -1 : 1) * (tabs.length - index) * 9,
          }}>
          <AnimatedComponent.OpacityFadeInDiv delay={(tabs.length - index) * 25}>
            <WindowTabIcon
              avatarSize={60 - (tabs.length - index)}
              windowTab={tab}
              style={{
                rotate: `${(tabs.length - index) * index}deg`,
                borderRadius: 15,
                width: 70 - (tabs.length - index),
                height: 70 - (tabs.length - index),
              }}
            />
          </AnimatedComponent.OpacityFadeInDiv>
        </div>
      ))}
    </div>
  );
};

Nested-directory data structure

As highlighted in our previous blog post, TabHub has been envisioned as an evolution of Chrome's bookmark manager—an upgrade for your "friendly neighborhood" tool. This realization prompted an important question that has been swirling in my mind: "Should we venture into building our own link management system?" It is only natural to contemplate such a question when running a startup from the ground up, as it demands careful consideration of where to invest our resources and prioritize our efforts.

After extensive deliberation, not only focusing on technical decisions but also assessing the potential benefits and scalability of forthcoming features, I arrived at a resolute decision. TabHub will indeed include a custom-built link system within our platform. The advantages stemming from this core feature, coupled with the long-term scalability and growth prospects it offers, solidified my conviction that investing in its development is the right path forward.

TabHub Feature: Multi-directory for link management

By taking this bold step, we are poised to offer users an elevated experience that goes beyond traditional bookmark management. Our custom link system will provide enhanced organization, efficient searching, and seamless synchronization, empowering you to effortlessly navigate and retrieve your saved resources. This strategic move exemplifies our dedication to crafting a truly comprehensive and indispensable tab and link management solution.

Designing a nested-folder data structure

From a programming aspect, our core system is designed to have an atomic-level data model called RepositoryTab. This is the smallest element of data to be stored in the database. Higher than the first citizen one level is the Directory which can be called a parent of RepositoryTab. All these atoms are stored in the Repository which are stored in Workspace.

This data structure is used in most of the part for tab storing in TabHub, but after a while, I realized this is quite similar to the way Chrome structure their bookmark system.

BookmarkTreeNode data structure in Chrome bookmark system

Here is a small piece of code for anyone who is interested with the way tabs are stored in TabHub. To render the tab and directory out, it requires a bit understanding of recursion. I believe every nested folder system like code editor, file system does follow a similar structure like this on the front end side.

const DraggableTreeNodeList = ({
  treeNodes,
  disableHeader,
  nodeOnContextMenu,
  draggableDisabled,
  itemSelectedCondition,
  directoryOnClickedEvent,
  itemOnClickedEvent,
  onItemDragEvent,
  isForceMobile,
  plugins = [],
}: Props) => {
  return (
    <LoadableContainer isLoading={treeNodes.length <= 0} loadComponent={<div>No tabs found</div>}>
      {!disableHeader && <TreeNodeListHeader isForceMobile={isForceMobile} />}
      <DraggableTreeNodeContainer
        disabled={draggableDisabled}
        onDragEnd={onItemDragEvent}
        renderTreeComponent={(treeItem: TreeNode) => {
          return (
            <div>
              {treeItem.type === 'tab' ? (
                <WindowTabItem
                  plugins={plugins}
                  id={buildTreeNodeId(treeItem, 'tab')}
                  actionable
                  key={treeItem.value.id}
                  isForceMobile={isForceMobile}
                  isSelected={itemSelectedCondition(treeItem.value as RepositoryTab)}
                  windowTab={treeItem.value as RepositoryTab}
                  onRightClick={nodeOnContextMenu}
                  onClick={itemOnClickedEvent}
                />
              ) : (
                <DirectoryNodeItem
                  plugins={plugins}
                  id={buildTreeNodeId(treeItem, 'directory')}
                  onClick={directoryOnClickedEvent}
                  node={treeItem.value as TabTreeDirectory}
                  isForceMobile={isForceMobile}
                  onItemDragEvent={onItemDragEvent}
                  onRightClick={nodeOnContextMenu}
                  draggableDisabled
                  isSelected={itemSelectedCondition(treeItem.value as RepositoryTab)}
                  itemSelectedCondition={itemSelectedCondition}
                  itemOnClickedEvent={itemOnClickedEvent}
                />
              )}
            </div>
          );
        }}
        treeNodes={treeNodes}
      />
    </LoadableContainer>
  );
};

Integrating with Chrome bookmark system

Due to the similarities between two architecture, I decided to add another feature to the app which helps user to synchronize their data from existing browser bookmarks. From the business perspective, this also reduces the context switch cost to the lowest.

Subscribe to Tin Chung's Blog

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe