mirror of
https://github.com/github/awesome-copilot.git
synced 2026-10-13 15:24:25 +00:00
139 lines
4.5 KiB
JavaScript
139 lines
4.5 KiB
JavaScript
// Boots the Draw canvas: theme, editor, input, UI and sync with the extension.
|
|
import { Editor } from "./editor.js";
|
|
import { LabelEditor } from "./labels.js";
|
|
import { attachPointer } from "./pointer.js";
|
|
import { attachKeys } from "./keys.js";
|
|
import { UI } from "./ui.js";
|
|
import { attachMenus } from "./menus.js";
|
|
import { Sync } from "./sync.js";
|
|
import { blobToBase64 } from "./util.js";
|
|
import { readTheme, applyThemeVars, watchTheme, setMeasureFont, clearMeasureCache, measure, THEME_MODES } from "./theme.js";
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
const params = new URLSearchParams(location.search);
|
|
const instanceId = params.get("i") || "";
|
|
const token = params.get("t") || "";
|
|
|
|
function loadTheme(mode) {
|
|
const t = readTheme(mode);
|
|
setMeasureFont(t.font);
|
|
applyThemeVars($("app"), t);
|
|
return { ...t, measure };
|
|
}
|
|
|
|
// The page stays invisible until the host's theme is in, or briefly when there is no host.
|
|
const reveal = () => $("app").classList.remove("booting");
|
|
|
|
async function boot() {
|
|
// The server writes the saved theme choice into the page, so the first paint is already right.
|
|
let mode = THEME_MODES.includes($("app").dataset.themeMode) ? $("app").dataset.themeMode : "app";
|
|
let theme = loadTheme(mode);
|
|
if (theme.present.length) reveal();
|
|
else setTimeout(reveal, 400);
|
|
const ed = new Editor(
|
|
{ stage: $("stage"), svg: $("svg"), world: $("world"), content: $("content"), overlay: $("overlay"), layer: $("layer") },
|
|
theme,
|
|
);
|
|
new LabelEditor(ed);
|
|
attachPointer(ed);
|
|
const ui = new UI(ed);
|
|
ui.themeMode = () => mode;
|
|
ui.setThemeMode = (next) => applyMode(next, true);
|
|
attachMenus(ui);
|
|
ui.syncThemeButton();
|
|
attachKeys(ed, ui);
|
|
|
|
// Match app, light or dark. Saving the choice also switches every other open panel.
|
|
function applyMode(next, save) {
|
|
if (!THEME_MODES.includes(next)) return;
|
|
if (next !== mode) {
|
|
mode = next;
|
|
$("app").dataset.themeMode = mode;
|
|
theme = loadTheme(mode);
|
|
ed.setTheme(theme);
|
|
ui.refresh();
|
|
ui.syncThemeButton();
|
|
}
|
|
if (save) {
|
|
sync.post("settings", { theme: next }).catch((err) => ui.toast(`Could not save the theme: ${err.message}`, { error: true }));
|
|
}
|
|
}
|
|
|
|
// Draws the picture an agent asked for. The reply names the drawing this panel shows, so the
|
|
// server can turn it down when that is not the one it asked for.
|
|
async function answerExport({ requestId, format, drawingId: wanted }) {
|
|
const drawingId = sync.drawingId;
|
|
const reply = (fields) => sync.post("export", { requestId, drawingId, format, ...fields });
|
|
try {
|
|
if (wanted !== drawingId) return await reply({});
|
|
await reply({ data: format === "svg" ? ed.exportSVG() : await blobToBase64(await ed.exportPNG()) });
|
|
} catch (err) {
|
|
reply({ error: err.message || String(err) }).catch(() => {});
|
|
}
|
|
}
|
|
|
|
const sync = new Sync({
|
|
instanceId,
|
|
token,
|
|
editor: ed,
|
|
handlers: {
|
|
connection: (ok) => ui.setConnection(ok),
|
|
list: (drawings) => ui.setDrawings(drawings),
|
|
switched: (drawing) => {
|
|
ui.closePopover();
|
|
ui.cancelRename?.();
|
|
ed.load(drawing);
|
|
ui.setDoc(drawing);
|
|
ui.syncEmpty();
|
|
},
|
|
exportRequest: answerExport,
|
|
select: (ids) => ed.select(ids),
|
|
status: (kind, msg) => ui.setStatus(kind, msg),
|
|
problem: (msg) => ui.toast(msg, { error: true, timeout: 8000 }),
|
|
unsaved: (drawing) => ui.warnUnsaved(() => ui.drawingsAction({ action: "open", id: drawing.id }, { discard: true })),
|
|
settings: (values) => applyMode(values.theme, false),
|
|
},
|
|
});
|
|
ui.sync = sync;
|
|
ed.on("selection", () => sync.sendSelection([...ed.selection]));
|
|
|
|
watchTheme(() => {
|
|
theme = loadTheme(mode);
|
|
ed.setTheme(theme);
|
|
ui.refresh();
|
|
if (theme.present.length) reveal();
|
|
});
|
|
document.fonts?.ready.then(() => {
|
|
clearMeasureCache();
|
|
ed.refresh();
|
|
});
|
|
|
|
if (!instanceId || !token) {
|
|
reveal();
|
|
ui.fatal("This page must be opened from the Draw canvas in Copilot.");
|
|
return;
|
|
}
|
|
let state;
|
|
try {
|
|
state = await sync.loadState();
|
|
} catch (err) {
|
|
reveal();
|
|
ui.fatal(err.message);
|
|
return;
|
|
}
|
|
if (state.settings) applyMode(state.settings.theme, false);
|
|
sync.setDoc(state.drawing);
|
|
ui.setup(state);
|
|
ed.load(state.drawing);
|
|
ui.syncEmpty();
|
|
sync.setDiskError(state.drawing.saveError);
|
|
sync.connect();
|
|
ed.focusCanvas();
|
|
}
|
|
|
|
boot().catch((err) => {
|
|
reveal();
|
|
const stage = $("stage");
|
|
if (stage) stage.textContent = `Draw failed to start: ${err.message}`;
|
|
});
|