93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
import {
|
||
ChevronDown,
|
||
Redo2,
|
||
Save,
|
||
Search,
|
||
Share2,
|
||
Undo2
|
||
} from "lucide-react";
|
||
import type { MouseEvent, ReactNode } from "react";
|
||
import type { QOfficeCommand } from "../hooks/useQOfficeCommand";
|
||
import type { AppId } from "../types";
|
||
|
||
interface AppShellProps {
|
||
activeApp: AppId;
|
||
windowTitle?: string;
|
||
status: {
|
||
savedLabel: string;
|
||
updatedAt?: string;
|
||
content?: ReactNode;
|
||
trailing?: ReactNode;
|
||
className?: string;
|
||
};
|
||
children: ReactNode;
|
||
}
|
||
|
||
const appTitles: Record<AppId, string> = {
|
||
word: "Документ1 - QWord",
|
||
sheets: "Книга1 - QExcell",
|
||
slides: "Презентация1 - QPowerPoint",
|
||
mail: "Почта - QOutlook"
|
||
};
|
||
|
||
function dispatchQOfficeCommand(command: QOfficeCommand) {
|
||
window.dispatchEvent(new CustomEvent("qoffice-command", { detail: { command } }));
|
||
}
|
||
|
||
function keepActiveEditorFocus(event: MouseEvent<HTMLButtonElement>) {
|
||
event.preventDefault();
|
||
}
|
||
|
||
export function AppShell({ activeApp, windowTitle, status, children }: AppShellProps) {
|
||
const statusClassName = ["statusbar", status.className].filter(Boolean).join(" ");
|
||
const displayWindowTitle = windowTitle?.trim() || appTitles[activeApp];
|
||
|
||
return (
|
||
<div className="qoffice-shell">
|
||
<main className="workspace">
|
||
<header className="titlebar">
|
||
<div className="quick-access" aria-label="Панель быстрого доступа">
|
||
<button type="button" title="Сохранить" aria-label="Сохранить" onMouseDown={keepActiveEditorFocus} onClick={() => dispatchQOfficeCommand("save")}>
|
||
<Save size={16} />
|
||
</button>
|
||
<button type="button" title="Отменить" aria-label="Отменить" onMouseDown={keepActiveEditorFocus} onClick={() => dispatchQOfficeCommand("undo")}>
|
||
<Undo2 size={16} />
|
||
</button>
|
||
<button type="button" title="Повторить" aria-label="Повторить" onMouseDown={keepActiveEditorFocus} onClick={() => dispatchQOfficeCommand("redo")}>
|
||
<Redo2 size={16} />
|
||
</button>
|
||
<button type="button" title="Настроить панель" aria-label="Настроить панель">
|
||
<ChevronDown size={14} />
|
||
</button>
|
||
</div>
|
||
<strong className="window-title">{displayWindowTitle}</strong>
|
||
<div className="titlebar-actions">
|
||
<label className="command-search">
|
||
<Search size={16} />
|
||
<input placeholder="Поиск" aria-label="Поиск" />
|
||
</label>
|
||
<button className="primary-command" type="button">
|
||
<Share2 size={16} />
|
||
Поделиться
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="surface-host">{children}</div>
|
||
|
||
<footer className={statusClassName}>
|
||
<div className="statusbar-main">
|
||
{status.content ?? (
|
||
<>
|
||
<span>Готово</span>
|
||
<span>{status.updatedAt ? `Обновлено: ${new Date(status.updatedAt).toLocaleString("ru-RU")}` : "Локальная почта"}</span>
|
||
</>
|
||
)}
|
||
</div>
|
||
<div className="statusbar-trailing">{status.trailing ?? <span>Масштаб 100%</span>}</div>
|
||
</footer>
|
||
</main>
|
||
</div>
|
||
);
|
||
}
|