151 lines
5.1 KiB
TypeScript
151 lines
5.1 KiB
TypeScript
import { Component, TFile } from "obsidian";
|
|
import { mount } from "svelte";
|
|
import App from "../src/ui/app.svelte";
|
|
import { DEFAULT_SETTINGS } from "../src/settings";
|
|
import type { BeaverCalendarSettings } from "../src/settings";
|
|
import { BoardStore } from "../src/vault/store.svelte";
|
|
|
|
const FIXTURES = ["personal", "work", "reading", "home"];
|
|
|
|
/**
|
|
* Settings come off the query string, so a screenshot can start from the
|
|
* same preferences a real vault has: ?groupBy=due&includeArchive=1&railCollapsed=1
|
|
*/
|
|
function prefsFromQuery(): Partial<BeaverCalendarSettings> {
|
|
const params = new URLSearchParams(location.search);
|
|
const out: Record<string, unknown> = {};
|
|
const flag = (value: string): boolean => value === "1" || value === "true";
|
|
for (const [key, value] of params) {
|
|
if (!(key in DEFAULT_SETTINGS)) continue;
|
|
const current = DEFAULT_SETTINGS[key as keyof BeaverCalendarSettings];
|
|
out[key] = typeof current === "boolean" ? flag(value) : value;
|
|
}
|
|
return out as Partial<BeaverCalendarSettings>;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
// Real boards when the server was given a folder, the fixtures otherwise.
|
|
const index = (await (await fetch("/boards.json")).json()) as {
|
|
folder: string;
|
|
names: string[];
|
|
} | null;
|
|
const folder = index?.folder ?? "Boards";
|
|
const sources = index
|
|
? index.names.map((name) => ({ path: `${folder}/${name}`, url: `/boards/${name}` }))
|
|
: FIXTURES.map((name) => ({
|
|
path: `${folder}/${name}.md`,
|
|
url: `/tests/fixtures/${name}.md`,
|
|
}));
|
|
|
|
const files = new Map<string, string>();
|
|
await Promise.all(
|
|
sources.map(async ({ path, url }) => {
|
|
const response = await fetch(url);
|
|
files.set(path, await response.text());
|
|
}),
|
|
);
|
|
const handles = new Map(
|
|
[...files.keys()].map((path) => [path, new TFile(path)]),
|
|
);
|
|
|
|
const app = {
|
|
vault: {
|
|
getMarkdownFiles: () => [...handles.values()],
|
|
getAbstractFileByPath: (path: string) => handles.get(path) ?? null,
|
|
cachedRead: async (file: TFile) => files.get(file.path) ?? "",
|
|
read: async (file: TFile) => files.get(file.path) ?? "",
|
|
process: async (file: TFile, fn: (data: string) => string) => {
|
|
const next = fn(files.get(file.path) ?? "");
|
|
files.set(file.path, next);
|
|
store.queueReload(file.path);
|
|
return next;
|
|
},
|
|
on: () => ({}),
|
|
},
|
|
workspace: { getLeaf: () => ({}), activeEditor: null },
|
|
plugins: { plugins: {} },
|
|
};
|
|
|
|
const settings = { ...DEFAULT_SETTINGS, boardsFolder: folder, ...prefsFromQuery() };
|
|
const store = new BoardStore(app as never, settings, () => {});
|
|
await store.reloadAll();
|
|
|
|
mount(App, {
|
|
target: document.getElementById("app") as HTMLElement,
|
|
props: {
|
|
app: app as never,
|
|
store,
|
|
settings,
|
|
component: new Component() as never,
|
|
},
|
|
});
|
|
|
|
// Scenes for screenshots: open the page with a hash to land in a state.
|
|
const scene = location.hash.slice(1);
|
|
if (!scene) return;
|
|
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
await wait(50);
|
|
store.keyboard = true;
|
|
const first = store.spheres[0];
|
|
const tab = async (times: number): Promise<void> => {
|
|
for (let i = 0; i < times; i += 1) {
|
|
document.activeElement?.dispatchEvent(
|
|
new KeyboardEvent("keydown", { key: "Tab", code: "Tab", bubbles: true }),
|
|
);
|
|
await wait(20);
|
|
}
|
|
};
|
|
if (scene.startsWith("rail")) {
|
|
store.toggleRail();
|
|
store.railExpanded = new Set([first.path, store.spheres[1].path]);
|
|
if (scene === "rail-some") {
|
|
store.toggleSphere(store.spheres[2].path);
|
|
store.toggleLane(first.path, first.projects[0]);
|
|
store.cursorRail = `lane:${encodeURIComponent(first.path)} ${encodeURIComponent(first.projects[0])}`;
|
|
}
|
|
if (scene === "rail-none") store.filterNone();
|
|
}
|
|
// The month grid on its own, opened on the month the fixtures are busiest in.
|
|
if (scene === "cal") {
|
|
const months = new Map<string, number>();
|
|
for (const item of store.visible) {
|
|
const key = (item.due ?? item.scheduled)?.slice(0, 7);
|
|
if (key) months.set(key, (months.get(key) ?? 0) + 1);
|
|
}
|
|
const busiest = [...months.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
store.view = "calendar";
|
|
if (busiest) store.focusDay(`${busiest}-15`);
|
|
}
|
|
if (scene.startsWith("peek")) {
|
|
if (scene === "peek-cal") store.view = "calendar";
|
|
if (scene === "peek-notes") store.showDone = true;
|
|
await wait(20);
|
|
const task = store.visible.find((item) => item.body.length > 0) ?? store.visible[0];
|
|
if (scene === "peek-cal") {
|
|
const key = task.due ?? task.scheduled;
|
|
if (key) store.focusDay(key);
|
|
}
|
|
store.zone = "task";
|
|
store.cursorTask = task.id;
|
|
await wait(50);
|
|
store.peek();
|
|
}
|
|
if (scene === "date") {
|
|
store.write(null, "[data-composer='toolbar']");
|
|
await wait(50);
|
|
await tab(3);
|
|
}
|
|
// The editor over an existing task, tabbed through to its date field.
|
|
if (scene === "edit-date") {
|
|
const task = store.visible[0];
|
|
store.zone = "task";
|
|
store.cursorTask = task.id;
|
|
await wait(20);
|
|
store.edit(task.id);
|
|
await wait(50);
|
|
await tab(3);
|
|
}
|
|
}
|
|
|
|
void main();
|