Перейти к содержимому
FastOffice

Среды выполнения и сквозной сценарий

Что меняется между настольным приложением, браузером и сервером, а что остаётся одинаковым; полный путь «открыть байты → изменить → вернуть XLSX».

Что меняется между средами, а что остаётся одинаковым

Заголовок раздела «Что меняется между средами, а что остаётся одинаковым»
Среда Доступ к структуре События пользователя Изменение документа
Браузер, WebAssembly Через единый интерфейс сеанса поверх вызовов WebAssembly. Щелчок, двойной щелчок, контекстное меню, клавиатура, выделение. Команда уходит в ядро внутри WebAssembly.
Настольное приложение Тот же интерфейс; вызовы уходят в локальный сеанс. Те же события, что и в браузере. Команда уходит в отдельный процесс ядра.
WebView на телефоне Тот же интерфейс, но видимая область меньше. Касание, двойное касание и долгое нажатие превращаются в те же события. Команда уходит в WebAssembly; нужен свой жизненный цикл приложения.
Сервер и агенты Сеанс без интерфейса: запросы и команды. Щелчков нет; есть события команды, сеанса и изменения. Команда уходит в процесс ядра на сервере.

Выделение, цель контекстного меню, операция, ревизия, результат и сохранённый файл описаны одинаково независимо от того, как устроен транспорт. На телефоне долгое нажатие превращается в то же событие открытия контекстного меню. На сервере щелчка мышью нет, но агент запрашивает те же сведения о ячейке и выполняет ту же операцию — с теми же правами и теми же отказами.

  • В браузере нельзя отдавать структуру большой книги целиком: каждый запрос ограничен участком.

  • На телефоне сеанс должен переживать выгрузку приложения из памяти — через контрольную точку и повторное открытие, а не через ссылку на память WebView, которой к тому моменту уже нет.

  • На сервере нужны изоляция клиентов друг от друга, квоты, отмена задач и журнал действий. Готовой серверной платформы поставка не включает.

  • Агенту разрешён только заранее согласованный список запросов и команд. Учётные данные и исходный текст запросов Power Query и макросов в события не попадают.

Открыть байты → прочитать → изменить → сохранить

Заголовок раздела «Открыть байты → прочитать → изменить → сохранить»

Пример ниже использует тот же протокол, которым проверяется сборка. Индексы строк и столбцов считаются с нуля. Поле viewport ограничивает область, которая уйдёт в ответ, а sourceBytesBase64 позволяет передать книгу байтами — тогда ядру не нужен доступ к вашей файловой системе.

Часть 1: открыть документ и получить структуру

import { spawn } from "node:child_process";
import { readFile } from "node:fs/promises";
import readline from "node:readline";
// Путь к процессору внутри установленного приложения — в переменной
// окружения F1_CLI; где он лежит в каждой системе, показано на
// странице «Установка F1».
const cli = spawn(process.env.F1_CLI, ["--session"],
{ stdio: ["pipe", "pipe", "inherit"] });
const lines = readline.createInterface({ input: cli.stdout });
const iterator = lines[Symbol.asyncIterator]();
const next = async () => JSON.parse((await iterator.next()).value);
const ask = async request => {
cli.stdin.write(JSON.stringify(request) + "\n");
return next();
};
const hello = await next();
if (hello.kind !== "compute_contract_cli_session_ready")
throw new Error("F1 core handshake failed");
const bytes = await readFile("./demo.xlsx");
const viewport = { top: 0, left: 0, rows: 40, cols: 12 };
const opened = await ask({
operation: "open_workbook_subset_from_bytes",
sourceName: "demo.xlsx",
sourceBytesBase64: bytes.toString("base64"),
viewport, requestedBackend: "native-desktop"
});
if (!opened.ok) throw new Error(opened.code);
const { subsetId, activeSheet, objectInventory = [] } = opened.payload;
console.log(activeSheet, objectInventory);

Адресная операция, проверка ревизии и возврат байтов XLSX

Заголовок раздела «Адресная операция, проверка ревизии и возврат байтов XLSX»

После открытия книги поле subsetId называет её живой сеанс в ядре, и все следующие запросы несут этот идентификатор. Запрос на изменение передаёт expectedRevision: если ревизия устарела, ядро отказывает до внесения правки, а не применяет её наполовину.

Часть 2: адресное чтение, атомарная правка и сохранение

const cell = await ask({
operation: "read_cell_details", subsetId,
row: 0, col: 0, viewport,
requestedBackend: "native-desktop"
});
const edited = await ask({
operation: "apply_cell_edits", subsetId,
edits: [{ inputKind: "text", sheetKey: activeSheet.path,
row: 1, col: 1, input: "Доставлено" }],
expectedRevision: 0, viewport,
requestedBackend: "native-desktop"
});
if (!edited.ok) throw new Error(`${edited.code}: ${edited.message ?? ""}`);
const saved = await ask({
operation: "save_workbook_subset_to_bytes", subsetId,
preserveCachedValues: true,
requestedBackend: "native-desktop"
});
const output = Buffer.from(saved.payload.bytesBase64, "base64");
// Закрыть ядро: пока порождённый процесс жив, программа не завершится,
// а процессор останется висеть после её конца.
cli.stdin.end();
cli.kill();

Тот же контракт в браузере: адаптер событий поверх полотна

Заголовок раздела «Тот же контракт в браузере: адаптер событий поверх полотна»

Сеанс в браузере: доставку берёт на себя код страницы, смысл документа остаётся за ядром.

Модуль fastsheet_bridge_wasm.js рядом со сборкой ядра под WebAssembly — отдельный артефакт поставки: он идёт пакетами движка (см. Состав поставки) и в установщик настольного приложения не входит. Пути к процессору со страницы установки для этого случая не годятся: там нативный исполняемый файл, а здесь модуль для браузера.

import init, { WasmComputeSession } from "./fastsheet_bridge_wasm.js";
await init();
const core = new WasmComputeSession(Date.now(), 0x362cafe);
const dispatch = request =>
JSON.parse(core.dispatch(JSON.stringify(request)));
const opened = dispatch({
operation: "open_workbook_subset_from_bytes",
sourceName: file.name,
sourceBytesBase64: await fileToBase64(file),
viewport: { top: 0, left: 0, rows: 40, cols: 12 },
requestedBackend: "wasm"
});

Сегодня оболочка уже шлёт внутреннее событие fastsheet:surface-object-selected и сама обрабатывает щелчок, двойной щелчок и вызов контекстного меню. Для пилота их стоит спрятать за одной точкой входа. Подписываться напрямую, как показано ниже, допустимо только во внутренней сборке — но не рассчитывать на это как на долгосрочный интерфейс.

Внутренний мост событий — его ещё предстоит превратить в устойчивый набор вызовов:

// INTERNAL / UNSTABLE: адаптер внутри поставки F1.
surface.addEventListener("fastsheet:surface-object-selected", event => {
hostEvents.emit("objectSelected", {
target: normalizeObjectTarget(event.detail),
revision: currentRevision()
});
});
surface.addEventListener("dblclick", event => {
const target = semanticHitTest(event.clientX, event.clientY);
hostEvents.emit("doubleClicked", { target });
});
surface.addEventListener("contextmenu", event => {
event.preventDefault();
const target = semanticHitTest(event.clientX, event.clientY);
openComposedMenu(target, hostMenuProviders);
});

Ассистент документации

Ответ собран из документации и может быть неточным — сверяйтесь с источниками.