This commit is contained in:
Alejandro Gómez
2025-11-26 09:47:21 +01:00
commit cd41034b2f
112 changed files with 18581 additions and 0 deletions

254
src/core/logic.ts Normal file
View File

@@ -0,0 +1,254 @@
import { v4 as uuidv4 } from "uuid";
import type { MosaicNode } from "react-mosaic-component";
import { GrimoireState, WindowInstance, UserRelays } from "@/types/app";
/**
* Creates a new, empty workspace.
*/
export const createWorkspace = (
state: GrimoireState,
label: string,
): GrimoireState => {
const newId = uuidv4();
return {
...state,
activeWorkspaceId: newId,
workspaces: {
...state.workspaces,
[newId]: {
id: newId,
label,
layout: null,
windowIds: [],
},
},
};
};
/**
* Adds a window to the global store and to the active workspace.
*/
export const addWindow = (
state: GrimoireState,
payload: { appId: string; title: string; props: any },
): GrimoireState => {
const activeId = state.activeWorkspaceId;
const ws = state.workspaces[activeId];
const newWindowId = uuidv4();
const newWindow: WindowInstance = {
id: newWindowId,
appId: payload.appId as any,
title: payload.title,
props: payload.props,
};
// Simple Binary Split Logic
let newLayout: MosaicNode<string>;
if (ws.layout === null) {
newLayout = newWindowId;
} else {
newLayout = {
direction: "row",
first: ws.layout,
second: newWindowId,
splitPercentage: 50,
};
}
return {
...state,
windows: {
...state.windows,
[newWindowId]: newWindow,
},
workspaces: {
...state.workspaces,
[activeId]: {
...ws,
layout: newLayout,
windowIds: [...ws.windowIds, newWindowId],
},
},
};
};
/**
* Recursively removes a window from the layout tree.
*/
const removeFromLayout = (
layout: MosaicNode<string> | null,
windowId: string,
): MosaicNode<string> | null => {
if (layout === null) {
return null;
}
if (typeof layout === "string") {
return layout === windowId ? null : layout;
}
const firstResult = removeFromLayout(layout.first, windowId);
const secondResult = removeFromLayout(layout.second, windowId);
if (firstResult === null && secondResult !== null) {
return secondResult;
}
if (secondResult === null && firstResult !== null) {
return firstResult;
}
if (firstResult === null && secondResult === null) {
return null;
}
if (firstResult === layout.first && secondResult === layout.second) {
return layout;
}
return {
...layout,
first: firstResult!,
second: secondResult!,
};
};
/**
* Removes a window from the active workspace's layout and windowIds.
* Also removes the window from the global windows object.
*/
export const removeWindow = (
state: GrimoireState,
windowId: string,
): GrimoireState => {
const activeId = state.activeWorkspaceId;
const ws = state.workspaces[activeId];
const newLayout = removeFromLayout(ws.layout, windowId);
const newWindowIds = ws.windowIds.filter((id) => id !== windowId);
// Remove from global windows object
const { [windowId]: removedWindow, ...remainingWindows } = state.windows;
return {
...state,
windows: remainingWindows,
workspaces: {
...state.workspaces,
[activeId]: {
...ws,
layout: newLayout,
windowIds: newWindowIds,
},
},
};
};
/**
* Moves a window from current workspace to target workspace.
*/
export const moveWindowToWorkspace = (
state: GrimoireState,
windowId: string,
targetWorkspaceId: string,
): GrimoireState => {
const currentId = state.activeWorkspaceId;
const currentWs = state.workspaces[currentId];
const targetWs = state.workspaces[targetWorkspaceId];
if (!targetWs) {
return state;
}
const newCurrentLayout = removeFromLayout(currentWs.layout, windowId);
const newCurrentWindowIds = currentWs.windowIds.filter(
(id) => id !== windowId,
);
let newTargetLayout: MosaicNode<string>;
if (targetWs.layout === null) {
newTargetLayout = windowId;
} else {
newTargetLayout = {
direction: "row",
first: targetWs.layout,
second: windowId,
splitPercentage: 50,
};
}
return {
...state,
workspaces: {
...state.workspaces,
[currentId]: {
...currentWs,
layout: newCurrentLayout,
windowIds: newCurrentWindowIds,
},
[targetWorkspaceId]: {
...targetWs,
layout: newTargetLayout,
windowIds: [...targetWs.windowIds, windowId],
},
},
};
};
export const updateLayout = (
state: GrimoireState,
layout: MosaicNode<string> | null,
): GrimoireState => {
const activeId = state.activeWorkspaceId;
return {
...state,
workspaces: {
...state.workspaces,
[activeId]: {
...state.workspaces[activeId],
layout,
},
},
};
};
/**
* Sets the active account (pubkey).
*/
export const setActiveAccount = (
state: GrimoireState,
pubkey: string | undefined,
): GrimoireState => {
if (!pubkey) {
return {
...state,
activeAccount: undefined,
};
}
return {
...state,
activeAccount: {
pubkey,
relays: state.activeAccount?.relays,
},
};
};
/**
* Updates the relay list for the active account.
*/
export const setActiveAccountRelays = (
state: GrimoireState,
relays: UserRelays,
): GrimoireState => {
if (!state.activeAccount) {
return state;
}
return {
...state,
activeAccount: {
...state.activeAccount,
relays,
},
};
};

