Cloud folder organization, file uploads, appearance themes, title bar rework, faster autosync
This commit is contained in:
+50
-8
@@ -7,7 +7,7 @@ export interface Settings {
|
||||
account_email: string | null;
|
||||
account_username: string | null;
|
||||
autosave_seconds: number;
|
||||
sync_minutes: number;
|
||||
sync_seconds: number;
|
||||
}
|
||||
|
||||
export interface FileEntry {
|
||||
@@ -57,6 +57,7 @@ export interface ProjectSummary {
|
||||
id: string;
|
||||
name: string;
|
||||
entrypoint: string;
|
||||
folder_id: string | null;
|
||||
role: string;
|
||||
updated_at: string;
|
||||
}
|
||||
@@ -248,7 +249,7 @@ export const updateSettings = (changes: {
|
||||
workspaceRoot?: string;
|
||||
serverUrl?: string;
|
||||
autosaveSeconds?: number;
|
||||
syncMinutes?: number;
|
||||
syncSeconds?: number;
|
||||
}) => invoke<Settings>("update_settings", changes);
|
||||
|
||||
export interface CompatibilityStatus {
|
||||
@@ -305,6 +306,21 @@ export interface DocumentLink {
|
||||
export const cloudListFolders = () =>
|
||||
invoke<CloudFolder[]>("cloud_list_folders");
|
||||
|
||||
export const cloudCreateFolder = (name: string, parentId?: string | null) =>
|
||||
invoke<CloudFolder>("cloud_create_folder", {
|
||||
name,
|
||||
parentId: parentId ?? null,
|
||||
});
|
||||
|
||||
export const cloudRenameFolder = (folderId: string, name: string) =>
|
||||
invoke<CloudFolder>("cloud_rename_folder", { folderId, name });
|
||||
|
||||
export const cloudMoveFolder = (folderId: string, parentId: string | null) =>
|
||||
invoke<CloudFolder>("cloud_move_folder", { folderId, parentId });
|
||||
|
||||
export const cloudDeleteFolder = (folderId: string) =>
|
||||
invoke<void>("cloud_delete_folder", { folderId });
|
||||
|
||||
export const cloudListDocuments = (folderId?: string | null) =>
|
||||
invoke<CloudDocument[]>("cloud_list_documents", {
|
||||
folderId: folderId ?? null,
|
||||
@@ -329,6 +345,15 @@ export const cloudDownloadFile = (fileId: string) =>
|
||||
export const cloudDeleteFile = (fileId: string) =>
|
||||
invoke<void>("cloud_delete_file", { fileId });
|
||||
|
||||
export const cloudUploadFile = (path: string, folderId?: string | null) =>
|
||||
invoke<CloudFile>("cloud_upload_file", { path, folderId: folderId ?? null });
|
||||
|
||||
export const cloudRenameFile = (fileId: string, name: string) =>
|
||||
invoke<CloudFile>("cloud_rename_file", { fileId, name });
|
||||
|
||||
export const cloudMoveFile = (fileId: string, folderId: string | null) =>
|
||||
invoke<CloudFile>("cloud_move_file", { fileId, folderId });
|
||||
|
||||
export const cloudDownloadDocument = (documentId: string, parent: string) =>
|
||||
invoke<string>("cloud_download_document", { documentId, parent });
|
||||
|
||||
@@ -338,6 +363,9 @@ export const cloudDeleteDocument = (documentId: string) =>
|
||||
export const cloudCreateDocument = (path: string, title: string) =>
|
||||
invoke<string>("cloud_create_document", { path, title });
|
||||
|
||||
export const cloudMoveDocument = (documentId: string, folderId: string | null) =>
|
||||
invoke<CloudDocument>("cloud_move_document", { documentId, folderId });
|
||||
|
||||
export interface DocumentContent {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -346,8 +374,11 @@ export interface DocumentContent {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export const cloudNewDocument = (title: string) =>
|
||||
invoke<DocumentContent>("cloud_new_document", { title });
|
||||
export const cloudNewDocument = (title: string, folderId?: string | null) =>
|
||||
invoke<DocumentContent>("cloud_new_document", {
|
||||
title,
|
||||
folderId: folderId ?? null,
|
||||
});
|
||||
|
||||
export const cloudSyncDocument = (path: string) =>
|
||||
invoke<SyncReport>("cloud_sync_document", { path });
|
||||
@@ -384,14 +415,25 @@ export const cloudDocumentLink = (path: string) =>
|
||||
export const cloudUnlinkDocument = (path: string) =>
|
||||
invoke<void>("cloud_unlink_document", { path });
|
||||
|
||||
export const cloudCreateProject = (name: string) =>
|
||||
invoke<ProjectSummary>("cloud_create_project", { name });
|
||||
export const cloudCreateProject = (name: string, folderId?: string | null) =>
|
||||
invoke<ProjectSummary>("cloud_create_project", {
|
||||
name,
|
||||
folderId: folderId ?? null,
|
||||
});
|
||||
|
||||
export const cloudDeleteProject = (cloudProjectId: string) =>
|
||||
invoke<void>("cloud_delete_project", { cloudProjectId });
|
||||
|
||||
export const cloudCloneProject = (cloudProjectId: string, projectName: string) =>
|
||||
invoke<SyncReport>("cloud_clone_project", { cloudProjectId, projectName });
|
||||
export const cloudMoveProject = (
|
||||
cloudProjectId: string,
|
||||
folderId: string | null,
|
||||
) => invoke<ProjectSummary>("cloud_move_project", { cloudProjectId, folderId });
|
||||
|
||||
export const cloudCloneProject = (
|
||||
cloudProjectId: string,
|
||||
projectName: string,
|
||||
parent: string,
|
||||
) => invoke<SyncReport>("cloud_clone_project", { cloudProjectId, projectName, parent });
|
||||
|
||||
export const cloudLinkProject = (project: string, cloudProjectId?: string) =>
|
||||
invoke<SyncReport>("cloud_link_project", {
|
||||
|
||||
@@ -22,6 +22,15 @@ export type LspStatus = "off" | "starting" | "on" | "unavailable";
|
||||
export type ThemePreference = "light" | "dark" | "system";
|
||||
export type TextScale = "small" | "default" | "large" | "xlarge";
|
||||
export type ContrastLevel = "normal" | "high";
|
||||
export type ColorTheme = "default" | "slate" | "sunset" | "forest" | "grape";
|
||||
|
||||
export const colorThemes: { id: ColorTheme; label: string; accent: string }[] = [
|
||||
{ id: "default", label: "Default", accent: "#3b6cf6" },
|
||||
{ id: "slate", label: "Slate", accent: "#0f9b8e" },
|
||||
{ id: "sunset", label: "Sunset", accent: "#e8623f" },
|
||||
{ id: "forest", label: "Forest", accent: "#2f9457" },
|
||||
{ id: "grape", label: "Grape", accent: "#8b47d6" },
|
||||
];
|
||||
|
||||
interface AppState {
|
||||
view: View;
|
||||
@@ -58,6 +67,7 @@ interface AppState {
|
||||
error: string;
|
||||
theme: "light" | "dark";
|
||||
themePreference: ThemePreference;
|
||||
colorTheme: ColorTheme;
|
||||
accent: string | null;
|
||||
textScale: TextScale;
|
||||
reduceMotion: boolean;
|
||||
@@ -99,6 +109,7 @@ export const app = $state<AppState>({
|
||||
error: "",
|
||||
theme: "light",
|
||||
themePreference: "light",
|
||||
colorTheme: "default",
|
||||
accent: null,
|
||||
textScale: "default",
|
||||
reduceMotion: false,
|
||||
@@ -146,6 +157,7 @@ export function clearMessages() {
|
||||
}
|
||||
|
||||
const THEME_KEY = "typst-desktop-theme";
|
||||
const COLOR_THEME_KEY = "typst-desktop-color-theme";
|
||||
const ACCENT_KEY = "typst-desktop-accent";
|
||||
const TEXT_SCALE_KEY = "typst-desktop-text-scale";
|
||||
const REDUCE_MOTION_KEY = "typst-desktop-reduce-motion";
|
||||
@@ -182,6 +194,13 @@ export function applyTheme(preference: ThemePreference) {
|
||||
if (app.accent) applyAccent(app.accent);
|
||||
}
|
||||
|
||||
export function applyColorTheme(theme: ColorTheme) {
|
||||
app.colorTheme = theme;
|
||||
document.documentElement.dataset.colorTheme = theme;
|
||||
localStorage.setItem(COLOR_THEME_KEY, theme);
|
||||
applyAccent(null);
|
||||
}
|
||||
|
||||
function hexToRgb(hex: string): [number, number, number] {
|
||||
const value = parseInt(hex.replace("#", ""), 16);
|
||||
return [(value >> 16) & 255, (value >> 8) & 255, value & 255];
|
||||
@@ -296,6 +315,13 @@ export async function bootstrap() {
|
||||
: "light",
|
||||
);
|
||||
|
||||
const storedColorTheme = localStorage.getItem(COLOR_THEME_KEY);
|
||||
const validColorTheme = colorThemes.some((entry) => entry.id === storedColorTheme);
|
||||
document.documentElement.dataset.colorTheme = validColorTheme
|
||||
? (storedColorTheme as ColorTheme)
|
||||
: "default";
|
||||
app.colorTheme = validColorTheme ? (storedColorTheme as ColorTheme) : "default";
|
||||
|
||||
const storedAccent = localStorage.getItem(ACCENT_KEY);
|
||||
if (storedAccent) applyAccent(storedAccent);
|
||||
|
||||
@@ -382,7 +408,11 @@ export async function refreshCloud() {
|
||||
(folder) => (folder.parent_id ?? null) === app.cloudFolder,
|
||||
);
|
||||
app.cloudDocuments = documents;
|
||||
app.cloudProjects = projects;
|
||||
app.cloudProjects = projects.filter(
|
||||
(project) =>
|
||||
project.role !== "owner" ||
|
||||
(project.folder_id ?? null) === app.cloudFolder,
|
||||
);
|
||||
app.cloudFiles = files;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -414,7 +444,7 @@ export async function openCloudFolder(id: string | null | "shared") {
|
||||
|
||||
export async function downloadDocument(documentId: string, title: string) {
|
||||
try {
|
||||
const path = await api.cloudDownloadDocument(documentId, "");
|
||||
const path = await api.cloudDownloadDocument(documentId, app.currentDir);
|
||||
await refreshCloud();
|
||||
setStatus(`Downloaded '${title}' to this device`);
|
||||
return path;
|
||||
@@ -634,12 +664,12 @@ export function restartAutoSync() {
|
||||
syncTimer = null;
|
||||
}
|
||||
|
||||
const minutes = app.settings?.sync_minutes ?? 0;
|
||||
if (minutes <= 0) return;
|
||||
const seconds = app.settings?.sync_seconds ?? 0;
|
||||
if (seconds <= 0) return;
|
||||
|
||||
syncTimer = setInterval(() => {
|
||||
autoSync();
|
||||
}, minutes * 60 * 1000);
|
||||
}, seconds * 1000);
|
||||
}
|
||||
|
||||
async function autoSync() {
|
||||
|
||||
Reference in New Issue
Block a user