Introduce network request handler (#4194)

* Impl NetworkMessageHandler

* Repalce Frontend fetch like messages with NetworkMessage

* Reimpl resource loading with NetworkMessage

* Fix wasm

* dedublicate resource requests

* Embedd font resources created by migration

* Fixup

* Fix tests

* Fix font catalog not loading

* Cleanup

* Fix layouts not updating when font catalog is loaded

* Review

* Review

* Cleanup
This commit is contained in:
Timon
2026-06-04 13:27:14 +00:00
committed by Keavon Chambers
parent cd8ea1f554
commit 08c6d02e5b
23 changed files with 348 additions and 224 deletions

View File

@@ -2,7 +2,6 @@
import { onMount, onDestroy, setContext } from "svelte";
import MainWindow from "/src/components/window/MainWindow.svelte";
import { createClipboardManager, destroyClipboardManager } from "/src/managers/clipboard";
import { createFontsManager, destroyFontsManager } from "/src/managers/fonts";
import { createHyperlinkManager, destroyHyperlinkManager } from "/src/managers/hyperlink";
import { createInputManager, destroyInputManager } from "/src/managers/input";
import { createLocalizationManager, destroyLocalizationManager } from "/src/managers/localization";
@@ -43,7 +42,6 @@
createLocalizationManager(subscriptions, editor);
createPanicManager(subscriptions);
createPersistenceManager(subscriptions, editor, stores.portfolio);
createFontsManager(subscriptions, editor);
createInputManager(subscriptions, editor, stores.dialog, stores.portfolio, stores.document);
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready.
@@ -71,7 +69,6 @@
destroyLocalizationManager();
destroyPanicManager();
destroyPersistenceManager();
destroyFontsManager();
destroyInputManager();
});
</script>

View File

@@ -1,75 +0,0 @@
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import type { EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
type ApiResponse = { family: string; variants: string[]; files: Record<string, string> }[];
const FONT_LIST_API = "https://api.graphite.art/font-list";
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
let editorWrapper: EditorWrapper | undefined = undefined;
let abortController: AbortController | undefined = undefined;
export function createFontsManager(subscriptions: SubscriptionsRouter, editor: EditorWrapper) {
destroyFontsManager();
subscriptionsRouter = subscriptions;
editorWrapper = editor;
abortController = new AbortController();
subscriptions.subscribeFrontendMessage("TriggerFontCatalogLoad", async () => {
try {
const response = await fetch(FONT_LIST_API, abortController ? { signal: abortController.signal } : undefined);
if (!response.ok) throw new Error(`Font catalog request failed with status ${response.status}`);
const fontListResponse: { items: ApiResponse } = await response.json();
const fontListData = fontListResponse.items;
const catalog = fontListData.map((font) => {
const styles = font.variants.map((variant) => {
const weight = variant === "regular" || variant === "italic" ? 400 : parseInt(variant, 10);
const italic = variant.endsWith("italic");
const url = font.files[variant].replace("http://", "https://");
return { weight, italic, url };
});
return { name: font.family, styles };
});
editor.onFontCatalogLoad(catalog);
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") return;
throw error;
}
});
// Generic URL resolver
// TODO(keavon): This is currently only used for fonts, but it could be used for other resources and thus should be moved to a more sesible location
subscriptions.subscribeFrontendMessage("TriggerResolveResource", async (data) => {
try {
if (!data.url) throw new Error("No URL provided for resource resolution");
const response = await fetch(data.url, abortController ? { signal: abortController.signal } : undefined);
if (!response.ok) throw new Error(`Resource request failed with status ${response.status}`);
const buffer = await response.arrayBuffer();
const bytes = new Uint8Array(buffer);
editor.onResourceResolved(data.documentId, data.resourceId, bytes);
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") return;
// eslint-disable-next-line no-console
console.error("Failed to resolve resource:", error);
}
});
}
export function destroyFontsManager() {
const subscriptions = subscriptionsRouter;
if (!subscriptions) return;
abortController?.abort();
subscriptions.unsubscribeFrontendMessage("TriggerFontCatalogLoad");
subscriptions.unsubscribeFrontendMessage("TriggerResolveResource");
}
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
import.meta.hot?.accept((newModule) => {
if (subscriptionsRouter && editorWrapper) newModule?.createFontsManager(subscriptionsRouter, editorWrapper);
});

View File

@@ -20,7 +20,6 @@ use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseStat
use editor::messages::layout::utility_types::layout_widget::LayoutTarget;
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use editor::messages::portfolio::document::utility_types::network_interface::ImportOrExport;
use editor::messages::portfolio::fonts::utility_types::{FontCatalog, FontCatalogFamily};
use editor::messages::portfolio::utility_types::{DockingSplitDirection, PanelGroupId, PanelType};
use editor::messages::prelude::*;
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
@@ -648,26 +647,6 @@ impl EditorWrapper {
Ok(())
}
/// The font catalog has been loaded
#[wasm_bindgen(js_name = onFontCatalogLoad)]
pub fn on_font_catalog_load(&self, catalog: Vec<FontCatalogFamily>) {
self.dispatch(FontsMessage::CatalogLoaded { catalog: FontCatalog::from(catalog) });
}
/// A requested resource has been resolved by the frontend.
#[wasm_bindgen(js_name = onResourceResolved)]
pub fn on_resource_resolved(&self, document_id: u64, resource_id: u64, data: Vec<u8>) -> Result<(), JsValue> {
self.dispatch(PortfolioMessage::DocumentPassMessage {
document_id: DocumentId(document_id),
message: DocumentMessage::Resource(ResourceMessage::Resolved {
resource_id: resource_id.into(),
data: std::sync::Arc::from(data),
}),
});
Ok(())
}
/// Dialog got dismissed
#[wasm_bindgen(js_name = onDialogDismiss)]
pub fn on_dialog_dismiss(&self) {