Skip to content
Draft
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
5 changes: 2 additions & 3 deletions src/apps/user/settings/Settings/Page/About.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
{/if}

<p>
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.
</p>

<!-- svelte-ignore a11y_invalid_attribute -->
<p class="contact">
Want to contact me? <a href="mailto:izaak.kuipers@gmail.com">Here you go</a>.
Want to contact us? <a href="mailto:contact@arcweb.nl">Here you go</a>.
</p>

<p class="version">
Expand Down
Binary file added src/assets/branding/christmas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/ts/branding/index.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
190 changes: 190 additions & 0 deletions src/ts/drives/localfs.ts
Original file line number Diff line number Diff line change
@@ -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<DriveCapabilities, boolean> = {
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<UserQuota> {
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<DirectoryReadReturn | undefined> {
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<boolean> {
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<ArrayBuffer | undefined> {
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<boolean> {
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<boolean> {
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<string | undefined> {
const handle = (await this.getItemHandle(path)) as FileSystemFileHandle;
const file = await handle.getFile();
return URL.createObjectURL(file);
}

async stat(path: string): Promise<FilesystemStat | undefined> {
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;
}
}
1 change: 1 addition & 0 deletions src/ts/images/branding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
27 changes: 27 additions & 0 deletions src/ts/server/user/daemon/contexts/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<LocalFilesystemDrive>(
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
Expand Down