2024-05-14 08:45:41 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
import * as jotai from "jotai";
|
2024-05-15 01:53:03 +02:00
|
|
|
import * as rxjs from "rxjs";
|
2024-05-27 22:59:58 +02:00
|
|
|
import * as WOS from "./wos";
|
2024-06-12 02:42:10 +02:00
|
|
|
import { WSControl } from "./ws";
|
2024-05-14 08:45:41 +02:00
|
|
|
|
2024-06-12 02:42:10 +02:00
|
|
|
// TODO remove the window dependency completely
|
|
|
|
// we should have the initialization be more orderly -- proceed directly from wave.ts instead of on its own.
|
2024-05-14 08:45:41 +02:00
|
|
|
const globalStore = jotai.createStore();
|
2024-06-12 02:42:10 +02:00
|
|
|
let globalWindowId: string = null;
|
|
|
|
let globalClientId: string = null;
|
|
|
|
if (typeof window !== "undefined") {
|
|
|
|
// this if statement allows us to use the code in nodejs as well
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
globalWindowId = urlParams.get("windowid") || "74eba2d0-22fc-4221-82ad-d028dd496342";
|
|
|
|
globalClientId = urlParams.get("clientid") || "f4bc1713-a364-41b3-a5c4-b000ba10d622";
|
|
|
|
}
|
2024-05-27 22:59:58 +02:00
|
|
|
const windowIdAtom = jotai.atom(null) as jotai.PrimitiveAtom<string>;
|
|
|
|
const clientIdAtom = jotai.atom(null) as jotai.PrimitiveAtom<string>;
|
|
|
|
globalStore.set(windowIdAtom, globalWindowId);
|
|
|
|
globalStore.set(clientIdAtom, globalClientId);
|
|
|
|
const uiContextAtom = jotai.atom((get) => {
|
2024-05-28 00:44:57 +02:00
|
|
|
const windowData = get(windowDataAtom);
|
2024-05-27 22:59:58 +02:00
|
|
|
const uiContext: UIContext = {
|
|
|
|
windowid: get(atoms.windowId),
|
2024-06-12 02:42:10 +02:00
|
|
|
activetabid: windowData?.activetabid,
|
2024-05-27 22:59:58 +02:00
|
|
|
};
|
|
|
|
return uiContext;
|
|
|
|
}) as jotai.Atom<UIContext>;
|
|
|
|
const clientAtom: jotai.Atom<Client> = jotai.atom((get) => {
|
|
|
|
const clientId = get(clientIdAtom);
|
|
|
|
if (clientId == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-05-29 00:41:03 +02:00
|
|
|
return WOS.getObjectValue(WOS.makeORef("client", clientId), get);
|
2024-05-27 22:59:58 +02:00
|
|
|
});
|
|
|
|
const windowDataAtom: jotai.Atom<WaveWindow> = jotai.atom((get) => {
|
|
|
|
const windowId = get(windowIdAtom);
|
|
|
|
if (windowId == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-06-12 02:42:10 +02:00
|
|
|
const rtn = WOS.getObjectValue<WaveWindow>(WOS.makeORef("window", windowId), get);
|
|
|
|
return rtn;
|
2024-05-27 22:59:58 +02:00
|
|
|
});
|
|
|
|
const workspaceAtom: jotai.Atom<Workspace> = jotai.atom((get) => {
|
|
|
|
const windowData = get(windowDataAtom);
|
|
|
|
if (windowData == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-05-29 00:41:03 +02:00
|
|
|
return WOS.getObjectValue(WOS.makeORef("workspace", windowData.workspaceid), get);
|
2024-05-27 22:59:58 +02:00
|
|
|
});
|
2024-05-14 08:45:41 +02:00
|
|
|
|
|
|
|
const atoms = {
|
2024-05-24 23:08:24 +02:00
|
|
|
// initialized in wave.ts (will not be null inside of application)
|
2024-05-27 22:59:58 +02:00
|
|
|
windowId: windowIdAtom,
|
|
|
|
clientId: clientIdAtom,
|
|
|
|
uiContext: uiContextAtom,
|
|
|
|
client: clientAtom,
|
|
|
|
waveWindow: windowDataAtom,
|
|
|
|
workspace: workspaceAtom,
|
2024-05-14 08:45:41 +02:00
|
|
|
};
|
|
|
|
|
2024-05-15 01:53:03 +02:00
|
|
|
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
|
|
|
|
|
2024-06-13 23:41:28 +02:00
|
|
|
// key is "eventType" or "eventType|oref"
|
|
|
|
const eventSubjects = new Map<string, SubjectWithRef<WSEventType>>();
|
2024-06-14 08:54:04 +02:00
|
|
|
const fileSubjects = new Map<string, SubjectWithRef<WSFileEventData>>();
|
2024-05-15 01:53:03 +02:00
|
|
|
|
2024-06-13 23:41:28 +02:00
|
|
|
function getSubjectInternal(subjectKey: string): SubjectWithRef<WSEventType> {
|
|
|
|
let subject = eventSubjects.get(subjectKey);
|
2024-05-15 01:53:03 +02:00
|
|
|
if (subject == null) {
|
|
|
|
subject = new rxjs.Subject<any>() as any;
|
|
|
|
subject.refCount = 0;
|
|
|
|
subject.release = () => {
|
|
|
|
subject.refCount--;
|
|
|
|
if (subject.refCount === 0) {
|
|
|
|
subject.complete();
|
2024-06-13 23:41:28 +02:00
|
|
|
eventSubjects.delete(subjectKey);
|
2024-05-15 01:53:03 +02:00
|
|
|
}
|
|
|
|
};
|
2024-06-13 23:41:28 +02:00
|
|
|
eventSubjects.set(subjectKey, subject);
|
2024-05-15 01:53:03 +02:00
|
|
|
}
|
|
|
|
subject.refCount++;
|
|
|
|
return subject;
|
|
|
|
}
|
|
|
|
|
2024-06-13 23:41:28 +02:00
|
|
|
function getEventSubject(eventType: string): SubjectWithRef<WSEventType> {
|
|
|
|
return getSubjectInternal(eventType);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getEventORefSubject(eventType: string, oref: string): SubjectWithRef<WSEventType> {
|
|
|
|
return getSubjectInternal(eventType + "|" + oref);
|
|
|
|
}
|
|
|
|
|
2024-06-14 08:54:04 +02:00
|
|
|
function getFileSubject(zoneId: string, fileName: string): SubjectWithRef<WSFileEventData> {
|
|
|
|
const subjectKey = zoneId + "|" + fileName;
|
|
|
|
let subject = fileSubjects.get(subjectKey);
|
|
|
|
if (subject == null) {
|
|
|
|
subject = new rxjs.Subject<any>() as any;
|
|
|
|
subject.refCount = 0;
|
|
|
|
subject.release = () => {
|
|
|
|
subject.refCount--;
|
|
|
|
if (subject.refCount === 0) {
|
|
|
|
subject.complete();
|
|
|
|
fileSubjects.delete(subjectKey);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
fileSubjects.set(subjectKey, subject);
|
|
|
|
}
|
|
|
|
subject.refCount++;
|
|
|
|
return subject;
|
|
|
|
}
|
|
|
|
|
2024-05-28 23:24:36 +02:00
|
|
|
const blockCache = new Map<string, Map<string, any>>();
|
|
|
|
|
|
|
|
function useBlockCache<T>(blockId: string, name: string, makeFn: () => T): T {
|
|
|
|
let blockMap = blockCache.get(blockId);
|
|
|
|
if (blockMap == null) {
|
|
|
|
blockMap = new Map<string, any>();
|
|
|
|
blockCache.set(blockId, blockMap);
|
|
|
|
}
|
|
|
|
let value = blockMap.get(name);
|
|
|
|
if (value == null) {
|
|
|
|
value = makeFn();
|
|
|
|
blockMap.set(name, value);
|
|
|
|
}
|
|
|
|
return value as T;
|
|
|
|
}
|
|
|
|
|
2024-05-28 00:44:57 +02:00
|
|
|
const blockAtomCache = new Map<string, Map<string, jotai.Atom<any>>>();
|
|
|
|
|
2024-05-16 09:29:58 +02:00
|
|
|
function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom<T>): jotai.Atom<T> {
|
|
|
|
let blockCache = blockAtomCache.get(blockId);
|
|
|
|
if (blockCache == null) {
|
|
|
|
blockCache = new Map<string, jotai.Atom<any>>();
|
|
|
|
blockAtomCache.set(blockId, blockCache);
|
|
|
|
}
|
|
|
|
let atom = blockCache.get(name);
|
|
|
|
if (atom == null) {
|
|
|
|
atom = makeFn();
|
|
|
|
blockCache.set(name, atom);
|
2024-05-28 00:44:57 +02:00
|
|
|
console.log("New BlockAtom", blockId, name);
|
2024-05-16 09:29:58 +02:00
|
|
|
}
|
|
|
|
return atom as jotai.Atom<T>;
|
|
|
|
}
|
|
|
|
|
2024-06-12 02:42:10 +02:00
|
|
|
function getBackendHostPort(): string {
|
|
|
|
// TODO deal with dev/production
|
2024-06-14 20:10:54 +02:00
|
|
|
return "http://127.0.0.1:8190";
|
2024-06-12 02:42:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getBackendWSHostPort(): string {
|
2024-06-14 20:10:54 +02:00
|
|
|
return "ws://127.0.0.1:8191";
|
2024-06-12 02:42:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let globalWS: WSControl = null;
|
|
|
|
|
|
|
|
function handleWSEventMessage(msg: WSEventType) {
|
2024-06-13 23:41:28 +02:00
|
|
|
if (msg.eventtype == null) {
|
2024-06-12 02:42:10 +02:00
|
|
|
console.log("unsupported event", msg);
|
|
|
|
return;
|
|
|
|
}
|
2024-06-14 08:54:04 +02:00
|
|
|
if (msg.eventtype == "blockfile") {
|
|
|
|
const fileData: WSFileEventData = msg.data;
|
|
|
|
const fileSubject = getFileSubject(fileData.zoneid, fileData.filename);
|
|
|
|
if (fileSubject != null) {
|
|
|
|
fileSubject.next(fileData);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-06-13 23:41:28 +02:00
|
|
|
// we send to two subjects just eventType and eventType|oref
|
2024-06-12 02:42:10 +02:00
|
|
|
// we don't use getORefSubject here because we don't want to create a new subject
|
2024-06-13 23:41:28 +02:00
|
|
|
const eventSubject = eventSubjects.get(msg.eventtype);
|
|
|
|
if (eventSubject != null) {
|
|
|
|
eventSubject.next(msg);
|
|
|
|
}
|
|
|
|
const eventOrefSubject = eventSubjects.get(msg.eventtype + "|" + msg.oref);
|
|
|
|
if (eventOrefSubject != null) {
|
|
|
|
eventOrefSubject.next(msg);
|
2024-06-12 02:42:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleWSMessage(msg: any) {
|
|
|
|
if (msg == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (msg.eventtype != null) {
|
|
|
|
handleWSEventMessage(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function initWS() {
|
|
|
|
globalWS = new WSControl(getBackendWSHostPort(), globalStore, globalWindowId, "", (msg) => {
|
|
|
|
handleWSMessage(msg);
|
|
|
|
});
|
|
|
|
globalWS.connectNow("initWS");
|
|
|
|
}
|
|
|
|
|
2024-06-12 23:18:03 +02:00
|
|
|
function sendWSCommand(command: WSCommandType) {
|
|
|
|
globalWS.pushMessage(command);
|
|
|
|
}
|
|
|
|
|
2024-06-13 23:41:28 +02:00
|
|
|
// more code that could be moved into an init
|
|
|
|
// here we want to set up a "waveobj:update" handler
|
|
|
|
const waveobjUpdateSubject = getEventSubject("waveobj:update");
|
|
|
|
waveobjUpdateSubject.subscribe((msg: WSEventType) => {
|
|
|
|
const update: WaveObjUpdate = msg.data;
|
|
|
|
WOS.updateWaveObject(update);
|
|
|
|
});
|
|
|
|
|
2024-06-14 01:49:25 +02:00
|
|
|
function getApi(): ElectronApi {
|
|
|
|
return (window as any).api;
|
|
|
|
}
|
|
|
|
|
2024-06-12 23:18:03 +02:00
|
|
|
export {
|
|
|
|
WOS,
|
|
|
|
atoms,
|
2024-06-14 01:49:25 +02:00
|
|
|
getApi,
|
2024-06-12 23:18:03 +02:00
|
|
|
getBackendHostPort,
|
2024-06-13 23:41:28 +02:00
|
|
|
getEventORefSubject,
|
|
|
|
getEventSubject,
|
2024-06-14 08:54:04 +02:00
|
|
|
getFileSubject,
|
2024-06-12 23:18:03 +02:00
|
|
|
globalStore,
|
|
|
|
globalWS,
|
|
|
|
initWS,
|
|
|
|
sendWSCommand,
|
|
|
|
useBlockAtom,
|
|
|
|
useBlockCache,
|
|
|
|
};
|