Change document tab place and add landing page
This commit is contained in:
parent
b06d5fdf95
commit
20e79f4361
8 changed files with 636 additions and 211 deletions
Binary file not shown.
|
|
@ -1,18 +1,50 @@
|
|||
<template>
|
||||
<div class="app-layout">
|
||||
<AppSidebar />
|
||||
<main class="main">
|
||||
<RouterView />
|
||||
</main>
|
||||
<header class="topbar">
|
||||
<button class="burger-btn" @click="sidebarOpen = !sidebarOpen" :title="sidebarOpen ? t('nav.collapse') : t('nav.expand')">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" class="burger-icon">
|
||||
<path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="topbar-logo">
|
||||
<span class="topbar-logo-icon">D</span>
|
||||
<span class="topbar-logo-text">Docling Studio</span>
|
||||
</div>
|
||||
<div class="topbar-spacer" />
|
||||
<button class="new-analysis-btn" @click="newAnalysis">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" class="new-analysis-icon"><path d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"/></svg>
|
||||
{{ t('topbar.newAnalysis') }}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="app-body">
|
||||
<AppSidebar :open="sidebarOpen" />
|
||||
<main class="main">
|
||||
<RouterView />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
import { ref } from 'vue'
|
||||
import { RouterView, useRouter } from 'vue-router'
|
||||
import { AppSidebar } from '../shared/ui/index.js'
|
||||
import { useSettingsStore } from '../features/settings/store.js'
|
||||
import { useDocumentStore } from '../features/document/store.js'
|
||||
import { useI18n } from '../shared/i18n.js'
|
||||
|
||||
useSettingsStore()
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const documentStore = useDocumentStore()
|
||||
|
||||
const sidebarOpen = ref(true)
|
||||
|
||||
function newAnalysis() {
|
||||
documentStore.selectedId = null
|
||||
router.push('/studio')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
@ -46,6 +78,7 @@ useSettingsStore()
|
|||
--radius-lg: 12px;
|
||||
|
||||
--sidebar-width: 240px;
|
||||
--topbar-height: 48px;
|
||||
--transition: 150ms ease;
|
||||
}
|
||||
|
||||
|
|
@ -85,12 +118,110 @@ body {
|
|||
}
|
||||
|
||||
.app-layout {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
height: var(--topbar-height);
|
||||
min-height: var(--topbar-height);
|
||||
background: var(--bg-surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.burger-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all var(--transition);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.burger-btn:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.burger-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.topbar-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.topbar-logo-icon {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.topbar-logo-text {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.topbar-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.new-analysis-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
background: var(--accent);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.new-analysis-btn:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.new-analysis-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.app-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('../../pages/HomePage.vue')
|
||||
},
|
||||
{
|
||||
path: '/studio',
|
||||
name: 'studio',
|
||||
component: () => import('../../pages/StudioPage.vue')
|
||||
},
|
||||
|
|
@ -11,6 +16,11 @@ const routes = [
|
|||
name: 'history',
|
||||
component: () => import('../../pages/HistoryPage.vue')
|
||||
},
|
||||
{
|
||||
path: '/documents',
|
||||
name: 'documents',
|
||||
component: () => import('../../pages/DocumentsPage.vue')
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
|
|
|
|||
158
frontend/src/pages/DocumentsPage.vue
Normal file
158
frontend/src/pages/DocumentsPage.vue
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<template>
|
||||
<div class="documents-page">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">{{ t('nav.documents') }}</h1>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<div v-if="docStore.documents.length === 0" class="tab-empty">
|
||||
{{ t('history.emptyDocs') }}
|
||||
</div>
|
||||
<div v-else class="doc-items">
|
||||
<div
|
||||
v-for="doc in docStore.documents"
|
||||
:key="doc.id"
|
||||
class="doc-row"
|
||||
>
|
||||
<div class="doc-row-info">
|
||||
<svg class="doc-row-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<div class="doc-row-meta">
|
||||
<span class="doc-row-name">{{ doc.filename }}</span>
|
||||
<span class="doc-row-detail">
|
||||
{{ formatSize(doc.fileSize) }}
|
||||
<template v-if="doc.pageCount"> — {{ doc.pageCount }} pages</template>
|
||||
<template v-if="doc.createdAt"> — {{ formatDate(doc.createdAt) }}</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="doc-row-delete" @click="docStore.remove(doc.id)">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { useDocumentStore } from '../features/document/store.js'
|
||||
import { useI18n } from '../shared/i18n.js'
|
||||
import { formatSize } from '../shared/format.js'
|
||||
|
||||
const docStore = useDocumentStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
function formatDate(iso) {
|
||||
if (!iso) return ''
|
||||
return new Date(iso).toLocaleString()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
docStore.load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.documents-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tab-empty {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 60px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.doc-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.doc-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: background var(--transition);
|
||||
}
|
||||
|
||||
.doc-row:hover { background: var(--bg-hover); }
|
||||
|
||||
.doc-row-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.doc-row-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.doc-row-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.doc-row-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.doc-row-detail {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
.doc-row-delete {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.doc-row:hover .doc-row-delete { opacity: 1; }
|
||||
.doc-row-delete:hover { color: var(--error); background: rgba(239, 68, 68, 0.1); }
|
||||
.doc-row-delete svg { width: 16px; height: 16px; }
|
||||
</style>
|
||||
|
|
@ -2,74 +2,23 @@
|
|||
<div class="history-page">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">{{ t('history.title') }}</h1>
|
||||
<div class="tab-bar">
|
||||
<button
|
||||
class="tab-btn"
|
||||
:class="{ active: tab === 'analyses' }"
|
||||
@click="tab = 'analyses'"
|
||||
>{{ t('history.tabAnalyses') }}</button>
|
||||
<button
|
||||
class="tab-btn"
|
||||
:class="{ active: tab === 'documents' }"
|
||||
@click="tab = 'documents'"
|
||||
>{{ t('history.tabDocuments') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<HistoryList v-if="tab === 'analyses'" />
|
||||
<div v-else class="doc-tab">
|
||||
<div v-if="docStore.documents.length === 0" class="tab-empty">
|
||||
{{ t('history.emptyDocs') }}
|
||||
</div>
|
||||
<div v-else class="doc-items">
|
||||
<div
|
||||
v-for="doc in docStore.documents"
|
||||
:key="doc.id"
|
||||
class="doc-row"
|
||||
>
|
||||
<div class="doc-row-info">
|
||||
<svg class="doc-row-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<div class="doc-row-meta">
|
||||
<span class="doc-row-name">{{ doc.filename }}</span>
|
||||
<span class="doc-row-detail">
|
||||
{{ formatSize(doc.fileSize) }}
|
||||
<template v-if="doc.pageCount"> — {{ doc.pageCount }} pages</template>
|
||||
<template v-if="doc.createdAt"> — {{ formatDate(doc.createdAt) }}</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="doc-row-delete" @click="docStore.remove(doc.id)">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HistoryList />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { HistoryList, useHistoryStore } from '../features/history/index.js'
|
||||
import { useDocumentStore } from '../features/document/store.js'
|
||||
import { useI18n } from '../shared/i18n.js'
|
||||
import { formatSize } from '../shared/format.js'
|
||||
|
||||
const historyStore = useHistoryStore()
|
||||
const docStore = useDocumentStore()
|
||||
const { t } = useI18n()
|
||||
const tab = ref('analyses')
|
||||
|
||||
function formatDate(iso) {
|
||||
if (!iso) return ''
|
||||
return new Date(iso).toLocaleString()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
historyStore.load()
|
||||
docStore.load()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
@ -85,9 +34,6 @@ onMounted(() => {
|
|||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
|
|
@ -96,115 +42,8 @@ onMounted(() => {
|
|||
color: var(--text);
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 6px 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: var(--bg-primary);
|
||||
color: var(--text);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tab-empty {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 60px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.doc-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.doc-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: background var(--transition);
|
||||
}
|
||||
|
||||
.doc-row:hover { background: var(--bg-hover); }
|
||||
|
||||
.doc-row-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.doc-row-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.doc-row-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.doc-row-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.doc-row-detail {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
.doc-row-delete {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.doc-row:hover .doc-row-delete { opacity: 1; }
|
||||
.doc-row-delete:hover { color: var(--error); background: rgba(239, 68, 68, 0.1); }
|
||||
.doc-row-delete svg { width: 16px; height: 16px; }
|
||||
</style>
|
||||
|
|
|
|||
270
frontend/src/pages/HomePage.vue
Normal file
270
frontend/src/pages/HomePage.vue
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
<template>
|
||||
<div class="home-page">
|
||||
<div class="home-center">
|
||||
<div class="hero">
|
||||
<div class="hero-icon">
|
||||
<span class="hero-d">D</span>
|
||||
</div>
|
||||
<h1 class="hero-title">{{ t('home.title') }}</h1>
|
||||
<p class="hero-subtitle">{{ t('home.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Quick upload -->
|
||||
<div class="home-upload">
|
||||
<DocumentUpload />
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="home-stats" v-if="docCount > 0 || analysisCount > 0">
|
||||
<div class="stat-card" @click="$router.push('/documents')">
|
||||
<div class="stat-value">{{ docCount }}</div>
|
||||
<div class="stat-label">{{ t('home.documents') }}</div>
|
||||
</div>
|
||||
<div class="stat-card" @click="$router.push('/history')">
|
||||
<div class="stat-value">{{ analysisCount }}</div>
|
||||
<div class="stat-label">{{ t('home.analyses') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent documents -->
|
||||
<div class="home-recent" v-if="documentStore.documents.length > 0">
|
||||
<label class="section-label">{{ t('home.recentDocs') }}</label>
|
||||
<div class="recent-list">
|
||||
<button
|
||||
v-for="doc in recentDocs"
|
||||
:key="doc.id"
|
||||
class="recent-item"
|
||||
@click="openInStudio(doc)"
|
||||
>
|
||||
<svg class="recent-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<div class="recent-meta">
|
||||
<span class="recent-name">{{ doc.filename }}</span>
|
||||
<span class="recent-detail">{{ doc.pageCount ? doc.pageCount + ' pages' : '' }}</span>
|
||||
</div>
|
||||
<svg class="recent-arrow" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useDocumentStore } from '../features/document/store.js'
|
||||
import { useHistoryStore } from '../features/history/index.js'
|
||||
import { DocumentUpload } from '../features/document/index.js'
|
||||
import { useI18n } from '../shared/i18n.js'
|
||||
|
||||
const router = useRouter()
|
||||
const documentStore = useDocumentStore()
|
||||
const historyStore = useHistoryStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const docCount = computed(() => documentStore.documents.length)
|
||||
const analysisCount = computed(() => historyStore.analyses.length)
|
||||
const recentDocs = computed(() => documentStore.documents.slice(0, 5))
|
||||
|
||||
function openInStudio(doc) {
|
||||
documentStore.select(doc.id)
|
||||
router.push('/studio')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
documentStore.load()
|
||||
historyStore.load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 40px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.home-center {
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-icon {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.hero-d {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border-radius: var(--radius-lg);
|
||||
font-weight: 700;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary);
|
||||
max-width: 400px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Upload */
|
||||
.home-upload {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.home-stats {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 16px;
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: var(--border-light);
|
||||
background: var(--bg-elevated);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Recent docs */
|
||||
.home-recent {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.recent-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.recent-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.recent-item:hover {
|
||||
background: var(--bg-surface);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.recent-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.recent-meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.recent-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.recent-detail {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
.recent-arrow {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition);
|
||||
}
|
||||
|
||||
.recent-item:hover .recent-arrow {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,9 +3,23 @@ import { useSettingsStore } from '../features/settings/store.js'
|
|||
const messages = {
|
||||
fr: {
|
||||
// Sidebar
|
||||
'nav.home': 'Accueil',
|
||||
'nav.studio': 'Studio',
|
||||
'nav.documents': 'Documents',
|
||||
'nav.history': 'Historique',
|
||||
'nav.settings': 'Paramètres',
|
||||
'nav.collapse': 'Réduire la barre latérale',
|
||||
'nav.expand': 'Développer la barre latérale',
|
||||
|
||||
// Top bar
|
||||
'topbar.newAnalysis': 'Nouvelle analyse',
|
||||
|
||||
// Home
|
||||
'home.title': 'Docling Studio',
|
||||
'home.subtitle': 'Analysez, explorez et validez la structure de vos documents PDF grâce à Docling.',
|
||||
'home.documents': 'Documents',
|
||||
'home.analyses': 'Analyses',
|
||||
'home.recentDocs': 'Documents récents',
|
||||
|
||||
// Studio — import
|
||||
'studio.title': 'Intelligence des documents',
|
||||
|
|
@ -84,9 +98,21 @@ const messages = {
|
|||
'settings.language': 'Langue',
|
||||
},
|
||||
en: {
|
||||
'nav.home': 'Home',
|
||||
'nav.studio': 'Studio',
|
||||
'nav.documents': 'Documents',
|
||||
'nav.history': 'History',
|
||||
'nav.settings': 'Settings',
|
||||
'nav.collapse': 'Collapse sidebar',
|
||||
'nav.expand': 'Expand sidebar',
|
||||
|
||||
'topbar.newAnalysis': 'New analysis',
|
||||
|
||||
'home.title': 'Docling Studio',
|
||||
'home.subtitle': 'Analyze, explore and validate the structure of your PDF documents with Docling.',
|
||||
'home.documents': 'Documents',
|
||||
'home.analyses': 'Analyses',
|
||||
'home.recentDocs': 'Recent documents',
|
||||
|
||||
'studio.title': 'Document Intelligence',
|
||||
'studio.subtitle': 'Upload a PDF document to start analyzing with Docling',
|
||||
|
|
|
|||
|
|
@ -1,26 +1,29 @@
|
|||
<template>
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="logo">
|
||||
<span class="logo-icon">D</span>
|
||||
<span class="logo-text">Docling Studio</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="sidebar" :class="{ open }">
|
||||
<nav class="sidebar-nav">
|
||||
<RouterLink to="/" class="nav-item" :class="{ active: route.name === 'studio' }">
|
||||
<RouterLink to="/" class="nav-item" :class="{ active: route.name === 'home' }">
|
||||
<svg class="nav-icon" viewBox="0 0 20 20" fill="currentColor"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/></svg>
|
||||
<span class="nav-label">{{ t('nav.home') }}</span>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink to="/studio" class="nav-item" :class="{ active: route.name === 'studio' }">
|
||||
<svg class="nav-icon" viewBox="0 0 20 20" fill="currentColor"><path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838l-2.727 1.17 1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zm5.99 7.176A9.026 9.026 0 007 15.96v-4.5l.61.26a2.5 2.5 0 001.98 0l.61-.26v4.5a9.026 9.026 0 00-1.7.613z"/></svg>
|
||||
<span>{{ t('nav.studio') }}</span>
|
||||
<span class="nav-label">{{ t('nav.studio') }}</span>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink to="/documents" class="nav-item" :class="{ active: route.name === 'documents' }">
|
||||
<svg class="nav-icon" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/></svg>
|
||||
<span class="nav-label">{{ t('nav.documents') }}</span>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink to="/history" class="nav-item" :class="{ active: route.name === 'history' }">
|
||||
<svg class="nav-icon" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>
|
||||
<span>{{ t('nav.history') }}</span>
|
||||
<span class="nav-label">{{ t('nav.history') }}</span>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink to="/settings" class="nav-item" :class="{ active: route.name === 'settings' }">
|
||||
<svg class="nav-icon" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"/></svg>
|
||||
<span>{{ t('nav.settings') }}</span>
|
||||
<span class="nav-label">{{ t('nav.settings') }}</span>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
|
||||
|
|
@ -36,6 +39,10 @@ import { useI18n } from '../i18n.js'
|
|||
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
defineProps({
|
||||
open: { type: Boolean, default: false }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -44,38 +51,16 @@ const { t } = useI18n()
|
|||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
transition: width 250ms ease, min-width 250ms ease;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
color: var(--text);
|
||||
.sidebar.open {
|
||||
width: var(--sidebar-width);
|
||||
min-width: var(--sidebar-width);
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
|
|
@ -84,6 +69,7 @@ const { t } = useI18n()
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
|
|
@ -97,6 +83,8 @@ const { t } = useI18n()
|
|||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all var(--transition);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
|
|
@ -112,12 +100,15 @@ const { t } = useI18n()
|
|||
.nav-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
min-width: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding: 16px;
|
||||
border-top: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.version {
|
||||
|
|
|
|||
Loading…
Reference in a new issue