Skip to content

Commit

Permalink
Improve bookmark folder logic (#1149)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxmilton authored Jul 11, 2021
1 parent 18dd802 commit 29bf438
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 78 deletions.
134 changes: 56 additions & 78 deletions src/components/BookmarkNode.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,32 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */

// FIXME: Calculate if the folder will be outside the viewport and render in a better place

// FIXME: Hide subfolder imidiately if another is openned to prevent overlap

// FIXME: Continue to show subfolder when moving mouse back onto parent

// FIXME: max-height (for scroll) needs to be dynamic depending on where the folder opens from

import { h } from 'stage1';
import { append, create } from '../utils';
import { Link, LinkComponent, LinkProps } from './Link';

type SubFolderComponent = HTMLDivElement;

interface SubFolderProps {
children: chrome.bookmarks.BookmarkTreeNode[];
level: number;
parent: Element;
}

type SubFolderScope = {
clearTimer(this: void): void;
resetTimer(this: void): void;
};
type FolderPopupComponent = HTMLDivElement;

const CLOSE_DELAY_MS = 400;
// @ts-expect-error - FIXME: Use this var or remove it
let openFolders = 0;
const CLOSE_DELAY_MS = 600;
let emptyPopupView;

const subFolderView = create('div');
subFolderView.className = 'sf';
const folderPopupView = create('div');
folderPopupView.className = 'sf';

function SubFolder(
{ children, level, parent }: SubFolderProps,
scope: SubFolderScope,
): SubFolderComponent {
const root = subFolderView.cloneNode(true) as SubFolderComponent;
function FolderPopup(
parent: Element,
children: chrome.bookmarks.BookmarkTreeNode[],
topLevel: boolean,
): FolderPopupComponent {
const root = folderPopupView.cloneNode(true) as FolderPopupComponent;

const parentPos = parent.getBoundingClientRect();

if (level > 0) {
// nested subfolders show beside their parent
if (!topLevel) {
// show nested folder popup beside its parent
root.style.top = parentPos.top + 'px';
root.style.left = parentPos.right + 'px';
} else {
// top level subfolders show bellow their parent
// show top level folder popup bellow its parent
// root.style.top = parentPos.bottom + 'px';
// root.style.left = parentPos.left + 'px';

Expand All @@ -61,20 +43,20 @@ function SubFolder(
}
}

children.forEach((item) => {
// @ts-expect-error - FIXME:!
// eslint-disable-next-line no-param-reassign
item.level = level + 1;
append(BookmarkNode(item), root);
});

root.onmouseenter = scope.clearTimer;
root.onmouseleave = scope.resetTimer;
if (!children.length) {
append((emptyPopupView ??= h`<div class=empty>(empty)</div>`), root);
} else {
children.forEach((item) => {
append(BookmarkNode(item), root);
});
}

return root;
}

type FolderComponent = HTMLDivElement;
type FolderComponent = HTMLDivElement & {
closePopup(this: void): void;
};

export interface FolderProps
extends Omit<chrome.bookmarks.BookmarkTreeNode, 'id'> {
Expand All @@ -88,51 +70,47 @@ folderView.className = 'f';

export function Folder(item: FolderProps): FolderComponent {
const root = folderView.cloneNode(true) as FolderComponent;
let popup: FolderPopupComponent | null;
let timer: NodeJS.Timeout;

const clearTimer = () => clearTimeout(timer);

const resetTimer = () => {
clearTimer();
timer = setTimeout(root.closePopup, CLOSE_DELAY_MS);
};

if (item.end) root.className += ' end';
root.textContent = item.title;

let subfolder: Element | null;
let timer: NodeJS.Timeout;

const scope = {
clearTimer(this: void) {
if (timer) clearTimeout(timer);
},
resetTimer(this: void) {
scope.clearTimer();

timer = setTimeout(() => {
if (subfolder) {
subfolder.remove();
subfolder = null;

if (!item.level || item.level === 0) {
openFolders -= 1;
}
}
}, CLOSE_DELAY_MS);
},
root.closePopup = () => {
if (popup) {
popup.remove();
popup = null;
}
};

root.onmouseenter = () => {
scope.clearTimer();

// TODO: Remove `item.children` here and instead of doing nothing show an "empty" folder
if (!subfolder && item.children) {
subfolder = SubFolder(
{
children: item.children,
level: item.level || 0,
parent: root,
},
scope,
);
append(subfolder, root);
clearTimer();

if (!popup) {
const parent = root.parentNode as Element;

// immediately close any folder popups on the parent level
parent
.querySelectorAll<FolderComponent>('.f')
.forEach((folder) => folder.closePopup());

popup = FolderPopup(root, item.children!, parent.id === 'b');

popup.onmouseenter = clearTimer;
popup.onmouseleave = resetTimer;

append(popup, root);
}
};

root.onmouseleave = scope.resetTimer;
root.onmouseleave = resetTimer;

return root;
}
Expand Down
5 changes: 5 additions & 0 deletions src/css/newtab.xcss
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ button {
}
}

.empty {
padding: 0 13px;
color: var(--c3);
}

// FIXME: Show this in the folder component
.caret {
float: right;
Expand Down

0 comments on commit 29bf438

Please sign in to comment.