Initial onlyoffice integration, sidebar changes
Some checks failed
Automated Container Build / build-and-push (push) Failing after 28s
Some checks failed
Automated Container Build / build-and-push (push) Failing after 28s
This commit is contained in:
parent
4110657557
commit
d80e31e7f5
7 changed files with 504 additions and 12 deletions
125
frontend/src/components/OfficeHome.tsx
Normal file
125
frontend/src/components/OfficeHome.tsx
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Plus, FileText, Presentation } from 'lucide-react';
|
||||
import api from '@/lib/api';
|
||||
|
||||
interface FileItem {
|
||||
name: string;
|
||||
path: string;
|
||||
is_dir: boolean;
|
||||
size: number;
|
||||
mime_type: string;
|
||||
is_pinned: boolean;
|
||||
is_trashed: boolean;
|
||||
mod_time: string;
|
||||
}
|
||||
|
||||
interface OfficeHomeProps {
|
||||
type: 'docs' | 'slides';
|
||||
onFileSelect: (file: FileItem) => void;
|
||||
onCreateBlank: (file: FileItem) => void;
|
||||
}
|
||||
|
||||
export default function OfficeHome({ type, onFileSelect, onCreateBlank }: OfficeHomeProps) {
|
||||
const [recentFiles, setRecentFiles] = useState<FileItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch all files from root directory (as a simple way to find recent files of this type)
|
||||
// Or we could use a search endpoint. For now we will fetch the root directory and filter.
|
||||
api.listFiles('.')
|
||||
.then(files => {
|
||||
const ext = type === 'docs' ? '.docx' : '.pptx';
|
||||
const filtered = files
|
||||
.filter(f => !f.is_dir && f.name.endsWith(ext) && !f.is_trashed)
|
||||
.sort((a, b) => new Date(b.mod_time).getTime() - new Date(a.mod_time).getTime())
|
||||
.slice(0, 10);
|
||||
setRecentFiles(filtered);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [type]);
|
||||
|
||||
const handleCreateBlank = async () => {
|
||||
try {
|
||||
const res = await api.client.post('/onlyoffice/create', { type: type === 'docs' ? 'word' : 'slide' });
|
||||
const { path, name } = res.data;
|
||||
|
||||
const newFile: FileItem = {
|
||||
name,
|
||||
path,
|
||||
is_dir: false,
|
||||
size: 0,
|
||||
mime_type: type === 'docs' ? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
is_pinned: false,
|
||||
is_trashed: false,
|
||||
mod_time: new Date().toISOString()
|
||||
};
|
||||
|
||||
onCreateBlank(newFile);
|
||||
} catch (err) {
|
||||
console.error("Failed to create document", err);
|
||||
}
|
||||
};
|
||||
|
||||
const title = type === 'docs' ? 'Documents' : 'Presentations';
|
||||
const Icon = type === 'docs' ? FileText : Presentation;
|
||||
const colorClass = type === 'docs' ? 'text-blue-500' : 'text-amber-500';
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto" style={{ backgroundColor: 'var(--color-bg-primary)' }}>
|
||||
{/* Top Banner (Template Gallery placeholder) */}
|
||||
<div className="py-8 px-10" style={{ backgroundColor: 'var(--color-bg-secondary)' }}>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h2 className="text-lg font-medium mb-4" style={{ color: 'var(--color-text-primary)' }}>Start a new document</h2>
|
||||
<div className="flex gap-4">
|
||||
{/* Blank Document */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={handleCreateBlank}
|
||||
className="w-40 h-52 bg-white rounded-lg border border-gray-200 hover:border-blue-500 transition-colors flex items-center justify-center shadow-sm group"
|
||||
>
|
||||
<Plus className="w-12 h-12 text-gray-300 group-hover:text-blue-500 transition-colors" />
|
||||
</button>
|
||||
<span className="text-sm font-medium" style={{ color: 'var(--color-text-primary)' }}>Blank</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Documents */}
|
||||
<div className="py-8 px-10">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h2 className="text-lg font-medium mb-6" style={{ color: 'var(--color-text-primary)' }}>Recent {title.toLowerCase()}</h2>
|
||||
|
||||
{loading ? (
|
||||
<div className="text-sm text-gray-500">Loading...</div>
|
||||
) : recentFiles.length === 0 ? (
|
||||
<div className="text-sm text-gray-500">No recent {title.toLowerCase()} found.</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6">
|
||||
{recentFiles.map(file => (
|
||||
<div key={file.path} className="flex flex-col gap-2 group">
|
||||
<button
|
||||
onClick={() => onFileSelect(file)}
|
||||
className="w-full aspect-[1/1.4] bg-white rounded-lg border border-gray-200 group-hover:border-blue-500 transition-colors flex items-center justify-center shadow-sm overflow-hidden"
|
||||
>
|
||||
<Icon className={`w-16 h-16 ${colorClass} opacity-20 group-hover:opacity-100 transition-opacity`} />
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className={`w-4 h-4 flex-shrink-0 ${colorClass}`} />
|
||||
<span className="text-sm font-medium truncate" style={{ color: 'var(--color-text-primary)' }} title={file.name}>
|
||||
{file.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in a new issue