159 lines
5.8 KiB
TypeScript
159 lines
5.8 KiB
TypeScript
import { useMemo } from "react";
|
|
import { AppShell } from "./components/AppShell";
|
|
import { QOutlook } from "./features/qoutlook/QOutlook";
|
|
import { QPowerPoint } from "./features/qpowerpoint/QPowerPoint";
|
|
import { QWord } from "./features/qword/QWord";
|
|
import { QExcell } from "./features/qexcell/QExcell";
|
|
import { initialState } from "./data/sampleData";
|
|
import { usePersistentState } from "./hooks/usePersistentState";
|
|
import type { AppId, MailMessage, QOfficeState, SlideDeck, WordDocument, Workbook } from "./types";
|
|
|
|
const STORAGE_KEY = "qoffice-state-v1";
|
|
|
|
function migrateQOfficeState(state: QOfficeState): QOfficeState {
|
|
const launchChecklistAttachments = initialState.mail.messages.find((message) => message.id === "mail-1")?.attachments;
|
|
const sampleSheet = initialState.workbook.sheets.find((sheet) => sheet.id === "sheet-1");
|
|
const sampleSlidesById = Object.fromEntries(initialState.deck.slides.map((slide) => [slide.id, slide]));
|
|
const sampleWordHtml = initialState.word.html;
|
|
const needsSampleWordColor =
|
|
state.word.id === initialState.word.id &&
|
|
sampleWordHtml.includes("Ключевой результат") &&
|
|
!state.word.html.includes("Ключевой результат");
|
|
|
|
let changed = false;
|
|
if (needsSampleWordColor) {
|
|
changed = true;
|
|
}
|
|
const messages = state.mail.messages.map((message) => {
|
|
if (!launchChecklistAttachments || message.id !== "mail-1" || (message.attachments?.length ?? 0) > 0) {
|
|
return message;
|
|
}
|
|
|
|
changed = true;
|
|
return { ...message, attachments: launchChecklistAttachments };
|
|
});
|
|
|
|
const sheets =
|
|
state.workbook.id === initialState.workbook.id && sampleSheet
|
|
? state.workbook.sheets.map((sheet) => {
|
|
if (sheet.id !== "sheet-1") {
|
|
return sheet;
|
|
}
|
|
|
|
const needsMergedCells = (sheet.mergedCells?.length ?? 0) === 0;
|
|
const needsSampleCell = !sheet.cells.A8 && sampleSheet.cells.A8;
|
|
const missingSampleFormats = Object.entries(sampleSheet.cellFormats ?? {}).filter(([address]) => !sheet.cellFormats?.[address]);
|
|
const missingSampleCharts = (sampleSheet.charts ?? []).filter((chart) => !(sheet.charts ?? []).some((existingChart) => existingChart.id === chart.id));
|
|
if (!needsMergedCells && !needsSampleCell && missingSampleFormats.length === 0 && missingSampleCharts.length === 0) {
|
|
return sheet;
|
|
}
|
|
|
|
changed = true;
|
|
return {
|
|
...sheet,
|
|
cells: needsSampleCell ? { ...sheet.cells, A8: sampleSheet.cells.A8 } : sheet.cells,
|
|
mergedCells: needsMergedCells ? sampleSheet.mergedCells : sheet.mergedCells,
|
|
cellFormats:
|
|
missingSampleFormats.length > 0
|
|
? {
|
|
...sheet.cellFormats,
|
|
...Object.fromEntries(missingSampleFormats)
|
|
}
|
|
: sheet.cellFormats,
|
|
charts: missingSampleCharts.length > 0 ? [...(sheet.charts ?? []), ...missingSampleCharts] : sheet.charts
|
|
};
|
|
})
|
|
: state.workbook.sheets;
|
|
|
|
const slides =
|
|
state.deck.id === initialState.deck.id
|
|
? state.deck.slides.map((slide) => {
|
|
const sampleSlide = sampleSlidesById[slide.id];
|
|
if (!sampleSlide?.backgroundColor || slide.backgroundColor) {
|
|
return slide;
|
|
}
|
|
|
|
changed = true;
|
|
return { ...slide, backgroundColor: sampleSlide.backgroundColor };
|
|
})
|
|
: state.deck.slides;
|
|
|
|
return changed
|
|
? {
|
|
...state,
|
|
word: needsSampleWordColor ? { ...state.word, html: sampleWordHtml } : state.word,
|
|
workbook: { ...state.workbook, sheets },
|
|
deck: { ...state.deck, slides },
|
|
mail: { ...state.mail, messages }
|
|
}
|
|
: state;
|
|
}
|
|
|
|
export function App() {
|
|
const [state, setState] = usePersistentState<QOfficeState>(STORAGE_KEY, initialState, migrateQOfficeState);
|
|
|
|
const status = useMemo(() => {
|
|
const updatedAt =
|
|
state.activeApp === "word"
|
|
? state.word.updatedAt
|
|
: state.activeApp === "sheets"
|
|
? state.workbook.updatedAt
|
|
: state.activeApp === "slides"
|
|
? state.deck.updatedAt
|
|
: undefined;
|
|
|
|
return {
|
|
savedLabel: "Сохранено локально",
|
|
updatedAt
|
|
};
|
|
}, [state]);
|
|
|
|
function setActiveApp(activeApp: AppId) {
|
|
setState((current) => ({ ...current, activeApp }));
|
|
}
|
|
|
|
function updateWord(word: WordDocument) {
|
|
setState((current) => ({ ...current, word: { ...word, updatedAt: new Date().toISOString() } }));
|
|
}
|
|
|
|
function updateWorkbook(workbook: Workbook) {
|
|
setState((current) => ({ ...current, workbook: { ...workbook, updatedAt: new Date().toISOString() } }));
|
|
}
|
|
|
|
function updateDeck(deck: SlideDeck) {
|
|
setState((current) => ({ ...current, deck: { ...deck, updatedAt: new Date().toISOString() } }));
|
|
}
|
|
|
|
function updateMail(updater: (mail: QOfficeState["mail"]) => QOfficeState["mail"]) {
|
|
setState((current) => ({ ...current, mail: updater(current.mail) }));
|
|
}
|
|
|
|
function setMailFolder(folder: MailMessage["folder"]) {
|
|
updateMail((mail) => {
|
|
const firstInFolder = mail.messages.find((message) => message.folder === folder);
|
|
return {
|
|
...mail,
|
|
activeFolder: folder,
|
|
activeMessageId: firstInFolder?.id ?? mail.activeMessageId
|
|
};
|
|
});
|
|
}
|
|
|
|
const activeSurface =
|
|
state.activeApp === "word" ? (
|
|
<QWord document={state.word} onChange={updateWord} />
|
|
) : state.activeApp === "sheets" ? (
|
|
<QExcell workbook={state.workbook} onChange={updateWorkbook} />
|
|
) : state.activeApp === "slides" ? (
|
|
<QPowerPoint deck={state.deck} onChange={updateDeck} />
|
|
) : (
|
|
<QOutlook mail={state.mail} onChange={updateMail} onFolderChange={setMailFolder} />
|
|
);
|
|
|
|
return (
|
|
<AppShell activeApp={state.activeApp} onAppChange={setActiveApp} status={status}>
|
|
{activeSurface}
|
|
</AppShell>
|
|
);
|
|
}
|