Work on client modes
This commit is contained in:
+4
-3
@@ -1,14 +1,15 @@
|
||||
import { safeRun } from "../common/util.ts";
|
||||
import { Client } from "./client.ts";
|
||||
|
||||
const thinClientMode = !!localStorage.getItem("thinClientMode");
|
||||
const syncMode = window.silverBulletConfig.supportOnlineMode !== "true" ||
|
||||
!!localStorage.getItem("syncMode");
|
||||
|
||||
safeRun(async () => {
|
||||
console.log("Booting SilverBullet...");
|
||||
|
||||
const client = new Client(
|
||||
document.getElementById("sb-root")!,
|
||||
thinClientMode,
|
||||
syncMode,
|
||||
);
|
||||
await client.init();
|
||||
window.client = client;
|
||||
@@ -22,7 +23,7 @@ if (navigator.serviceWorker) {
|
||||
.then(() => {
|
||||
console.log("Service worker registered...");
|
||||
});
|
||||
if (!thinClientMode) {
|
||||
if (syncMode) {
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
registration.active!.postMessage({
|
||||
type: "config",
|
||||
|
||||
+31
-24
@@ -16,12 +16,17 @@ import { PathPageNavigator } from "./navigator.ts";
|
||||
import { AppViewState, BuiltinSettings } from "./types.ts";
|
||||
|
||||
import type { AppEvent, CompleteEvent } from "../plug-api/app_event.ts";
|
||||
import { throttle } from "../common/async_util.ts";
|
||||
import { throttle } from "$sb/lib/async.ts";
|
||||
import { PlugSpacePrimitives } from "../common/spaces/plug_space_primitives.ts";
|
||||
import { IndexedDBSpacePrimitives } from "../common/spaces/indexeddb_space_primitives.ts";
|
||||
import { FileMetaSpacePrimitives } from "../common/spaces/file_meta_space_primitives.ts";
|
||||
import { EventedSpacePrimitives } from "../common/spaces/evented_space_primitives.ts";
|
||||
import { pageSyncInterval, SyncService } from "./sync_service.ts";
|
||||
import {
|
||||
ISyncService,
|
||||
NoSyncSyncService,
|
||||
pageSyncInterval,
|
||||
SyncService,
|
||||
} from "./sync_service.ts";
|
||||
import { simpleHash } from "../common/crypto.ts";
|
||||
import { DexieKVStore } from "../plugos/lib/kv_store.dexie.ts";
|
||||
import { SyncStatus } from "../common/spaces/sync.ts";
|
||||
@@ -47,6 +52,7 @@ declare global {
|
||||
// Injected via index.html
|
||||
silverBulletConfig: {
|
||||
spaceFolderPath: string;
|
||||
supportOnlineMode: string;
|
||||
};
|
||||
client: Client;
|
||||
}
|
||||
@@ -75,7 +81,7 @@ export class Client {
|
||||
// Track if plugs have been updated since sync cycle
|
||||
fullSyncCompleted = false;
|
||||
|
||||
syncService: SyncService;
|
||||
syncService: ISyncService;
|
||||
settings!: BuiltinSettings;
|
||||
kvStore: DexieKVStore;
|
||||
mq: DexieMQ;
|
||||
@@ -88,7 +94,7 @@ export class Client {
|
||||
|
||||
constructor(
|
||||
parent: Element,
|
||||
private thinClientMode = false,
|
||||
public syncMode = false,
|
||||
) {
|
||||
// Generate a semi-unique prefix for the database so not to reuse databases for different space paths
|
||||
this.dbPrefix = "" + simpleHash(window.silverBulletConfig.spaceFolderPath);
|
||||
@@ -117,26 +123,26 @@ export class Client {
|
||||
this.mq,
|
||||
this.dbPrefix,
|
||||
this.eventHook,
|
||||
this.thinClientMode,
|
||||
);
|
||||
|
||||
const localSpacePrimitives = this.initSpace();
|
||||
|
||||
this.syncService = new SyncService(
|
||||
localSpacePrimitives,
|
||||
this.plugSpaceRemotePrimitives,
|
||||
this.kvStore,
|
||||
this.eventHook,
|
||||
(path) => {
|
||||
// TODO: At some point we should remove the data.db exception here
|
||||
return path !== "data.db" &&
|
||||
// Exclude all plug space primitives paths
|
||||
!this.plugSpaceRemotePrimitives.isLikelyHandled(path) ||
|
||||
// Except federated ones
|
||||
path.startsWith("!");
|
||||
},
|
||||
!this.thinClientMode,
|
||||
);
|
||||
this.syncService = this.syncMode
|
||||
? new SyncService(
|
||||
localSpacePrimitives,
|
||||
this.plugSpaceRemotePrimitives,
|
||||
this.kvStore,
|
||||
this.eventHook,
|
||||
(path) => {
|
||||
// TODO: At some point we should remove the data.db exception here
|
||||
return path !== "data.db" &&
|
||||
// Exclude all plug space primitives paths
|
||||
!this.plugSpaceRemotePrimitives.isLikelyHandled(path) ||
|
||||
// Except federated ones
|
||||
path.startsWith("!");
|
||||
},
|
||||
)
|
||||
: new NoSyncSyncService(this.space);
|
||||
|
||||
this.ui = new MainUI(this);
|
||||
this.ui.render(parent);
|
||||
@@ -243,14 +249,15 @@ export class Client {
|
||||
Math.round(status.filesProcessed / status.totalFiles * 100),
|
||||
);
|
||||
});
|
||||
this.syncService.spaceSync.on({
|
||||
fileSynced: (meta, direction) => {
|
||||
this.eventHook.addLocalListener(
|
||||
"file:synced",
|
||||
(meta: FileMeta, direction: string) => {
|
||||
if (meta.name.endsWith(".md") && direction === "secondary->primary") {
|
||||
// We likely polled the currently open page which trigggered a local update, let's update the editor accordingly
|
||||
this.space.getPageMeta(meta.name.slice(0, -3));
|
||||
}
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
private initNavigator() {
|
||||
@@ -337,7 +344,7 @@ export class Client {
|
||||
|
||||
let localSpacePrimitives: SpacePrimitives | undefined;
|
||||
|
||||
if (!this.thinClientMode) {
|
||||
if (this.syncMode) {
|
||||
localSpacePrimitives = new FilteredSpacePrimitives(
|
||||
new FileMetaSpacePrimitives(
|
||||
new EventedSpacePrimitives(
|
||||
|
||||
+31
-21
@@ -53,10 +53,9 @@ export class ClientSystem {
|
||||
private mq: DexieMQ,
|
||||
dbPrefix: string,
|
||||
private eventHook: EventHook,
|
||||
private thinClientMode: boolean,
|
||||
) {
|
||||
// Only set environment to "client" when running in thin client mode, otherwise we run everything locally (hybrid)
|
||||
this.system = new System(thinClientMode ? "client" : undefined);
|
||||
this.system = new System(client.syncMode ? undefined : "client");
|
||||
|
||||
this.system.addHook(this.eventHook);
|
||||
|
||||
@@ -68,9 +67,11 @@ export class ClientSystem {
|
||||
const cronHook = new CronHook(this.system);
|
||||
this.system.addHook(cronHook);
|
||||
|
||||
if (thinClientMode) {
|
||||
if (!client.syncMode) {
|
||||
// In non-sync mode, proxy these to the server
|
||||
this.indexSyscalls = indexProxySyscalls(client);
|
||||
} else {
|
||||
// In sync mode, run them locally
|
||||
this.indexSyscalls = pageIndexSyscalls(
|
||||
`${dbPrefix}_page_index`,
|
||||
globalThis.indexedDB,
|
||||
@@ -83,7 +84,8 @@ export class ClientSystem {
|
||||
this.system.addHook(this.codeWidgetHook);
|
||||
|
||||
// MQ hook
|
||||
if (!this.thinClientMode) {
|
||||
if (client.syncMode) {
|
||||
// Process MQ messages locally
|
||||
this.system.addHook(new MQHook(this.system, this.mq));
|
||||
}
|
||||
|
||||
@@ -103,19 +105,21 @@ export class ClientSystem {
|
||||
this.slashCommandHook = new SlashCommandHook(this.client);
|
||||
this.system.addHook(this.slashCommandHook);
|
||||
|
||||
this.eventHook.addLocalListener("plug:changed", async (fileName) => {
|
||||
console.log("Plug updated, reloading:", fileName);
|
||||
this.system.unload(fileName);
|
||||
const plug = await this.system.load(
|
||||
new URL(`/${fileName}`, location.href),
|
||||
createSandbox,
|
||||
this.client.settings.plugOverrides,
|
||||
);
|
||||
if ((plug.manifest! as Manifest).syntax) {
|
||||
// If there are syntax extensions, rebuild the markdown parser immediately
|
||||
this.updateMarkdownParser();
|
||||
this.eventHook.addLocalListener("file:changed", async (path: string) => {
|
||||
if (path.startsWith("_plug/") && path.endsWith(".plug.js")) {
|
||||
console.log("Plug updated, reloading:", path);
|
||||
this.system.unload(path);
|
||||
const plug = await this.system.load(
|
||||
new URL(`/${path}`, location.href),
|
||||
createSandbox,
|
||||
this.client.settings.plugOverrides,
|
||||
);
|
||||
if ((plug.manifest! as Manifest).syntax) {
|
||||
// If there are syntax extensions, rebuild the markdown parser immediately
|
||||
this.updateMarkdownParser();
|
||||
}
|
||||
this.plugsUpdated = true;
|
||||
}
|
||||
this.plugsUpdated = true;
|
||||
});
|
||||
|
||||
// Debugging
|
||||
@@ -139,9 +143,11 @@ export class ClientSystem {
|
||||
}
|
||||
|
||||
registerSyscalls() {
|
||||
const storeCalls = this.thinClientMode
|
||||
? storeProxySyscalls(this.client)
|
||||
: storeSyscalls(this.kvStore);
|
||||
const storeCalls = this.client.syncMode
|
||||
// In sync mode handle locally
|
||||
? storeSyscalls(this.kvStore)
|
||||
// In non-sync mode proxy to server
|
||||
: storeProxySyscalls(this.client);
|
||||
|
||||
// Slash command hook
|
||||
this.slashCommandHook = new SlashCommandHook(this.client);
|
||||
@@ -153,11 +159,15 @@ export class ClientSystem {
|
||||
eventSyscalls(this.eventHook),
|
||||
editorSyscalls(this.client),
|
||||
spaceSyscalls(this.client),
|
||||
systemSyscalls(this.client, this.system),
|
||||
systemSyscalls(this.system, this.client),
|
||||
markdownSyscalls(buildMarkdown(this.mdExtensions)),
|
||||
assetSyscalls(this.system),
|
||||
yamlSyscalls(),
|
||||
this.thinClientMode ? mqProxySyscalls(this.client) : mqSyscalls(this.mq),
|
||||
this.client.syncMode
|
||||
// In sync mode handle locally
|
||||
? mqSyscalls(this.mq)
|
||||
// In non-sync mode proxy to server
|
||||
: mqProxySyscalls(this.client),
|
||||
storeCalls,
|
||||
this.indexSyscalls,
|
||||
debugSyscalls(),
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MiniEditor } from "./mini_editor.tsx";
|
||||
export type ActionButton = {
|
||||
icon: FunctionalComponent<FeatherProps>;
|
||||
description: string;
|
||||
class?: string;
|
||||
callback: () => void;
|
||||
href?: string;
|
||||
};
|
||||
@@ -141,6 +142,7 @@ export function TopBar({
|
||||
e.stopPropagation();
|
||||
}}
|
||||
title={actionButton.description}
|
||||
className={actionButton.class}
|
||||
>
|
||||
<actionButton.icon size={18} />
|
||||
</button>
|
||||
|
||||
@@ -12,6 +12,7 @@ export {
|
||||
export {
|
||||
Book as BookIcon,
|
||||
Home as HomeIcon,
|
||||
RefreshCw as RefreshCwIcon,
|
||||
Terminal as TerminalIcon,
|
||||
} from "https://esm.sh/preact-feather@4.2.1?external=preact";
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
BookIcon,
|
||||
HomeIcon,
|
||||
preactRender,
|
||||
RefreshCwIcon,
|
||||
runScopeHandlers,
|
||||
TerminalIcon,
|
||||
useEffect,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
import type { Client } from "./client.ts";
|
||||
import { Panel } from "./components/panel.tsx";
|
||||
import { h } from "./deps.ts";
|
||||
import { async } from "https://cdn.skypack.dev/-/regenerator-runtime@v0.13.9-4Dxus9nU31cBsHxnWq2H/dist=es2020,mode=imports/optimized/regenerator-runtime.js";
|
||||
|
||||
export class MainUI {
|
||||
viewState: AppViewState = initialViewState;
|
||||
@@ -202,6 +204,40 @@ export class MainUI {
|
||||
editor.focus();
|
||||
}}
|
||||
actionButtons={[
|
||||
...window.silverBulletConfig.supportOnlineMode === "true"
|
||||
? [{
|
||||
icon: RefreshCwIcon,
|
||||
description: this.editor.syncMode
|
||||
? "Currently in sync mode: switch to online mode"
|
||||
: "Currently in online mode: switch to sync mode",
|
||||
class: this.editor.syncMode ? "sb-enabled" : undefined,
|
||||
callback: () => {
|
||||
(async () => {
|
||||
const newValue = !this.editor.syncMode;
|
||||
|
||||
if (newValue) {
|
||||
if (
|
||||
await this.editor.confirm(
|
||||
"This will enable local sync. Are you sure?",
|
||||
)
|
||||
) {
|
||||
localStorage.setItem("syncMode", "true");
|
||||
location.reload();
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
await this.editor.confirm(
|
||||
"This will disable local sync. Are you sure?",
|
||||
)
|
||||
) {
|
||||
localStorage.removeItem("syncMode");
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
})().catch(console.error);
|
||||
},
|
||||
}]
|
||||
: [],
|
||||
{
|
||||
icon: HomeIcon,
|
||||
description: `Go home (Alt-h)`,
|
||||
|
||||
+3
-1
@@ -34,12 +34,14 @@
|
||||
};
|
||||
window.silverBulletConfig = {
|
||||
// These {{VARIABLES}} are replaced by http_server.ts
|
||||
spaceFolderPath: "{{SPACE_PATH}}"
|
||||
spaceFolderPath: "{{SPACE_PATH}}",
|
||||
supportOnlineMode: "{{SUPPORT_ONLINE_MODE}}",
|
||||
};
|
||||
// But in case these variables aren't replaced by the server, fall back fully static mode (no sync)
|
||||
if (window.silverBulletConfig.spaceFolderPath.includes("{{")) {
|
||||
window.silverBulletConfig = {
|
||||
spaceFolderPath: "",
|
||||
supportOnlineMode: false,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -89,6 +89,11 @@ self.addEventListener("fetch", (event: any) => {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
if (!fileContentTable) {
|
||||
// Not initialzed yet, or in thin client mode, let's just proxy
|
||||
return fetch(request);
|
||||
}
|
||||
|
||||
const requestUrl = new URL(request.url);
|
||||
const pathname = requestUrl.pathname;
|
||||
|
||||
@@ -119,17 +124,12 @@ async function handleLocalFileRequest(
|
||||
request: Request,
|
||||
pathname: string,
|
||||
): Promise<Response> {
|
||||
if (!fileContentTable) {
|
||||
// Not initialzed yet, or explicitly in sync mode (so direct server communication requested)
|
||||
return fetch(request);
|
||||
}
|
||||
|
||||
if (!db?.isOpen()) {
|
||||
console.log("Detected that the DB was closed, reopening");
|
||||
await db!.open();
|
||||
}
|
||||
const path = decodeURIComponent(pathname.slice(1));
|
||||
const data = await fileContentTable.get(path);
|
||||
const data = await fileContentTable!.get(path);
|
||||
if (data) {
|
||||
// console.log("Serving from space", path);
|
||||
if (!data.meta) {
|
||||
|
||||
+2
-1
@@ -2,10 +2,11 @@ import { SpacePrimitives } from "../common/spaces/space_primitives.ts";
|
||||
import { plugPrefix } from "../common/spaces/constants.ts";
|
||||
import { safeRun } from "../common/util.ts";
|
||||
import { AttachmentMeta, PageMeta } from "./types.ts";
|
||||
import { throttle } from "../common/async_util.ts";
|
||||
|
||||
import { KVStore } from "../plugos/lib/kv_store.ts";
|
||||
import { FileMeta } from "$sb/types.ts";
|
||||
import { EventHook } from "../plugos/hooks/event.ts";
|
||||
import { throttle } from "$sb/lib/async.ts";
|
||||
|
||||
const pageWatchInterval = 5000;
|
||||
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sb-actions button.sb-enabled {
|
||||
color: var(--action-button-active-color);
|
||||
}
|
||||
|
||||
.sb-actions button:hover {
|
||||
color: var(--action-button-hover-color);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ html {
|
||||
--action-button-background-color: transparent;
|
||||
--action-button-color: #292929;
|
||||
--action-button-hover-color: #0772be;
|
||||
--action-button-active-color: #0772be;
|
||||
|
||||
--editor-caret-color: black;
|
||||
--editor-selection-background-color: #d7e1f6;
|
||||
@@ -159,6 +160,7 @@ html[data-theme="dark"] {
|
||||
--action-button-background-color: transparent;
|
||||
--action-button-color: #adadad;
|
||||
--action-button-hover-color: #37a1ed;
|
||||
--action-button-active-color: #37a1ed;
|
||||
|
||||
--editor-caret-color: #fff;
|
||||
--editor-selection-background-color: #d7e1f630;
|
||||
|
||||
+62
-50
@@ -1,4 +1,4 @@
|
||||
import { sleep } from "../common/async_util.ts";
|
||||
import { sleep } from "$sb/lib/async.ts";
|
||||
import type { SpacePrimitives } from "../common/spaces/space_primitives.ts";
|
||||
import {
|
||||
SpaceSync,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "../common/spaces/sync.ts";
|
||||
import { EventHook } from "../plugos/hooks/event.ts";
|
||||
import { KVStore } from "../plugos/lib/kv_store.ts";
|
||||
import { Space } from "./space.ts";
|
||||
|
||||
// Keeps the current sync snapshot
|
||||
const syncSnapshotKey = "syncSnapshot";
|
||||
@@ -31,11 +32,21 @@ const spaceSyncInterval = 17 * 1000; // Every 17s or so
|
||||
// Used from Client
|
||||
export const pageSyncInterval = 6000;
|
||||
|
||||
export interface ISyncService {
|
||||
start(): void;
|
||||
isSyncing(): Promise<boolean>;
|
||||
hasInitialSyncCompleted(): Promise<boolean>;
|
||||
noOngoingSync(_timeout: number): Promise<void>;
|
||||
syncFile(name: string): Promise<void>;
|
||||
scheduleFileSync(_path: string): Promise<void>;
|
||||
scheduleSpaceSync(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SyncService primarily wraps the SpaceSync engine but also coordinates sync between
|
||||
* different browser tabs. It is using the KVStore to keep track of sync state.
|
||||
*/
|
||||
export class SyncService {
|
||||
export class SyncService implements ISyncService {
|
||||
spaceSync: SpaceSync;
|
||||
lastReportedSyncStatus = Date.now();
|
||||
|
||||
@@ -45,7 +56,6 @@ export class SyncService {
|
||||
private kvStore: KVStore,
|
||||
private eventHook: EventHook,
|
||||
private isSyncCandidate: (path: string) => boolean,
|
||||
private enabled: boolean,
|
||||
) {
|
||||
this.spaceSync = new SpaceSync(
|
||||
this.localSpacePrimitives,
|
||||
@@ -72,12 +82,15 @@ export class SyncService {
|
||||
const path = `${name}.md`;
|
||||
this.scheduleFileSync(path).catch(console.error);
|
||||
});
|
||||
|
||||
this.spaceSync.on({
|
||||
fileSynced: (meta, direction) => {
|
||||
eventHook.dispatchEvent("file:synced", meta, direction);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async isSyncing(): Promise<boolean> {
|
||||
if (!this.enabled) {
|
||||
return false;
|
||||
}
|
||||
const startTime = await this.kvStore.get(syncStartTimeKey);
|
||||
if (!startTime) {
|
||||
return false;
|
||||
@@ -95,19 +108,11 @@ export class SyncService {
|
||||
}
|
||||
|
||||
hasInitialSyncCompleted(): Promise<boolean> {
|
||||
if (!this.enabled) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
// Initial sync has happened when sync progress has been reported at least once, but the syncStartTime has been reset (which happens after sync finishes)
|
||||
return this.kvStore.has(syncInitialFullSyncCompletedKey);
|
||||
}
|
||||
|
||||
async registerSyncStart(fullSync: boolean): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assumption: this is called after an isSyncing() check
|
||||
await this.kvStore.batchSet([
|
||||
{
|
||||
@@ -128,10 +133,6 @@ export class SyncService {
|
||||
}
|
||||
|
||||
async registerSyncProgress(status?: SyncStatus): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit a sync event at most every 2s
|
||||
if (status && this.lastReportedSyncStatus < Date.now() - 2000) {
|
||||
this.eventHook.dispatchEvent("sync:progress", status);
|
||||
@@ -142,10 +143,6 @@ export class SyncService {
|
||||
}
|
||||
|
||||
async registerSyncStop(isFullSync: boolean): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.registerSyncProgress();
|
||||
await this.kvStore.del(syncStartTimeKey);
|
||||
if (isFullSync) {
|
||||
@@ -162,10 +159,6 @@ export class SyncService {
|
||||
|
||||
// Await a moment when the sync is no longer running
|
||||
async noOngoingSync(timeout: number): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Not completely safe, could have race condition on setting the syncStartTimeKey
|
||||
const startTime = Date.now();
|
||||
while (await this.isSyncing()) {
|
||||
@@ -179,10 +172,6 @@ export class SyncService {
|
||||
|
||||
filesScheduledForSync = new Set<string>();
|
||||
async scheduleFileSync(path: string): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.filesScheduledForSync.has(path)) {
|
||||
// Already scheduled, no need to duplicate
|
||||
console.info(`File ${path} already scheduled for sync`);
|
||||
@@ -195,19 +184,11 @@ export class SyncService {
|
||||
}
|
||||
|
||||
async scheduleSpaceSync(): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.noOngoingSync(5000);
|
||||
await this.syncSpace();
|
||||
}
|
||||
|
||||
start() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncSpace().catch(console.error);
|
||||
|
||||
setInterval(async () => {
|
||||
@@ -227,10 +208,6 @@ export class SyncService {
|
||||
}
|
||||
|
||||
async syncSpace(): Promise<number> {
|
||||
if (!this.enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (await this.isSyncing()) {
|
||||
console.log("Aborting space sync: already syncing");
|
||||
return 0;
|
||||
@@ -258,10 +235,6 @@ export class SyncService {
|
||||
|
||||
// Syncs a single file
|
||||
async syncFile(name: string) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log("Checking if we can sync file", name);
|
||||
if (!this.isSyncCandidate(name)) {
|
||||
console.info("Requested sync, but not a sync candidate", name);
|
||||
@@ -326,10 +299,6 @@ export class SyncService {
|
||||
}
|
||||
await this.saveSnapshot(snapshot);
|
||||
await this.registerSyncStop(false);
|
||||
// HEAD
|
||||
// console.log("And done with file sync for", name);
|
||||
//
|
||||
//main
|
||||
}
|
||||
|
||||
async saveSnapshot(snapshot: Map<string, SyncStatusItem>) {
|
||||
@@ -383,3 +352,46 @@ export class SyncService {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A no-op sync service that doesn't do anything used when running in thin client mode
|
||||
*/
|
||||
export class NoSyncSyncService implements ISyncService {
|
||||
constructor(private space: Space) {
|
||||
}
|
||||
|
||||
isSyncing(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
hasInitialSyncCompleted(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
noOngoingSync(_timeout: number): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
scheduleFileSync(_path: string): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
scheduleSpaceSync(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
start() {
|
||||
setInterval(() => {
|
||||
// Trigger a page upload for change events
|
||||
this.space.updatePageList().catch(console.error);
|
||||
}, spaceSyncInterval);
|
||||
}
|
||||
|
||||
syncSpace(): Promise<number> {
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
syncFile(_name: string): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,20 +171,9 @@ export function editorSyscalls(editor: Client): SysCallMapping {
|
||||
return editor.confirm(message);
|
||||
},
|
||||
"editor.getUiOption": (_ctx, key: string): any => {
|
||||
if (key === "thinClientMode") {
|
||||
return !!localStorage.getItem("thinClientMode");
|
||||
}
|
||||
return (editor.ui.viewState.uiOptions as any)[key];
|
||||
},
|
||||
"editor.setUiOption": (_ctx, key: string, value: any) => {
|
||||
if (key === "thinClientMode") {
|
||||
if (value) {
|
||||
localStorage.setItem("thinClientMode", "true");
|
||||
} else {
|
||||
localStorage.removeItem("thinClientMode");
|
||||
}
|
||||
return;
|
||||
}
|
||||
editor.ui.viewDispatch({
|
||||
type: "set-ui-option",
|
||||
key,
|
||||
|
||||
+18
-6
@@ -5,8 +5,8 @@ import { CommandDef } from "../hooks/command.ts";
|
||||
import { proxySyscall } from "./util.ts";
|
||||
|
||||
export function systemSyscalls(
|
||||
editor: Client,
|
||||
system: System<any>,
|
||||
client?: Client,
|
||||
): SysCallMapping {
|
||||
const api: SysCallMapping = {
|
||||
"system.invokeFunction": (
|
||||
@@ -38,24 +38,36 @@ export function systemSyscalls(
|
||||
if (!functionDef) {
|
||||
throw Error(`Function ${name} not found`);
|
||||
}
|
||||
if (functionDef.env && system.env && functionDef.env !== system.env) {
|
||||
if (
|
||||
client && functionDef.env && system.env &&
|
||||
functionDef.env !== system.env
|
||||
) {
|
||||
// Proxy to another environment
|
||||
return proxySyscall(ctx, editor.remoteSpacePrimitives, name, args);
|
||||
return proxySyscall(ctx, client.remoteSpacePrimitives, name, args);
|
||||
}
|
||||
return plug.invoke(name, args);
|
||||
},
|
||||
"system.invokeCommand": (_ctx, name: string) => {
|
||||
return editor.runCommandByName(name);
|
||||
if (!client) {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
return client.runCommandByName(name);
|
||||
},
|
||||
"system.listCommands": (): { [key: string]: CommandDef } => {
|
||||
if (!client) {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
const allCommands: { [key: string]: CommandDef } = {};
|
||||
for (const [cmd, def] of editor.system.commandHook.editorCommands) {
|
||||
for (const [cmd, def] of client.system.commandHook.editorCommands) {
|
||||
allCommands[cmd] = def.command;
|
||||
}
|
||||
return allCommands;
|
||||
},
|
||||
"system.reloadPlugs": () => {
|
||||
return editor.loadPlugs();
|
||||
if (!client) {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
return client.loadPlugs();
|
||||
},
|
||||
"system.getEnv": () => {
|
||||
return system.env;
|
||||
|
||||
Reference in New Issue
Block a user