mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
port to electron (#33)
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@import "./reset.less";
|
||||
@import "./theme.less";
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: var(--main-bg-color);
|
||||
color: var(--main-text-color);
|
||||
font: var(--base-font);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background-color: var(--scrollbar-background-color) !important;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-thumb-color) !important;
|
||||
border-radius: 4px;
|
||||
margin: 0 1px 0 1px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--scrollbar-thumb-hover-color) !important;
|
||||
}
|
||||
|
||||
.flex-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.text-fixed {
|
||||
font: var(--fixed-font);
|
||||
}
|
||||
|
||||
#main,
|
||||
.mainapp {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 35px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
flex-shrink: 0;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.error-boundary {
|
||||
color: var(--error-color);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Provider } from "jotai";
|
||||
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import "../../public/style.less";
|
||||
import "./app.less";
|
||||
import { CenteredDiv } from "./element/quickelems";
|
||||
|
||||
const App = () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from "react";
|
||||
import "./quickelems.less";
|
||||
|
||||
function CenteredLoadingDiv() {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1.2;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
img,
|
||||
picture,
|
||||
video,
|
||||
canvas,
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Events } from "@wailsio/runtime";
|
||||
import * as jotai from "jotai";
|
||||
import * as rxjs from "rxjs";
|
||||
import * as WOS from "./wos";
|
||||
import { WSControl } from "./ws";
|
||||
|
||||
// TODO remove the window dependency completely
|
||||
// we should have the initialization be more orderly -- proceed directly from wave.ts instead of on its own.
|
||||
const globalStore = jotai.createStore();
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const globalWindowId = urlParams.get("windowid");
|
||||
const globalClientId = urlParams.get("clientid");
|
||||
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";
|
||||
}
|
||||
const windowIdAtom = jotai.atom(null) as jotai.PrimitiveAtom<string>;
|
||||
const clientIdAtom = jotai.atom(null) as jotai.PrimitiveAtom<string>;
|
||||
globalStore.set(windowIdAtom, globalWindowId);
|
||||
@@ -18,7 +25,7 @@ const uiContextAtom = jotai.atom((get) => {
|
||||
const windowData = get(windowDataAtom);
|
||||
const uiContext: UIContext = {
|
||||
windowid: get(atoms.windowId),
|
||||
activetabid: windowData.activetabid,
|
||||
activetabid: windowData?.activetabid,
|
||||
};
|
||||
return uiContext;
|
||||
}) as jotai.Atom<UIContext>;
|
||||
@@ -34,7 +41,8 @@ const windowDataAtom: jotai.Atom<WaveWindow> = jotai.atom((get) => {
|
||||
if (windowId == null) {
|
||||
return null;
|
||||
}
|
||||
return WOS.getObjectValue(WOS.makeORef("window", windowId), get);
|
||||
const rtn = WOS.getObjectValue<WaveWindow>(WOS.makeORef("window", windowId), get);
|
||||
return rtn;
|
||||
});
|
||||
const workspaceAtom: jotai.Atom<Workspace> = jotai.atom((get) => {
|
||||
const windowData = get(windowDataAtom);
|
||||
@@ -56,10 +64,10 @@ const atoms = {
|
||||
|
||||
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
|
||||
|
||||
const blockSubjects = new Map<string, SubjectWithRef<any>>();
|
||||
const orefSubjects = new Map<string, SubjectWithRef<any>>();
|
||||
|
||||
function getBlockSubject(blockId: string): SubjectWithRef<any> {
|
||||
let subject = blockSubjects.get(blockId);
|
||||
function getORefSubject(oref: string): SubjectWithRef<any> {
|
||||
let subject = orefSubjects.get(oref);
|
||||
if (subject == null) {
|
||||
subject = new rxjs.Subject<any>() as any;
|
||||
subject.refCount = 0;
|
||||
@@ -67,29 +75,15 @@ function getBlockSubject(blockId: string): SubjectWithRef<any> {
|
||||
subject.refCount--;
|
||||
if (subject.refCount === 0) {
|
||||
subject.complete();
|
||||
blockSubjects.delete(blockId);
|
||||
orefSubjects.delete(oref);
|
||||
}
|
||||
};
|
||||
blockSubjects.set(blockId, subject);
|
||||
orefSubjects.set(oref, subject);
|
||||
}
|
||||
subject.refCount++;
|
||||
return subject;
|
||||
}
|
||||
|
||||
Events.On("block:ptydata", (event: any) => {
|
||||
const data = event?.data;
|
||||
if (data?.blockid == null) {
|
||||
console.log("block:ptydata with null blockid");
|
||||
return;
|
||||
}
|
||||
// we don't use getBlockSubject here because we don't want to create a new subject
|
||||
const subject = blockSubjects.get(data.blockid);
|
||||
if (subject == null) {
|
||||
return;
|
||||
}
|
||||
subject.next(data);
|
||||
});
|
||||
|
||||
const blockCache = new Map<string, Map<string, any>>();
|
||||
|
||||
function useBlockCache<T>(blockId: string, name: string, makeFn: () => T): T {
|
||||
@@ -123,4 +117,44 @@ function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom
|
||||
return atom as jotai.Atom<T>;
|
||||
}
|
||||
|
||||
export { WOS, atoms, getBlockSubject, globalStore, useBlockAtom, useBlockCache };
|
||||
function getBackendHostPort(): string {
|
||||
// TODO deal with dev/production
|
||||
return "http://localhost:8190";
|
||||
}
|
||||
|
||||
function getBackendWSHostPort(): string {
|
||||
return "ws://localhost:8191";
|
||||
}
|
||||
|
||||
let globalWS: WSControl = null;
|
||||
|
||||
function handleWSEventMessage(msg: WSEventType) {
|
||||
if (msg.oref == null) {
|
||||
console.log("unsupported event", msg);
|
||||
return;
|
||||
}
|
||||
// we don't use getORefSubject here because we don't want to create a new subject
|
||||
const subject = orefSubjects.get(msg.oref);
|
||||
if (subject == null) {
|
||||
return;
|
||||
}
|
||||
subject.next(msg.data);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
export { WOS, atoms, getBackendHostPort, getORefSubject, globalStore, globalWS, initWS, useBlockAtom, useBlockCache };
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// generated by cmd/generate/main-generate.go
|
||||
|
||||
import * as WOS from "./wos";
|
||||
|
||||
// blockservice.BlockService (block)
|
||||
class BlockServiceType {
|
||||
// send command to block
|
||||
SendCommand(blockid: string, command: MetaType): Promise<void> {
|
||||
return WOS.callBackendService("block", "SendCommand", Array.from(arguments))
|
||||
}
|
||||
}
|
||||
|
||||
export const BlockService = new BlockServiceType()
|
||||
|
||||
// clientservice.ClientService (client)
|
||||
class ClientServiceType {
|
||||
GetClientData(): Promise<Client> {
|
||||
return WOS.callBackendService("client", "GetClientData", Array.from(arguments))
|
||||
}
|
||||
GetTab(arg1: string): Promise<Tab> {
|
||||
return WOS.callBackendService("client", "GetTab", Array.from(arguments))
|
||||
}
|
||||
GetWindow(arg1: string): Promise<Window> {
|
||||
return WOS.callBackendService("client", "GetWindow", Array.from(arguments))
|
||||
}
|
||||
GetWorkspace(arg1: string): Promise<Workspace> {
|
||||
return WOS.callBackendService("client", "GetWorkspace", Array.from(arguments))
|
||||
}
|
||||
}
|
||||
|
||||
export const ClientService = new ClientServiceType()
|
||||
|
||||
// fileservice.FileService (file)
|
||||
class FileServiceType {
|
||||
GetWaveFile(arg1: string, arg2: string): Promise<any> {
|
||||
return WOS.callBackendService("file", "GetWaveFile", Array.from(arguments))
|
||||
}
|
||||
ReadFile(arg1: string): Promise<FullFile> {
|
||||
return WOS.callBackendService("file", "ReadFile", Array.from(arguments))
|
||||
}
|
||||
StatFile(arg1: string): Promise<FileInfo> {
|
||||
return WOS.callBackendService("file", "StatFile", Array.from(arguments))
|
||||
}
|
||||
}
|
||||
|
||||
export const FileService = new FileServiceType()
|
||||
|
||||
// objectservice.ObjectService (object)
|
||||
class ObjectServiceType {
|
||||
// @returns tabId (and object updates)
|
||||
AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<string> {
|
||||
return WOS.callBackendService("object", "AddTabToWorkspace", Array.from(arguments))
|
||||
}
|
||||
|
||||
// @returns object updates
|
||||
CloseTab(tabId: string): Promise<void> {
|
||||
return WOS.callBackendService("object", "CloseTab", Array.from(arguments))
|
||||
}
|
||||
|
||||
// @returns blockId (and object updates)
|
||||
CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<string> {
|
||||
return WOS.callBackendService("object", "CreateBlock", Array.from(arguments))
|
||||
}
|
||||
|
||||
// @returns object updates
|
||||
DeleteBlock(blockId: string): Promise<void> {
|
||||
return WOS.callBackendService("object", "DeleteBlock", Array.from(arguments))
|
||||
}
|
||||
|
||||
// get wave object by oref
|
||||
GetObject(oref: string): Promise<WaveObj> {
|
||||
return WOS.callBackendService("object", "GetObject", Array.from(arguments))
|
||||
}
|
||||
|
||||
// @returns objects
|
||||
GetObjects(orefs: string[]): Promise<WaveObj[]> {
|
||||
return WOS.callBackendService("object", "GetObjects", Array.from(arguments))
|
||||
}
|
||||
|
||||
// @returns object updates
|
||||
SetActiveTab(tabId: string): Promise<void> {
|
||||
return WOS.callBackendService("object", "SetActiveTab", Array.from(arguments))
|
||||
}
|
||||
|
||||
// @returns object updates
|
||||
UpdateObject(waveObj: WaveObj, returnUpdates: boolean): Promise<void> {
|
||||
return WOS.callBackendService("object", "UpdateObject", Array.from(arguments))
|
||||
}
|
||||
|
||||
// @returns object updates
|
||||
UpdateObjectMeta(oref: string, meta: MetaType): Promise<void> {
|
||||
return WOS.callBackendService("object", "UpdateObjectMeta", Array.from(arguments))
|
||||
}
|
||||
}
|
||||
|
||||
export const ObjectService = new ObjectServiceType()
|
||||
|
||||
+74
-68
@@ -3,10 +3,13 @@
|
||||
|
||||
// WaveObjectStore
|
||||
|
||||
import { Call as $Call, Events } from "@wailsio/runtime";
|
||||
// import { Call as $Call, Events } from "@wailsio/runtime";
|
||||
import * as jotai from "jotai";
|
||||
import * as React from "react";
|
||||
import { atoms, globalStore } from "./global";
|
||||
import { atoms, getBackendHostPort, globalStore } from "./global";
|
||||
import * as services from "./services";
|
||||
|
||||
const IsElectron = true;
|
||||
|
||||
type WaveObjectDataItemType<T extends WaveObj> = {
|
||||
value: T;
|
||||
@@ -54,7 +57,51 @@ function makeORef(otype: string, oid: string): string {
|
||||
}
|
||||
|
||||
function GetObject<T>(oref: string): Promise<T> {
|
||||
return $Call.ByName("github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService.GetObject", oref);
|
||||
return callBackendService("object", "GetObject", [oref], true);
|
||||
}
|
||||
|
||||
function callBackendService(service: string, method: string, args: any[], noUIContext?: boolean): Promise<any> {
|
||||
const startTs = Date.now();
|
||||
let uiContext: UIContext = null;
|
||||
if (!noUIContext) {
|
||||
uiContext = globalStore.get(atoms.uiContext);
|
||||
}
|
||||
let waveCall: WebCallType = {
|
||||
service: service,
|
||||
method: method,
|
||||
args: args,
|
||||
uicontext: uiContext,
|
||||
};
|
||||
// usp is just for debugging (easier to filter URLs)
|
||||
let methodName = service + "." + method;
|
||||
let usp = new URLSearchParams();
|
||||
usp.set("service", service);
|
||||
usp.set("method", method);
|
||||
let fetchPromise = fetch(getBackendHostPort() + "/wave/service?" + usp.toString(), {
|
||||
method: "POST",
|
||||
body: JSON.stringify(waveCall),
|
||||
});
|
||||
let prtn = fetchPromise
|
||||
.then((resp) => {
|
||||
if (!resp.ok) {
|
||||
throw new Error(`call ${methodName} failed: ${resp.status} ${resp.statusText}`);
|
||||
}
|
||||
return resp.json();
|
||||
})
|
||||
.then((respData: WebReturnType) => {
|
||||
if (respData == null) {
|
||||
return null;
|
||||
}
|
||||
if (respData.updates != null) {
|
||||
updateWaveObjects(respData.updates);
|
||||
}
|
||||
if (respData.error != null) {
|
||||
throw new Error(`call ${methodName} error: ${respData.error}`);
|
||||
}
|
||||
console.log("Call", methodName, Date.now() - startTs + "ms");
|
||||
return respData.data;
|
||||
});
|
||||
return prtn;
|
||||
}
|
||||
|
||||
const waveObjectValueCache = new Map<string, WaveObjectValue<any>>();
|
||||
@@ -75,6 +122,7 @@ function createWaveValueObject<T extends WaveObj>(oref: string, shouldFetch: boo
|
||||
const localPromise = GetObject<T>(oref);
|
||||
wov.pendingPromise = localPromise;
|
||||
localPromise.then((val) => {
|
||||
console.log("GetObject resolved", oref, val);
|
||||
if (wov.pendingPromise != localPromise) {
|
||||
return;
|
||||
}
|
||||
@@ -187,7 +235,7 @@ function useWaveObject<T extends WaveObj>(oref: string): [T, boolean, (val: T) =
|
||||
const [atomVal, setAtomVal] = jotai.useAtom(wov.dataAtom);
|
||||
const simpleSet = (val: T) => {
|
||||
setAtomVal({ value: val, loading: false });
|
||||
UpdateObject(val, false);
|
||||
services.ObjectService.UpdateObject(val, false);
|
||||
};
|
||||
return [atomVal.value, atomVal.loading, simpleSet];
|
||||
}
|
||||
@@ -236,48 +284,32 @@ function cleanWaveObjectCache() {
|
||||
}
|
||||
}
|
||||
|
||||
Events.On("waveobj:update", (event: any) => {
|
||||
const data: WaveObjUpdate[] = event?.data;
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(data)) {
|
||||
console.log("invalid waveobj:update, not an array", data);
|
||||
return;
|
||||
}
|
||||
if (data.length == 0) {
|
||||
return;
|
||||
}
|
||||
updateWaveObjects(data);
|
||||
});
|
||||
|
||||
function wrapObjectServiceCall<T>(fnName: string, ...args: any[]): Promise<T> {
|
||||
const uiContext = globalStore.get(atoms.uiContext);
|
||||
const startTs = Date.now();
|
||||
let prtn = $Call.ByName(
|
||||
"github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService." + fnName,
|
||||
uiContext,
|
||||
...args
|
||||
);
|
||||
prtn = prtn.then((val) => {
|
||||
console.log("Call", fnName, Date.now() - startTs + "ms");
|
||||
if (val.updates) {
|
||||
updateWaveObjects(val.updates);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
return prtn;
|
||||
}
|
||||
// Events.On("waveobj:update", (event: any) => {
|
||||
// const data: WaveObjUpdate[] = event?.data;
|
||||
// if (data == null) {
|
||||
// return;
|
||||
// }
|
||||
// if (!Array.isArray(data)) {
|
||||
// console.log("invalid waveobj:update, not an array", data);
|
||||
// return;
|
||||
// }
|
||||
// if (data.length == 0) {
|
||||
// return;
|
||||
// }
|
||||
// updateWaveObjects(data);
|
||||
// });
|
||||
|
||||
// gets the value of a WaveObject from the cache.
|
||||
// should provide getFn if it is available (e.g. inside of a jotai atom)
|
||||
// otherwise it will use the globalStore.get function
|
||||
function getObjectValue<T>(oref: string, getFn?: jotai.Getter): T {
|
||||
const wov = waveObjectValueCache.get(oref);
|
||||
if (wov === undefined) {
|
||||
return null;
|
||||
let wov = waveObjectValueCache.get(oref);
|
||||
if (wov == null) {
|
||||
console.log("wov is null, creating new wov", oref);
|
||||
wov = createWaveValueObject(oref, true);
|
||||
waveObjectValueCache.set(oref, wov);
|
||||
}
|
||||
if (getFn === undefined) {
|
||||
if (getFn == null) {
|
||||
getFn = globalStore.get;
|
||||
}
|
||||
const atomVal = getFn(wov.dataAtom);
|
||||
@@ -298,38 +330,12 @@ function setObjectValue<T extends WaveObj>(value: T, setFn?: jotai.Setter, pushT
|
||||
}
|
||||
setFn(wov.dataAtom, { value: value, loading: false });
|
||||
if (pushToServer) {
|
||||
UpdateObject(value, false);
|
||||
services.ObjectService.UpdateObject(value, false);
|
||||
}
|
||||
}
|
||||
|
||||
export function AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<{ tabId: string }> {
|
||||
return wrapObjectServiceCall("AddTabToWorkspace", tabName, activateTab);
|
||||
}
|
||||
|
||||
export function SetActiveTab(tabId: string): Promise<void> {
|
||||
return wrapObjectServiceCall("SetActiveTab", tabId);
|
||||
}
|
||||
|
||||
export function CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<{ blockId: string }> {
|
||||
return wrapObjectServiceCall("CreateBlock", blockDef, rtOpts);
|
||||
}
|
||||
|
||||
export function DeleteBlock(blockId: string): Promise<void> {
|
||||
return wrapObjectServiceCall("DeleteBlock", blockId);
|
||||
}
|
||||
|
||||
export function CloseTab(tabId: string): Promise<void> {
|
||||
return wrapObjectServiceCall("CloseTab", tabId);
|
||||
}
|
||||
|
||||
export function UpdateObjectMeta(blockId: string, meta: MetadataType): Promise<void> {
|
||||
return wrapObjectServiceCall("UpdateObjectMeta", blockId, meta);
|
||||
}
|
||||
|
||||
export function UpdateObject(waveObj: WaveObj, returnUpdates: boolean): Promise<WaveObjUpdate[]> {
|
||||
return wrapObjectServiceCall("UpdateObject", waveObj, returnUpdates);
|
||||
}
|
||||
export {
|
||||
callBackendService,
|
||||
cleanWaveObjectCache,
|
||||
clearWaveObjectCache,
|
||||
getObjectValue,
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as jotai from "jotai";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
const MaxWebSocketSendSize = 1024 * 1024; // 1MB
|
||||
|
||||
type RpcEntry = {
|
||||
reqId: string;
|
||||
startTs: number;
|
||||
method: string;
|
||||
resolve: (any) => void;
|
||||
reject: (any) => void;
|
||||
promise: Promise<any>;
|
||||
};
|
||||
|
||||
type JotaiStore = {
|
||||
get: <Value>(atom: jotai.Atom<Value>) => Value;
|
||||
set: <Value>(atom: jotai.WritableAtom<Value, [Value], void>, value: Value) => void;
|
||||
};
|
||||
|
||||
class WSControl {
|
||||
wsConn: any;
|
||||
open: jotai.WritableAtom<boolean, [boolean], void>;
|
||||
opening: boolean = false;
|
||||
reconnectTimes: number = 0;
|
||||
msgQueue: any[] = [];
|
||||
windowId: string;
|
||||
messageCallback: (any) => void = null;
|
||||
watchSessionId: string = null;
|
||||
watchScreenId: string = null;
|
||||
wsLog: string[] = [];
|
||||
authKey: string;
|
||||
baseHostPort: string;
|
||||
lastReconnectTime: number = 0;
|
||||
rpcMap: Map<string, RpcEntry> = new Map(); // reqId -> RpcEntry
|
||||
jotaiStore: JotaiStore;
|
||||
|
||||
constructor(
|
||||
baseHostPort: string,
|
||||
jotaiStore: JotaiStore,
|
||||
windowId: string,
|
||||
authKey: string,
|
||||
messageCallback: (any) => void
|
||||
) {
|
||||
this.baseHostPort = baseHostPort;
|
||||
this.messageCallback = messageCallback;
|
||||
this.windowId = windowId;
|
||||
this.authKey = authKey;
|
||||
this.open = jotai.atom(false);
|
||||
this.jotaiStore = jotaiStore;
|
||||
setInterval(this.sendPing.bind(this), 5000);
|
||||
}
|
||||
|
||||
log(str: string) {
|
||||
let ts = Date.now();
|
||||
this.wsLog.push("[" + ts + "] " + str);
|
||||
if (this.wsLog.length > 50) {
|
||||
this.wsLog.splice(0, this.wsLog.length - 50);
|
||||
}
|
||||
}
|
||||
|
||||
setOpen(val: boolean) {
|
||||
this.jotaiStore.set(this.open, val);
|
||||
}
|
||||
|
||||
isOpen() {
|
||||
return this.jotaiStore.get(this.open);
|
||||
}
|
||||
|
||||
connectNow(desc: string) {
|
||||
if (this.isOpen()) {
|
||||
return;
|
||||
}
|
||||
this.lastReconnectTime = Date.now();
|
||||
this.log(sprintf("try reconnect (%s)", desc));
|
||||
this.opening = true;
|
||||
this.wsConn = new WebSocket(this.baseHostPort + "/ws?windowid=" + this.windowId);
|
||||
this.wsConn.onopen = this.onopen.bind(this);
|
||||
this.wsConn.onmessage = this.onmessage.bind(this);
|
||||
this.wsConn.onclose = this.onclose.bind(this);
|
||||
// turns out onerror is not necessary (onclose always follows onerror)
|
||||
// this.wsConn.onerror = this.onerror;
|
||||
}
|
||||
|
||||
reconnect(forceClose?: boolean) {
|
||||
if (this.isOpen()) {
|
||||
if (forceClose) {
|
||||
this.wsConn.close(); // this will force a reconnect
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.reconnectTimes++;
|
||||
if (this.reconnectTimes > 20) {
|
||||
this.log("cannot connect, giving up");
|
||||
return;
|
||||
}
|
||||
let timeoutArr = [0, 0, 2, 5, 10, 10, 30, 60];
|
||||
let timeout = 60;
|
||||
if (this.reconnectTimes < timeoutArr.length) {
|
||||
timeout = timeoutArr[this.reconnectTimes];
|
||||
}
|
||||
if (Date.now() - this.lastReconnectTime < 500) {
|
||||
timeout = 1;
|
||||
}
|
||||
if (timeout > 0) {
|
||||
this.log(sprintf("sleeping %ds", timeout));
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connectNow(String(this.reconnectTimes));
|
||||
}, timeout * 1000);
|
||||
}
|
||||
|
||||
onclose(event: any) {
|
||||
// console.log("close", event);
|
||||
if (event.wasClean) {
|
||||
this.log("connection closed");
|
||||
} else {
|
||||
this.log("connection error/disconnected");
|
||||
}
|
||||
if (this.isOpen() || this.opening) {
|
||||
this.setOpen(false);
|
||||
this.opening = false;
|
||||
this.reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
onopen() {
|
||||
this.log("connection open");
|
||||
this.setOpen(true);
|
||||
this.opening = false;
|
||||
this.runMsgQueue();
|
||||
// reconnectTimes is reset in onmessage:hello
|
||||
}
|
||||
|
||||
runMsgQueue() {
|
||||
if (!this.isOpen()) {
|
||||
return;
|
||||
}
|
||||
if (this.msgQueue.length == 0) {
|
||||
return;
|
||||
}
|
||||
let msg = this.msgQueue.shift();
|
||||
this.sendMessage(msg);
|
||||
setTimeout(() => {
|
||||
this.runMsgQueue();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
onmessage(event: any) {
|
||||
let eventData = null;
|
||||
if (event.data != null) {
|
||||
eventData = JSON.parse(event.data);
|
||||
}
|
||||
if (eventData == null) {
|
||||
return;
|
||||
}
|
||||
if (eventData.type == "ping") {
|
||||
this.wsConn.send(JSON.stringify({ type: "pong", stime: Date.now() }));
|
||||
return;
|
||||
}
|
||||
if (eventData.type == "pong") {
|
||||
// nothing
|
||||
return;
|
||||
}
|
||||
if (eventData.type == "hello") {
|
||||
this.reconnectTimes = 0;
|
||||
return;
|
||||
}
|
||||
if (eventData.type == "rpcresp") {
|
||||
this.handleRpcResp(eventData);
|
||||
return;
|
||||
}
|
||||
if (this.messageCallback) {
|
||||
try {
|
||||
this.messageCallback(eventData);
|
||||
} catch (e) {
|
||||
console.log("[error] messageCallback", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendPing() {
|
||||
if (!this.isOpen()) {
|
||||
return;
|
||||
}
|
||||
this.wsConn.send(JSON.stringify({ type: "ping", stime: Date.now() }));
|
||||
}
|
||||
|
||||
handleRpcResp(data: any) {
|
||||
let reqId = data.reqid;
|
||||
let rpcEntry = this.rpcMap.get(reqId);
|
||||
if (rpcEntry == null) {
|
||||
console.log("rpcresp for unknown reqid", reqId);
|
||||
return;
|
||||
}
|
||||
this.rpcMap.delete(reqId);
|
||||
console.log("rpcresp", rpcEntry.method, Math.round(performance.now() - rpcEntry.startTs) + "ms");
|
||||
if (data.error != null) {
|
||||
rpcEntry.reject(data.error);
|
||||
} else {
|
||||
rpcEntry.resolve(data.data);
|
||||
}
|
||||
}
|
||||
|
||||
doRpc(method: string, params: any[]): Promise<any> {
|
||||
if (!this.isOpen()) {
|
||||
return Promise.reject("not connected");
|
||||
}
|
||||
let reqId = uuidv4();
|
||||
let req = { type: "rpc", method: method, params: params, reqid: reqId };
|
||||
let rpcEntry: RpcEntry = {
|
||||
method: method,
|
||||
startTs: performance.now(),
|
||||
reqId: reqId,
|
||||
resolve: null,
|
||||
reject: null,
|
||||
promise: null,
|
||||
};
|
||||
let rpcPromise = new Promise((resolve, reject) => {
|
||||
rpcEntry.resolve = resolve;
|
||||
rpcEntry.reject = reject;
|
||||
});
|
||||
rpcEntry.promise = rpcPromise;
|
||||
this.rpcMap.set(reqId, rpcEntry);
|
||||
this.wsConn.send(JSON.stringify(req));
|
||||
return rpcPromise;
|
||||
}
|
||||
|
||||
sendMessage(data: any) {
|
||||
if (!this.isOpen()) {
|
||||
return;
|
||||
}
|
||||
let msg = JSON.stringify(data);
|
||||
const byteSize = new Blob([msg]).size;
|
||||
if (byteSize > MaxWebSocketSendSize) {
|
||||
console.log("ws message too large", byteSize, data.type, msg.substring(0, 100));
|
||||
return;
|
||||
}
|
||||
this.wsConn.send(msg);
|
||||
}
|
||||
|
||||
pushMessage(data: any) {
|
||||
if (!this.isOpen()) {
|
||||
this.msgQueue.push(data);
|
||||
return;
|
||||
}
|
||||
this.sendMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
export { WSControl };
|
||||
@@ -2,13 +2,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Block, BlockHeader } from "@/app/block/block";
|
||||
import * as services from "@/store/services";
|
||||
import * as WOS from "@/store/wos";
|
||||
|
||||
import { CenteredDiv, CenteredLoadingDiv } from "@/element/quickelems";
|
||||
import { TileLayout } from "@/faraday/index";
|
||||
import { getLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { CenteredDiv, CenteredLoadingDiv } from "../element/quickelems";
|
||||
import "./tab.less";
|
||||
|
||||
const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
@@ -34,7 +35,7 @@ const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
|
||||
const onNodeDelete = useCallback((data: TabLayoutData) => {
|
||||
console.log("onNodeDelete", data);
|
||||
return WOS.DeleteBlock(data.blockId);
|
||||
return services.ObjectService.DeleteBlock(data.blockId);
|
||||
}, []);
|
||||
|
||||
if (tabLoading) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
:root {
|
||||
/*
|
||||
* term colors (16 + 6) form the base terminal theme
|
||||
* for consistency these colors should be used by plugins/applications
|
||||
*/
|
||||
--term-black: #000000;
|
||||
--term-red: #cc0000;
|
||||
--term-green: #4e9a06;
|
||||
--term-yellow: #c4a000;
|
||||
--term-blue: #3465a4;
|
||||
--term-magenta: #bc3fbc;
|
||||
--term-cyan: #06989a;
|
||||
--term-white: #d0d0d0;
|
||||
--term-bright-black: #555753;
|
||||
--term-bright-red: #ef2929;
|
||||
--term-bright-green: #58c142;
|
||||
--term-bright-yellow: #fce94f;
|
||||
--term-bright-blue: #32afff;
|
||||
--term-bright-magenta: #ad7fa8;
|
||||
--term-bright-cyan: #34e2e2;
|
||||
--term-bright-white: #e7e7e7;
|
||||
|
||||
--term-gray: #8b918a; /* not an official terminal color */
|
||||
--term-cmdtext: #ffffff;
|
||||
--term-foreground: #d3d7cf;
|
||||
--term-background: #000000;
|
||||
--term-selection-background: #ffffff60;
|
||||
--term-cursor-accent: #000000;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
:root {
|
||||
--main-text-color: #f7f7f7;
|
||||
--title-font-size: 18px;
|
||||
--secondary-text-color: rgb(195, 200, 194);
|
||||
--main-bg-color: #000000;
|
||||
--border-color: #333333;
|
||||
--base-font: normal 15px / normal "Lato", sans-serif;
|
||||
--fixed-font: normal 12px / normal "Hack", monospace;
|
||||
--accent-color: rgb(88, 193, 66);
|
||||
--panel-bg-color: rgba(31, 33, 31, 1);
|
||||
--highlight-bg-color: rgba(255, 255, 255, 0.2);
|
||||
--markdown-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji";
|
||||
--error-color: rgb(229, 77, 46);
|
||||
--warning-color: rgb(224, 185, 86);
|
||||
--success-color: rgb(78, 154, 6);
|
||||
|
||||
/* scrollbar colors */
|
||||
--scrollbar-background-color: var(--main-bg-color);
|
||||
--scrollbar-thumb-color: rgba(255, 255, 255, 0.3);
|
||||
--scrollbar-thumb-hover-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ declare var monaco: Monaco;
|
||||
let monacoLoadedAtom = jotai.atom(false);
|
||||
|
||||
function loadMonaco() {
|
||||
loader.config({ paths: { vs: "./monaco" } });
|
||||
loader.config({ paths: { vs: "./dist-dev/monaco" } });
|
||||
loader
|
||||
.init()
|
||||
.then(() => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { FileInfo } from "@/bindings/fileservice";
|
||||
import { Table, createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
import * as jotai from "jotai";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { FileInfo, FileService, FullFile } from "@/bindings/fileservice";
|
||||
import { Markdown } from "@/element/markdown";
|
||||
import { useBlockAtom, useBlockCache } from "@/store/global";
|
||||
import { getBackendHostPort, useBlockAtom, useBlockCache } from "@/store/global";
|
||||
import * as services from "@/store/services";
|
||||
import * as WOS from "@/store/wos";
|
||||
import * as util from "@/util/util";
|
||||
import clsx from "clsx";
|
||||
@@ -69,7 +69,7 @@ function MarkdownPreview({ contentAtom }: { contentAtom: jotai.Atom<Promise<stri
|
||||
|
||||
function StreamingPreview({ fileInfo }: { fileInfo: FileInfo }) {
|
||||
const filePath = fileInfo.path;
|
||||
const streamingUrl = "/wave/stream-file?path=" + encodeURIComponent(filePath);
|
||||
const streamingUrl = getBackendHostPort() + "/wave/stream-file?path=" + encodeURIComponent(filePath);
|
||||
if (fileInfo.mimetype == "application/pdf") {
|
||||
return (
|
||||
<div className="view-preview view-preview-pdf">
|
||||
@@ -114,7 +114,7 @@ function PreviewView({ blockId }: { blockId: string }) {
|
||||
},
|
||||
(get, set, update) => {
|
||||
const blockId = get(blockAtom)?.oid;
|
||||
WOS.UpdateObjectMeta(`block:${blockId}`, { file: update });
|
||||
services.ObjectService.UpdateObjectMeta(`block:${blockId}`, { file: update });
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -124,7 +124,8 @@ function PreviewView({ blockId }: { blockId: string }) {
|
||||
if (fileName == null) {
|
||||
return null;
|
||||
}
|
||||
const statFile = await FileService.StatFile(fileName);
|
||||
// const statFile = await FileService.StatFile(fileName);
|
||||
const statFile = await services.FileService.StatFile(fileName);
|
||||
return statFile;
|
||||
})
|
||||
);
|
||||
@@ -134,7 +135,8 @@ function PreviewView({ blockId }: { blockId: string }) {
|
||||
if (fileName == null) {
|
||||
return null;
|
||||
}
|
||||
const file = await FileService.ReadFile(fileName);
|
||||
// const file = await FileService.ReadFile(fileName);
|
||||
const file = await services.FileService.ReadFile(fileName);
|
||||
return file;
|
||||
})
|
||||
);
|
||||
|
||||
+22
-22
@@ -1,15 +1,14 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { BlockService } from "@/bindings/blockservice";
|
||||
import { getBlockSubject } from "@/store/global";
|
||||
import { getBackendHostPort, getORefSubject, WOS } from "@/store/global";
|
||||
import * as services from "@/store/services";
|
||||
import { base64ToArray } from "@/util/util";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import type { ITheme } from "@xterm/xterm";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import * as React from "react";
|
||||
|
||||
import useResizeObserver from "@react-hook/resize-observer";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import "./view.less";
|
||||
import "/public/xterm.css";
|
||||
@@ -43,12 +42,15 @@ function getThemeFromCSSVars(el: Element): ITheme {
|
||||
}
|
||||
|
||||
function handleResize(fitAddon: FitAddon, blockId: string, term: Terminal) {
|
||||
if (term == null) {
|
||||
return;
|
||||
}
|
||||
const oldRows = term.rows;
|
||||
const oldCols = term.cols;
|
||||
fitAddon.fit();
|
||||
if (oldRows !== term.rows || oldCols !== term.cols) {
|
||||
const resizeCommand = { command: "controller:input", termsize: { rows: term.rows, cols: term.cols } };
|
||||
BlockService.SendCommand(blockId, resizeCommand);
|
||||
services.BlockService.SendCommand(blockId, resizeCommand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +63,6 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
const connectElemRef = React.useRef<HTMLDivElement>(null);
|
||||
const termRef = React.useRef<Terminal>(null);
|
||||
const initialLoadRef = React.useRef<InitialLoadDataType>({ loaded: false, heldData: [] });
|
||||
|
||||
const [fitAddon, setFitAddon] = React.useState<FitAddon>(null);
|
||||
const [term, setTerm] = React.useState<Terminal>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("terminal created");
|
||||
const newTerm = new Terminal({
|
||||
@@ -80,18 +78,22 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
newTerm.loadAddon(newFitAddon);
|
||||
newTerm.open(connectElemRef.current);
|
||||
newFitAddon.fit();
|
||||
BlockService.SendCommand(blockId, {
|
||||
// BlockService.SendCommand(blockId, {
|
||||
// command: "controller:input",
|
||||
// termsize: { rows: newTerm.rows, cols: newTerm.cols },
|
||||
// });
|
||||
services.BlockService.SendCommand(blockId, {
|
||||
command: "controller:input",
|
||||
termsize: { rows: newTerm.rows, cols: newTerm.cols },
|
||||
});
|
||||
newTerm.onData((data) => {
|
||||
const b64data = btoa(data);
|
||||
const inputCmd = { command: "controller:input", blockid: blockId, inputdata64: b64data };
|
||||
BlockService.SendCommand(blockId, inputCmd);
|
||||
services.BlockService.SendCommand(blockId, inputCmd);
|
||||
});
|
||||
|
||||
// block subject
|
||||
const blockSubject = getBlockSubject(blockId);
|
||||
const blockSubject = getORefSubject(WOS.makeORef("block", blockId));
|
||||
blockSubject.subscribe((data) => {
|
||||
// base64 decode
|
||||
const decodedData = base64ToArray(data.ptydata);
|
||||
@@ -101,10 +103,6 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
initialLoadRef.current.heldData.push(decodedData);
|
||||
}
|
||||
});
|
||||
|
||||
setTerm(newTerm);
|
||||
setFitAddon(newFitAddon);
|
||||
|
||||
// load data from filestore
|
||||
const startTs = Date.now();
|
||||
let loadedBytes = 0;
|
||||
@@ -112,7 +110,7 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
const usp = new URLSearchParams();
|
||||
usp.set("zoneid", blockId);
|
||||
usp.set("name", "main");
|
||||
fetch("/wave/file?" + usp.toString())
|
||||
fetch(getBackendHostPort() + "/wave/file?" + usp.toString())
|
||||
.then((resp) => {
|
||||
if (resp.ok) {
|
||||
return resp.arrayBuffer();
|
||||
@@ -133,18 +131,20 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
console.log(`terminal loaded file ${loadedBytes} bytes, ${Date.now() - startTs}ms`);
|
||||
});
|
||||
|
||||
const resize_debounced = debounce(50, () => {
|
||||
handleResize(newFitAddon, blockId, newTerm);
|
||||
});
|
||||
const rszObs = new ResizeObserver(() => {
|
||||
resize_debounced();
|
||||
});
|
||||
rszObs.observe(connectElemRef.current);
|
||||
|
||||
return () => {
|
||||
newTerm.dispose();
|
||||
blockSubject.release();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleResizeCallback = React.useCallback(() => {
|
||||
debounce(50, () => handleResize(fitAddon, blockId, term));
|
||||
}, [fitAddon, term]);
|
||||
|
||||
useResizeObserver(connectElemRef, handleResizeCallback);
|
||||
|
||||
return (
|
||||
<div className="view-term">
|
||||
<div key="conntectElem" className="term-connectelem" ref={connectElemRef}></div>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { TabContent } from "@/app/tab/tab";
|
||||
import { atoms } from "@/store/global";
|
||||
import * as services from "@/store/services";
|
||||
import * as WOS from "@/store/wos";
|
||||
import { clsx } from "clsx";
|
||||
import * as jotai from "jotai";
|
||||
@@ -21,10 +22,10 @@ function Tab({ tabId }: { tabId: string }) {
|
||||
const windowData = jotai.useAtomValue(atoms.waveWindow);
|
||||
const [tabData, tabLoading] = WOS.useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId));
|
||||
function setActiveTab() {
|
||||
WOS.SetActiveTab(tabId);
|
||||
services.ObjectService.SetActiveTab(tabId);
|
||||
}
|
||||
function handleCloseTab() {
|
||||
WOS.CloseTab(tabId);
|
||||
services.ObjectService.CloseTab(tabId);
|
||||
deleteLayoutStateAtomForTab(tabId);
|
||||
}
|
||||
return (
|
||||
@@ -45,7 +46,7 @@ function Tab({ tabId }: { tabId: string }) {
|
||||
function TabBar({ workspace }: { workspace: Workspace }) {
|
||||
function handleAddTab() {
|
||||
const newTabName = `Tab-${workspace.tabids.length + 1}`;
|
||||
WOS.AddTabToWorkspace(newTabName, true);
|
||||
services.ObjectService.AddTabToWorkspace(newTabName, true);
|
||||
}
|
||||
const tabIds = workspace?.tabids ?? [];
|
||||
return (
|
||||
@@ -83,7 +84,7 @@ function Widgets() {
|
||||
|
||||
async function createBlock(blockDef: BlockDef) {
|
||||
const rtOpts: RuntimeOpts = { termsize: { rows: 25, cols: 80 } };
|
||||
const { blockId } = await WOS.CreateBlock(blockDef, rtOpts);
|
||||
const blockId = await services.ObjectService.CreateBlock(blockDef, rtOpts);
|
||||
addBlockToTab(blockId);
|
||||
}
|
||||
|
||||
@@ -122,13 +123,13 @@ function Widgets() {
|
||||
<div className="widget" onClick={() => clickTerminal()}>
|
||||
<i className="fa fa-solid fa-square-terminal fa-fw" />
|
||||
</div>
|
||||
<div className="widget" onClick={() => clickPreview("README.md")}>
|
||||
<div className="widget" onClick={() => clickPreview("~/work/wails/thenextwave/README.md")}>
|
||||
<i className="fa fa-solid fa-files fa-fw" />
|
||||
</div>
|
||||
<div className="widget" onClick={() => clickPreview("go.mod")}>
|
||||
<div className="widget" onClick={() => clickPreview("~/work/wails/thenextwave/go.mod")}>
|
||||
<i className="fa fa-solid fa-files fa-fw" />
|
||||
</div>
|
||||
<div className="widget" onClick={() => clickPreview("build/appicon.png")}>
|
||||
<div className="widget" onClick={() => clickPreview("~/work/wails/thenextwave/build/appicon.png")}>
|
||||
<i className="fa fa-solid fa-files fa-fw" />
|
||||
</div>
|
||||
<div className="widget" onClick={() => clickPreview("~")}>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { TileLayout } from "./lib/TileLayout.jsx";
|
||||
import { newLayoutTreeStateAtom, useLayoutTreeStateReducerAtom, withLayoutTreeState } from "./lib/layoutAtom.js";
|
||||
import { newLayoutNode } from "./lib/layoutNode.js";
|
||||
import { TileLayout } from "./lib/TileLayout";
|
||||
import { newLayoutTreeStateAtom, useLayoutTreeStateReducerAtom, withLayoutTreeState } from "./lib/layoutAtom";
|
||||
import { newLayoutNode } from "./lib/layoutNode";
|
||||
import type {
|
||||
LayoutNode,
|
||||
LayoutTreeCommitPendingAction,
|
||||
@@ -14,8 +14,8 @@ import type {
|
||||
LayoutTreeState,
|
||||
WritableLayoutNodeAtom,
|
||||
WritableLayoutTreeStateAtom,
|
||||
} from "./lib/model.js";
|
||||
import { LayoutTreeActionType } from "./lib/model.js";
|
||||
} from "./lib/model";
|
||||
import { LayoutTreeActionType } from "./lib/model";
|
||||
|
||||
export {
|
||||
LayoutTreeActionType,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
import React, {
|
||||
CSSProperties,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
@@ -18,8 +18,8 @@ import { useDrag, useDragLayer, useDrop } from "react-dnd";
|
||||
|
||||
import useResizeObserver from "@react-hook/resize-observer";
|
||||
import { toPng } from "html-to-image";
|
||||
import { useLayoutTreeStateReducerAtom } from "./layoutAtom.js";
|
||||
import { findNode } from "./layoutNode.js";
|
||||
import { useLayoutTreeStateReducerAtom } from "./layoutAtom";
|
||||
import { findNode } from "./layoutNode";
|
||||
import {
|
||||
ContentRenderer,
|
||||
LayoutNode,
|
||||
@@ -31,15 +31,9 @@ import {
|
||||
LayoutTreeState,
|
||||
PreviewRenderer,
|
||||
WritableLayoutTreeStateAtom,
|
||||
} from "./model.js";
|
||||
} from "./model";
|
||||
import "./tilelayout.less";
|
||||
import {
|
||||
Dimensions,
|
||||
FlexDirection,
|
||||
setTransform as createTransform,
|
||||
debounce,
|
||||
determineDropDirection,
|
||||
} from "./utils.js";
|
||||
import { Dimensions, FlexDirection, setTransform as createTransform, debounce, determineDropDirection } from "./utils";
|
||||
|
||||
export interface TileLayoutProps<T> {
|
||||
layoutTreeStateAtom: WritableLayoutTreeStateAtom<T>;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { WOS } from "@/app/store/global.js";
|
||||
import { WOS } from "@/app/store/global";
|
||||
import { Atom, Getter, PrimitiveAtom, WritableAtom, atom, useAtom } from "jotai";
|
||||
import { useCallback } from "react";
|
||||
import { layoutTreeStateReducer, newLayoutTreeState } from "./layoutState.js";
|
||||
import { layoutTreeStateReducer, newLayoutTreeState } from "./layoutState";
|
||||
import {
|
||||
LayoutNode,
|
||||
LayoutNodeWaveObj,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { LayoutNode } from "./model.js";
|
||||
import { FlexDirection, getCrypto, reverseFlexDirection } from "./utils.js";
|
||||
import { LayoutNode } from "./model";
|
||||
import { FlexDirection, getCrypto, reverseFlexDirection } from "./utils";
|
||||
|
||||
const crypto = getCrypto();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user