mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
feature/improve_plugin_config_for_slicing_plugin
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
<span id="statusText" class="status-text"></span>
|
||||
</footer>
|
||||
</main>
|
||||
<script src="../js/plugin-config-ui.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,6 +9,11 @@ let selectedCapabilityType = "";
|
||||
let selectedHasPresetOverride = false;
|
||||
let selectedReadOnly = false;
|
||||
|
||||
// Identity of the capability whose custom UI is currently loaded in the frame. Saving re-sends the
|
||||
// whole capability_config payload, and rebuilding the frame from it would reload the plugin's page
|
||||
// under the user's cursor; when this still matches, the new values are posted in instead.
|
||||
let customFrameKey = "";
|
||||
|
||||
function SafeJsonParse(text) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
@@ -168,6 +173,10 @@ function IsCurrentCapability(payload) {
|
||||
&& String(payload?.capability_type || "") === selectedCapabilityType;
|
||||
}
|
||||
|
||||
function CapabilityKey(payload) {
|
||||
return JSON.stringify([payload?.plugin_key, payload?.capability_name, payload?.capability_type]);
|
||||
}
|
||||
|
||||
function RequestCapabilityConfig() {
|
||||
if (!selectedPluginKey || !selectedCapabilityName)
|
||||
return;
|
||||
@@ -193,6 +202,7 @@ function ClearCapabilityConfigView() {
|
||||
if (custom) {
|
||||
custom.hidden = true;
|
||||
custom.removeAttribute("srcdoc");
|
||||
customFrameKey = "";
|
||||
}
|
||||
if (text)
|
||||
text.value = "";
|
||||
@@ -248,8 +258,14 @@ function ApplyCapabilityConfig(payload) {
|
||||
|
||||
if (html) {
|
||||
if (custom) {
|
||||
const context = OrcaConfigContext(payload, "preset");
|
||||
custom.hidden = false;
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config);
|
||||
if (customFrameKey === CapabilityKey(payload) && custom.contentWindow) {
|
||||
custom.contentWindow.postMessage({ __orca: "config", config: config, context: context }, "*");
|
||||
} else {
|
||||
customFrameKey = CapabilityKey(payload);
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config, context);
|
||||
}
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = true;
|
||||
@@ -260,6 +276,7 @@ function ApplyCapabilityConfig(payload) {
|
||||
if (custom) {
|
||||
custom.hidden = true;
|
||||
custom.removeAttribute("srcdoc");
|
||||
customFrameKey = "";
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = false;
|
||||
@@ -354,39 +371,6 @@ function ApplyCapabilityConfigSaved(payload) {
|
||||
SetConfigValidation("");
|
||||
}
|
||||
|
||||
// The whole host surface a custom config UI gets: read the config, save one, drop the preset's
|
||||
// override, and be told when either lands. The frame is sandboxed into an opaque origin, so this
|
||||
// bridge is its only channel.
|
||||
function BuildCustomConfigDocument(html, config) {
|
||||
// Inlined into a <script>: a stored "</script>" would close the tag early, so escape "<" — the
|
||||
// literal stays valid JSON.
|
||||
const seed = JSON.stringify(config).replace(/</g, "\\u003c");
|
||||
const bridge = `<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var current = ${seed};
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data || event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
function OnCustomConfigMessage(event) {
|
||||
const custom = document.getElementById("configCustom");
|
||||
// Only the frame we created, and only while it is actually showing.
|
||||
@@ -432,6 +416,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
||||
// sandboxed), and ignores anything else.
|
||||
window.addEventListener("message", OnCustomConfigMessage);
|
||||
OrcaWatchThemeForFrame(() => document.getElementById("configCustom"));
|
||||
|
||||
SendMessage("request_capabilities");
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<script type="text/javascript" src="../../data/text.js"></script>
|
||||
<script type="text/javascript" src="../js/globalapi.js"></script>
|
||||
<script type="text/javascript" src="../js/common.js"></script>
|
||||
<script src="../js/plugin-config-ui.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
<script src="./plugin-sort.js"></script>
|
||||
<script src="../js/fuzzy-search.js"></script>
|
||||
|
||||
@@ -90,6 +90,7 @@ function OnInit() {
|
||||
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
||||
// sandboxed), and ignores anything else.
|
||||
window.addEventListener("message", OnCustomConfigMessage);
|
||||
OrcaWatchThemeForFrame(() => document.getElementById("configCustom"));
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!event.target.closest(".ctx"))
|
||||
@@ -1077,7 +1078,7 @@ function ApplyCapabilityConfig(payload) {
|
||||
if (html) {
|
||||
if (custom) {
|
||||
custom.hidden = false;
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config);
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config, OrcaConfigContext(payload, "global"));
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = true;
|
||||
@@ -1178,39 +1179,6 @@ function ApplyCapabilityConfigSaved(payload) {
|
||||
SetConfigValidation("");
|
||||
}
|
||||
|
||||
// The whole host surface a custom config UI gets: read the config, save one, restore the plugin's
|
||||
// defaults, and be told when either lands. The frame is sandboxed into an opaque origin, so this
|
||||
// bridge is its only channel.
|
||||
function BuildCustomConfigDocument(html, config) {
|
||||
// Inlined into a <script>: a stored "</script>" would close the tag early, so escape "<" — the
|
||||
// literal stays valid JSON.
|
||||
const seed = JSON.stringify(config).replace(/</g, "\\u003c");
|
||||
const bridge = `<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var current = ${seed};
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data || event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
function OnCustomConfigMessage(event) {
|
||||
const custom = document.getElementById("configCustom");
|
||||
// Only the frame we created, and only while it is actually showing.
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// The host surface a plugin capability's custom configuration UI gets, shared by the Plugins dialog
|
||||
// and by a preset's plugin configuration dialog. Both host the page in an iframe sandboxed into an
|
||||
// opaque origin, so this bridge is its only channel, and both must offer plugin authors exactly the
|
||||
// same one — hence a single module rather than a copy per dialog.
|
||||
|
||||
// The host theme "contract" (WebViewHostDialog::host_theme_vars_css). The document-start injector
|
||||
// stamps it on the top-level page only — it returns early in child frames — so a sandboxed config UI
|
||||
// never sees it unless we hand it over.
|
||||
const ORCA_THEME_VARS = [
|
||||
"--orca-bg",
|
||||
"--orca-fg",
|
||||
"--orca-muted",
|
||||
"--orca-border",
|
||||
"--orca-accent",
|
||||
"--orca-accent-fg",
|
||||
"--orca-font"
|
||||
];
|
||||
|
||||
// Read the contract off this page as it is rendering right now, so the frame always opens in the
|
||||
// live theme rather than whatever the app started in.
|
||||
function OrcaThemeSnapshot() {
|
||||
const style = getComputedStyle(document.documentElement);
|
||||
const vars = {};
|
||||
ORCA_THEME_VARS.forEach((name) => {
|
||||
// Values are host-produced colors and a pre-sanitized font stack; strip anything that could end
|
||||
// the declaration or the <style> block it gets inlined into.
|
||||
const value = String(style.getPropertyValue(name) || "").trim().replace(/[<>{};]/g, "");
|
||||
if (value)
|
||||
vars[name] = value;
|
||||
});
|
||||
return {
|
||||
theme: document.documentElement.getAttribute("data-orca-theme") === "dark" ? "dark" : "light",
|
||||
vars: vars
|
||||
};
|
||||
}
|
||||
|
||||
// Inlined into a <script> or a JSON payload: a stored "</script>" would close the tag early, so
|
||||
// escape "<" — the literal stays valid JSON.
|
||||
function OrcaInlineJson(value) {
|
||||
return JSON.stringify(value === undefined ? null : value).replace(/</g, "\\u003c");
|
||||
}
|
||||
|
||||
// What a custom UI can learn about the surface it is being edited on. Kept to what changes the
|
||||
// page's own behavior: "Restore defaults" writes the plugin's get_default_config() in the Plugins
|
||||
// dialog but drops the preset's override in a preset dialog, so a page cannot label its own button
|
||||
// without knowing the scope.
|
||||
function OrcaConfigContext(payload, scope) {
|
||||
return {
|
||||
scope: scope,
|
||||
readOnly: payload ? payload.read_only === true : false,
|
||||
hasPresetOverride: payload ? payload.has_preset_override === true : false
|
||||
};
|
||||
}
|
||||
|
||||
// The whole host surface: read the config, save one, restore defaults, follow the theme, and be told
|
||||
// when any of it lands.
|
||||
function BuildCustomConfigDocument(html, config, context) {
|
||||
const theme = OrcaThemeSnapshot();
|
||||
const bridge = `<style id="orca-host-theme-vars"></style>
|
||||
<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var themeHandlers = [];
|
||||
var current = ${OrcaInlineJson(config === undefined ? {} : config)};
|
||||
var context = ${OrcaInlineJson(context || {})};
|
||||
var theme = ${OrcaInlineJson(theme)};
|
||||
|
||||
function applyTheme(next) {
|
||||
if (next && next.vars) theme = next;
|
||||
var css = ":root{";
|
||||
for (var name in theme.vars)
|
||||
if (Object.prototype.hasOwnProperty.call(theme.vars, name)) css += name + ":" + theme.vars[name] + ";";
|
||||
css += "color-scheme:" + theme.theme + ";}";
|
||||
var style = document.getElementById("orca-host-theme-vars");
|
||||
if (style) style.textContent = css;
|
||||
if (document.documentElement) document.documentElement.setAttribute("data-orca-theme", theme.theme);
|
||||
themeHandlers.forEach(function (handler) {
|
||||
try { handler(theme.theme); } catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
getContext: function () { return context; },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
},
|
||||
onTheme: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
themeHandlers.push(cb);
|
||||
try { cb(theme.theme); } catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data) return;
|
||||
if (event.data.__orca === "theme") { applyTheme(event.data.theme); return; }
|
||||
if (event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
if (event.data.context) context = event.data.context;
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
applyTheme();
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
// The host re-themes an open dialog in place (WebViewHostDialog::host_theme_apply_js rewrites the
|
||||
// injected variables and re-stamps data-orca-theme), which a sandboxed child frame never sees. Relay
|
||||
// it so a custom UI follows a light/dark switch without being reopened.
|
||||
function OrcaWatchThemeForFrame(getFrame) {
|
||||
const relay = () => {
|
||||
const frame = getFrame();
|
||||
if (!frame || frame.hidden || !frame.contentWindow)
|
||||
return;
|
||||
frame.contentWindow.postMessage({ __orca: "theme", theme: OrcaThemeSnapshot() }, "*");
|
||||
};
|
||||
new MutationObserver(relay).observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-orca-theme"]
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user