diff --git a/plugin/Source/PluginEditor.cpp b/plugin/Source/PluginEditor.cpp index 7d4f1f8..091986e 100644 --- a/plugin/Source/PluginEditor.cpp +++ b/plugin/Source/PluginEditor.cpp @@ -61,6 +61,19 @@ namespace #if PRISM_USE_DEV_SERVER const juce::String kDevServerUrl { "http://localhost:5174" }; #else + const char* getWebResourceData(const juce::String& name, int& dataSize) + { + dataSize = 0; + + for (int i = 0; i < BinaryData::namedResourceListSize; ++i) + { + if (name == juce::String(BinaryData::originalFilenames[i])) + return BinaryData::getNamedResource(BinaryData::namedResourceList[i], dataSize); + } + + return nullptr; + } + juce::String mimeForExtension(const juce::String& name) { if (name.endsWithIgnoreCase(".html")) return "text/html"; @@ -80,17 +93,14 @@ namespace : url.fromLastOccurrenceOf("/", false, false); name = name.upToFirstOccurrenceOf("?", false, false); - for (int i = 0; i < BinaryData::namedResourceListSize; ++i) + int dataSize = 0; + if (const char* data = getWebResourceData(name, dataSize)) { - if (name == juce::String(BinaryData::originalFilenames[i])) - { - int dataSize = 0; - const char* data = BinaryData::getNamedResource(BinaryData::namedResourceList[i], dataSize); - std::vector bytes ((size_t) dataSize); - std::memcpy (bytes.data(), data, (size_t) dataSize); - return juce::WebBrowserComponent::Resource { std::move (bytes), mimeForExtension (name) }; - } + std::vector bytes ((size_t) dataSize); + std::memcpy (bytes.data(), data, (size_t) dataSize); + return juce::WebBrowserComponent::Resource { std::move (bytes), mimeForExtension (name) }; } + return std::nullopt; } @@ -98,6 +108,32 @@ namespace { return juce::WebBrowserComponent::getResourceProviderRoot().trimCharactersAtEnd("/"); } + +#if JUCE_LINUX + juce::File writeLinuxWebViewIndexFile() + { + int dataSize = 0; + const char* data = getWebResourceData("index.html", dataSize); + + if (data == nullptr || dataSize <= 0) + return {}; + + const auto webViewDir = + juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory) + .getChildFile("prism") + .getChildFile("plugin-webview"); + + if (! webViewDir.createDirectory()) + return {}; + + auto indexFile = webViewDir.getChildFile("index.html"); + + if (! indexFile.replaceWithData(data, (size_t) dataSize)) + return {}; + + return indexFile; + } +#endif #endif juce::WebBrowserComponent::Options makeWebOptions(PrismSpectrumEditor& editor, const char* scopeId) @@ -197,6 +233,15 @@ void PrismSpectrumEditor::loadWebView() #if PRISM_USE_DEV_SERVER webView->goToURL(kDevServerUrl); #else + #if JUCE_LINUX + const auto indexFile = writeLinuxWebViewIndexFile(); + + if (indexFile.existsAsFile()) + { + webView->goToURL(juce::URL(indexFile).toString(false)); + return; + } + #endif webView->goToURL(juce::WebBrowserComponent::getResourceProviderRoot()); #endif } diff --git a/vite.plugin-ui.config.ts b/vite.plugin-ui.config.ts index 0dc31bb..d5a7dff 100644 --- a/vite.plugin-ui.config.ts +++ b/vite.plugin-ui.config.ts @@ -1,21 +1,65 @@ +import { readFileSync, rmSync, writeFileSync } from 'node:fs' import { resolve } from 'node:path' import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +function inlinePluginUiAssets() { + const outDir = resolve(__dirname, 'plugin/webview-dist') + const indexPath = resolve(outDir, 'index.html') + const scriptPath = resolve(outDir, 'assets/index.js') + const stylePath = resolve(outDir, 'assets/index.css') + + return { + name: 'prism-inline-plugin-ui-assets', + apply: 'build' as const, + enforce: 'post' as const, + closeBundle() { + const script = readFileSync(scriptPath, 'utf8').replace(/<\/script/gi, '<\\/script') + const style = readFileSync(stylePath, 'utf8').replace(/<\/style/gi, '<\\/style') + let html = readFileSync(indexPath, 'utf8') + let inlinedScript = false + let inlinedStyle = false + + html = html.replace( + /` + } + ) + html = html.replace( + //, + () => { + inlinedStyle = true + return `` + } + ) + + if (! inlinedScript || ! inlinedStyle) { + throw new Error('Failed to inline plugin UI assets into index.html') + } + + writeFileSync(indexPath, html) + rmSync(resolve(outDir, 'assets'), { recursive: true, force: true }) + }, + } +} + /** * Standalone Vite build for the plugin webview UI (src/plugin-ui). * * - dev: `npx vite --config vite.plugin-ui.config.ts` serves on :5174, which * the JUCE plugin's WebBrowserComponent points at for hot reload. - * - build: emits a static bundle the C++ side can later serve via JUCE's - * resource provider (production path; not used by the dev-server POC). + * - build: emits a self-contained static HTML bundle. The C++ side embeds it; + * Linux writes it to a local file before loading to avoid custom-scheme + * WebKitGTK issues in DAW hosts. * * Reuses the existing visualizer source under src/renderer via relative imports. */ export default defineConfig({ root: resolve(__dirname, 'src/plugin-ui'), base: './', - plugins: [react()], + plugins: [react(), inlinePluginUiAssets()], resolve: { alias: { '@': resolve(__dirname, 'src'),