Skip to content

Commit

Permalink
[drift] - improved fast refreshing and window state tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
emilbon99 committed Nov 13, 2024
1 parent 023bec1 commit a5d7fa1
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 191 deletions.
4 changes: 0 additions & 4 deletions console/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
#[cfg(target_os = "macos")]
extern crate cocoa;

#[cfg(target_os = "macos")]
extern crate objc;

use device_query::{DeviceEvents, DeviceQuery, DeviceState, MouseState};
use std::thread;
use std::time::Duration;
Expand Down Expand Up @@ -55,7 +52,6 @@ fn set_transparent_titlebar(_: &Window, _: bool) {}

fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::new().build())
.on_page_load(|window, _| {
set_transparent_titlebar(&window.window(), true);
return;
Expand Down
151 changes: 151 additions & 0 deletions console/src/Console.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright 2024 Synnax Labs, Inc.
//
// Use of this software is governed by the Business Source License included in the file
// licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with the Business Source
// License, use of this software will be governed by the Apache License, Version 2.0,
// included in the file licenses/APL.txt.

import "@/index.css";
import "@synnaxlabs/media/dist/style.css";
import "@synnaxlabs/pluto/dist/style.css";

import { Provider } from "@synnaxlabs/drift/react";
import { type Haul, Pluto, type state, type Triggers } from "@synnaxlabs/pluto";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { type ReactElement, useCallback, useEffect } from "react";
import { useDispatch } from "react-redux";

import { Channel } from "@/channel";
import { Cluster } from "@/cluster";
import { Confirm } from "@/confirm";
import { Docs } from "@/docs";
import { ErrorOverlayWithoutStore, ErrorOverlayWithStore } from "@/error/Overlay";
import { LabJack } from "@/hardware/labjack";
import { NI } from "@/hardware/ni";
import { OPC } from "@/hardware/opc";
import { Label } from "@/label";
import { Layout } from "@/layout";
import { Layouts } from "@/layouts";
import { LinePlot } from "@/lineplot";
import { Log } from "@/log";
import { Ontology } from "@/ontology";
import { Permissions } from "@/permissions";
import { Range } from "@/range";
import { Schematic } from "@/schematic";
import { SERVICES } from "@/services";
import { store } from "@/store";
import { User } from "@/user";
import { Version } from "@/version";
import { Vis } from "@/vis";
import WorkerURL from "@/worker?worker&url";
import { Workspace } from "@/workspace";

const LAYOUT_RENDERERS: Record<string, Layout.Renderer> = {
...Layouts.LAYOUTS,
...Docs.LAYOUTS,
...Workspace.LAYOUTS,
...Schematic.LAYOUTS,
...LinePlot.LAYOUTS,
...LabJack.LAYOUTS,
...OPC.LAYOUTS,
...Range.LAYOUTS,
...Cluster.LAYOUTS,
...NI.LAYOUTS,
...Channel.LAYOUTS,
...Version.LAYOUTS,
...Confirm.LAYOUTS,
...Label.LAYOUTS,
...User.LAYOUTS,
...Permissions.LAYOUTS,
...Log.LAYOUTS,
};

const CONTEXT_MENU_RENDERERS: Record<string, Layout.ContextMenuRenderer> = {
...Schematic.CONTEXT_MENUS,
...LinePlot.CONTEXT_MENUS,
};

const PREVENT_DEFAULT_TRIGGERS: Triggers.Trigger[] = [
["Control", "P"],
["Control", "Shift", "P"],
["Control", "MouseLeft"],
["Control", "W"],
];

const TRIGGERS_PROVIDER_PROPS: Triggers.ProviderProps = {
preventDefaultOn: PREVENT_DEFAULT_TRIGGERS,
preventDefaultOptions: { double: true },
};

const client = new QueryClient();

const useHaulState: state.PureUse<Haul.DraggingState> = () => {
const hauled = Layout.useSelectHauling();
const dispatch = useDispatch();
const onHauledChange = useCallback(
(state: Haul.DraggingState) => dispatch(Layout.setHauled(state)),
[dispatch],
);
return [hauled, onHauledChange];
};

const useBlockDefaultDropBehavior = (): void =>
useEffect(() => {
const doc = document.documentElement;
doc.addEventListener("dragover", (e) => e.preventDefault());
doc.addEventListener("drop", (e) => e.preventDefault());
return () => {
doc.removeEventListener("dragover", (e) => e.preventDefault());
doc.removeEventListener("drop", (e) => e.preventDefault());
};
}, []);

const MainUnderContext = (): ReactElement => {
const theme = Layout.useThemeProvider();
const cluster = Cluster.useSelect();
const activeRange = Range.useSelect();
useBlockDefaultDropBehavior();
return (
<QueryClientProvider client={client}>
<Pluto.Provider
theming={theme}
channelAlias={{
// Set the alias active range to undefined if the range is not saved in Synnax,
// otherwise it will try to pull aliases from a range that doesn't exist.
activeRange: activeRange?.persisted ? activeRange.key : undefined,
}}
workerEnabled
connParams={cluster?.props}
workerURL={WorkerURL}
triggers={TRIGGERS_PROVIDER_PROPS}
haul={{ useState: useHaulState }}
alamos={{
level: "debug",
include: [],
}}
>
<Vis.Canvas>
<Layout.Window />
</Vis.Canvas>
</Pluto.Provider>
</QueryClientProvider>
);
};

export const Console = (): ReactElement => (
<ErrorOverlayWithoutStore>
<Provider store={store}>
<ErrorOverlayWithStore>
<Layout.RendererProvider value={LAYOUT_RENDERERS}>
<Layout.ContextMenuProvider value={CONTEXT_MENU_RENDERERS}>
<Ontology.ServicesProvider services={SERVICES}>
<MainUnderContext />
</Ontology.ServicesProvider>
</Layout.ContextMenuProvider>
</Layout.RendererProvider>
</ErrorOverlayWithStore>
</Provider>
</ErrorOverlayWithoutStore>
);
10 changes: 10 additions & 0 deletions console/src/isDev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2024 Synnax Labs, Inc.
//
// Use of this software is governed by the Business Source License included in the file
// licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with the Business Source
// License, use of this software will be governed by the Apache License, Version 2.0,
// included in the file licenses/APL.txt.

export const isDev = () => (window as any).isDev;
146 changes: 3 additions & 143 deletions console/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,150 +7,10 @@
// License, use of this software will be governed by the Apache License, Version 2.0,
// included in the file licenses/APL.txt.

import "@/index.css";
import "@synnaxlabs/media/dist/style.css";
import "@synnaxlabs/pluto/dist/style.css";
import { createRoot } from "react-dom/client";

import { Provider } from "@synnaxlabs/drift/react";
import { type Haul, Pluto, type state, type Triggers } from "@synnaxlabs/pluto";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { type ReactElement, useCallback, useEffect } from "react";
import ReactDOM from "react-dom/client";
import { useDispatch } from "react-redux";

import { Channel } from "@/channel";
import { Cluster } from "@/cluster";
import { Confirm } from "@/confirm";
import { Docs } from "@/docs";
import { ErrorOverlayWithoutStore, ErrorOverlayWithStore } from "@/error/Overlay";
import { LabJack } from "@/hardware/labjack";
import { NI } from "@/hardware/ni";
import { OPC } from "@/hardware/opc";
import { Label } from "@/label";
import { Layout } from "@/layout";
import { Layouts } from "@/layouts";
import { LinePlot } from "@/lineplot";
import { Log } from "@/log";
import { Ontology } from "@/ontology";
import { Permissions } from "@/permissions";
import { Range } from "@/range";
import { Schematic } from "@/schematic";
import { SERVICES } from "@/services";
import { store } from "@/store";
import { User } from "@/user";
import { Version } from "@/version";
import { Vis } from "@/vis";
import WorkerURL from "@/worker?worker&url";
import { Workspace } from "@/workspace";

const LAYOUT_RENDERERS: Record<string, Layout.Renderer> = {
...Layouts.LAYOUTS,
...Docs.LAYOUTS,
...Workspace.LAYOUTS,
...Schematic.LAYOUTS,
...LinePlot.LAYOUTS,
...LabJack.LAYOUTS,
...OPC.LAYOUTS,
...Range.LAYOUTS,
...Cluster.LAYOUTS,
...NI.LAYOUTS,
...Channel.LAYOUTS,
...Version.LAYOUTS,
...Confirm.LAYOUTS,
...Label.LAYOUTS,
...User.LAYOUTS,
...Permissions.LAYOUTS,
...Log.LAYOUTS,
};

const CONTEXT_MENU_RENDERERS: Record<string, Layout.ContextMenuRenderer> = {
...Schematic.CONTEXT_MENUS,
...LinePlot.CONTEXT_MENUS,
};

const PREVENT_DEFAULT_TRIGGERS: Triggers.Trigger[] = [
["Control", "P"],
["Control", "Shift", "P"],
["Control", "MouseLeft"],
["Control", "W"],
];

const TRIGGERS_PROVIDER_PROPS: Triggers.ProviderProps = {
preventDefaultOn: PREVENT_DEFAULT_TRIGGERS,
preventDefaultOptions: { double: true },
};

const client = new QueryClient();

const useHaulState: state.PureUse<Haul.DraggingState> = () => {
const hauled = Layout.useSelectHauling();
const dispatch = useDispatch();
const onHauledChange = useCallback(
(state: Haul.DraggingState) => dispatch(Layout.setHauled(state)),
[dispatch],
);
return [hauled, onHauledChange];
};

const useBlockDefaultDropBehavior = (): void =>
useEffect(() => {
const doc = document.documentElement;
doc.addEventListener("dragover", (e) => e.preventDefault());
doc.addEventListener("drop", (e) => e.preventDefault());
return () => {
doc.removeEventListener("dragover", (e) => e.preventDefault());
doc.removeEventListener("drop", (e) => e.preventDefault());
};
}, []);

const MainUnderContext = (): ReactElement => {
const theme = Layout.useThemeProvider();
const cluster = Cluster.useSelect();
const activeRange = Range.useSelect();
useBlockDefaultDropBehavior();
return (
<QueryClientProvider client={client}>
<Pluto.Provider
theming={theme}
channelAlias={{
// Set the alias active range to undefined if the range is not saved in Synnax,
// otherwise it will try to pull aliases from a range that doesn't exist.
activeRange: activeRange?.persisted ? activeRange.key : undefined,
}}
workerEnabled
connParams={cluster?.props}
workerURL={WorkerURL}
triggers={TRIGGERS_PROVIDER_PROPS}
haul={{ useState: useHaulState }}
alamos={{
level: "debug",
include: [],
}}
>
<Vis.Canvas>
<Layout.Window />
</Vis.Canvas>
</Pluto.Provider>
</QueryClientProvider>
);
};

const Main = (): ReactElement => (
<ErrorOverlayWithoutStore>
<Provider store={store}>
<ErrorOverlayWithStore>
<Layout.RendererProvider value={LAYOUT_RENDERERS}>
<Layout.ContextMenuProvider value={CONTEXT_MENU_RENDERERS}>
<Ontology.ServicesProvider services={SERVICES}>
<MainUnderContext />
</Ontology.ServicesProvider>
</Layout.ContextMenuProvider>
</Layout.RendererProvider>
</ErrorOverlayWithStore>
</Provider>
</ErrorOverlayWithoutStore>
);
import { Console } from "@/Console";

const rootEl = document.getElementById("root") as HTMLElement;

ReactDOM.createRoot(rootEl).render(<Main />);
createRoot(rootEl).render(<Console />);
6 changes: 4 additions & 2 deletions console/src/persist/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
type UnknownAction,
} from "@reduxjs/toolkit";
import { MAIN_WINDOW } from "@synnaxlabs/drift";
import { debounce, deep, type UnknownRecord } from "@synnaxlabs/x";
import { debounce, deep, TimeSpan, type UnknownRecord } from "@synnaxlabs/x";
import { getCurrentWindow } from "@tauri-apps/api/window";

import { createTauriKV } from "@/persist/kv";
Expand Down Expand Up @@ -57,6 +57,8 @@ export const hardClearAndReload = () => {
.finally(window.location.reload);
};

const PERSIST_DEBOUNCE = TimeSpan.milliseconds(250).milliseconds;

export const open = async <S extends RequiredState>({
exclude = [],
initial,
Expand Down Expand Up @@ -102,7 +104,7 @@ export const open = async <S extends RequiredState>({
await db.set(DB_VERSION_KEY, { version }).catch(console.error);
await db.delete(persistedStateKey(version - KEEP_HISTORY)).catch(console.error);
})();
}, 500);
}, PERSIST_DEBOUNCE);

