mirror of
https://github.com/zadam/trilium.git
synced 2025-11-11 15:55:52 +01:00
chore(monorepo/client): move client source files
This commit is contained in:
64
apps/client/src/widgets/mobile_widgets/mobile_detail_menu.ts
Normal file
64
apps/client/src/widgets/mobile_widgets/mobile_detail_menu.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import contextMenu from "../../menus/context_menu.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
const TPL = /*html*/`<button type="button" class="action-button bx"></button>`;
|
||||
|
||||
class MobileDetailMenuWidget extends BasicWidget {
|
||||
private isHorizontalLayout: boolean;
|
||||
|
||||
constructor(isHorizontalLayout: boolean) {
|
||||
super();
|
||||
this.isHorizontalLayout = isHorizontalLayout;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$widget.addClass(this.isHorizontalLayout ? "bx-dots-vertical-rounded" : "bx-menu");
|
||||
|
||||
this.$widget.on("click", async (e) => {
|
||||
const note = appContext.tabManager.getActiveContextNote();
|
||||
|
||||
contextMenu.show<"insertChildNote" | "delete" | "showRevisions">({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items: [
|
||||
{ title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" },
|
||||
{ title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note?.noteId !== "root" },
|
||||
{ title: "----" },
|
||||
{ title: "Note revisions", command: "showRevisions", uiIcon: "bx bx-history" }
|
||||
],
|
||||
selectMenuItemHandler: async ({ command }) => {
|
||||
if (command === "insertChildNote") {
|
||||
noteCreateService.createNote(appContext.tabManager.getActiveContextNotePath() ?? undefined);
|
||||
} else if (command === "delete") {
|
||||
const notePath = appContext.tabManager.getActiveContextNotePath();
|
||||
if (!notePath) {
|
||||
throw new Error("Cannot get note path to delete.");
|
||||
}
|
||||
|
||||
const branchId = await treeService.getBranchIdFromUrl(notePath);
|
||||
|
||||
if (!branchId) {
|
||||
throw new Error(t("mobile_detail_menu.error_cannot_get_branch_id", { notePath }));
|
||||
}
|
||||
|
||||
if (await branchService.deleteNotes([branchId])) {
|
||||
this.triggerCommand("setActiveScreen", { screen: "tree" });
|
||||
}
|
||||
} else if (command) {
|
||||
this.triggerCommand(command);
|
||||
}
|
||||
},
|
||||
forcePositionOnMobile: true
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default MobileDetailMenuWidget;
|
||||
15
apps/client/src/widgets/mobile_widgets/screen_container.ts
Normal file
15
apps/client/src/widgets/mobile_widgets/screen_container.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import FlexContainer, { type FlexDirection } from "../containers/flex_container.js";
|
||||
|
||||
export default class ScreenContainer extends FlexContainer<BasicWidget> {
|
||||
private screenName: string;
|
||||
|
||||
constructor(screenName: string, direction: FlexDirection) {
|
||||
super(direction);
|
||||
|
||||
this.screenName = screenName;
|
||||
}
|
||||
|
||||
activeScreenChangedEvent({ activeScreen }: EventData<"activeScreenChanged">) {}
|
||||
}
|
||||
168
apps/client/src/widgets/mobile_widgets/sidebar_container.ts
Normal file
168
apps/client/src/widgets/mobile_widgets/sidebar_container.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import type { Screen } from "../../components/mobile_screen_switcher.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import FlexContainer, { type FlexDirection } from "../containers/flex_container.js";
|
||||
|
||||
const DRAG_STATE_NONE = 0;
|
||||
const DRAG_STATE_INITIAL_DRAG = 1;
|
||||
const DRAG_STATE_DRAGGING = 2;
|
||||
|
||||
/** Percentage of drag that the user has to do in order for the popup to open (0-100). */
|
||||
const DRAG_OPEN_THRESHOLD = 10;
|
||||
|
||||
/** The number of pixels the user has to drag across the screen to the right when the sidebar is closed to trigger the drag open animation. */
|
||||
const DRAG_CLOSED_START_THRESHOLD = 10;
|
||||
/** The number of pixels the user has to drag across the screen to the left when the sidebar is opened to trigger the drag close animation. */
|
||||
const DRAG_OPENED_START_THRESHOLD = 80;
|
||||
|
||||
export default class SidebarContainer extends FlexContainer<BasicWidget> {
|
||||
private screenName: Screen;
|
||||
/** The screen name that is currently active, according to the screen changed event. */
|
||||
private activeScreenName?: Screen;
|
||||
private currentTranslate: number;
|
||||
private dragState: number;
|
||||
private startX?: number;
|
||||
private translatePercentage: number;
|
||||
private sidebarEl!: HTMLElement;
|
||||
private backdropEl!: HTMLElement;
|
||||
private originalSidebarTransition: string;
|
||||
private originalBackdropTransition: string;
|
||||
|
||||
constructor(screenName: Screen, direction: FlexDirection) {
|
||||
super(direction);
|
||||
|
||||
this.screenName = screenName;
|
||||
this.currentTranslate = -100;
|
||||
this.translatePercentage = 0;
|
||||
this.dragState = DRAG_STATE_NONE;
|
||||
this.originalSidebarTransition = "";
|
||||
this.originalBackdropTransition = "";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
document.addEventListener("touchstart", (e) => this.#onDragStart(e));
|
||||
document.addEventListener("touchmove", (e) => this.#onDragMove(e), { passive: false });
|
||||
document.addEventListener("touchend", (e) => this.#onDragEnd(e));
|
||||
}
|
||||
|
||||
#onDragStart(e: TouchEvent | MouseEvent) {
|
||||
const x = "touches" in e ? e.touches[0].clientX : e.clientX;
|
||||
this.startX = x;
|
||||
|
||||
if (x > 30 && this.currentTranslate === -100) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#setInitialState();
|
||||
this.dragState = DRAG_STATE_INITIAL_DRAG;
|
||||
this.translatePercentage = 0;
|
||||
}
|
||||
|
||||
#onDragMove(e: TouchEvent | MouseEvent) {
|
||||
if (this.dragState === DRAG_STATE_NONE || !this.startX) {
|
||||
return;
|
||||
}
|
||||
|
||||
const x = "touches" in e ? e.touches[0].clientX : e.clientX;
|
||||
const deltaX = x - this.startX;
|
||||
if (this.dragState === DRAG_STATE_INITIAL_DRAG) {
|
||||
if (this.currentTranslate === -100 ? deltaX > DRAG_CLOSED_START_THRESHOLD : deltaX < -DRAG_OPENED_START_THRESHOLD) {
|
||||
/* Disable the transitions since they affect performance, they are going to reenabled once drag ends. */
|
||||
this.sidebarEl.style.transition = "none";
|
||||
this.backdropEl.style.transition = "none";
|
||||
|
||||
this.backdropEl.style.opacity = String(this.currentTranslate === -100 ? 0 : 1);
|
||||
this.backdropEl.classList.add("show");
|
||||
|
||||
this.dragState = DRAG_STATE_DRAGGING;
|
||||
}
|
||||
|
||||
if (this.currentTranslate !== -100) {
|
||||
// Return early to avoid consuming the event, this allows the user to scroll vertically.
|
||||
return;
|
||||
}
|
||||
} else if (this.dragState === DRAG_STATE_DRAGGING) {
|
||||
const width = this.sidebarEl.offsetWidth;
|
||||
const translatePercentage = Math.min(0, Math.max(this.currentTranslate + (deltaX / width) * 100, -100));
|
||||
this.translatePercentage = translatePercentage;
|
||||
this.sidebarEl.style.transform = `translateX(${translatePercentage}%)`;
|
||||
this.backdropEl.style.opacity = String(Math.max(0, 1 + translatePercentage / 100));
|
||||
}
|
||||
|
||||
// Consume the event to prevent the user from doing the back to previous page gesture on iOS.
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
#onDragEnd(e: TouchEvent | MouseEvent) {
|
||||
if (this.dragState === DRAG_STATE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dragState === DRAG_STATE_INITIAL_DRAG) {
|
||||
this.dragState = DRAG_STATE_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the sidebar is closed, snap the sidebar open only if the user swiped over a threshold.
|
||||
// When the sidebar is open, always close for a smooth experience.
|
||||
const isOpen = this.currentTranslate === -100 && this.translatePercentage > -(100 - DRAG_OPEN_THRESHOLD);
|
||||
const screen = isOpen ? "tree" : "detail";
|
||||
|
||||
if (this.activeScreenName !== screen) {
|
||||
// Trigger the set active screen command for the rest of the UI to know whether the sidebar is active or not.
|
||||
// This allows the toggle sidebar button to work, by knowing the right state.
|
||||
this.triggerCommand("setActiveScreen", { screen });
|
||||
} else {
|
||||
// If the active screen hasn't changed, usually due to the user making a very short gesture that results in the sidebar not being closed/opened,
|
||||
// we need to hide the animation but setActiveScreen command will not trigger the event since we are still on the same screen.
|
||||
this.#setSidebarOpen(isOpen);
|
||||
}
|
||||
}
|
||||
|
||||
#setInitialState() {
|
||||
if (this.sidebarEl) {
|
||||
// Already initialized.
|
||||
return;
|
||||
}
|
||||
|
||||
const sidebarEl = document.getElementById("mobile-sidebar-wrapper");
|
||||
const backdropEl = document.getElementById("mobile-sidebar-container");
|
||||
backdropEl?.addEventListener("click", () => {
|
||||
this.triggerCommand("setActiveScreen", { screen: "detail" });
|
||||
});
|
||||
|
||||
if (!sidebarEl || !backdropEl) {
|
||||
throw new Error("Unable to find the sidebar or backdrop.");
|
||||
}
|
||||
|
||||
this.sidebarEl = sidebarEl;
|
||||
this.backdropEl = backdropEl;
|
||||
this.originalSidebarTransition = this.sidebarEl.style.transition;
|
||||
this.originalBackdropTransition = this.backdropEl.style.transition;
|
||||
}
|
||||
|
||||
#setSidebarOpen(isOpen: boolean) {
|
||||
if (!this.sidebarEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sidebarEl.classList.toggle("show", isOpen);
|
||||
this.sidebarEl.style.transform = isOpen ? "translateX(0)" : "translateX(-100%)";
|
||||
this.sidebarEl.style.transition = this.originalSidebarTransition;
|
||||
|
||||
this.backdropEl.classList.toggle("show", isOpen);
|
||||
this.backdropEl.style.transition = this.originalBackdropTransition;
|
||||
this.backdropEl.style.opacity = String(isOpen ? 1 : 0);
|
||||
|
||||
this.currentTranslate = isOpen ? 0 : -100;
|
||||
this.dragState = DRAG_STATE_NONE;
|
||||
}
|
||||
|
||||
activeScreenChangedEvent({ activeScreen }: EventData<"activeScreenChanged">) {
|
||||
this.activeScreenName = activeScreen;
|
||||
this.#setInitialState();
|
||||
this.#setSidebarOpen(activeScreen === this.screenName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<button type="button" class="action-button bx bx-sidebar"></button>`;
|
||||
|
||||
class ToggleSidebarButtonWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$widget.on("click", () =>
|
||||
this.triggerCommand("setActiveScreen", {
|
||||
screen: "tree"
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ToggleSidebarButtonWidget;
|
||||
Reference in New Issue
Block a user