parent
bc17629a67
commit
760bfb79cb
@ -1,24 +1,19 @@ |
||||
<script> |
||||
import { useSignal } from "@gn8/alien-signals-svelte"; |
||||
import { |
||||
deepSignalExample, |
||||
computedSignalFromExample, |
||||
} from "src/ng-mock/js-land/signalLibs/alienSignals"; |
||||
import useShape from "../../ng-mock/js-land/frontendAdapters/svelte/useShape"; |
||||
|
||||
const nestedObject = useSignal(deepSignalExample); |
||||
const computed = useSignal(computedSignalFromExample); |
||||
const nestedObject = useShape("Shape1", null); |
||||
</script> |
||||
|
||||
<div> |
||||
<p>Hello World from Svelte!</p> |
||||
|
||||
<p>Here is a reactive object with {$computed.get()} props.</p> |
||||
<pre>{JSON.stringify($nestedObject, null, 4)}</pre> |
||||
<button |
||||
on:click={() => { |
||||
$nestedObject.array.splice(0, 1); |
||||
window.svelteState = $nestedObject; |
||||
$nestedObject.name = $nestedObject.name.toUpperCase(); |
||||
}} |
||||
> |
||||
Click to remove first array element |
||||
upper-case name |
||||
</button> |
||||
</div> |
||||
|
@ -1,22 +1,29 @@ |
||||
<script setup lang="ts"> |
||||
import {useSignal} from '@gn8/alien-signals-vue'; |
||||
import { |
||||
deepSignalExample, |
||||
computedSignalFromExample, |
||||
} from "src/ng-mock/js-land/signalLibs/alienSignals"; |
||||
import type { WritableComputedRef } from 'vue'; |
||||
import useShape from '../../ng-mock/js-land/frontendAdapters/vue/useShape'; |
||||
|
||||
const state = useSignal(deepSignalExample); |
||||
const computed = useSignal(computedSignalFromExample); |
||||
const shapeObj = useShape("Shape1", "null"); |
||||
|
||||
window.vueState= shapeObj; |
||||
|
||||
const setName = () => { |
||||
shapeObj.value.name = "Bobby" |
||||
} |
||||
</script> |
||||
|
||||
|
||||
<template> |
||||
<div> |
||||
<p>Hello World from Vue!</p> |
||||
<p>Here is a reactive object with {{computed.get()}} props.</p> |
||||
<pre>{{JSON.stringify(state, null, 4)}}</pre> |
||||
<button @click="state.array.push(state.array.at(-1) + 1)"> |
||||
Click to add val |
||||
</button> |
||||
<div v-if="shapeObj != null"> |
||||
<p>Type is <em>{{shapeObj.type}}</em> with street <em>{{shapeObj.address.street}}</em></p> |
||||
|
||||
<button @click="setName"> |
||||
Click to switch name |
||||
</button> |
||||
</div> |
||||
<div v-else> |
||||
Loading state |
||||
</div> |
||||
</div> |
||||
</template> |
@ -1,6 +1,9 @@ |
||||
import type { Diff } from "../types"; |
||||
|
||||
/** Mock function to apply diffs. Just uses a copy of the diff as the new object. */ |
||||
export function applyDiff(currentState: object, diff: Diff): object { |
||||
return JSON.parse(JSON.stringify(diff)); |
||||
export function applyDiff(currentState: any, diff: Diff) { |
||||
const clone = JSON.parse(JSON.stringify(diff)); |
||||
Object.keys(clone).forEach((k) => { |
||||
currentState[k] = clone[k]; |
||||
}); |
||||
} |
||||
|
@ -1,31 +1,70 @@ |
||||
import handleShapeUpdate from "src/ng-mock/wasm-land/handleShapeUpdate"; |
||||
import updateShape from "src/ng-mock/wasm-land/updateShape"; |
||||
import type { Connection, Diff, Scope, Shape } from "../types"; |
||||
import handleShapeRequest from "src/ng-mock/wasm-land/handleShapeRequest"; |
||||
import requestShape from "src/ng-mock/wasm-land/requestShape"; |
||||
import { applyDiff } from "./applyDiff"; |
||||
import { batch, deepSignal, watch } from "alien-deepsignals"; |
||||
import { signal } from "alien-signals"; |
||||
|
||||
export async function createSignalObjectForShape(shape: Shape, scope?: Scope) { |
||||
const ret: { |
||||
state?: any; |
||||
connectionId?: Connection["id"]; |
||||
update: (diff: Diff) => Promise<void>; |
||||
} = { |
||||
async update(diff) { |
||||
if (!ret.connectionId) |
||||
throw new Error("Connection not established yet for shape" + shape); |
||||
await handleShapeUpdate(ret.connectionId, diff); |
||||
}, |
||||
// TODO: The code is horrible.
|
||||
export function createSignalObjectForShape(shape: Shape, scope?: Scope) { |
||||
// TODO:
|
||||
// DeepSignal has a different API to alien-signals.
|
||||
// Therefore, we need to create a "root signal" wrapper that is
|
||||
// triggered on deepSignal changes.
|
||||
const rootSignal = signal<null | object>(null); |
||||
|
||||
// State
|
||||
let stopWatcher: any = null; |
||||
let suspendWatcher = false; |
||||
|
||||
// To update the root signal
|
||||
const setUpDeepSignal = ( |
||||
newSignal: ReturnType<typeof deepSignal>, |
||||
connectionId: Connection["id"] |
||||
) => { |
||||
stopWatcher?.(); |
||||
|
||||
// Notify DB on changes.
|
||||
stopWatcher = watch( |
||||
newSignal, |
||||
(newVal, oldVal, onCleanup) => { |
||||
if (!suspendWatcher) updateShape(connectionId, newVal); |
||||
}, |
||||
{ deep: true } |
||||
); |
||||
|
||||
// Update the root signal.
|
||||
rootSignal(newSignal); |
||||
}; |
||||
|
||||
const onDbUpdate = (diff: Diff) => { |
||||
ret.state = applyDiff(ret.state || {}, diff); |
||||
const onUpdateFromDb = (diff: Diff, connectionId: Connection["id"]) => { |
||||
const nestedObj = rootSignal(); |
||||
console.log("Update received", connectionId, diff); |
||||
// Set new value from applying the diffs to the old value.
|
||||
|
||||
// suspendWatcher = true;
|
||||
// We need to replace the root signal for now, so this is redundant.
|
||||
// batch(() => {
|
||||
// if (!nestedObj) return; // This shouldn't happen but we make the compiler happy.
|
||||
// applyDiff(nestedObj, diff);
|
||||
// });
|
||||
// suspendWatcher = false;nestedObj
|
||||
|
||||
// Create a new deep signal.
|
||||
// We need to do that because the deepSignals ref hasn't changed otherwise
|
||||
// and no update is triggered.
|
||||
const newDeepSignal = deepSignal(JSON.parse(JSON.stringify(diff))); |
||||
setUpDeepSignal(newDeepSignal, connectionId); |
||||
}; |
||||
|
||||
await handleShapeRequest(shape, onDbUpdate).then( |
||||
// Do the actual db request.
|
||||
requestShape(shape, scope, onUpdateFromDb).then( |
||||
({ connectionId, shapeObject }) => { |
||||
ret.state = shapeObject; |
||||
ret.connectionId = connectionId; |
||||
// Create a deepSignal to put into the vanilla alien-signal.
|
||||
const deepSignalFromDb = deepSignal(shapeObject); |
||||
setUpDeepSignal(deepSignalFromDb, connectionId); |
||||
} |
||||
); |
||||
|
||||
return ret; |
||||
return rootSignal; |
||||
} |
||||
|
@ -0,0 +1,19 @@ |
||||
import { useSignal } from "@gn8/alien-signals-react"; |
||||
import { useMemo } from "react"; |
||||
import { createSignalObjectForShape } from "src/ng-mock/js-land/connector/ngSignals"; |
||||
import type { Scope, Shape } from "src/ng-mock/js-land/types"; |
||||
|
||||
const useShape = (shape: Shape, scope: Scope) => { |
||||
const signalOfShape = useMemo(() => { |
||||
console.log("react memo called..."); |
||||
return createSignalObjectForShape(shape, scope); |
||||
}, [shape, scope]); |
||||
|
||||
const [shapeObject, setShapeObject] = useSignal(signalOfShape); |
||||
|
||||
// We don't need the setter.
|
||||
// The object is recursively proxied and changes are recorded there.
|
||||
return shapeObject; |
||||
}; |
||||
|
||||
export default useShape; |
@ -0,0 +1,13 @@ |
||||
import { useSignal } from "@gn8/alien-signals-svelte"; |
||||
import { createSignalObjectForShape } from "src/ng-mock/js-land/connector/ngSignals"; |
||||
import type { Scope, Shape } from "src/ng-mock/js-land/types"; |
||||
|
||||
const useShape = (shape: Shape, scope: Scope) => { |
||||
const signalOfShape = createSignalObjectForShape(shape, scope); |
||||
|
||||
const writeableStoreForShape = useSignal(signalOfShape); |
||||
|
||||
return writeableStoreForShape; |
||||
}; |
||||
|
||||
export default useShape; |
@ -0,0 +1,13 @@ |
||||
import { useSignal } from "@gn8/alien-signals-vue"; |
||||
import { createSignalObjectForShape } from "src/ng-mock/js-land/connector/ngSignals"; |
||||
import type { Scope, Shape } from "src/ng-mock/js-land/types"; |
||||
|
||||
const useShape = (shape: Shape, scope: Scope) => { |
||||
const signalOfShape = createSignalObjectForShape(shape, scope); |
||||
|
||||
const refOfShape = useSignal(signalOfShape); |
||||
|
||||
return refOfShape; |
||||
}; |
||||
|
||||
export default useShape; |
@ -1,45 +0,0 @@ |
||||
import { deepSignal, watch, computed } from "alien-deepsignals"; |
||||
import { signal } from "alien-signals"; |
||||
|
||||
const deepSignalExample = deepSignal({ |
||||
count: 0, |
||||
name: "John", |
||||
nested: { |
||||
deep: "value", |
||||
}, |
||||
array: [1, 2, 3], |
||||
set: new Set(["el1"]), |
||||
}); |
||||
|
||||
const computedSignalFromExample = computed(() => { |
||||
return Object.keys(deepSignalExample).length; |
||||
}); |
||||
|
||||
/** Create a vanilla alien-signals Signal so that it can be normally used by universe-alien-signals. */ |
||||
const wrapDeepIntoAlienSignal = <T extends object>( |
||||
nestedSignal: ReturnType<typeof deepSignal<T>> |
||||
) => { |
||||
const alienSignal = signal(nestedSignal); |
||||
|
||||
// Watch deep signal and notify vanilla alien-signal on changes.
|
||||
watch( |
||||
nestedSignal, |
||||
(value) => { |
||||
// We need to destructure because the object otherwise remained the same.
|
||||
alienSignal({ ...nestedSignal }); |
||||
}, |
||||
{ deep: true } |
||||
); |
||||
|
||||
return alienSignal; |
||||
}; |
||||
|
||||
const wrappedDeepSignalState = wrapDeepIntoAlienSignal(deepSignalExample); |
||||
const wrappedComputedPropertiesOfObj = wrapDeepIntoAlienSignal( |
||||
computedSignalFromExample |
||||
); |
||||
|
||||
export { |
||||
wrappedDeepSignalState as deepSignalExample, |
||||
wrappedComputedPropertiesOfObj as computedSignalFromExample, |
||||
}; |
@ -1,23 +1,30 @@ |
||||
import { expect, test } from "vitest"; |
||||
import { createSignalObjectForShape } from "../js-land/connector/ngSignals"; |
||||
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
||||
|
||||
// TODO: Redo
|
||||
test("shape object notification comes back to others", async () => { |
||||
const object1 = await createSignalObjectForShape("Shape1"); |
||||
const object2 = await createSignalObjectForShape("Shape1"); |
||||
const object1 = createSignalObjectForShape("Shape1"); |
||||
const object2 = createSignalObjectForShape("Shape1"); |
||||
|
||||
const object3 = createSignalObjectForShape("Shape2"); |
||||
const object4 = createSignalObjectForShape("Shape2"); |
||||
|
||||
const object3 = await createSignalObjectForShape("Shape2"); |
||||
const object4 = await createSignalObjectForShape("Shape2"); |
||||
wait(50); |
||||
|
||||
// Update object 1 and expect object 2 to update as well.
|
||||
await object1.update({ name: "Updated name from object1" }); |
||||
object1()!.name = "Updated name from object1"; |
||||
|
||||
expect(object2.state?.name).toBe("Updated name from object1"); |
||||
wait(30); |
||||
expect(object2()!.name).toBe("Updated name from object1"); |
||||
|
||||
// Expect object of different shape not to have changed.
|
||||
expect(object3.state?.name).toBe("Niko's cat"); |
||||
expect(object3().name).toBe("Niko's cat"); |
||||
|
||||
// Update object 4 and expect object 3 with same shape to have updated.
|
||||
await object4.update({ name: "Updated name from object4" }); |
||||
object4()!.name = "Updated name from object4"; |
||||
|
||||
expect(object3.state?.name).toBe("Updated name from object4"); |
||||
wait(30); |
||||
expect(object3()!.name).toBe("Updated name from object4"); |
||||
}); |
||||
|
@ -1,13 +1,15 @@ |
||||
import * as shapeManager from "./shapeManager"; |
||||
import type { WasmConnection, Diff } from "./types"; |
||||
|
||||
export default async function handleShapeUpdate( |
||||
export default async function updateShape( |
||||
connectionId: WasmConnection["id"], |
||||
diff: Diff |
||||
) { |
||||
const connection = shapeManager.connections.get(connectionId); |
||||
if (!connection) throw new Error("No Connection found."); |
||||
|
||||
console.log("BACKEND: Received update request from ", connectionId); |
||||
|
||||
const newState = shapeManager.applyDiff(connection.state, diff); |
||||
connection.state = newState; |
||||
|
Loading…
Reference in new issue