Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/Screenshot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async function takeScreenshot(): Promise<void> {
params,
},
{
// `blob` is a wslink attachment: the viewer sends its key, the client receives the binary in its place
// `blob` is the image file content, received as binary (Uint8Array) through wslink
response_function: async ({ blob }) => {
if (screenshot_type.value === "file") {
fileDownload(blob, `${current_filename}.${output_extension.value}`);
Expand Down
10 changes: 6 additions & 4 deletions app/composables/clipping_planes_widget_entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ function asWidgetFactory(widget: PlaneWidget): vtkAbstractWidgetFactory<vtkAbstr
return widget as unknown as vtkAbstractWidgetFactory<vtkAbstractWidget>;
}

function asWidgetHandle(widget: unknown): WidgetHandle {
// oxlint-disable-next-line no-unsafe-type-assertion -- vtk.js WidgetManager.addWidget is untyped for our custom WidgetHandle shape; narrowed here.
return widget as WidgetHandle;
}

interface WidgetEntryManagerParams {
planes: Ref<ClippingPlane[]>;
debouncedApply: (...args: readonly unknown[]) => void;
Expand Down Expand Up @@ -136,10 +141,7 @@ function useWidgetEntryManager({ planes, debouncedApply }: WidgetEntryManagerPar
if (!widgetEntries[idx]) {
// oxlint-disable-next-line no-unsafe-call no-unsafe-type-assertion -- vtk.js has no types for ImplicitPlaneWidget's factory; narrowed here.
const planeWidget = vtkImplicitPlaneWidget() as PlaneWidget;
const widgetHandle = widgetManager.addWidget(
asWidgetFactory(planeWidget),
// oxlint-disable-next-line no-unsafe-type-assertion -- vtk.js WidgetManager.addWidget is untyped for our custom WidgetHandle shape; narrowed here.
) as unknown as WidgetHandle;
const widgetHandle = asWidgetHandle(widgetManager.addWidget(asWidgetFactory(planeWidget)));
widgetEntries.push(createWidgetEntry(planeWidget, widgetHandle, idx));
}
const entry = widgetEntries[idx];
Expand Down
13 changes: 4 additions & 9 deletions app/stores/camera_manager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { type Table, liveQuery } from "dexie";
import type { Observable } from "rxjs";
import type { Table } from "dexie";
import { useObservable } from "@vueuse/rxjs";

// Local imports
import type { CameraOptions } from "@ogw_internal/stores/hybrid_viewer/vtk_types.js";
import { database } from "@ogw_internal/database/database.js";
import { liveObservable } from "@ogw_front/utils/live_observable";
import { useViewerStore } from "@ogw_front/stores/viewer";
import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";

Expand All @@ -26,16 +26,11 @@ export const useCameraManagerStore = defineStore("camera_manager", () => {
>;

function refAllCameraPositions(): Ref<CameraPositionRecord[]> {
// Dexie's liveQuery() returns Dexie's own minimal Observable shape, not an
// Actual rxjs Observable instance (useObservable's declared parameter type);
// The two are structurally close enough at runtime (vueuse only calls
// `.subscribe`) but not identical, hence the cast.
return useObservable(
liveQuery(async () => {
liveObservable(async () => {
const positions = await camera_positions_db.toArray();
return positions;
// oxlint-disable-next-line no-unsafe-type-assertion -- trusted vueuse/Dexie Observable boundary; see comment above.
}) as unknown as Observable<CameraPositionRecord[]>,
}),
{ initialValue: [] as CameraPositionRecord[] },
);
}
Expand Down
18 changes: 6 additions & 12 deletions app/stores/data.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// oxlint-disable eslint/max-lines
// Third party imports
import { type Table, liveQuery } from "dexie";
import type { Observable } from "rxjs";
import type { Table } from "dexie";
import { useObservable } from "@vueuse/rxjs";
import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";

// Local imports
import { type ModelComponentRecord, useDataMesh } from "./data_helpers/mesh.js";
import { database } from "@ogw_internal/database/database.js";
import { liveObservable } from "@ogw_front/utils/live_observable";
import { useDataCollections } from "./data_helpers/collections.js";
import { useViewerStore } from "@ogw_front/stores/viewer";

Expand Down Expand Up @@ -137,16 +137,11 @@ export const useDataStore = defineStore("data", () => {
return items;
}
function refItem(id: string): Ref<DataItem | undefined> {
// Dexie's liveQuery() returns Dexie's own minimal Observable shape, not an
// Actual rxjs Observable instance (useObservable's declared parameter type);
// The two are structurally close enough at runtime (vueuse only calls
// `.subscribe`) but not identical, hence the cast.
return useObservable(
liveQuery(async () => {
liveObservable(async () => {
const data_item = await data_db.get(id);
return data_item;
// oxlint-disable-next-line no-unsafe-type-assertion -- trusted vueuse/Dexie Observable boundary; see comment above.
}) as unknown as Observable<DataItem | undefined>,
}),
{
// oxlint-disable-next-line no-unsafe-type-assertion -- placeholder until the live query resolves; consumers must treat this as possibly incomplete.
initialValue: {} as DataItem,
Expand All @@ -155,11 +150,10 @@ export const useDataStore = defineStore("data", () => {
}
function refAllItems(): Ref<DataItem[]> {
return useObservable(
liveQuery(async () => {
liveObservable(async () => {
const items = await data_db.toArray();
return items;
// oxlint-disable-next-line no-unsafe-type-assertion -- trusted vueuse/Dexie Observable boundary; see comment above.
}) as unknown as Observable<DataItem[]>,
}),
{
initialValue: [] as DataItem[],
},
Expand Down
9 changes: 3 additions & 6 deletions app/stores/data_helpers/collections.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { type FormattedComponent, type ModelComponentRecord, useDataMesh } from "./mesh.js";
import { MESH_COMPONENT_TYPES } from "@ogw_front/utils/default_styles";
import type { Observable } from "rxjs";
import type { Ref } from "vue";
import { getTable } from "@ogw_internal/database/database.js";
import { liveQuery } from "dexie";
import { liveObservable } from "@ogw_front/utils/live_observable";
import { useObservable } from "@vueuse/rxjs";

interface ModelComponentRelationRecord {
Expand Down Expand Up @@ -115,12 +114,10 @@ export function useDataCollections(): {
modelId: string,
): Ref<CollectionComponentGroup[] | undefined> {
return useObservable(
// Dexie's liveQuery returns its own Observable-like type, not rxjs's Observable, so bridging needs a cast.
liveQuery(async () => {
liveObservable(async () => {
const groups = await formatedCollectionComponents(modelId);
return groups;
// oxlint-disable-next-line no-unsafe-type-assertion
}) as unknown as Observable<CollectionComponentGroup[]>,
}),
{
initialValue: undefined,
},
Expand Down
9 changes: 4 additions & 5 deletions app/stores/data_helpers/mesh.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Table, liveQuery } from "dexie";
import type { Observable } from "rxjs";
import type { Table } from "dexie";
import { database } from "@ogw_internal/database/database.js";
import { liveObservable } from "@ogw_front/utils/live_observable";
import { useObservable } from "@vueuse/rxjs";

interface ModelComponentRecord {
Expand Down Expand Up @@ -87,11 +87,10 @@ export function useDataMesh(): {

function refFormatedMeshComponents(modelId: string): Ref<FormattedComponentGroup[] | undefined> {
return useObservable(
liveQuery(async () => {
liveObservable(async () => {
const components = await formatedMeshComponents(modelId);
return components;
// oxlint-disable-next-line no-unsafe-type-assertion
}) as unknown as Observable<FormattedComponentGroup[]>,
}),
{
initialValue: undefined,
},
Expand Down
7 changes: 4 additions & 3 deletions app/stores/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,13 @@ export const useViewerStore = defineStore(
}
try {
status.value = Status.CONNECTING;
client.value = (await initWebSocketClient(base_url.value, client.value, {
const wsClient = await initWebSocketClient(base_url.value, client.value, {
onConnectionClose: () => {
status.value = Status.NOT_CONNECTED;
},
// oxlint-disable-next-line no-unsafe-type-assertion -- initWebSocketClient's return is not typed as RpcClient.
})) as unknown as RpcClient;
});
// oxlint-disable-next-line no-unsafe-type-assertion -- initWebSocketClient's return is not typed as RpcClient.
client.value = wsClient as unknown as RpcClient;
connectImageStream(client.value.getConnection().getSession());
// oxlint-disable-next-line no-unsafe-type-assertion -- endBusy is not part of the RpcClient type.
(client.value as unknown as { endBusy: () => void }).endBusy();
Expand Down
14 changes: 14 additions & 0 deletions app/utils/live_observable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Third party imports
import type { Observable } from "rxjs";
import { liveQuery } from "dexie";

// Dexie's liveQuery() returns Dexie's own minimal Observable shape, not an
// Actual rxjs Observable instance (useObservable's declared parameter type);
// The two are structurally close enough at runtime (vueuse only calls
// `.subscribe`) but not identical, hence the cast.
function liveObservable<Value>(query: () => Promise<Value>): Observable<Value> {
// oxlint-disable-next-line no-unsafe-type-assertion -- trusted vueuse/Dexie Observable boundary; see comment above.
return liveQuery(query) as unknown as Observable<Value>;
}

export { liveObservable };
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"@geode/opengeodeweb-viewer": "0.0.0",
"@kitware/vtk.js": "33.3.0",
"@mdi/font": "7.4.47",
"@msgpack/msgpack": "2.8.0",
"@pinia/nuxt": "1.0.1",
"@types/busboy": "1.5.4",
"@types/node": "22.20.3",
Expand Down Expand Up @@ -101,7 +102,7 @@
"vuetify": "4.2.0",
"vuetify-nuxt-module": "1.0.0-rc.5",
"ws": "8.21.2",
"wslink": "1.12.4",
"wslink": "2.5.0",
"zod": "4.4.3"
},
"devDependencies": {
Expand Down
58 changes: 16 additions & 42 deletions server/utils/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import path from "node:path";
import { setTimeout } from "node:timers/promises";

// Third party imports
import { WebSocket } from "ws";
import { consola } from "consola";
import pTimeout from "p-timeout";
import { rimraf } from "rimraf";

// Local imports
import { createServerWsRpcClient } from "./ws_client.js";

interface Microservice {
type: "back" | "viewer";
name: string;
Expand Down Expand Up @@ -88,49 +90,21 @@ async function killWebsocketMicroservice(microservice: Microservice): Promise<vo
if (microservice.url === undefined) {
return;
}
const socketUrl = microservice.url;
const client = createServerWsRpcClient(microservice.url);
// oxlint-disable-next-line promise/avoid-new
await new Promise<void>((resolve) => {
const socket = new WebSocket(socketUrl);
socket.on("open", () => {
consola.info("Connected to WebSocket server");
socket.send(
JSON.stringify({
id: "system:hello",
method: "wslink.hello",
args: [
{
secret: "wslink-secret",
},
],
}),
);
});
socket.on("message", (data: Buffer | string) => {
const message = data.toString();
consola.debug("Received from server:", message);
if (message.includes("hello")) {
socket.send(
JSON.stringify({
id: "application.exit",
method: "application.exit",
}),
);
consola.info(successMessage);
socket.close();
resolve();
}
});
socket.on("close", () => {
consola.info(successMessage);
resolve();
});
socket.on("error", (error: Error) => {
consola.error("WebSocket error:", error);
socket.close();
resolve();
});
const closed = new Promise<void>((resolve) => {
client.onConnectionClose(resolve);
});
try {
await client.notify("application.exit");
// Wslink handles messages asynchronously: closing right away would drop the exit request
await closed;
consola.info(successMessage);
} catch (error) {
consola.error("WebSocket error:", error);
} finally {
client.close();
}
}
await pTimeout(do_kill(), {
milliseconds: 5000,
Expand Down
Loading
Loading