the vendoring machinery: one command reproduces the whole state
scripts/vendor.sh clones upstream, rewrites @/ imports to relative paths, and applies scripts/apply-host.py. web/host.ts is ours and gets placed into the vendored tree, because upstream declined it. a script rather than a .patch, and that is not a preference. between cfde698 and 5c83ac6 — one day — upstream changed an import line, re-indented the component from 6 spaces to 8, and added an editAccess guard. a context diff fails on all three, none of which concern us. so every anchor matches structure with flexible whitespace and re-indents to whatever it finds, and a missing anchor stops the run loudly. all three prompt-export paths are wired, which is what upstream said a complete version needs. the other two clipboard writes are ShareMenu's share-link and ask-an-AI blurb, and the reason they are excluded is written down so the count does not read as the same oversight twice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Vendored
+27
-23
@@ -99,6 +99,7 @@ import { ThemeContext, ensureFontLoaded, ensureLangFontLoaded } from "./lib/them
|
||||
import { BottomSheet, MobileActionBar, MobileInspector, MobileLang, MobileSettings } from "./components/Mobile";
|
||||
import { ConfirmDialog, IconBtn, Segmented } from "./components/ui";
|
||||
import { Lang, LangContext, SEED_TEXT, getLang, isLang, setGlobalLang, t, translateDefaultFrameName, translateDefaultText } from "./lib/i18n";
|
||||
import { useHost } from "./lib/host";
|
||||
|
||||
/** the screens while a model drafts: primary, tertiary and primary container, drifting */
|
||||
const DRAFT_GRADIENT = (p: Palette) => `linear-gradient(120deg, ${p.primaryContainer}, ${p.tertiaryContainer}, ${p.primary}, ${p.secondaryContainer}, ${p.primaryContainer})`;
|
||||
@@ -323,6 +324,8 @@ const LEFT_TABS: { key: LeftTab; icon: string; title: "parts" | "layers" | "colo
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
// Optional host hooks; every default is the standalone behaviour. See lib/host.ts.
|
||||
const host = useHost();
|
||||
/* ---------- document ---------- */
|
||||
const [editAccess, setEditAccess] = useState<"checking" | "editable" | "readonly">("checking");
|
||||
const [groups, setGroups] = useState<Group[]>(seed);
|
||||
@@ -605,8 +608,15 @@ export default function Page() {
|
||||
// React's development double-run would otherwise read back its own first save
|
||||
if (loadedRef.current) return;
|
||||
try {
|
||||
const d = localStorage.getItem(DOC_KEY);
|
||||
if (d) {
|
||||
// Branch on whether the host SUPPLIED a loader, not on what it returned: a host
|
||||
// returning null means "no saved document", which must start fresh rather than
|
||||
// fall back to this browser's standalone copy. Raised on lnkiai/m3e-canvas#56.
|
||||
const hosted = host.loadDoc ? host.loadDoc() : null;
|
||||
const d = host.loadDoc ? null : localStorage.getItem(DOC_KEY);
|
||||
if (hosted) {
|
||||
hadDocRef.current = true;
|
||||
applyDoc(hosted, false);
|
||||
} else if (d) {
|
||||
hadDocRef.current = true;
|
||||
applyDoc(JSON.parse(d) as Partial<Doc>, false);
|
||||
// frame mode is decided by the device (media-query effect), not restored
|
||||
@@ -619,9 +629,9 @@ export default function Page() {
|
||||
else localStorage.removeItem(BEFORE_KEY);
|
||||
}
|
||||
let initialLang: Lang = "ja";
|
||||
const u = localStorage.getItem(UI_KEY);
|
||||
if (u) {
|
||||
const ui = JSON.parse(u);
|
||||
const stored = host.loadUi ? null : localStorage.getItem(UI_KEY);
|
||||
const ui = host.loadUi ? host.loadUi() : stored ? JSON.parse(stored) : null;
|
||||
if (ui) {
|
||||
if (ui.view) setView(ui.view);
|
||||
if (typeof ui.leftOpen === "boolean") setLeftOpen(ui.leftOpen);
|
||||
if (typeof ui.rightOpen === "boolean") setRightOpen(ui.rightOpen);
|
||||
@@ -732,29 +742,18 @@ export default function Page() {
|
||||
useEffect(() => {
|
||||
if (!loadedRef.current || editAccess !== "editable") return;
|
||||
try {
|
||||
localStorage.setItem(
|
||||
DOC_KEY,
|
||||
JSON.stringify({ groups, frames, paletteKey, frame, title, brief, promptEdit, platform: platform ?? undefined, customPalette: customPalette ?? undefined, dynamicColor, theme }),
|
||||
);
|
||||
const payload = { groups, frames, paletteKey, frame, title, brief, promptEdit, platform: platform ?? undefined, customPalette: customPalette ?? undefined, dynamicColor, theme };
|
||||
if (host.saveDoc) host.saveDoc(payload as Partial<Doc>);
|
||||
else localStorage.setItem(DOC_KEY, JSON.stringify(payload));
|
||||
} catch {}
|
||||
}, [editAccess, groups, frames, paletteKey, frame, title, brief, promptEdit, platform, customPalette, dynamicColor, theme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadedRef.current) return;
|
||||
try {
|
||||
localStorage.setItem(
|
||||
UI_KEY,
|
||||
JSON.stringify({
|
||||
view,
|
||||
leftOpen,
|
||||
rightOpen,
|
||||
leftW,
|
||||
rightW,
|
||||
favorites,
|
||||
mode,
|
||||
lang,
|
||||
}),
|
||||
);
|
||||
const ui = {view, leftOpen, rightOpen, leftW, rightW, favorites, mode, lang,};
|
||||
if (host.saveUi) host.saveUi(ui);
|
||||
else localStorage.setItem(UI_KEY, JSON.stringify(ui));
|
||||
} catch {}
|
||||
}, [
|
||||
view,
|
||||
@@ -3467,7 +3466,12 @@ export default function Page() {
|
||||
onLangSheet={() => setSheet(sheet === "lang" ? null : "lang")}
|
||||
onPrompt={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(effectivePrompt(doc, widths, lang));
|
||||
const text = effectivePrompt(doc, widths, lang);
|
||||
// The hook's own failure is isolated: a broken host must not also break copying.
|
||||
let handled = false;
|
||||
try { handled = host.onExport?.(text, doc) === true; } catch {}
|
||||
if (handled) return;
|
||||
await navigator.clipboard.writeText(text);
|
||||
showToast(t("copied", lang), 1400, "check");
|
||||
} catch {}
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user