Rename Space to Project, add desktop document creation
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
import InfoModal from '$lib/components/dashboard/InfoModal.svelte';
|
||||
import RenameModal from '$lib/components/dashboard/RenameModal.svelte';
|
||||
import CreateDocModal from '$lib/components/dashboard/CreateDocModal.svelte';
|
||||
import CreateSpaceModal from '$lib/components/dashboard/CreateSpaceModal.svelte';
|
||||
import CreateProjectModal from '$lib/components/dashboard/CreateProjectModal.svelte';
|
||||
import CreateFolderModal from '$lib/components/dashboard/CreateFolderModal.svelte';
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
let newFolderName = $state('');
|
||||
let loading = $state(true);
|
||||
let showCreateModal = $state(false);
|
||||
let showCreateSpaceModal = $state(false);
|
||||
let showCreateProjectModal = $state(false);
|
||||
let newDocTitle = $state('');
|
||||
let showPlusDropdown = $state(false);
|
||||
let dragOverFolderId = $state<string | null>(null);
|
||||
@@ -200,24 +200,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateSpaceModal() {
|
||||
function openCreateProjectModal() {
|
||||
showPlusDropdown = false;
|
||||
showCreateSpaceModal = true;
|
||||
showCreateProjectModal = true;
|
||||
}
|
||||
|
||||
async function createSpace(name: string) {
|
||||
async function createProject(name: string) {
|
||||
if (!name.trim()) return;
|
||||
|
||||
const res = await fetch('/api/spaces', {
|
||||
const res = await fetch('/api/projects', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: name.trim(), folder_id: currentFolderId || undefined })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const space = await res.json();
|
||||
showCreateSpaceModal = false;
|
||||
goto(`/space/${space.id}`);
|
||||
const project = await res.json();
|
||||
showCreateProjectModal = false;
|
||||
goto(`/project/${project.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,9 +437,9 @@
|
||||
<Icon icon="mdi:file-document-plus" class="text-lg text-blue-500" />
|
||||
New Document
|
||||
</button>
|
||||
<button onclick={openCreateSpaceModal} class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-black/5 dark:hover:bg-white/5 flex items-center gap-2">
|
||||
<button onclick={openCreateProjectModal} class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-black/5 dark:hover:bg-white/5 flex items-center gap-2">
|
||||
<Icon icon="mdi:folder-multiple-plus" class="text-lg text-indigo-500" />
|
||||
New Space
|
||||
New Project
|
||||
</button>
|
||||
<button onclick={openCreateFolderModal} class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-black/5 dark:hover:bg-white/5 flex items-center gap-2">
|
||||
<Icon icon="mdi:folder-plus" class="text-lg text-yellow-500" />
|
||||
@@ -629,8 +629,8 @@
|
||||
<CreateDocModal {createDoc} onClose={() => showCreateModal = false} />
|
||||
{/if}
|
||||
|
||||
{#if showCreateSpaceModal}
|
||||
<CreateSpaceModal {createSpace} onClose={() => showCreateSpaceModal = false} />
|
||||
{#if showCreateProjectModal}
|
||||
<CreateProjectModal {createProject} onClose={() => showCreateProjectModal = false} />
|
||||
{/if}
|
||||
|
||||
{#if showCreateFolderModal}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
Packages
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
Instance-local Typst packages, published from Spaces and importable as
|
||||
Instance-local Typst packages, published from Projects and importable as
|
||||
<code class="font-mono text-xs bg-gray-100 dark:bg-white/10 px-1.5 py-0.5 rounded">@typstdrive/<name>:<version></code>.
|
||||
</p>
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@
|
||||
{:else if packages.length === 0}
|
||||
<div class="text-center py-16 text-gray-500 dark:text-gray-400">
|
||||
<Icon icon="mdi:package-variant" class="text-5xl mx-auto mb-3 opacity-50" />
|
||||
<p>No packages published yet. Open a Space and use “Publish” to create one.</p>
|
||||
<p>No packages published yet. Open a Project and use “Publish” to create one.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
|
||||
@@ -5,26 +5,26 @@
|
||||
import Preview from '$lib/components/Preview.svelte';
|
||||
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
|
||||
import DocFooter from '$lib/components/DocFooter.svelte';
|
||||
import FileTree from '$lib/components/space/FileTree.svelte';
|
||||
import SpaceToolbar from '$lib/components/space/SpaceToolbar.svelte';
|
||||
import FileTree from '$lib/components/project/FileTree.svelte';
|
||||
import ProjectToolbar from '$lib/components/project/ProjectToolbar.svelte';
|
||||
import PublishPackageModal from '$lib/components/PublishPackageModal.svelte';
|
||||
import { compileSpace } from '$lib/ts/typst-api';
|
||||
import { compileProject } from '$lib/ts/typst-api';
|
||||
import type { Diagnostic } from '$lib/ts/typst-api';
|
||||
import { editorErrors, documentStatsStore, previewOpenStore, editorViewStore } from '$lib/ts/store';
|
||||
import { setSpace, openFile, getOpenFile, closeFile, renameOpenFile, getAllText, cleanupSpace } from '$lib/ts/yjs-space';
|
||||
import { setProject, openFile, getOpenFile, closeFile, renameOpenFile, getAllText, cleanupProject } from '$lib/ts/yjs-project';
|
||||
|
||||
interface SpaceFile {
|
||||
interface ProjectFile {
|
||||
id: string;
|
||||
path: string;
|
||||
kind: string;
|
||||
}
|
||||
|
||||
const spaceId = $page.params.id as string;
|
||||
const projectId = $page.params.id as string;
|
||||
|
||||
let spaceName = $state('Space');
|
||||
let projectName = $state('Project');
|
||||
let entrypoint = $state('main.typ');
|
||||
let role = $state('owner');
|
||||
let files = $state<SpaceFile[]>([]);
|
||||
let files = $state<ProjectFile[]>([]);
|
||||
let activeFileId = $state('');
|
||||
let svgs = $state<string[]>([]);
|
||||
let errors = $state<Diagnostic[]>([]);
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
function triggerCompile() {
|
||||
if (!$previewOpenStore) return;
|
||||
compileSpace(spaceId, getAllText())
|
||||
compileProject(projectId, getAllText())
|
||||
.then((res) => {
|
||||
if (res.stats) $documentStatsStore = res.stats;
|
||||
if (res.svgs) {
|
||||
@@ -58,12 +58,12 @@
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
errors = [{ message: 'Network or server error compiling space.', severity: 'error' }];
|
||||
errors = [{ message: 'Network or server error compiling project.', severity: 'error' }];
|
||||
});
|
||||
}
|
||||
|
||||
async function loadFiles() {
|
||||
const res = await fetch(`/api/spaces/${spaceId}/files`);
|
||||
const res = await fetch(`/api/projects/${projectId}/files`);
|
||||
if (!res.ok) return;
|
||||
files = await res.json();
|
||||
|
||||
@@ -80,13 +80,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function selectFile(file: SpaceFile) {
|
||||
function selectFile(file: ProjectFile) {
|
||||
if (file.kind !== 'text') return;
|
||||
activeFileId = file.id;
|
||||
}
|
||||
|
||||
async function createFile(path: string) {
|
||||
const res = await fetch(`/api/spaces/${spaceId}/files`, {
|
||||
const res = await fetch(`/api/projects/${projectId}/files`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path, kind: 'text', content: '' })
|
||||
@@ -103,15 +103,15 @@
|
||||
async function uploadFiles(fileList: FileList) {
|
||||
const form = new FormData();
|
||||
for (const f of fileList) form.append('file', f);
|
||||
const res = await fetch(`/api/spaces/${spaceId}/files/upload`, { method: 'POST', body: form });
|
||||
const res = await fetch(`/api/projects/${projectId}/files/upload`, { method: 'POST', body: form });
|
||||
if (res.ok) {
|
||||
await loadFiles();
|
||||
triggerCompile();
|
||||
}
|
||||
}
|
||||
|
||||
async function renameFile(file: SpaceFile, path: string) {
|
||||
const res = await fetch(`/api/spaces/${spaceId}/files/${file.id}`, {
|
||||
async function renameFile(file: ProjectFile, path: string) {
|
||||
const res = await fetch(`/api/projects/${projectId}/files/${file.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path })
|
||||
@@ -123,9 +123,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFile(file: SpaceFile) {
|
||||
async function deleteFile(file: ProjectFile) {
|
||||
if (!confirm(`Delete ${file.path}?`)) return;
|
||||
const res = await fetch(`/api/spaces/${spaceId}/files/${file.id}`, { method: 'DELETE' });
|
||||
const res = await fetch(`/api/projects/${projectId}/files/${file.id}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
closeFile(file.id);
|
||||
files = files.filter((f) => f.id !== file.id);
|
||||
@@ -136,8 +136,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function setEntry(file: SpaceFile) {
|
||||
const res = await fetch(`/api/spaces/${spaceId}`, {
|
||||
async function setEntry(file: ProjectFile) {
|
||||
const res = await fetch(`/api/projects/${projectId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ entrypoint: file.path })
|
||||
@@ -166,38 +166,38 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setSpace(spaceId);
|
||||
fetch(`/api/spaces/${spaceId}`)
|
||||
setProject(projectId);
|
||||
fetch(`/api/projects/${projectId}`)
|
||||
.then((r) => r.json())
|
||||
.then((s) => {
|
||||
if (s && s.name) spaceName = s.name;
|
||||
if (s && s.entrypoint) entrypoint = s.entrypoint;
|
||||
if (s && s.effective_role) role = s.effective_role;
|
||||
.then((p) => {
|
||||
if (p && p.name) projectName = p.name;
|
||||
if (p && p.entrypoint) entrypoint = p.entrypoint;
|
||||
if (p && p.effective_role) role = p.effective_role;
|
||||
})
|
||||
.then(loadFiles)
|
||||
.then(() => {
|
||||
ready = true;
|
||||
triggerCompile();
|
||||
})
|
||||
.catch((e) => console.error('Failed to load space', e));
|
||||
.catch((e) => console.error('Failed to load project', e));
|
||||
|
||||
return () => {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
cleanupSpace();
|
||||
cleanupProject();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{spaceName} - TypstDrive</title>
|
||||
<title>{projectName} - TypstDrive</title>
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window onclick={closeContextMenu} />
|
||||
|
||||
<div class="flex flex-col h-screen relative">
|
||||
<SpaceToolbar
|
||||
{spaceName}
|
||||
{spaceId}
|
||||
<ProjectToolbar
|
||||
{projectName}
|
||||
{projectId}
|
||||
{entrypoint}
|
||||
{role}
|
||||
activeText={activeEntry?.text ?? null}
|
||||
@@ -254,5 +254,5 @@
|
||||
{/if}
|
||||
|
||||
{#if showPublish}
|
||||
<PublishPackageModal {spaceId} onClose={() => (showPublish = false)} />
|
||||
<PublishPackageModal {projectId} onClose={() => (showPublish = false)} />
|
||||
{/if}
|
||||
@@ -3,9 +3,9 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import Icon from '@iconify/svelte';
|
||||
import Navbar from '$lib/components/dashboard/Navbar.svelte';
|
||||
import SpaceCard from '$lib/components/dashboard/SpaceCard.svelte';
|
||||
import ProjectCard from '$lib/components/dashboard/ProjectCard.svelte';
|
||||
|
||||
interface Space {
|
||||
interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
entrypoint: string;
|
||||
@@ -14,8 +14,8 @@
|
||||
effective_role?: string;
|
||||
}
|
||||
|
||||
let spaces = $state<Space[]>([]);
|
||||
let shared = $state<Space[]>([]);
|
||||
let projects = $state<Project[]>([]);
|
||||
let shared = $state<Project[]>([]);
|
||||
let loading = $state(true);
|
||||
let showCreate = $state(false);
|
||||
let newName = $state('');
|
||||
@@ -26,22 +26,22 @@
|
||||
let renameId = $state('');
|
||||
let renameName = $state('');
|
||||
let showInfo = $state(false);
|
||||
let infoSpace = $state<Space | null>(null);
|
||||
let infoProject = $state<Project | null>(null);
|
||||
|
||||
function setActiveMenu(id: string | null) { activeMenu = id; }
|
||||
function openInfo(space: Space) { activeMenu = null; infoSpace = space; showInfo = true; }
|
||||
function openInfo(project: Project) { activeMenu = null; infoProject = project; showInfo = true; }
|
||||
function openRename(id: string, name: string) { activeMenu = null; renameId = id; renameName = name; showRename = true; }
|
||||
|
||||
async function submitRename(e: Event) {
|
||||
e.preventDefault();
|
||||
if (!renameName.trim()) return;
|
||||
const res = await fetch(`/api/spaces/${renameId}`, {
|
||||
const res = await fetch(`/api/projects/${renameId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: renameName.trim() })
|
||||
});
|
||||
if (res.ok) {
|
||||
spaces = spaces.map((s) => (s.id === renameId ? { ...s, name: renameName.trim() } : s));
|
||||
projects = projects.map((p) => (p.id === renameId ? { ...p, name: renameName.trim() } : p));
|
||||
}
|
||||
showRename = false;
|
||||
}
|
||||
@@ -54,10 +54,10 @@
|
||||
async function load() {
|
||||
loading = true;
|
||||
const [own, sh] = await Promise.all([
|
||||
fetch('/api/spaces').then((r) => (r.ok ? r.json() : [])),
|
||||
fetch('/api/spaces/shared').then((r) => (r.ok ? r.json() : []))
|
||||
fetch('/api/projects').then((r) => (r.ok ? r.json() : [])),
|
||||
fetch('/api/projects/shared').then((r) => (r.ok ? r.json() : []))
|
||||
]);
|
||||
spaces = own;
|
||||
projects = own;
|
||||
shared = sh;
|
||||
loading = false;
|
||||
}
|
||||
@@ -65,29 +65,29 @@
|
||||
async function create() {
|
||||
if (!newName.trim()) return;
|
||||
creating = true;
|
||||
const res = await fetch('/api/spaces', {
|
||||
const res = await fetch('/api/projects', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: newName.trim() })
|
||||
});
|
||||
creating = false;
|
||||
if (res.ok) {
|
||||
const space = await res.json();
|
||||
goto(`/space/${space.id}`);
|
||||
const project = await res.json();
|
||||
goto(`/project/${project.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(id: string, name: string) {
|
||||
if (!confirm(`Delete space "${name}"? This cannot be undone.`)) return;
|
||||
const res = await fetch(`/api/spaces/${id}`, { method: 'DELETE' });
|
||||
if (res.ok) spaces = spaces.filter((s) => s.id !== id);
|
||||
if (!confirm(`Delete project "${name}"? This cannot be undone.`)) return;
|
||||
const res = await fetch(`/api/projects/${id}`, { method: 'DELETE' });
|
||||
if (res.ok) projects = projects.filter((p) => p.id !== id);
|
||||
}
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Spaces - TypstDrive</title>
|
||||
<title>Projects - TypstDrive</title>
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window onclick={handleWindowClick} />
|
||||
@@ -104,33 +104,33 @@
|
||||
</button>
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<Icon icon="mdi:folder-multiple-outline" class="text-blue-500" />
|
||||
Spaces
|
||||
Projects
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Multi-file Typst workspaces with their own <code class="font-mono text-xs">typst.toml</code>.</p>
|
||||
</div>
|
||||
<button onclick={() => { showCreate = true; newName = ''; }} class="px-4 py-2 text-sm rounded-lg bg-blue-600 text-white hover:bg-blue-700 flex items-center gap-2">
|
||||
<Icon icon="mdi:plus" class="text-lg" /> New Space
|
||||
<Icon icon="mdi:plus" class="text-lg" /> New Project
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<p class="text-gray-500 dark:text-gray-400">Loading…</p>
|
||||
{:else}
|
||||
{#if spaces.length === 0}
|
||||
{#if projects.length === 0}
|
||||
<div class="text-center py-16 text-gray-500 dark:text-gray-400">
|
||||
<Icon icon="mdi:folder-multiple-outline" class="text-5xl mx-auto mb-3 opacity-50" />
|
||||
<p>No spaces yet. Create one to start a multi-file project.</p>
|
||||
<p>No projects yet. Create one to start a multi-file project.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{#each spaces as space (space.id)}
|
||||
<SpaceCard
|
||||
{space}
|
||||
{#each projects as project (project.id)}
|
||||
<ProjectCard
|
||||
{project}
|
||||
{activeMenu}
|
||||
{setActiveMenu}
|
||||
{openInfo}
|
||||
{openRename}
|
||||
deleteSpace={remove}
|
||||
deleteProject={remove}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -141,18 +141,18 @@
|
||||
<Icon icon="mdi:account-group-outline" class="text-blue-500" /> Shared with me
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{#each shared as space (space.id)}
|
||||
<button onclick={() => goto(`/space/${space.id}`)} class="text-left bg-white dark:bg-black/20 rounded-xl border border-gray-200 dark:border-white/10 overflow-hidden hover:shadow-md transition-shadow">
|
||||
{#each shared as project (project.id)}
|
||||
<button onclick={() => goto(`/project/${project.id}`)} class="text-left bg-white dark:bg-black/20 rounded-xl border border-gray-200 dark:border-white/10 overflow-hidden hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-gray-50 dark:bg-black/30 flex items-center justify-center overflow-hidden border-b border-gray-100 dark:border-white/5">
|
||||
{#if space.thumbnail_svg}
|
||||
{@html space.thumbnail_svg}
|
||||
{#if project.thumbnail_svg}
|
||||
{@html project.thumbnail_svg}
|
||||
{:else}
|
||||
<Icon icon="mdi:folder-multiple-outline" class="text-4xl text-gray-300 dark:text-gray-600" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="p-3">
|
||||
<p class="font-medium text-gray-900 dark:text-white truncate">{space.name}</p>
|
||||
<p class="text-xs text-gray-400 mt-0.5">{space.effective_role}</p>
|
||||
<p class="font-medium text-gray-900 dark:text-white truncate">{project.name}</p>
|
||||
<p class="text-xs text-gray-400 mt-0.5">{project.effective_role}</p>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
@@ -165,8 +165,8 @@
|
||||
{#if showCreate}
|
||||
<div class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50" onclick={() => (showCreate = false)} role="presentation">
|
||||
<div class="bg-[var(--theme-bg)] text-[var(--theme-text)] rounded-xl shadow-2xl border border-gray-200 dark:border-white/10 w-full max-w-md p-6" onclick={(e) => e.stopPropagation()} role="presentation">
|
||||
<h2 class="text-lg font-bold mb-4 flex items-center gap-2"><Icon icon="mdi:folder-plus-outline" class="text-blue-500" /> New Space</h2>
|
||||
<input bind:value={newName} placeholder="Space name" onkeydown={(e) => e.key === 'Enter' && create()} class="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-white/10 bg-transparent text-sm mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500/40" />
|
||||
<h2 class="text-lg font-bold mb-4 flex items-center gap-2"><Icon icon="mdi:folder-plus-outline" class="text-blue-500" /> New Project</h2>
|
||||
<input bind:value={newName} placeholder="Project name" onkeydown={(e) => e.key === 'Enter' && create()} class="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-white/10 bg-transparent text-sm mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500/40" />
|
||||
<div class="flex justify-end gap-2">
|
||||
<button onclick={() => (showCreate = false)} class="px-4 py-2 text-sm rounded-lg bg-gray-100 dark:bg-white/5 hover:bg-gray-200 dark:hover:bg-white/10">Cancel</button>
|
||||
<button onclick={create} disabled={creating} class="px-4 py-2 text-sm rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50">Create</button>
|
||||
@@ -178,7 +178,7 @@
|
||||
{#if showRename}
|
||||
<div class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50" onclick={() => (showRename = false)} role="presentation">
|
||||
<form onsubmit={submitRename} class="bg-[var(--theme-bg)] text-[var(--theme-text)] rounded-xl shadow-2xl border border-gray-200 dark:border-white/10 w-full max-w-md p-6" onclick={(e) => e.stopPropagation()}>
|
||||
<h2 class="text-lg font-bold mb-4 flex items-center gap-2"><Icon icon="mdi:pencil-outline" class="text-yellow-500" /> Rename Space</h2>
|
||||
<h2 class="text-lg font-bold mb-4 flex items-center gap-2"><Icon icon="mdi:pencil-outline" class="text-yellow-500" /> Rename Project</h2>
|
||||
<input bind:value={renameName} class="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-white/10 bg-transparent text-sm mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500/40" />
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" onclick={() => (showRename = false)} class="px-4 py-2 text-sm rounded-lg bg-gray-100 dark:bg-white/5 hover:bg-gray-200 dark:hover:bg-white/10">Cancel</button>
|
||||
@@ -188,16 +188,16 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showInfo && infoSpace}
|
||||
{#if showInfo && infoProject}
|
||||
<div class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50" onclick={() => (showInfo = false)} role="presentation">
|
||||
<div class="bg-[var(--theme-bg)] text-[var(--theme-text)] rounded-xl shadow-2xl border border-gray-200 dark:border-white/10 w-full max-w-sm overflow-hidden" onclick={(e) => e.stopPropagation()} role="presentation">
|
||||
<div class="p-6 border-b border-gray-100 dark:border-white/10 flex items-center gap-3">
|
||||
<Icon icon="mdi:folder-multiple-outline" class="text-xl text-blue-500" />
|
||||
<h3 class="text-lg font-semibold flex-grow truncate">{infoSpace.name}</h3>
|
||||
<h3 class="text-lg font-semibold flex-grow truncate">{infoProject.name}</h3>
|
||||
</div>
|
||||
<div class="p-6 space-y-4 text-sm">
|
||||
<div><p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1">Entrypoint</p><p class="font-mono">{infoSpace.entrypoint}</p></div>
|
||||
<div><p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1">Last Modified</p><p>{new Date(infoSpace.updated_at.endsWith('Z') ? infoSpace.updated_at : infoSpace.updated_at + 'Z').toLocaleString()}</p></div>
|
||||
<div><p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1">Entrypoint</p><p class="font-mono">{infoProject.entrypoint}</p></div>
|
||||
<div><p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1">Last Modified</p><p>{new Date(infoProject.updated_at.endsWith('Z') ? infoProject.updated_at : infoProject.updated_at + 'Z').toLocaleString()}</p></div>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-white/5 border-t border-gray-100 dark:border-white/10 flex justify-end">
|
||||
<button onclick={() => (showInfo = false)} class="px-5 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg">Close</button>
|
||||
Reference in New Issue
Block a user