233 lines
7.2 KiB
JavaScript
233 lines
7.2 KiB
JavaScript
import {
|
|
SIDEBAR_STORAGE_KEY,
|
|
ASSISTANT_STORAGE_KEY,
|
|
UI_LANGUAGE_STORAGE_KEY,
|
|
WORKBENCH_SESSION_STORAGE_KEY_PREFIX
|
|
} from "../constants.js";
|
|
import {
|
|
parseJsonObject,
|
|
setMediaThumbnailLoaded,
|
|
syncMediaThumbnailState,
|
|
clamp
|
|
} from "../utils/dom.js";
|
|
import { shellWidth, setShellWidth, persistWidth, readStoredSize } from "../utils/layout.js";
|
|
import {
|
|
parseShortcutConfig,
|
|
normalizeShortcutKey,
|
|
shortcutMatchesEvent,
|
|
shortcutTargetIsEditable
|
|
} from "../utils/shortcuts.js";
|
|
import { syncTitlebarOverlayInsets } from "../bridges/titlebar_overlay.js";
|
|
import { runMenuRuntimeCommand } from "../bridges/menu_runtime.js";
|
|
|
|
export const AppShell = {
|
|
mounted() {
|
|
this.shortcuts = parseShortcutConfig(this.el.dataset.shortcuts);
|
|
this.currentProjectId = this.el.dataset.projectId || "";
|
|
this.syncStoredLayout();
|
|
this.syncStoredUiLanguage();
|
|
this.destroyOverlaySync = syncTitlebarOverlayInsets();
|
|
|
|
this.workbenchStorageKey = (projectId) =>
|
|
projectId ? `${WORKBENCH_SESSION_STORAGE_KEY_PREFIX}${projectId}` : null;
|
|
|
|
this.restoreStoredWorkbenchSession = () => {
|
|
const projectId = this.el.dataset.projectId || "";
|
|
const storageKey = this.workbenchStorageKey(projectId);
|
|
|
|
if (!storageKey) {
|
|
return false;
|
|
}
|
|
|
|
const session = parseJsonObject(window.localStorage.getItem(storageKey));
|
|
|
|
if (!session) {
|
|
return false;
|
|
}
|
|
|
|
this.pushEvent("restore_workbench_session", { session });
|
|
return true;
|
|
};
|
|
|
|
this.persistWorkbenchSession = () => {
|
|
const projectId = this.el.dataset.projectId || "";
|
|
const storageKey = this.workbenchStorageKey(projectId);
|
|
const session = this.el.dataset.workbenchSession;
|
|
|
|
if (!storageKey || !session) {
|
|
return;
|
|
}
|
|
|
|
window.localStorage.setItem(storageKey, session);
|
|
};
|
|
|
|
this.handleMouseDown = (event) => {
|
|
const handle = event.target.closest("[data-role='resize-handle']");
|
|
|
|
if (!handle || !this.el.contains(handle)) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
const target = handle.dataset.resize;
|
|
const startX = event.clientX;
|
|
const startWidth =
|
|
target === "assistant"
|
|
? shellWidth("[data-testid='assistant-shell']")
|
|
: shellWidth("[data-testid='sidebar-shell']");
|
|
|
|
const min = target === "assistant" ? 280 : 200;
|
|
const max = target === "assistant" ? 640 : 500;
|
|
const invert = target === "assistant";
|
|
|
|
const onMouseMove = (moveEvent) => {
|
|
const delta = invert ? startX - moveEvent.clientX : moveEvent.clientX - startX;
|
|
const width = clamp(startWidth + delta, min, max);
|
|
const selector = target === "assistant" ? "[data-testid='assistant-shell']" : "[data-testid='sidebar-shell']";
|
|
|
|
setShellWidth(selector, width);
|
|
persistWidth(target, width);
|
|
};
|
|
|
|
const onMouseUp = (upEvent) => {
|
|
const delta = invert ? startX - upEvent.clientX : upEvent.clientX - startX;
|
|
const width = clamp(startWidth + delta, min, max);
|
|
|
|
persistWidth(target, width);
|
|
this.pushEvent("resize_panel", { target, width });
|
|
|
|
window.removeEventListener("mousemove", onMouseMove);
|
|
window.removeEventListener("mouseup", onMouseUp);
|
|
};
|
|
|
|
window.addEventListener("mousemove", onMouseMove);
|
|
window.addEventListener("mouseup", onMouseUp);
|
|
};
|
|
|
|
this.el.addEventListener("mousedown", this.handleMouseDown);
|
|
|
|
this.handleNativeMenuAction = (event) => {
|
|
const action = event.detail?.action;
|
|
const ackId = event.detail?.ackId;
|
|
|
|
if (action) {
|
|
this.pushEvent("native_menu_action", { action }, () => {
|
|
if (ackId) {
|
|
window.dispatchEvent(
|
|
new CustomEvent("bds:native-menu-action-ack", { detail: { ackId } })
|
|
);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
this.handleChange = (event) => {
|
|
const select = event.target.closest(".status-bar-language-select");
|
|
|
|
if (select && this.el.contains(select)) {
|
|
window.localStorage.setItem(UI_LANGUAGE_STORAGE_KEY, select.value);
|
|
}
|
|
};
|
|
|
|
this.handleShortcutKeyDown = (event) => {
|
|
if (shortcutTargetIsEditable(event)) {
|
|
return;
|
|
}
|
|
|
|
const shortcut = this.shortcuts.find((candidate) => shortcutMatchesEvent(candidate, event));
|
|
|
|
if (!shortcut) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
this.pushEvent("shortcut", {
|
|
key: normalizeShortcutKey(event.key),
|
|
meta: event.metaKey,
|
|
ctrl: event.ctrlKey,
|
|
alt: event.altKey,
|
|
shift: event.shiftKey,
|
|
tag: event.target?.tagName || null,
|
|
contentEditable: event.target?.isContentEditable || false
|
|
});
|
|
};
|
|
|
|
this.handleThumbnailLoad = (event) => {
|
|
if (event.target instanceof HTMLImageElement && event.target.classList.contains("media-thumbnail-image")) {
|
|
setMediaThumbnailLoaded(event.target, true);
|
|
}
|
|
};
|
|
|
|
this.handleThumbnailError = (event) => {
|
|
if (event.target instanceof HTMLImageElement && event.target.classList.contains("media-thumbnail-image")) {
|
|
setMediaThumbnailLoaded(event.target, false);
|
|
}
|
|
};
|
|
|
|
this.handleEvent("menu-runtime-command", ({ action }) => {
|
|
if (action) {
|
|
runMenuRuntimeCommand(String(action));
|
|
}
|
|
});
|
|
|
|
this.handleEvent("url-state", ({ path }) => {
|
|
if (path && window.location.pathname + window.location.search !== path) {
|
|
window.history.replaceState({}, "", path);
|
|
}
|
|
});
|
|
|
|
window.addEventListener("bds:native-menu-action", this.handleNativeMenuAction);
|
|
window.addEventListener("keydown", this.handleShortcutKeyDown, true);
|
|
this.el.addEventListener("load", this.handleThumbnailLoad, true);
|
|
this.el.addEventListener("error", this.handleThumbnailError, true);
|
|
this.el.addEventListener("change", this.handleChange);
|
|
syncMediaThumbnailState(this.el);
|
|
this.restoreStoredWorkbenchSession();
|
|
},
|
|
|
|
updated() {
|
|
const nextProjectId = this.el.dataset.projectId || "";
|
|
|
|
if (nextProjectId !== this.currentProjectId) {
|
|
this.currentProjectId = nextProjectId;
|
|
|
|
if (this.restoreStoredWorkbenchSession()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
syncMediaThumbnailState(this.el);
|
|
this.persistWorkbenchSession();
|
|
},
|
|
|
|
destroyed() {
|
|
this.el.removeEventListener("mousedown", this.handleMouseDown);
|
|
this.el.removeEventListener("load", this.handleThumbnailLoad, true);
|
|
this.el.removeEventListener("error", this.handleThumbnailError, true);
|
|
this.el.removeEventListener("change", this.handleChange);
|
|
window.removeEventListener("bds:native-menu-action", this.handleNativeMenuAction);
|
|
window.removeEventListener("keydown", this.handleShortcutKeyDown, true);
|
|
if (this.destroyOverlaySync) {
|
|
this.destroyOverlaySync();
|
|
}
|
|
},
|
|
|
|
syncStoredLayout() {
|
|
this.pushEvent("sync_layout", {
|
|
sidebar_width: readStoredSize(SIDEBAR_STORAGE_KEY, shellWidth("[data-testid='sidebar-shell']"), 200, 500),
|
|
assistant_sidebar_width: readStoredSize(ASSISTANT_STORAGE_KEY, 360, 280, 640)
|
|
});
|
|
},
|
|
|
|
syncStoredUiLanguage() {
|
|
const stored = window.localStorage.getItem(UI_LANGUAGE_STORAGE_KEY);
|
|
|
|
if (stored) {
|
|
this.pushEvent("sync_ui_language", { language: stored });
|
|
}
|
|
}
|
|
};
|