feat: add tag context menu (#890)

This commit is contained in:
Aman Harwara
2022-02-22 13:05:01 +05:30
committed by GitHub
parent a5da191034
commit 22718d8a9f
10 changed files with 411 additions and 62 deletions

View File

@@ -1,5 +1,9 @@
import { confirmDialog } from '@/services/alertService';
import { STRING_DELETE_TAG } from '@/strings';
import {
MAX_MENU_SIZE_MULTIPLIER,
MENU_MARGIN_FROM_APP_BORDER,
} from '@/constants';
import {
ComponentAction,
ContentType,
@@ -72,6 +76,15 @@ export class TagsState {
selected_: AnyTag | undefined;
previouslySelected_: AnyTag | undefined;
editing_: SNTag | undefined;
addingSubtagTo: SNTag | undefined;
contextMenuOpen = false;
contextMenuPosition: { top?: number; left: number; bottom?: number } = {
top: 0,
left: 0,
};
contextMenuClickLocation: { x: number; y: number } = { x: 0, y: 0 };
contextMenuMaxHeight: number | 'auto' = 'auto';
private readonly tagsCountsState: TagsCountsState;
@@ -85,6 +98,7 @@ export class TagsState {
this.selected_ = undefined;
this.previouslySelected_ = undefined;
this.editing_ = undefined;
this.addingSubtagTo = undefined;
this.smartTags = this.application.getSmartTags();
this.selected_ = this.smartTags[0];
@@ -105,6 +119,9 @@ export class TagsState {
selectedUuid: computed,
editingTag: computed,
addingSubtagTo: observable,
setAddingSubtagTo: action,
assignParent: action,
rootTags: computed,
@@ -114,6 +131,15 @@ export class TagsState {
undoCreateNewTag: action,
save: action,
remove: action,
contextMenuOpen: observable,
contextMenuPosition: observable,
contextMenuMaxHeight: observable,
contextMenuClickLocation: observable,
setContextMenuOpen: action,
setContextMenuClickLocation: action,
setContextMenuPosition: action,
setContextMenuMaxHeight: action,
});
appEventListeners.push(
@@ -159,6 +185,114 @@ export class TagsState {
);
}
async createSubtagAndAssignParent(parent: SNTag, title: string) {
const hasEmptyTitle = title.length === 0;
if (hasEmptyTitle) {
this.setAddingSubtagTo(undefined);
return;
}
const createdTag = await this.application.createTagOrSmartTag(title);
const futureSiblings = this.application.getTagChildren(parent);
if (!isValidFutureSiblings(this.application, futureSiblings, createdTag)) {
this.setAddingSubtagTo(undefined);
this.remove(createdTag, false);
return;
}
this.assignParent(createdTag.uuid, parent.uuid);
this.application.sync();
runInAction(() => {
this.selected = createdTag as SNTag;
});
this.setAddingSubtagTo(undefined);
}
setAddingSubtagTo(tag: SNTag | undefined): void {
this.addingSubtagTo = tag;
}
setContextMenuOpen(open: boolean): void {
this.contextMenuOpen = open;
}
setContextMenuClickLocation(location: { x: number; y: number }): void {
this.contextMenuClickLocation = location;
}
setContextMenuPosition(position: {
top?: number;
left: number;
bottom?: number;
}): void {
this.contextMenuPosition = position;
}
setContextMenuMaxHeight(maxHeight: number | 'auto'): void {
this.contextMenuMaxHeight = maxHeight;
}
reloadContextMenuLayout(): void {
const { clientHeight } = document.documentElement;
const defaultFontSize = window.getComputedStyle(
document.documentElement
).fontSize;
const maxContextMenuHeight =
parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER;
const footerElementRect = document
.getElementById('footer-bar')
?.getBoundingClientRect();
const footerHeightInPx = footerElementRect?.height;
// Open up-bottom is default behavior
let openUpBottom = true;
if (footerHeightInPx) {
const bottomSpace =
clientHeight - footerHeightInPx - this.contextMenuClickLocation.y;
const upSpace = this.contextMenuClickLocation.y;
// If not enough space to open up-bottom
if (maxContextMenuHeight > bottomSpace) {
// If there's enough space, open bottom-up
if (upSpace > maxContextMenuHeight) {
openUpBottom = false;
this.setContextMenuMaxHeight('auto');
// Else, reduce max height (menu will be scrollable) and open in whichever direction there's more space
} else {
if (upSpace > bottomSpace) {
this.setContextMenuMaxHeight(upSpace - MENU_MARGIN_FROM_APP_BORDER);
openUpBottom = false;
} else {
this.setContextMenuMaxHeight(
bottomSpace - MENU_MARGIN_FROM_APP_BORDER
);
}
}
} else {
this.setContextMenuMaxHeight('auto');
}
}
if (openUpBottom) {
this.setContextMenuPosition({
top: this.contextMenuClickLocation.y,
left: this.contextMenuClickLocation.x,
});
} else {
this.setContextMenuPosition({
bottom: clientHeight - this.contextMenuClickLocation.y,
left: this.contextMenuClickLocation.x,
});
}
}
public get allLocalRootTags(): SNTag[] {
if (this.editing_ && this.application.isTemplateItem(this.editing_)) {
return [this.editing_, ...this.rootTags];
@@ -270,9 +404,9 @@ export class TagsState {
this.selected_ = tag;
}
public setExpanded(tag: SNTag, exapnded: boolean) {
public setExpanded(tag: SNTag, expanded: boolean) {
this.application.changeAndSaveItem<TagMutator>(tag.uuid, (mutator) => {
mutator.expanded = exapnded;
mutator.expanded = expanded;
});
}
@@ -312,13 +446,15 @@ export class TagsState {
this.selected = previousTag;
}
public async remove(tag: SNTag) {
if (
await confirmDialog({
public async remove(tag: SNTag, userTriggered: boolean) {
let shouldDelete = !userTriggered;
if (userTriggered) {
shouldDelete = await confirmDialog({
text: STRING_DELETE_TAG,
confirmButtonStyle: 'danger',
})
) {
});
}
if (shouldDelete) {
this.application.deleteItem(tag);
this.selected = this.smartTags[0];
}