let state = (await db.get<S>(persistedStateKey(version))) ?? undefined;
if (state != null && migrator != null) {
Expand Down
6 changes: 4 additions & 2 deletions console/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { type deep } from "@synnaxlabs/x";

import { Cluster } from "@/cluster";
import { Docs } from "@/docs";
import { isDev } from "@/isDev";
import { Layout } from "@/layout";
import { LinePlot } from "@/lineplot";
import { Log } from "@/log";
Expand Down Expand Up @@ -87,7 +88,7 @@ export type RootAction =

export type RootStore = Store<RootState, RootAction>;

const DEFAULT_WINDOW_PROPS: Omit<Drift.WindowProps, "key"> = { visible: false };
const DEFAULT_WINDOW_PROPS: Omit<Drift.WindowProps, "key"> = { visible: isDev() };

export const migrateState = (prev: RootState): RootState => {
console.log("--------------- Migrating State ---------------");
Expand Down Expand Up @@ -125,7 +126,8 @@ const newStore = async (): Promise<RootStore> => {
if (preloadedState != null && Drift.SLICE_NAME in preloadedState) {
const windows = preloadedState[Drift.SLICE_NAME].windows;
Object.keys(windows).forEach((key) => {
windows[key].visible = false;
if (windows[key].key === "prerender") return;
windows[key].visible = isDev();
windows[key].focusCount = 0;
windows[key].centerCount = 0;
});
Expand Down
3 changes: 3 additions & 0 deletions console/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,7 @@ export default defineConfig({
// is loaded directly from disc instead of OTN
chunkSizeWarningLimit: 10000 /* kbs */,
},
define: {
isDev,
},
});
Loading

0 comments on commit a5d7fa1

Please sign in to comment.