111
src/core/state.ts Normal file
View File

@@ -0,0 +1,111 @@
import { useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
import { GrimoireState, AppId } from "@/types/app";
import * as Logic from "./logic";
// Initial State Definition
const initialState: GrimoireState = {
windows: {
"win-1": {
id: "win-1",
appId: "win",
title: "WIN - Window Tree",
props: {},
},
"feed-1": {
id: "feed-1",
appId: "feed",
title: "FEED - Nostr Feed",
props: {},
},
"nip-1": {
id: "nip-1",
appId: "nip",
title: "NIP-01 - Basic protocol",
props: { number: "01" },
},
"kind-1": {
id: "kind-1",
appId: "kind",
title: "KIND-1 - Short Text Note",
props: { number: "1" },
},
"man-1": {
id: "man-1",
appId: "man",
title: "MAN - Help",
props: { cmd: "help" },
},
},
activeWorkspaceId: "default",
workspaces: {
default: {
id: "default",
label: "1",
windowIds: ["win-1", "feed-1", "nip-1", "kind-1", "man-1"],
layout: {
direction: "row",
first: {
direction: "column",
first: "win-1",
second: "feed-1",
splitPercentage: 50,
},
second: {
direction: "column",
first: {
direction: "row",
first: "nip-1",
second: "kind-1",
splitPercentage: 50,
},
second: "man-1",
splitPercentage: 50,
},
splitPercentage: 50,
},
},
},
};
// Persistence Atom
export const grimoireStateAtom = atomWithStorage<GrimoireState>(
"grimoire_v6",
initialState,
);
// The Hook
export const useGrimoire = () => {
const [state, setState] = useAtom(grimoireStateAtom);
return {
state,
activeWorkspace: state.workspaces[state.activeWorkspaceId],
createWorkspace: () => {
const count = Object.keys(state.workspaces).length + 1;
setState((prev) => Logic.createWorkspace(prev, count.toString()));
},
addWindow: (appId: AppId, props: any, title?: string) =>
setState((prev) =>
Logic.addWindow(prev, {
appId,
props,
title: title || appId.toUpperCase(),
}),
),
removeWindow: (id: string) =>
setState((prev) => Logic.removeWindow(prev, id)),
moveWindowToWorkspace: (windowId: string, targetWorkspaceId: string) =>
setState((prev) =>
Logic.moveWindowToWorkspace(prev, windowId, targetWorkspaceId),
),
updateLayout: (layout: any) =>
setState((prev) => Logic.updateLayout(prev, layout)),
setActiveWorkspace: (id: string) =>
setState((prev) => ({ ...prev, activeWorkspaceId: id })),
setActiveAccount: (pubkey: string | undefined) =>
setState((prev) => Logic.setActiveAccount(prev, pubkey)),
setActiveAccountRelays: (relays: any) =>
setState((prev) => Logic.setActiveAccountRelays(prev, relays)),
};
};