diff --git a/src/apps/user/settings/Settings/Page/About.svelte b/src/apps/user/settings/Settings/Page/About.svelte
index 0dababe62..09ac97287 100755
--- a/src/apps/user/settings/Settings/Page/About.svelte
+++ b/src/apps/user/settings/Settings/Page/About.svelte
@@ -16,13 +16,12 @@
{/if}
- Thank you for using ArcOS! I'm working really hard to give you the best experience possible. Want to learn more about ArcOS? Go
+ Thank you for using ArcOS! We're working really hard to give you the best experience possible. Want to learn more about ArcOS? Go
ahead and visit our GitHub organization, there's some useful stuff there.
-
- Want to contact me? Here you go.
+ Want to contact us? Here you go.
diff --git a/src/assets/branding/christmas.png b/src/assets/branding/christmas.png
new file mode 100644
index 000000000..ca17e0f35
Binary files /dev/null and b/src/assets/branding/christmas.png differ
diff --git a/src/ts/branding/index.ts b/src/ts/branding/index.ts
index 48d987d6e..1eeb9c951 100755
--- a/src/ts/branding/index.ts
+++ b/src/ts/branding/index.ts
@@ -1,10 +1,16 @@
import { ReleaseLogo } from "$ts/images/branding";
+import { ChristmasLogo } from "$ts/images/branding";
import { ArcMode } from "$ts/metadata/mode";
import { TryGetDaemon } from "$ts/server/user/daemon";
import { ALIASED_MODES, MODES } from "./stores";
export const Logo = () => {
const daemon = TryGetDaemon();
+
+ const d = new Date();
+ if (d.getDate() >= 24 && d.getDate() <= 31 && d.getMonth() === 11) // Check whether to enable the Christmas logo
+ return ChristmasLogo;
+
const defaultLogo = daemon?.icons!.getIconCached?.("ReleaseLogo") || ReleaseLogo;
return (daemon ? daemon.icons!.getIconCached(ALIASED_MODES[ArcMode()]) : MODES[ArcMode()]) || defaultLogo;
diff --git a/src/ts/drives/localfs.ts b/src/ts/drives/localfs.ts
new file mode 100644
index 000000000..64146150e
--- /dev/null
+++ b/src/ts/drives/localfs.ts
@@ -0,0 +1,190 @@
+import type {
+ DirectoryReadReturn,
+ DriveCapabilities,
+ FileEntry,
+ FilesystemProgressCallback,
+ FilesystemStat,
+ FolderEntry,
+ UserQuota,
+} from "$types/fs";
+import { FilesystemDrive } from "./drive";
+
+export class LocalFilesystemDrive extends FilesystemDrive {
+ override FILESYSTEM_LONG: string = "Local Filesystem Folder";
+ override FILESYSTEM_SHORT: string = "lfsf";
+ override IDENTIFIES_AS: string = "lfsf";
+ override REMOVABLE: boolean = true;
+
+ root: FileSystemDirectoryHandle;
+
+ // todo: finnish implementing stuff
+ protected override CAPABILITIES: Record = {
+ readDir: true,
+ makeDir: true,
+ readFile: true,
+ writeFile: true,
+ tree: false,
+ copyItem: false,
+ moveItem: false,
+ deleteItem: true,
+ direct: true,
+ quota: true,
+ bulk: false,
+ stat: true,
+ };
+
+ constructor(uuid: string, letter: string, root: FileSystemDirectoryHandle) {
+ super(uuid, letter);
+ this.root = root;
+ this.label = `${root.name} (Local)`;
+ }
+
+ async quota(): Promise {
+ return {
+ max: 0,
+ free: 0,
+ percentage: 0,
+ used: 0,
+ unknown: true,
+ };
+ }
+
+ private async getItemHandle(path: string, create?: "directory" | "file") {
+ const parts = path.split("/").filter(Boolean);
+ if (parts.length === 0) return this.root;
+ let dir: FileSystemDirectoryHandle = this.root;
+
+ try {
+ for (let i = 0; i < parts.length - 1; i++) dir = await dir.getDirectoryHandle(parts[i]);
+ const lastPart = parts[parts.length - 1];
+
+ try {
+ return await dir.getDirectoryHandle(lastPart, { create: create === "directory" });
+ } catch {
+ return await dir.getFileHandle(lastPart, { create: create === "file" });
+ }
+ } catch {
+ return undefined;
+ }
+ }
+
+ async readDir(path: string): Promise {
+ const dir = (await this.getItemHandle(path)) as FileSystemDirectoryHandle;
+ if (!dir || dir.kind !== "directory") return undefined;
+
+ const children = await Array.fromAsync(dir.values());
+ const dirs: FolderEntry[] = [];
+ const files: FileEntry[] = [];
+
+ for (const child of children) {
+ if (child.kind === "file") {
+ const file = await (child as FileSystemFileHandle).getFile();
+ files.push({
+ name: file.name,
+ itemId: path + "/" + file.name, // fixme: don't do this
+ dateCreated: new Date(file.lastModified),
+ dateModified: new Date(file.lastModified),
+ mimeType: file.type,
+ size: file.size,
+ });
+
+ continue;
+ }
+
+ if (child.kind === "directory") {
+ const dir = child as FileSystemDirectoryHandle;
+ dirs.push({
+ name: dir.name,
+ itemId: path + "/" + dir.name, // fixme: don't do this
+
+ // we don't know these
+ dateCreated: new Date(0),
+ dateModified: new Date(0),
+ });
+ continue;
+ }
+
+ continue;
+ }
+
+ return {
+ dirs,
+ files,
+ shortcuts: {},
+ totalFiles: files.length,
+ totalFolders: dirs.length,
+ totalSize: files.map((f) => f.size).reduce((a, b) => a + b),
+ };
+ }
+
+ async createDirectory(path: string): Promise {
+ const handle = (await this.getItemHandle(path, "directory")) as FileSystemDirectoryHandle;
+ if (!handle || handle.kind !== "directory") return false;
+
+ return true;
+ }
+
+ async readFile(path: string, onProgress?: FilesystemProgressCallback): Promise {
+ const handle = (await this.getItemHandle(path)) as FileSystemFileHandle;
+ if (handle.kind !== "file") return undefined;
+
+ const file = await handle.getFile();
+ return await file.arrayBuffer();
+ }
+
+ async writeFile(path: string, data: Blob, onProgress?: FilesystemProgressCallback): Promise {
+ const handle = (await this.getItemHandle(path, "file")) as FileSystemFileHandle;
+ if (handle.kind !== "file") return false;
+
+ const writer = await handle.createWritable();
+ await writer.write(data);
+ await writer.close();
+
+ return true;
+ }
+
+ async deleteItem(path: string): Promise {
+ const split = path.split("/");
+
+ const parent = (await this.getItemHandle(split.slice(0, split.length - 1).join("/"))) as FileSystemDirectoryHandle;
+ if (!parent || parent.kind !== "directory") return false;
+
+ try {
+ await parent.removeEntry(split[split.length - 1]);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ async direct(path: string): Promise {
+ const handle = (await this.getItemHandle(path)) as FileSystemFileHandle;
+ const file = await handle.getFile();
+ return URL.createObjectURL(file);
+ }
+
+ async stat(path: string): Promise {
+ const handle = (await this.getItemHandle(path))!;
+ if (!handle) return undefined;
+
+ if (handle.kind === "file") {
+ const file = await (handle as FileSystemFileHandle).getFile();
+ return {
+ isFile: true,
+ isDirectory: false,
+ created: file.lastModified,
+ modified: file.lastModified,
+ size: file.size,
+ };
+ } else if (handle.kind === "directory")
+ return {
+ isFile: false,
+ isDirectory: true,
+ created: 0,
+ modified: 0,
+ size: -1,
+ };
+
+ return undefined;
+ }
+}
diff --git a/src/ts/images/branding.ts b/src/ts/images/branding.ts
index 3b38279b6..0dc8d1e0b 100755
--- a/src/ts/images/branding.ts
+++ b/src/ts/images/branding.ts
@@ -6,3 +6,4 @@ export { default as NightlyLogo } from "$assets/branding/nightly.svg";
export { default as RcLogo } from "$assets/branding/rc.svg";
export { default as ReleaseLogo } from "$assets/branding/release.svg";
export { default as UnstableLogo } from "$assets/branding/unstable.svg";
+export { default as ChristmasLogo } from "$assets/branding/christmas.png"
\ No newline at end of file
diff --git a/src/ts/server/user/daemon/contexts/filesystem.ts b/src/ts/server/user/daemon/contexts/filesystem.ts
index 0783c6b45..2ba28d29c 100644
--- a/src/ts/server/user/daemon/contexts/filesystem.ts
+++ b/src/ts/server/user/daemon/contexts/filesystem.ts
@@ -7,6 +7,7 @@ import {
import type { LoadSaveDialogData } from "$apps/user/filemanager/types";
import { MessageBox } from "$ts/dialog";
import { LegacyServerDrive } from "$ts/drives/legacy";
+import { LocalFilesystemDrive } from "$ts/drives/localfs";
import type { MemoryFilesystemDrive } from "$ts/drives/temp";
import { ZIPDrive } from "$ts/drives/zipdrive";
import { Env, Fs, Stack, SysDispatch } from "$ts/env";
@@ -523,6 +524,32 @@ export class FilesystemUserContext extends UserContext {
);
}
+ async mountLocalFilesystem() {
+ if (!("showDirectoryPicker" in window)) return;
+
+ let handle: FileSystemDirectoryHandle | undefined;
+
+ try {
+ handle = await (window.showDirectoryPicker as any)({
+ id: "arcos",
+ mode: "readwrite",
+ startIn: "desktop",
+ });
+ } catch (e) {
+ if ((e as any).name === "AbortError") {
+ } else throw e;
+ }
+
+ if (!handle) return;
+ return await Fs.mountDrive(
+ btoa(`${handle.name}`), // fixme: use something better as id
+ LocalFilesystemDrive,
+ undefined,
+ undefined,
+ handle
+ );
+ }
+
/**
* Deletes the specified item WITH SUPPORT FOR RECYCLING
* @param path The file or folder to delete