Add CMMS embedding hooks: load a .scad attachment via window.CMMS_SCAD_CONFIG,
save the current source back to CMMS via a new "Save to CMMS" button Mirrors the window.CAD_EDITOR_CONFIG pattern already used by the cad-editor fork. Load: on boot, if CMMS_SCAD_CONFIG.loadUrl is set, fetch it and seed the editor with that content instead of restoring the app's own URL- fragment/localStorage state - reloading always shows the current CMMS attachment, not a stale local edit. Save: POSTs the current editor source as plain text to CMMS_SCAD_CONFIG.saveUrl (public/attachment.php's new scad_save/scad_save_new action) - the server re-renders the DXF companion itself via render_scad_to_dxf(), so there's one OpenSCAD render implementation, not a second one duplicated in the browser's WASM engine. Every new code path is gated on window.CMMS_SCAD_CONFIG being present, so standalone/upstream use is unaffected.
This commit is contained in:
parent
012111f06b
commit
f471e0245a
2 changed files with 88 additions and 3 deletions
|
|
@ -13,6 +13,41 @@ import SettingsMenu from './SettingsMenu.tsx';
|
|||
import MultimaterialColorsDialog from './MultimaterialColorsDialog.tsx';
|
||||
|
||||
|
||||
// CMMS "Save to CMMS" action (patch, not upstream) - POSTs the current
|
||||
// editor source text back to public/attachment.php's scad_save/
|
||||
// scad_save_new action (see public/vendor/openscad-playground/edit.php and
|
||||
// CLAUDE.md). Deliberately posts only the raw .scad text, not a rendered
|
||||
// DXF - the server re-renders the DXF companion itself via the same
|
||||
// render_scad_to_dxf() the AI Assistant's generate_cad_drawing tool uses,
|
||||
// so there's one render implementation, not a second one duplicated here
|
||||
// in the browser's WASM engine.
|
||||
async function saveToCmms(model: any, toast: React.RefObject<Toast>) {
|
||||
const cfg = window.CMMS_SCAD_CONFIG;
|
||||
if (!cfg) return;
|
||||
const state = model.state;
|
||||
const content = state.params.sources.find((s: any) => s.path === state.params.activePath)?.content ?? '';
|
||||
const form = new FormData();
|
||||
form.set('csrf_token', cfg.csrfToken);
|
||||
form.set('job_id', cfg.jobId);
|
||||
if (cfg.attachmentId) {
|
||||
form.set('action', 'scad_save');
|
||||
form.set('id', cfg.attachmentId);
|
||||
} else {
|
||||
form.set('action', 'scad_save_new');
|
||||
form.set('original_name', cfg.originalName);
|
||||
}
|
||||
form.set('file', new Blob([content], {type: 'text/plain'}), cfg.originalName);
|
||||
try {
|
||||
const resp = await fetch(cfg.saveUrl, {method: 'POST', credentials: 'same-origin', body: form});
|
||||
const data = await resp.json();
|
||||
if (!resp.ok || !data.ok) throw new Error(data.error || `HTTP ${resp.status}`);
|
||||
if (data.id) cfg.attachmentId = String(data.id);
|
||||
toast.current?.show({severity: 'success', summary: 'Saved to CMMS', life: 3000});
|
||||
} catch (e: any) {
|
||||
toast.current?.show({severity: 'error', summary: 'Save failed', detail: String(e?.message ?? e), life: 6000});
|
||||
}
|
||||
}
|
||||
|
||||
export default function Footer({style}: {style?: CSSProperties}) {
|
||||
const model = useContext(ModelContext);
|
||||
if (!model) throw new Error('No model');
|
||||
|
|
@ -73,6 +108,13 @@ export default function Footer({style}: {style?: CSSProperties}) {
|
|||
) : undefined
|
||||
}
|
||||
<MultimaterialColorsDialog />
|
||||
{window.CMMS_SCAD_CONFIG &&
|
||||
<Button
|
||||
icon="pi pi-cloud-upload"
|
||||
className="p-button-sm"
|
||||
label="Save to CMMS"
|
||||
onClick={() => saveToCmms(model, toast)}
|
||||
/>}
|
||||
{/* <Button
|
||||
icon="pi pi-bolt"
|
||||
onClick={() => model.render({isPreview: false, now: true})}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { createEditorFS } from './fs/filesystem.ts';
|
|||
import { registerOpenSCADLanguage } from './language/openscad-register-language.ts';
|
||||
import { zipArchives } from './fs/zip-archives.ts';
|
||||
import {readStateFromFragment} from './state/fragment-state.ts'
|
||||
import { createInitialState } from './state/initial-state.ts';
|
||||
import { createInitialState, defaultSourcePath } from './state/initial-state.ts';
|
||||
import './index.css';
|
||||
|
||||
import debug from 'debug';
|
||||
|
|
@ -31,6 +31,25 @@ if (process.env.NODE_ENV !== 'production') {
|
|||
|
||||
declare var BrowserFS: BrowserFSInterface
|
||||
|
||||
// CMMS embedding hook (patch, not upstream) - set only by
|
||||
// public/vendor/openscad-playground/edit.php when this app is loaded inside
|
||||
// the CMMS. Mirrors the window.CAD_EDITOR_CONFIG pattern already used by the
|
||||
// vendored cad-editor fork: a real file (an existing .scad attachment) is
|
||||
// fetched from loadUrl on boot instead of restoring the URL-fragment/
|
||||
// localStorage state this app normally persists between visits - reloading
|
||||
// this editor should always show the current CMMS-side attachment, not a
|
||||
// stale local edit. See Footer.tsx for the matching "Save to CMMS" action.
|
||||
interface CmmsScadConfig {
|
||||
loadUrl?: string;
|
||||
saveUrl: string;
|
||||
attachmentId?: string;
|
||||
jobId: string;
|
||||
csrfToken: string;
|
||||
originalName: string;
|
||||
}
|
||||
declare global {
|
||||
interface Window { CMMS_SCAD_CONFIG?: CmmsScadConfig; }
|
||||
}
|
||||
|
||||
window.addEventListener('load', async () => {
|
||||
//*
|
||||
|
|
@ -68,7 +87,31 @@ window.addEventListener('load', async () => {
|
|||
let statePersister: StatePersister;
|
||||
let persistedState: State | null = null;
|
||||
|
||||
if (isInStandaloneMode()) {
|
||||
const cmmsConfig = window.CMMS_SCAD_CONFIG;
|
||||
if (cmmsConfig) {
|
||||
// No-op persister: a CMMS-embedded session is explicitly saved via the
|
||||
// "Save to CMMS" button (Footer.tsx), never via this app's own
|
||||
// fragment/localStorage auto-persist - same "load fresh every time"
|
||||
// rule as the vendored cad-editor.
|
||||
statePersister = { set: async () => {} };
|
||||
if (cmmsConfig.loadUrl) {
|
||||
try {
|
||||
const resp = await fetch(cmmsConfig.loadUrl, {credentials: 'same-origin'});
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const content = await resp.text();
|
||||
const initialState = createInitialState(null, {content, path: cmmsConfig.originalName || defaultSourcePath});
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App initialState={initialState} statePersister={statePersister} fs={fs} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
return;
|
||||
} catch (e) {
|
||||
console.error('CMMS: failed to load attachment from', cmmsConfig.loadUrl, e);
|
||||
}
|
||||
}
|
||||
} else if (isInStandaloneMode()) {
|
||||
const fs: FS = BrowserFS.BFSRequire('fs')
|
||||
try {
|
||||
const data = JSON.parse(new TextDecoder("utf-8").decode(fs.readFileSync('/state.json')));
|
||||
|
|
|
|||
Loading…
Reference in a new issue