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';
|
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}) {
|
export default function Footer({style}: {style?: CSSProperties}) {
|
||||||
const model = useContext(ModelContext);
|
const model = useContext(ModelContext);
|
||||||
if (!model) throw new Error('No model');
|
if (!model) throw new Error('No model');
|
||||||
|
|
@ -73,6 +108,13 @@ export default function Footer({style}: {style?: CSSProperties}) {
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
<MultimaterialColorsDialog />
|
<MultimaterialColorsDialog />
|
||||||
|
{window.CMMS_SCAD_CONFIG &&
|
||||||
|
<Button
|
||||||
|
icon="pi pi-cloud-upload"
|
||||||
|
className="p-button-sm"
|
||||||
|
label="Save to CMMS"
|
||||||
|
onClick={() => saveToCmms(model, toast)}
|
||||||
|
/>}
|
||||||
{/* <Button
|
{/* <Button
|
||||||
icon="pi pi-bolt"
|
icon="pi pi-bolt"
|
||||||
onClick={() => model.render({isPreview: false, now: true})}
|
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 { registerOpenSCADLanguage } from './language/openscad-register-language.ts';
|
||||||
import { zipArchives } from './fs/zip-archives.ts';
|
import { zipArchives } from './fs/zip-archives.ts';
|
||||||
import {readStateFromFragment} from './state/fragment-state.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 './index.css';
|
||||||
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
|
@ -31,6 +31,25 @@ if (process.env.NODE_ENV !== 'production') {
|
||||||
|
|
||||||
declare var BrowserFS: BrowserFSInterface
|
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 () => {
|
window.addEventListener('load', async () => {
|
||||||
//*
|
//*
|
||||||
|
|
@ -68,7 +87,31 @@ window.addEventListener('load', async () => {
|
||||||
let statePersister: StatePersister;
|
let statePersister: StatePersister;
|
||||||
let persistedState: State | null = null;
|
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')
|
const fs: FS = BrowserFS.BFSRequire('fs')
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(new TextDecoder("utf-8").decode(fs.readFileSync('/state.json')));
|
const data = JSON.parse(new TextDecoder("utf-8").decode(fs.readFileSync('/state.json')));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue