From 25c2beba94631946ce9da8750ae28b98a9b9bad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon?= Date: Wed, 23 Oct 2024 16:18:28 +0200 Subject: [PATCH] version: 0.1.0 --- docs/docs/apps.md | 12 +- docs/docs/community.md | 12 +- docs/docs/components.md | 52 +++++++ docs/docs/customization.md | 2 +- docs/docs/desktop.md | 30 ++-- docs/docs/faq.md | 2 +- docs/docs/fs.md | 8 +- docs/docs/open-file-dialog.jpg | Bin 0 -> 60307 bytes docs/docs/save-as-dialog.jpg | Bin 0 -> 69491 bytes packages/radix-os/README.md | 11 +- packages/radix-os/package.json | 2 +- .../components/AppLauncher/AppLauncher.tsx | 4 +- .../src/components/Desktop/Desktop.tsx | 2 + .../components/MultiTaskBar/MultiTaskBar.tsx | 2 +- .../OpenFileDialog/OpenFileDialog.tsx | 78 ++++++++++ .../RadixColorPicker/RadixColorPicker.tsx | 69 +++++++++ .../src/components/apps/Code/Code.tsx | 40 +++++- .../src/components/apps/Explorer/Explorer.tsx | 74 ++++++---- .../apps/ImageViewer/ImageViewer.tsx | 26 +++- .../src/components/apps/Settings/Settings.tsx | 133 +++++++----------- packages/radix-os/src/defaultApps.tsx | 1 + packages/radix-os/src/index.ts | 8 +- .../src/services/applications/setupApps.ts | 17 ++- .../src/services/fs/fs-integration.ts | 88 ++++++++++++ packages/radix-os/src/stores/fs.constants.ts | 4 +- packages/radix-os/src/stores/fs.tsx | 6 + src/main.tsx | 40 ++++-- 27 files changed, 555 insertions(+), 168 deletions(-) create mode 100644 docs/docs/components.md create mode 100644 docs/docs/open-file-dialog.jpg create mode 100644 docs/docs/save-as-dialog.jpg create mode 100644 packages/radix-os/src/components/OpenFileDialog/OpenFileDialog.tsx create mode 100644 packages/radix-os/src/components/RadixColorPicker/RadixColorPicker.tsx diff --git a/docs/docs/apps.md b/docs/docs/apps.md index 42cd0d8..fd8facf 100644 --- a/docs/docs/apps.md +++ b/docs/docs/apps.md @@ -42,7 +42,7 @@ To add your custom application to the operating system and make it launchable, s ```tsx title="App.tsx" import { SomeApp } from "./SomeApp"; -const applications = setupApps( +const applications = setupApps([ { appId: "some-app", appName: "Some App", @@ -52,8 +52,8 @@ const applications = setupApps( { appId: "some-other-app", /* ... */ - } -); + }, +]); const useAppLauncher = createUseAppLauncher(applications); @@ -137,7 +137,7 @@ Gives you direct access to the internal zustand window store, allowing you to in You may overwrite existing applications by giving your own applications matching appIds. ```tsx -const applications = setupApps( +const applications = setupApps([ { appId: "code", component: CustomCodeComponent, @@ -147,6 +147,6 @@ const applications = setupApps( appId: "explorer", component: CustomExplorerComponent, defaultWindowSettings: {}, - } -); + }, +]); ``` diff --git a/docs/docs/community.md b/docs/docs/community.md index f8801cf..d53756f 100644 --- a/docs/docs/community.md +++ b/docs/docs/community.md @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 7 --- # Community @@ -7,3 +7,13 @@ sidebar_position: 6 If you have make and publish applications for use with Radix OS, make sure to reach out through Github so that we can feature your applications here. To submit your own application, click the edit link below. + +# Contributing + +Contributions are always welcome, whether it's documentation, feature requests or code. + +To contribute to code, you should first [submit an issue](https://github.com/imp-dance/radix-os/issues/new) (and then optionally create a pull request). + +## Workflow for local development + +After pulling the repository, run `pnpm install` and `pnpm dev` in the root directory. The dev server will import the code directly from the `packages/radix-os` folder so you can edit the package files and have hot reloading for the demo app. diff --git a/docs/docs/components.md b/docs/docs/components.md new file mode 100644 index 0000000..5a1e1e6 --- /dev/null +++ b/docs/docs/components.md @@ -0,0 +1,52 @@ +--- +sidebar_position: 5 +--- + +# Components + +Radix OS exports some helper components for use in the operating system. + +## `SaveAsDialog` + +A dialog that lets a user choose a path and filename. + +```typescript +type SaveAsDialogProps = { + open: boolean; + setOpen: (open: boolean) => void; + onPathCreate: (path: string) => Promise; +}; +``` + +![Preview](./save-as-dialog.jpg) + +## `OpenFileDialog` + +A dialog that lets a user open a file + +```typescript +type SaveAsDialogProps = { + open: boolean; + setOpen: (open: boolean) => void; + onFileOpened: (file: FsFile, path: string) => void; + /** Determine if given file should be disabled */ + fileDisabled?: (file: FsFile) => boolean; +}; +``` + +![Preview](./open-file-dialog.jpg) + +## `Explorer` + +The raw underlying version of the Explorer application, without the application window. Used internally in `OpenFileDialog` and `SaveAsDialog`. + +```tsx +type ExplorerProps = { + initialPath?: string; + windowId?: symbol; + onPathChange?: (path: string) => void; + disableFiles?: boolean; + fileDisabled?: (file: FsFile) => boolean; + onRequestOpenFile?: (file: FsFile, path: string) => void; +}; +``` diff --git a/docs/docs/customization.md b/docs/docs/customization.md index 1d14100..f1b0bdd 100644 --- a/docs/docs/customization.md +++ b/docs/docs/customization.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 --- # Customization diff --git a/docs/docs/desktop.md b/docs/docs/desktop.md index d14fe5a..f27402b 100644 --- a/docs/docs/desktop.md +++ b/docs/docs/desktop.md @@ -12,16 +12,23 @@ You can also launch applications by using the command palette, opened with the k ## Application shortcuts -You can add apps to the desktop by configuring them to be added in `setupApps`: +You can add apps to the desktop by configuring them to be added in `setupApps`. You can also configure which of the default apps appear in the desktop by passing a second argument. ```tsx -const applications = setupApps({ - appId: "some-app", - appName: "Some App", - component: SomeApp, - addToDesktop: true, - defaultWindowSettings: {}, -}); +const applications = setupApps( + [ + { + appId: "some-app", + appName: "Some App", + component: SomeApp, + addToDesktop: true, + defaultWindowSettings: {}, + }, + ], + { + defaultAppsOnDesktop: ["explorer", "code"], + } +); ``` ## Custom shortcuts @@ -30,11 +37,12 @@ You can also create desktop items that execute any code you want. You add these ```tsx const applications = setupApps(); + const desktopItems = setupDesktopItems({ - icon: , - label: "News", + icon: , + label: "Boop", onClick: () => { - /* ... */ + alert("boop"); }, }); diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 5866553..196b9a3 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 8 --- # FAQ diff --git a/docs/docs/fs.md b/docs/docs/fs.md index bdbcda0..0c5a1ec 100644 --- a/docs/docs/fs.md +++ b/docs/docs/fs.md @@ -7,15 +7,19 @@ sidebar_position: 2 Radix OS comes with a preconfigured client-side file system that can be imported and passed directly: ```tsx -import { fsZustandIntegration, RadixOS } from "radix-os"; +import { createZustandFsIntegration, RadixOS } from "radix-os"; + +const fs = createZustandFsIntegration(); createRoot(...).render( - + ); ``` +You can optionally pass an object with `initialTree`, or `onAction` (to listen to actions) to `createZustandFsIntegration`. + ## Create custom integration You can also create your own custom file system integration. diff --git a/docs/docs/open-file-dialog.jpg b/docs/docs/open-file-dialog.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c9f74abccd4f2a84596324f1277ada2d37e1bb72 GIT binary patch literal 60307 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 literal 0 HcmV?d00001 diff --git a/docs/docs/save-as-dialog.jpg b/docs/docs/save-as-dialog.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ac0e0af73179155f29a4110468dacdde2fcbd0d8 GIT binary patch literal 69491 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 literal 0 HcmV?d00001 diff --git a/packages/radix-os/README.md b/packages/radix-os/README.md index 5c1c411..a55432c 100644 --- a/packages/radix-os/README.md +++ b/packages/radix-os/README.md @@ -26,13 +26,14 @@ npm i radix-os ```tsx title="lib/radix-os.ts" import { - fsZustandIntegration, setupApps, - createUseAppLauncher + createUseAppLauncher, + createZustandFsIntegration } from "radix-os; -export const applications = setupApps(); export const useAppLauncher = createUseAppLauncher(applications); +export const applications = setupApps(); +export const fs = createZustandFsIntegration(); ``` ```tsx title="App.tsx" @@ -41,9 +42,9 @@ import { RadixOS, fsZustandIntegration } from "radix-os; -import { applications } from "./lib/radix-os"; +import { applications, fs } from "./lib/radix-os"; export default function App(){ - return + return } ``` diff --git a/packages/radix-os/package.json b/packages/radix-os/package.json index 455247b..8648c06 100644 --- a/packages/radix-os/package.json +++ b/packages/radix-os/package.json @@ -1,6 +1,6 @@ { "name": "radix-os", - "version": "0.0.22", + "version": "0.1.0", "description": "A simulated operating system built with React and Radix UI", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/packages/radix-os/src/components/AppLauncher/AppLauncher.tsx b/packages/radix-os/src/components/AppLauncher/AppLauncher.tsx index 3cb30e0..3858547 100644 --- a/packages/radix-os/src/components/AppLauncher/AppLauncher.tsx +++ b/packages/radix-os/src/components/AppLauncher/AppLauncher.tsx @@ -64,7 +64,6 @@ export function AppLauncher(props: { ref={ref} value={value} onChange={(e) => setValue(e.target.value)} - color="indigo" onKeyDown={(e) => { if (e.key === "Escape") { close(); @@ -94,9 +93,10 @@ export function AppLauncher(props: { /> {matchingApps.map((app, i) => ( + + + + + + + + ); +} diff --git a/packages/radix-os/src/components/RadixColorPicker/RadixColorPicker.tsx b/packages/radix-os/src/components/RadixColorPicker/RadixColorPicker.tsx new file mode 100644 index 0000000..661d5ea --- /dev/null +++ b/packages/radix-os/src/components/RadixColorPicker/RadixColorPicker.tsx @@ -0,0 +1,69 @@ +import { + Badge, + Button, + Flex, + Popover, + Tooltip, +} from "@radix-ui/themes"; + +type Color = NonNullable[0]["color"]>; + +export function RadixColorPicker(props: { + onColorSelected: (color: Color) => void; + selectedColor: Color; + label?: string; +}) { + return ( + + + + + + + {( + [ + "gray", + "cyan", + "blue", + "indigo", + "crimson", + "pink", + "plum", + "violet", + "teal", + "jade", + "green", + "grass", + "gold", + "bronze", + "yellow", + "amber", + ] as const + ).map((color) => ( + + + + + + ))} + + + + ); +} diff --git a/packages/radix-os/src/components/apps/Code/Code.tsx b/packages/radix-os/src/components/apps/Code/Code.tsx index c6f0c58..7b68b6c 100644 --- a/packages/radix-os/src/components/apps/Code/Code.tsx +++ b/packages/radix-os/src/components/apps/Code/Code.tsx @@ -21,6 +21,7 @@ import { } from "../../../stores/window"; import { ConfirmDialog } from "../../ConfirmDialog/ConfirmDialog"; import { MenuBar } from "../../MenuBar/MenuBar"; +import { OpenFileDialog } from "../../OpenFileDialog/OpenFileDialog"; import { SaveAsDialog } from "../../SaveAsDialog/SaveAsDialog"; type EditorType = Parameters< @@ -28,6 +29,10 @@ type EditorType = Parameters< >[0]; export const CodeApp: RadixOsAppComponent = (props) => { + const [openedFile, setOpenedFile] = useState(null); const { launch } = useUntypedAppContext(); const [createdFile, setCreatedFile] = useState( null @@ -35,10 +40,13 @@ export const CodeApp: RadixOsAppComponent = (props) => { const [createdPath, setCreatedPath] = useState( null ); + const [openDialogOpen, setOpenDialogOpen] = useState(false); const [createDialogOpen, setCreateDialogOpen] = useState(false); - const file = props.file?.file ?? createdFile; - const path = props.file?.path ?? createdPath; + const file = + openedFile?.file ?? props.file?.file ?? createdFile; + const path = + openedFile?.path ?? props.file?.path ?? createdPath; const [deleteOpen, setDeleteOpen] = useState(false); const [value, setValue] = useState(file?.data ?? ""); const editorRef = useRef(null); @@ -103,12 +111,25 @@ export const CodeApp: RadixOsAppComponent = (props) => { overflow: "hidden", }} > + {openDialogOpen && ( + { + setOpenedFile({ file, path }); + setValue(file.data); + }} + fileDisabled={(file) => + !file.launcher.includes("code") + } + /> + )} { label: "ctrl N", }, }, + { + label: "Open file", + onClick: () => { + setOpenDialogOpen(true); + }, + shortcut: { + key: "O", + modifiers: ["ctrl"], + label: "ctrl O", + }, + }, { label: "Save", onClick: () => { @@ -133,7 +165,7 @@ export const CodeApp: RadixOsAppComponent = (props) => { label: "ctrl S", dependency: value, }, - color: touched ? "indigo" : "gray", + color: touched ? "grass" : "gray", }, "separator", { diff --git a/packages/radix-os/src/components/apps/Explorer/Explorer.tsx b/packages/radix-os/src/components/apps/Explorer/Explorer.tsx index 714e69d..879701a 100644 --- a/packages/radix-os/src/components/apps/Explorer/Explorer.tsx +++ b/packages/radix-os/src/components/apps/Explorer/Explorer.tsx @@ -50,7 +50,7 @@ import { parsePath, } from "../../../services/fs/tree-helpers"; import { useFavouriteFolderStore } from "../../../stores/explorer"; -import { FsNode, Launcher } from "../../../stores/fs"; +import { FsFile, FsNode, Launcher } from "../../../stores/fs"; import { RadixOsAppComponent, useWindowStore, @@ -68,11 +68,15 @@ export function Explorer({ windowId, onPathChange, disableFiles, + fileDisabled, + onRequestOpenFile, }: { initialPath?: string; windowId?: symbol; onPathChange?: (path: string) => void; disableFiles?: boolean; + fileDisabled?: (file: FsFile) => boolean; + onRequestOpenFile?: (file: FsFile, path: string) => void; }) { const { openFile } = useUntypedAppContext(); const mouseSensor = useSensor(MouseSensor, { @@ -262,7 +266,7 @@ export function Explorer({ ) } variant="ghost" - color={sortDir === null ? "gray" : "indigo"} + color={sortDir === null ? "gray" : undefined} size="2" style={{ display: "block", @@ -316,26 +320,41 @@ export function Explorer({ )} {currentFolder && isFolder(currentFolder) ? sortedChildren.map((child, i) => { + const isDisabled = + (disableFiles && isFile(child)) || + (isFile(child) && + fileDisabled?.(child)); + + const itemIsBeingDragged = + selected.includes( + `${path}/${child.name}` + ) && + isDragging && + selected.length > 1; return ( 1 - } + isDragging={itemIsBeingDragged} onSelect={({ shiftKey, metaKey, }) => { + if ( + onRequestOpenFile && + isFile(child) + ) { + setSelected([ + `${path}/${child.name}`, + ]); + return onRequestOpenFile( + child, + `${path}/${child.name}` + ); + } setSelected((prev) => { if (shiftKey) { const start = @@ -384,12 +403,25 @@ export function Explorer({ } }); }} - onClick={() => { + onClick={(e) => { if (isFolder(child)) { setPath( (prev) => `${prev}/${child.name}` ); + } else if (isFile(child)) { + e.stopPropagation(); + if (onRequestOpenFile) { + onRequestOpenFile( + child, + `${path}/${child.name}` + ); + } else { + openFile({ + file: child, + path: `${path}/${child.name}`, + }); + } } }} returnFocus={i === 0 ? true : false} @@ -532,7 +564,7 @@ const launcherToIcon: Record = { function ExplorerItem(props: { returnFocus?: boolean; item: FsNode; - onClick?: () => void; + onClick?: (e: React.MouseEvent) => void; path: string; onRename: () => void; selected?: string[]; @@ -600,11 +632,7 @@ function ExplorerItem(props: { color="gray" onDoubleClick={(e) => { if (props.disabled) return; - props.onClick?.(); - if (isFile(props.item)) { - e.stopPropagation(); - openFile({ file: props.item, path: props.path }); - } + props.onClick?.(e); }} onClick={(e) => { if (props.disabled) return; @@ -615,11 +643,7 @@ function ExplorerItem(props: { metaKey: e.metaKey, }); } else { - props.onClick?.(); - if (isFile(props.item)) { - e.stopPropagation(); - openFile({ file: props.item, path: props.path }); - } + props.onClick?.(e); } }} disabled={props.disabled} @@ -716,7 +740,7 @@ function Step(props: { droppable.isOver ? "amber" : props.isCurrent - ? "indigo" + ? undefined : "gray" } size="1" diff --git a/packages/radix-os/src/components/apps/ImageViewer/ImageViewer.tsx b/packages/radix-os/src/components/apps/ImageViewer/ImageViewer.tsx index 71b234b..995ef31 100644 --- a/packages/radix-os/src/components/apps/ImageViewer/ImageViewer.tsx +++ b/packages/radix-os/src/components/apps/ImageViewer/ImageViewer.tsx @@ -1,8 +1,13 @@ -import { Flex } from "@radix-ui/themes"; +import { Button, Flex } from "@radix-ui/themes"; +import { useState } from "react"; import { RadixOsAppComponent } from "../../../stores/window"; +import { OpenFileDialog } from "../../OpenFileDialog/OpenFileDialog"; export const ImageViewer: RadixOsAppComponent = (props) => { - const trimmedData = props.file?.file.data.trim() ?? ""; + const [openedFile, setOpenedFile] = useState(""); + const [fileDialogOpen, setFileDialogOpen] = useState(false); + const trimmedData = + props.file?.file.data.trim() ?? openedFile ?? ""; const isImageUrl = trimmedData.startsWith("http") || trimmedData.startsWith("data:image"); @@ -12,6 +17,23 @@ export const ImageViewer: RadixOsAppComponent = (props) => { trimmedData.startsWith(" + {fileDialogOpen && ( + setOpenedFile(file.data)} + fileDisabled={(file) => + !file.launcher.includes("image") + } + /> + )} + {trimmedData === "" && ( +
+ +
+ )} {isImageUrl && ( { const initialTab = - (props.file?.file?.data as "customize") ?? "customize"; + (props.file?.file?.data as "customize") ?? "about"; const [tab, setTab] = useState< "customize" | "storage" | "shortcuts" | "about" >(initialTab); @@ -43,11 +42,45 @@ export const Settings: RadixOsAppComponent = (props) => { style={{ height: "calc(100% - 3rem)" }} > + About Customize Storage Shortcuts - About + + + + RadixOS is an open source project built using{" "} + React, Radix,{" "} + Zustand & dnd kit. + + + The file system and settings are stored in{" "} + localStorage. + + + Written in 2024. + + + Made by{" "} + + Håkon Underbakke + {" "} + ( + + Ryfylke React AS + + ) + + + @@ -93,40 +126,6 @@ export const Settings: RadixOsAppComponent = (props) => { - - - - RadixOS is an open source project built using{" "} - React, Radix,{" "} - Zustand & dnd kit. - - - The file system and settings are stored in{" "} - localStorage. - - - Written in 2024. - - - Made by{" "} - - Håkon Underbakke - {" "} - ( - - Ryfylke React AS - - ) - - - ); }; @@ -139,6 +138,9 @@ function CustomizeTab() { return ( + + Options + - Translucency + Translucent windows Background - - {( - [ - "gray", - "cyan", - "blue", - "indigo", - "crimson", - "pink", - "plum", - "violet", - "teal", - "jade", - "green", - "grass", - "gold", - "bronze", - "yellow", - "amber", - ] as const - ).map((color) => ( - settingsStore.setBg(color)} - > - - - ))} - +
+ settingsStore.setBg(clr)} + selectedColor={settingsStore.bg as "gray"} + label="Select color" + /> +
or diff --git a/packages/radix-os/src/defaultApps.tsx b/packages/radix-os/src/defaultApps.tsx index 5383b7f..5df1527 100644 --- a/packages/radix-os/src/defaultApps.tsx +++ b/packages/radix-os/src/defaultApps.tsx @@ -83,6 +83,7 @@ export const defaultApps = [ component: ImageViewer, appId: "image", appName: "Image Viewer", + addToDesktop: false, defaultWindowSettings: { title: "Image Viewer", icon: , diff --git a/packages/radix-os/src/index.ts b/packages/radix-os/src/index.ts index 7b876ee..ef2bf9f 100644 --- a/packages/radix-os/src/index.ts +++ b/packages/radix-os/src/index.ts @@ -1,6 +1,9 @@ import RadixOSComponent from "./components/RadixOS/RadixOS"; import { FsIntegration as TFsIntegration } from "./services/fs/fs-integration"; +export { Explorer } from "./components/apps/Explorer/Explorer"; +export { OpenFileDialog } from "./components/OpenFileDialog/OpenFileDialog"; +export { SaveAsDialog } from "./components/SaveAsDialog/SaveAsDialog"; export { useAppWindow } from "./hooks/useAppWindow"; export { setupDesktopShortcuts } from "./services/applications/desktop-shortcuts"; export { @@ -12,13 +15,12 @@ export { setupApps, } from "./services/applications/setupApps"; export { + createZustandFsIntegration, fsZustandIntegration, useFs, } from "./services/fs/fs-integration"; export { useSettingsStore } from "./stores/settings"; export { useWindowStore } from "./stores/window"; -export const RadixOS = RadixOSComponent; - export type { RadixOsAppComponent } from "./stores/window"; - +export const RadixOS = RadixOSComponent; export type FsIntegration = TFsIntegration; diff --git a/packages/radix-os/src/services/applications/setupApps.ts b/packages/radix-os/src/services/applications/setupApps.ts index 7f40121..9c451af 100644 --- a/packages/radix-os/src/services/applications/setupApps.ts +++ b/packages/radix-os/src/services/applications/setupApps.ts @@ -24,10 +24,23 @@ export const setupApps = < ? DefaultApps : TProvided | DefaultApps >( - ...apps: RadixAppList + apps: RadixAppList, + options?: { + defaultAppsOnDesktop?: DefaultApps[]; + } ): RadixAppList => { const applications = Object.values( - [...defaultApps, ...(apps ?? [])].reduce((acc, item) => { + [ + ...defaultApps.map((app) => ({ + ...app, + addToDesktop: options?.defaultAppsOnDesktop + ? options.defaultAppsOnDesktop.includes( + app.appId as "explorer" + ) + : app.addToDesktop, + })), + ...(apps ?? []), + ].reduce((acc, item) => { acc[item.appId] = item as RadixAppList[number]; return acc; }, {} as Record[number]>) diff --git a/packages/radix-os/src/services/fs/fs-integration.ts b/packages/radix-os/src/services/fs/fs-integration.ts index 94f0508..618bfd5 100644 --- a/packages/radix-os/src/services/fs/fs-integration.ts +++ b/packages/radix-os/src/services/fs/fs-integration.ts @@ -1,5 +1,6 @@ import { createContext, useContext } from "react"; import { + FileSystemStore, FsFile, FsNode, useFileSystemStore, @@ -36,6 +37,93 @@ export const useFs = () => { return context; }; +export const createZustandFsIntegration = (opts?: { + initialTree?: FileSystemStore["tree"]; + onAction?: ( + action: + | "makeDir" + | "makeFile" + | "move" + | "readDir" + | "updateFile" + | "removeFile" + ) => void; +}) => { + if (opts?.initialTree) { + useFileSystemStore.getState().setTree(opts.initialTree); + } + const fsZustandIntegration: FsIntegration = { + makeDir: (path) => + new Promise((resolve) => { + const { createFolder } = useFileSystemStore.getState(); + try { + createFolder(path); + opts?.onAction?.("makeDir"); + resolve(true); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_err) { + resolve(false); + } + }), + makeFile: (path, file) => + new Promise((resolve) => { + const { createFile } = useFileSystemStore.getState(); + try { + createFile(path, file); + opts?.onAction?.("makeFile"); + return resolve(true); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (err) { + return resolve(false); + } + }), + move: (from, to) => + new Promise((resolve) => { + const { move } = useFileSystemStore.getState(); + try { + move(from, to); + opts?.onAction?.("move"); + return resolve(true); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (err) { + return resolve(false); + } + }), + readDir: (path) => + new Promise((resolve) => { + const { tree } = useFileSystemStore.getState(); + const target = !path ? tree : findNodeByPath(path, tree); + opts?.onAction?.("readDir"); + return resolve(target ?? null); + }), + updateFile: (path, file) => + new Promise((resolve) => { + const { updateFile } = useFileSystemStore.getState(); + try { + updateFile(path, file); + opts?.onAction?.("updateFile"); + return resolve(true); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (err) { + return resolve(false); + } + }), + removeFile: (path) => + new Promise((resolve) => { + const { remove } = useFileSystemStore.getState(); + try { + remove(path); + opts?.onAction?.("removeFile"); + return resolve(true); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (err) { + return resolve(false); + } + }), + }; + return fsZustandIntegration; +}; + export const fsZustandIntegration: FsIntegration = { makeDir: (path) => new Promise((resolve) => { diff --git a/packages/radix-os/src/stores/fs.constants.ts b/packages/radix-os/src/stores/fs.constants.ts index 0ac4f3a..e40209f 100644 --- a/packages/radix-os/src/stores/fs.constants.ts +++ b/packages/radix-os/src/stores/fs.constants.ts @@ -18,8 +18,8 @@ export const initialTree: FileSystemStore["tree"] = { `, }, { - title: "Ryfylke React AS", - name: "Ryfylke React", + title: "Ryfylke React Logo", + name: "Ryfylke React Logo", launcher: ["image", "code"] as Launcher[], data: `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUzMSIgaGVpZ2h0PSIxNTMxIiB2aWV3Qm94PSIwIDAgMTUzMSAxNTMxIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB3aWR0aD0iMTUzMSIgaGVpZ2h0PSIxNTMxIiByeD0iNzY1LjUiIGZpbGw9IiMxMzIxMkIiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CjxwYXRoIGQ9Ik0xMTU5LjY0IDkyMi4zMzRDMTIwMS4xOSAxMDYxLjQ1IDEwMTcuNzIgMTIzMy4yNSA4MzAuNjMyIDEyMzMuMjVDNjQzLjU0IDEyMzMuMjUgNzM3LjMyNyAxMDU0LjUyIDY2Ni44OSA5MjcuNTkzQzYwMi40MjkgODExLjQzMiA4MzcuMDQ2IDkyNC41OSA5MzguMjU2IDcxMy44MTZDMTA0MC43MSA3MjcuNjQ2IDExMzkuNyA4NTAuNjMyIDExNTkuNjQgOTIyLjMzNFoiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcikiLz4KPHBhdGggZD0iTTYyMC4yNTEgMTYxLjAwMUM2MjAuMjUxIDE2MS4wMDEgNjEyLjUzNyAxODkuNTUyIDYxMS4xNzUgMjA4Ljc1NUM2MDkuODg1IDIyNi45MzUgNjI2Ljc5NiAyNjEuMDM1IDYyNi43OTYgMjYxLjAzNUw0ODYuNDc1IDI1OC45MDRDNDg2LjQ3NSAyNTguOTA0IDQzNy45ODkgMzA3LjU5NyA0MDkuNzM4IDMyNi43NzJDMzg2LjAxNSAzNDIuODczIDMzMyAzNjUuNiAzMzMgMzY1LjZDMzMzIDM2NS42IDMzMi45ODMgNDI1LjA4NCAzODQuMTU2IDQ4NS4xMDFDNDIzLjYzIDUzMS4zOTggNDk3LjM0NyA1MjcuOTk2IDQ5Ny4zNDcgNTI3Ljk5Nkw2MDguMzQyIDQzMi40NTRMNjQ1LjQ3OSA0MzIuMjYyTDY3My44MTcgNDQ3LjE1NUwzNzQuNTE4IDEwMTAuNTVDNDAyLjI3OSAxMjU3LjM4IDU2Mi41ODYgMTM2My43NCA3NjEuMTc1IDEzNjkuNzlDOTU5Ljc2NCAxMzc1Ljg1IDExNDEuOTEgMTI0OS40NyAxMTk4IDEwMTAuNTVDMTE4My4xOCA5MTUuNzcgMTEyNi4yNSA4NDUuMTc3IDEwOTIuNjEgODE1LjQzMkwxMDM0Ljg0IDgyOS40MThDMTA3MC42IDgzOS4xOTIgOTk5LjgxNiA4NTMuNzUgMTAxOS40MiA4ODUuMjVDMTAzOS4wMiA5MTYuNzUxIDEwNDYuMjQgOTU1Ljc0NyAxMDM5LjI0IDk5Mi4xOUMxMDMyLjIzIDEwMjguNjMgMTAxMC4zNyAxMDYyLjY2IDk4MS4xODIgMTA4NC4xMkM5NTEuOTk0IDExMDUuNTggOTEzLjAxOCAxMTE2LjI4IDg3Ni4xODcgMTExMi4wN0M4NDUuMjYgMTEwOC41MyA4MzcuNDE1IDExMDQuMjUgODEzLjMxNCAxMDg0LjUzQzc4OS4yMTMgMTA2NC44MSA3NzAuNDIzIDEwMzguOTMgNzU3LjU2MiAxMDEwLjU2QzczMS44NCA5NTMuNzk4IDczMC4wMjYgODg4LjY1NSA3MzkuOCA4MjcuMTAyQzc0OS41NzQgNzY1LjU1IDc3MC4yODUgNzA2LjM3NSA3ODguMzA3IDY0Ni43MThDODA2LjMyOSA1ODcuMDYxIDgyMS44NTIgNTI1LjY5MyA4MjAuNzQzIDQ2My4zNzhDODE5LjkyMiA0MTcuMjQ4IDgwOS41NzYgMzcxLjU3IDc5MS4zNzkgMzI5LjE5M0M3NjEuNTUgMjc3LjYxNSA3MTYuNjg3IDI2MS4wMzUgNzE2LjY4NyAyNjEuMDM1QzcxNi42ODcgMjYxLjAzNSA3MTAuMjMxIDI1My45NTEgNzA3LjU2NiAyNDkuOTg2QzY5Ny4yNjQgMjM0LjY1NiA2OTIuODM2IDIxOC4yMzkgNjgxLjMyNCAyMDEuMTc5QzY1Ni4zNDUgMTY0LjE2MyA2MjAuMjUxIDE2MSA2MjAuMjUxIDE2MVYxNjEuMDAxWiIgZmlsbD0idXJsKCNwYWludDFfbGluZWFyKSIvPgo8cGF0aCBvcGFjaXR5PSIwLjM5MTAyNiIgZD0iTTM2MS45NTYgNDUzLjY4Nkw1NTEuNDY3IDM3OS4zMzhMNjM5LjU1OCAyNjkuMTk2TDYyNi43OTYgMjYxLjAzNkw0ODYuNDc2IDI1OC45MDNDNDg2LjQ3NiAyNTguOTAzIDQzOC4xNiAzMDguMzM5IDQwOS43MzggMzI3LjU3OEMzODYuMDk0IDM0My41ODEgMzMzIDM2NS41OTkgMzMzIDM2NS41OTlDMzMzIDM2NS41OTkgMzMzLjExNSAzNzUuNjk2IDMzNi43OTQgMzkxLjc1NkMzNDAuNDcyIDQwNy44MTYgMzYxLjk1NiA0MjkuODM3IDM2MS45NTYgNDUzLjY4NlY0NTMuNjg2WiIgZmlsbD0idXJsKCNwYWludDJfbGluZWFyKSIvPgo8cGF0aCBkPSJNOTM3Ljc3MiA3MjUuNjlMOTMxLjc0MyA2NTMuMjYxQzkzMS43NDMgNjUzLjI2MSA5NzkuMjg2IDYzMi4wNTcgMTAzMi44OSA2MzMuODcyQzEwODIuNjYgNjM1LjU1NiAxMTI0LjY0IDY4MC40NTMgMTEyNC42NCA2ODAuNDUzQzExMjQuNjQgNjgwLjQ1MyAxMDc4LjQ5IDcxNS41MTQgMTA1OS4yOCA3MzYuOTg1QzEwNDEuMTYgNzU3LjIzOCAxMDEzLjEgODA0LjE4NiAxMDEzLjEgODA0LjE4Nkw5MzcuNzcyIDcyNS42OVoiIGZpbGw9IiNGRkE5NDUiLz4KPC9nPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyIiB4MT0iOTgyLjIwMyIgeTE9Ijg0MC44NjIiIHgyPSI3NTAuMTU1IiB5Mj0iMTAyMS4xIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkE5NDUiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjN0QxRTAzIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhciIgeDE9IjE2Mi4zNjMiIHkxPSIxMTA4Ljc4IiB4Mj0iMTAxOC40NSIgeTI9IjEwMTQuMDYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGODEzOCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRkE5NDUiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDJfbGluZWFyIiB4MT0iNTM1LjI0OCIgeTE9IjM3Ni40NjQiIHgyPSI1MDIuMTk3IiB5Mj0iMjUzLjI1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRjVCMDkiIHN0b3Atb3BhY2l0eT0iMCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRjU4NTgiLz4KPC9saW5lYXJHcmFkaWVudD4KPGNsaXBQYXRoIGlkPSJjbGlwMCI+CjxyZWN0IHg9IjMzMyIgeT0iMTYxIiB3aWR0aD0iODY1IiBoZWlnaHQ9IjEyMDkiIHJ4PSI0MzIuNSIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K`, }, diff --git a/packages/radix-os/src/stores/fs.tsx b/packages/radix-os/src/stores/fs.tsx index 81786c0..fa3d538 100644 --- a/packages/radix-os/src/stores/fs.tsx +++ b/packages/radix-os/src/stores/fs.tsx @@ -48,6 +48,10 @@ export type FileSystemStore = { file: { name: string } & Partial ) => void; updateFile: (path: string, file: Partial) => void; + setTree: (newTree: { + name: "Home"; + children: FsNode[]; + }) => void; }; // TODO: Zod validation @@ -56,6 +60,8 @@ export const useFileSystemStore = create( persist( (set) => ({ tree: initialTree, + setTree: (newTree) => + set((state) => ({ ...state, tree: newTree })), createFolder: (path) => set((state) => { const newState = { ...state }; diff --git a/src/main.tsx b/src/main.tsx index 732798e..e9ec566 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,33 +1,45 @@ +import { QuestionMarkCircledIcon } from "@radix-ui/react-icons"; import "@radix-ui/themes/styles.css"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; import { createUseAppLauncher, - fsZustandIntegration, + createZustandFsIntegration, RadixOS, setupApps, -} from "radix-os"; -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; +} from "../packages/radix-os/src/index.ts"; import { ExampleApp } from "./applications/ExampleApp.tsx"; import "./index.css"; -export const applications = setupApps({ - component: ExampleApp, - appId: "example-app", - appName: "Example App", - addToDesktop: true, - defaultWindowSettings: { - title: "Example App", - }, -}); +export const applications = setupApps( + [ + { + component: ExampleApp, + appId: "example-app", + appName: "Example App", + defaultWindowSettings: { + title: "Example App", + icon: , + }, + }, + ], + { + defaultAppsOnDesktop: ["explorer"], + } +); + +const fs = createZustandFsIntegration({}); export const useAppLauncher = createUseAppLauncher(applications); createRoot(document.getElementById("root")!).render( );