Initial xlxs support
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m25s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m25s
This commit is contained in:
parent
2648a5ae84
commit
709c13995c
5 changed files with 164 additions and 22 deletions
|
|
@ -127,12 +127,16 @@ func (h *OnlyOfficeHandler) CreateBlank(c *fiber.Ctx) error {
|
||||||
req.Name = "Untitled Document"
|
req.Name = "Untitled Document"
|
||||||
if req.Type == "slide" {
|
if req.Type == "slide" {
|
||||||
req.Name = "Untitled Presentation"
|
req.Name = "Untitled Presentation"
|
||||||
|
} else if req.Type == "cell" {
|
||||||
|
req.Name = "Untitled Spreadsheet"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext := ".docx"
|
ext := ".docx"
|
||||||
if req.Type == "slide" {
|
if req.Type == "slide" {
|
||||||
ext = ".pptx"
|
ext = ".pptx"
|
||||||
|
} else if req.Type == "cell" {
|
||||||
|
ext = ".xlsx"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add extension if missing
|
// Add extension if missing
|
||||||
|
|
@ -227,13 +231,15 @@ func (h *OnlyOfficeHandler) CreateFromTemplate(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListOfficeFiles returns all .docx or .pptx files across the entire storage directory.
|
// ListOfficeFiles returns all .docx, .pptx, or .xlsx files across the entire storage directory.
|
||||||
// GET /api/onlyoffice/files?type=docx|pptx
|
// GET /api/onlyoffice/files?type=docx|pptx|xlsx
|
||||||
func (h *OnlyOfficeHandler) ListOfficeFiles(c *fiber.Ctx) error {
|
func (h *OnlyOfficeHandler) ListOfficeFiles(c *fiber.Ctx) error {
|
||||||
fileType := c.Query("type", "docx") // "docx" or "pptx"
|
fileType := c.Query("type", "docx") // "docx", "pptx", or "xlsx"
|
||||||
ext := ".docx"
|
ext := ".docx"
|
||||||
if fileType == "pptx" {
|
if fileType == "pptx" {
|
||||||
ext = ".pptx"
|
ext = ".pptx"
|
||||||
|
} else if fileType == "xlsx" {
|
||||||
|
ext = ".xlsx"
|
||||||
}
|
}
|
||||||
|
|
||||||
type OfficeFile struct {
|
type OfficeFile struct {
|
||||||
|
|
@ -295,6 +301,8 @@ func createBlankOfficeFile(path string, ext string) error {
|
||||||
|
|
||||||
if ext == ".pptx" {
|
if ext == ".pptx" {
|
||||||
return writePptxContents(w)
|
return writePptxContents(w)
|
||||||
|
} else if ext == ".xlsx" {
|
||||||
|
return writeXlsxContents(w)
|
||||||
}
|
}
|
||||||
return writeDocxContents(w)
|
return writeDocxContents(w)
|
||||||
}
|
}
|
||||||
|
|
@ -458,6 +466,46 @@ func writePptxContents(w *zip.Writer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeXlsxContents(w *zip.Writer) error {
|
||||||
|
files := map[string]string{
|
||||||
|
"[Content_Types].xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||||
|
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||||
|
<Default Extension="xml" ContentType="application/xml"/>
|
||||||
|
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
||||||
|
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
|
||||||
|
</Types>`,
|
||||||
|
"_rels/.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
||||||
|
</Relationships>`,
|
||||||
|
"xl/workbook.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||||
|
<sheets>
|
||||||
|
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
|
||||||
|
</sheets>
|
||||||
|
</workbook>`,
|
||||||
|
"xl/_rels/workbook.xml.rels": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
|
||||||
|
</Relationships>`,
|
||||||
|
"xl/worksheets/sheet1.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||||
|
<sheetData/>
|
||||||
|
</worksheet>`,
|
||||||
|
}
|
||||||
|
for name, content := range files {
|
||||||
|
fw, err := w.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fw.Write([]byte(content)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SignConfig takes the OnlyOffice configuration payload and signs it with the JWT secret
|
// SignConfig takes the OnlyOffice configuration payload and signs it with the JWT secret
|
||||||
func (h *OnlyOfficeHandler) SignConfig(c *fiber.Ctx) error {
|
func (h *OnlyOfficeHandler) SignConfig(c *fiber.Ctx) error {
|
||||||
if h.Config.OnlyOfficeJWT == "" {
|
if h.Config.OnlyOfficeJWT == "" {
|
||||||
|
|
|
||||||
40
backend/test_xlsx.py
Normal file
40
backend/test_xlsx.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
def create_empty_xlsx(filename):
|
||||||
|
with zipfile.ZipFile(filename, 'w') as zf:
|
||||||
|
# [Content_Types].xml
|
||||||
|
zf.writestr('[Content_Types].xml', """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||||
|
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||||
|
<Default Extension="xml" ContentType="application/xml"/>
|
||||||
|
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
||||||
|
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
|
||||||
|
</Types>""")
|
||||||
|
|
||||||
|
# _rels/.rels
|
||||||
|
zf.writestr('_rels/.rels', """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
||||||
|
</Relationships>""")
|
||||||
|
|
||||||
|
# xl/workbook.xml
|
||||||
|
zf.writestr('xl/workbook.xml', """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||||
|
<sheets>
|
||||||
|
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
|
||||||
|
</sheets>
|
||||||
|
</workbook>""")
|
||||||
|
|
||||||
|
# xl/_rels/workbook.xml.rels
|
||||||
|
zf.writestr('xl/_rels/workbook.xml.rels', """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
|
||||||
|
</Relationships>""")
|
||||||
|
|
||||||
|
# xl/worksheets/sheet1.xml
|
||||||
|
zf.writestr('xl/worksheets/sheet1.xml', """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||||
|
<sheetData/>
|
||||||
|
</worksheet>""")
|
||||||
|
|
||||||
|
create_empty_xlsx('test.xlsx')
|
||||||
|
|
@ -239,7 +239,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
const [files, setFiles] = useState<FileItem[]>([]);
|
const [files, setFiles] = useState<FileItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
||||||
const [appMode, setAppMode] = useState<'drive' | 'docs' | 'slides'>('drive');
|
const [appMode, setAppMode] = useState<'drive' | 'docs' | 'slides' | 'sheets'>('drive');
|
||||||
const [editingFile, setEditingFile] = useState<FileItem | null>(null);
|
const [editingFile, setEditingFile] = useState<FileItem | null>(null);
|
||||||
const [activeSection, setActiveSection] = useState<SidebarSection>('files');
|
const [activeSection, setActiveSection] = useState<SidebarSection>('files');
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
@ -558,6 +558,9 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
} else if (file.name.endsWith('.pptx')) {
|
} else if (file.name.endsWith('.pptx')) {
|
||||||
setAppMode('slides');
|
setAppMode('slides');
|
||||||
setEditingFile(file);
|
setEditingFile(file);
|
||||||
|
} else if (file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) {
|
||||||
|
setAppMode('sheets');
|
||||||
|
setEditingFile(file);
|
||||||
} else {
|
} else {
|
||||||
setPreviewFile(file);
|
setPreviewFile(file);
|
||||||
}
|
}
|
||||||
|
|
@ -1181,6 +1184,14 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ext === 'xlsx' || ext === 'xls') {
|
||||||
|
return (
|
||||||
|
<div className={`${large ? 'w-20 h-20 rounded-2xl text-2xl' : 'w-5 h-5 rounded text-[6px] flex-shrink-0 aspect-square'} flex items-center justify-center font-black tracking-tighter shadow-sm text-white bg-[#107C41]`}>
|
||||||
|
X
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const fallbackIcon = (() => {
|
const fallbackIcon = (() => {
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
|
|
@ -1447,6 +1458,20 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
</div>
|
</div>
|
||||||
Slides
|
Slides
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => { setAppMode('sheets'); setEditingFile(null); }}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-bold transition-all duration-150"
|
||||||
|
style={{
|
||||||
|
backgroundColor: appMode === 'sheets' ? 'var(--color-accent-subtle)' : 'transparent',
|
||||||
|
color: appMode === 'sheets' ? 'var(--color-accent)' : 'var(--color-text-primary)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="w-7 h-7 rounded-lg flex items-center justify-center" style={{ background: appMode === 'sheets' ? 'linear-gradient(135deg, #10b981, #059669)' : 'var(--color-bg-elevated)' }}>
|
||||||
|
<Grid3X3 className={`w-4 h-4 ${appMode === 'sheets' ? 'text-white' : ''}`} style={{ color: appMode !== 'sheets' ? 'var(--color-text-secondary)' : undefined }} />
|
||||||
|
</div>
|
||||||
|
Sheets
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="flex-1 px-3 py-2 space-y-1 overflow-y-auto">
|
<nav className="flex-1 px-3 py-2 space-y-1 overflow-y-auto">
|
||||||
|
|
@ -1560,6 +1585,24 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
{renderPinnedSidebarSection('.pptx')}
|
{renderPinnedSidebarSection('.pptx')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{appMode === 'sheets' && (
|
||||||
|
<>
|
||||||
|
<div className="px-3 py-2 text-xs font-semibold uppercase tracking-wider text-gray-500">Spreadsheets</div>
|
||||||
|
<button
|
||||||
|
onClick={() => { setActiveSection('files'); setEditingFile(null); }}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-150"
|
||||||
|
style={{
|
||||||
|
backgroundColor: !editingFile ? 'var(--color-accent-subtle)' : 'transparent',
|
||||||
|
color: !editingFile ? 'var(--color-accent)' : 'var(--color-text-secondary)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid3X3 className="w-4.5 h-4.5" />
|
||||||
|
Recent Spreadsheets
|
||||||
|
</button>
|
||||||
|
{renderPinnedSidebarSection('.xlsx')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="px-3 py-4 border-t" style={{ borderColor: 'var(--color-border-subtle)' }}>
|
<div className="px-3 py-4 border-t" style={{ borderColor: 'var(--color-border-subtle)' }}>
|
||||||
|
|
@ -1620,7 +1663,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<span className="text-lg font-semibold" style={{ color: 'var(--color-text-primary)' }}>
|
<span className="text-lg font-semibold" style={{ color: 'var(--color-text-primary)' }}>
|
||||||
{appMode === 'docs' ? 'Documents' : 'Presentations'}
|
{appMode === 'docs' ? 'Documents' : appMode === 'slides' ? 'Presentations' : 'Spreadsheets'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1896,7 +1939,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
) : editingFile ? (
|
) : editingFile ? (
|
||||||
<OnlyOfficeEditor
|
<OnlyOfficeEditor
|
||||||
file={editingFile}
|
file={editingFile}
|
||||||
type={editingFile.name.endsWith('.docx') ? 'word' : editingFile.name.endsWith('.pptx') ? 'slide' : 'word'}
|
type={editingFile.name.endsWith('.docx') ? 'word' : editingFile.name.endsWith('.pptx') ? 'slide' : 'cell'}
|
||||||
onClose={() => setEditingFile(null)}
|
onClose={() => setEditingFile(null)}
|
||||||
onRename={(oldPath, newName, newPath) => {
|
onRename={(oldPath, newName, newPath) => {
|
||||||
setEditingFile(prev => prev ? { ...prev, name: newName, path: newPath } : null);
|
setEditingFile(prev => prev ? { ...prev, name: newName, path: newPath } : null);
|
||||||
|
|
@ -1904,7 +1947,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
||||||
theme={theme as 'light' | 'dark'}
|
theme={theme as 'light' | 'dark'}
|
||||||
toggleTheme={toggleTheme}
|
toggleTheme={toggleTheme}
|
||||||
/>
|
/>
|
||||||
) : (appMode === 'docs' || appMode === 'slides') && activeSection === 'files' ? (
|
) : (appMode === 'docs' || appMode === 'slides' || appMode === 'sheets') && activeSection === 'files' ? (
|
||||||
<OfficeHome
|
<OfficeHome
|
||||||
type={appMode}
|
type={appMode}
|
||||||
onFileSelect={(file) => setEditingFile(file)}
|
onFileSelect={(file) => setEditingFile(file)}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ interface FileItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OfficeHomeProps {
|
interface OfficeHomeProps {
|
||||||
type: 'docs' | 'slides';
|
type: 'docs' | 'slides' | 'sheets';
|
||||||
onFileSelect: (file: FileItem) => void;
|
onFileSelect: (file: FileItem) => void;
|
||||||
onCreateBlank: (file: FileItem) => void;
|
onCreateBlank: (file: FileItem) => void;
|
||||||
onPinChange?: () => void;
|
onPinChange?: () => void;
|
||||||
|
|
@ -38,6 +38,15 @@ function PptIcon({ className }: { className?: string }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom Excel icon (green)
|
||||||
|
function SheetIcon({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center justify-center font-black tracking-tighter text-white bg-[#107C41] rounded-lg ${className || ''}`}>
|
||||||
|
X
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface ContextMenuState {
|
interface ContextMenuState {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
|
@ -55,8 +64,8 @@ export default function OfficeHome({ type, onFileSelect, onCreateBlank, onPinCha
|
||||||
const [renameValue, setRenameValue] = useState('');
|
const [renameValue, setRenameValue] = useState('');
|
||||||
const renameInputRef = useRef<HTMLInputElement>(null);
|
const renameInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const ext = type === 'docs' ? '.docx' : '.pptx';
|
const ext = type === 'docs' ? '.docx' : type === 'slides' ? '.pptx' : '.xlsx';
|
||||||
const fileTypeParam = type === 'docs' ? 'docx' : 'pptx';
|
const fileTypeParam = type === 'docs' ? 'docx' : type === 'slides' ? 'pptx' : 'xlsx';
|
||||||
|
|
||||||
const fetchFiles = () => {
|
const fetchFiles = () => {
|
||||||
// Fetch recent files from root directory
|
// Fetch recent files from root directory
|
||||||
|
|
@ -132,7 +141,7 @@ export default function OfficeHome({ type, onFileSelect, onCreateBlank, onPinCha
|
||||||
|
|
||||||
const handleCreateBlank = async () => {
|
const handleCreateBlank = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await api.createOnlyOfficeFile(type === 'docs' ? 'word' : 'slide');
|
const res = await api.createOnlyOfficeFile(type === 'docs' ? 'word' : type === 'slides' ? 'slide' : 'cell');
|
||||||
const { path, name } = res;
|
const { path, name } = res;
|
||||||
|
|
||||||
const newFile: FileItem = {
|
const newFile: FileItem = {
|
||||||
|
|
@ -140,7 +149,9 @@ export default function OfficeHome({ type, onFileSelect, onCreateBlank, onPinCha
|
||||||
path,
|
path,
|
||||||
is_dir: false,
|
is_dir: false,
|
||||||
size: 0,
|
size: 0,
|
||||||
mime_type: type === 'docs' ? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
mime_type: type === 'docs' ? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' :
|
||||||
|
type === 'slides' ? 'application/vnd.openxmlformats-officedocument.presentationml.presentation' :
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
is_pinned: false,
|
is_pinned: false,
|
||||||
is_trashed: false,
|
is_trashed: false,
|
||||||
mod_time: new Date().toISOString()
|
mod_time: new Date().toISOString()
|
||||||
|
|
@ -204,9 +215,9 @@ export default function OfficeHome({ type, onFileSelect, onCreateBlank, onPinCha
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const title = type === 'docs' ? 'Documents' : 'Presentations';
|
const title = type === 'docs' ? 'Documents' : type === 'slides' ? 'Presentations' : 'Spreadsheets';
|
||||||
const Icon = type === 'docs' ? WordIcon : PptIcon;
|
const Icon = type === 'docs' ? WordIcon : type === 'slides' ? PptIcon : SheetIcon;
|
||||||
const accentColor = type === 'docs' ? '#2B579A' : '#D24726';
|
const accentColor = type === 'docs' ? '#2B579A' : type === 'slides' ? '#D24726' : '#107C41';
|
||||||
|
|
||||||
const renderFileCard = (file: FileItem) => {
|
const renderFileCard = (file: FileItem) => {
|
||||||
const isRenaming = renaming === file.path;
|
const isRenaming = renaming === file.path;
|
||||||
|
|
@ -218,7 +229,7 @@ export default function OfficeHome({ type, onFileSelect, onCreateBlank, onPinCha
|
||||||
<button
|
<button
|
||||||
onClick={() => !isRenaming && onFileSelect(file)}
|
onClick={() => !isRenaming && onFileSelect(file)}
|
||||||
onContextMenu={(e) => handleContextMenu(e, file)}
|
onContextMenu={(e) => handleContextMenu(e, file)}
|
||||||
className={`w-full ${type === 'docs' ? 'aspect-[1/1.4]' : 'aspect-[1.4/1]'} rounded-lg border hover:border-blue-500 transition-colors flex items-center justify-center shadow-sm overflow-hidden relative`}
|
className={`w-full ${type === 'slides' ? 'aspect-[1.4/1]' : 'aspect-[1/1.4]'} rounded-lg border hover:border-blue-500 transition-colors flex items-center justify-center shadow-sm overflow-hidden relative`}
|
||||||
style={{ backgroundColor: 'var(--color-bg-elevated)', borderColor: 'var(--color-border)' }}
|
style={{ backgroundColor: 'var(--color-bg-elevated)', borderColor: 'var(--color-border)' }}
|
||||||
>
|
>
|
||||||
<Icon className="w-16 h-16 text-3xl opacity-30 group-hover:opacity-60 transition-opacity" />
|
<Icon className="w-16 h-16 text-3xl opacity-30 group-hover:opacity-60 transition-opacity" />
|
||||||
|
|
@ -257,12 +268,12 @@ export default function OfficeHome({ type, onFileSelect, onCreateBlank, onPinCha
|
||||||
{/* Top Banner */}
|
{/* Top Banner */}
|
||||||
<div className="py-8 px-10" style={{ backgroundColor: 'var(--color-bg-secondary)' }}>
|
<div className="py-8 px-10" style={{ backgroundColor: 'var(--color-bg-secondary)' }}>
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<h2 className="text-lg font-medium mb-4" style={{ color: 'var(--color-text-primary)' }}>Start a new {type === 'docs' ? 'document' : 'presentation'}</h2>
|
<h2 className="text-lg font-medium mb-4" style={{ color: 'var(--color-text-primary)' }}>Start a new {type === 'docs' ? 'document' : type === 'slides' ? 'presentation' : 'spreadsheet'}</h2>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={handleCreateBlank}
|
onClick={handleCreateBlank}
|
||||||
className={`${type === 'docs' ? 'w-40 h-52' : 'w-52 h-40'} rounded-lg border hover:border-blue-500 transition-colors flex items-center justify-center shadow-sm group`}
|
className={`${type === 'slides' ? 'w-52 h-40' : 'w-40 h-52'} rounded-lg border hover:border-blue-500 transition-colors flex items-center justify-center shadow-sm group`}
|
||||||
style={{ backgroundColor: 'var(--color-bg-elevated)', borderColor: 'var(--color-border)' }}
|
style={{ backgroundColor: 'var(--color-bg-elevated)', borderColor: 'var(--color-border)' }}
|
||||||
>
|
>
|
||||||
<Plus className="w-12 h-12 text-gray-300 group-hover:text-blue-500 transition-colors" />
|
<Plus className="w-12 h-12 text-gray-300 group-hover:text-blue-500 transition-colors" />
|
||||||
|
|
@ -284,7 +295,7 @@ export default function OfficeHome({ type, onFileSelect, onCreateBlank, onPinCha
|
||||||
) : recentFiles.length === 0 ? (
|
) : recentFiles.length === 0 ? (
|
||||||
<div className="text-sm text-gray-500">No recent {title.toLowerCase()} found.</div>
|
<div className="text-sm text-gray-500">No recent {title.toLowerCase()} found.</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={`grid ${type === 'docs' ? 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6' : 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5'} gap-6`}>
|
<div className={`grid ${type === 'slides' ? 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5' : 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6'} gap-6`}>
|
||||||
{recentFiles.map(file => renderFileCard(file))}
|
{recentFiles.map(file => renderFileCard(file))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -301,7 +312,7 @@ export default function OfficeHome({ type, onFileSelect, onCreateBlank, onPinCha
|
||||||
) : allFiles.length === 0 ? (
|
) : allFiles.length === 0 ? (
|
||||||
<div className="text-sm text-gray-500">No {title.toLowerCase()} found in your drive.</div>
|
<div className="text-sm text-gray-500">No {title.toLowerCase()} found in your drive.</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={`grid ${type === 'docs' ? 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6' : 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5'} gap-6`}>
|
<div className={`grid ${type === 'slides' ? 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5' : 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6'} gap-6`}>
|
||||||
{allFiles.map(file => renderFileCard(file))}
|
{allFiles.map(file => renderFileCard(file))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -470,7 +470,7 @@ class ApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- OnlyOffice ---
|
// --- OnlyOffice ---
|
||||||
async createOnlyOfficeFile(type: 'word' | 'slide') {
|
async createOnlyOfficeFile(type: 'word' | 'slide' | 'cell') {
|
||||||
const res = await this.request('/api/onlyoffice/create', {
|
const res = await this.request('/api/onlyoffice/create', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { type }
|
body: { type }
|
||||||
|
|
@ -494,7 +494,7 @@ class ApiClient {
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async listOfficeFiles(type: 'docx' | 'pptx') {
|
async listOfficeFiles(type: 'docx' | 'pptx' | 'xlsx') {
|
||||||
const res = await this.request(`/api/onlyoffice/files?type=${type}`);
|
const res = await this.request(`/api/onlyoffice/files?type=${type}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue