CMMS embedding: support creating a brand-new file, not just editing
cad-edit.php?new=1 launches the editor with no attachment_id/load_url yet - CmmsConfig's load_url/attachment_id/original_name become Optional (only save_url/job_id/csrf_token are always required). load_from_cmms_web() and boot_web()'s auto-load task both skip cleanly when load_url is absent (no error, just the normal blank Start tab). post_bytes_to_host() now takes the filename and picks cad_save (update, has attachment_id) vs cad_save_new (create); on a successful create it writes the new id/loadUrl/originalName back into window.CAD_EDITOR_CONFIG in place, so the tab's *next* save updates that row instead of creating a duplicate each time.
This commit is contained in:
parent
494ddfece9
commit
64d0230952
4 changed files with 86 additions and 25 deletions
|
|
@ -3673,13 +3673,16 @@ impl OpenCADStudio {
|
||||||
);
|
);
|
||||||
let thumbs_fetch = s.refresh_recent_thumbs();
|
let thumbs_fetch = s.refresh_recent_thumbs();
|
||||||
|
|
||||||
// CMMS embedding (not upstream): if launched from cad-edit.php,
|
// CMMS embedding (not upstream): if launched from cad-edit.php to
|
||||||
// window.CAD_EDITOR_CONFIG is set and the host has already validated
|
// *edit* an existing attachment, window.CAD_EDITOR_CONFIG carries a
|
||||||
// the attachment belongs to the job the editor was opened from - load
|
// load_url the host has already validated - load it immediately
|
||||||
// it immediately instead of waiting for File > Open. Standalone use
|
// instead of waiting for File > Open, mirrors on_open_file's wasm
|
||||||
// (opencadstudio.com, no config present) is unaffected; the Start tab
|
// branch. Gated on load_url specifically, not just cmms_config()'s
|
||||||
// just shows as normal. Mirrors on_open_file's wasm branch.
|
// presence: a "new file" launch (cad-edit.php?new=1) sets the config
|
||||||
let cmms_load = if crate::sys::cmms_config().is_some() {
|
// but omits load_url on purpose (nothing exists yet to load), and
|
||||||
|
// should boot to the normal blank Start tab, not show a load error.
|
||||||
|
// Standalone use (opencadstudio.com, no config at all) is unaffected.
|
||||||
|
let cmms_load = if crate::sys::cmms_config().is_some_and(|c| c.load_url.is_some()) {
|
||||||
let state = std::sync::Arc::new(crate::io::OpenProgressState::new(
|
let state = std::sync::Arc::new(crate::io::OpenProgressState::new(
|
||||||
crate::app::OPEN_PHASE_READING,
|
crate::app::OPEN_PHASE_READING,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -2217,8 +2217,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
if crate::sys::cmms_config().is_some() {
|
if crate::sys::cmms_config().is_some() {
|
||||||
let post_bytes = bytes.clone();
|
let post_bytes = bytes.clone();
|
||||||
|
let post_filename = filename.clone();
|
||||||
cmms_save_task = Task::perform(
|
cmms_save_task = Task::perform(
|
||||||
async move { crate::sys::post_bytes_to_host(&post_bytes).await },
|
async move { crate::sys::post_bytes_to_host(&post_bytes, &post_filename).await },
|
||||||
Message::CmmsSaveResult,
|
Message::CmmsSaveResult,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -525,21 +525,24 @@ pub async fn pick_and_load_web(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CMMS embedding (not upstream): load the one file the host page already
|
/// CMMS embedding (not upstream): load the one file the host page already
|
||||||
/// validated, instead of showing the browser picker. `crate::sys::cmms_config()`
|
/// validated, instead of showing the browser picker. Returns `None` both
|
||||||
/// returning `None` means this build is running standalone (opencadstudio.com
|
/// when this build is running standalone (no `cmms_config()` at all) and
|
||||||
/// or a plain local file), so callers should fall back to `pick_and_load_web`
|
/// when it's embedded but launched to create a brand-new file (`cad-edit.php
|
||||||
/// in that case — this never shows a picker itself.
|
/// ?new=1` - config present, but no `load_url` since there's nothing to
|
||||||
|
/// load yet) - callers treat both the same: don't auto-load, let the app
|
||||||
|
/// boot to its normal blank Start tab.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub async fn load_from_cmms_web(progress: Arc<OpenProgressState>) -> Option<WebOpenOutcome> {
|
pub async fn load_from_cmms_web(progress: Arc<OpenProgressState>) -> Option<WebOpenOutcome> {
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen_futures::JsFuture;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
|
|
||||||
let cfg = crate::sys::cmms_config()?;
|
let cfg = crate::sys::cmms_config()?;
|
||||||
|
let load_url = cfg.load_url?;
|
||||||
progress.set(crate::app::OPEN_PHASE_READING, 500, 1, 2);
|
progress.set(crate::app::OPEN_PHASE_READING, 500, 1, 2);
|
||||||
let name = cfg.original_name.clone();
|
let name = cfg.original_name.clone().unwrap_or_else(|| "CMMS attachment".to_string());
|
||||||
|
|
||||||
let window = web_sys::window()?;
|
let window = web_sys::window()?;
|
||||||
let response: web_sys::Response = JsFuture::from(window.fetch_with_str(&cfg.load_url))
|
let response: web_sys::Response = JsFuture::from(window.fetch_with_str(&load_url))
|
||||||
.await
|
.await
|
||||||
.ok()?
|
.ok()?
|
||||||
.dyn_into()
|
.dyn_into()
|
||||||
|
|
|
||||||
76
src/sys.rs
76
src/sys.rs
|
|
@ -311,12 +311,15 @@ pub fn download_bytes(name: &str, bytes: &[u8]) {
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub struct CmmsConfig {
|
pub struct CmmsConfig {
|
||||||
pub load_url: String,
|
|
||||||
pub save_url: String,
|
pub save_url: String,
|
||||||
pub attachment_id: String,
|
|
||||||
pub job_id: String,
|
pub job_id: String,
|
||||||
pub csrf_token: String,
|
pub csrf_token: String,
|
||||||
pub original_name: String,
|
// Absent when the editor was launched to create a brand-new file
|
||||||
|
// (public/cad-edit.php?job_id=&new=1) rather than to edit an existing
|
||||||
|
// attachment - there is nothing to auto-load and nothing to update yet.
|
||||||
|
pub load_url: Option<String>,
|
||||||
|
pub attachment_id: Option<String>,
|
||||||
|
pub original_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
|
@ -334,30 +337,46 @@ pub fn cmms_config() -> Option<CmmsConfig> {
|
||||||
.as_string()
|
.as_string()
|
||||||
};
|
};
|
||||||
Some(CmmsConfig {
|
Some(CmmsConfig {
|
||||||
load_url: get_str("loadUrl")?,
|
|
||||||
save_url: get_str("saveUrl")?,
|
save_url: get_str("saveUrl")?,
|
||||||
attachment_id: get_str("attachmentId")?,
|
|
||||||
job_id: get_str("jobId")?,
|
job_id: get_str("jobId")?,
|
||||||
csrf_token: get_str("csrfToken")?,
|
csrf_token: get_str("csrfToken")?,
|
||||||
original_name: get_str("originalName")?,
|
load_url: get_str("loadUrl"),
|
||||||
|
attachment_id: get_str("attachmentId"),
|
||||||
|
original_name: get_str("originalName"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// POST the current document's bytes back to the CMMS in place of a browser
|
/// POST the current document's bytes back to the CMMS in place of a browser
|
||||||
/// download. Mirrors `download_bytes`'s Blob construction but sends it as a
|
/// download. Mirrors `download_bytes`'s Blob construction but sends it as a
|
||||||
/// multipart file field instead of triggering an `<a download>` click.
|
/// multipart file field instead of triggering an `<a download>` click.
|
||||||
|
///
|
||||||
|
/// `cfg.attachment_id` decides create vs. update: present -> `cad_save`
|
||||||
|
/// (overwrite that exact attachment); absent -> `cad_save_new` (this is the
|
||||||
|
/// first save of a brand-new, editor-created file) using `filename` as the
|
||||||
|
/// new attachment's name. On a successful create, the new id is written back
|
||||||
|
/// into `window.CAD_EDITOR_CONFIG` so the *next* save on this same tab
|
||||||
|
/// updates that row instead of creating a duplicate each time - there is no
|
||||||
|
/// other state to thread through the app's own Message/Task plumbing for
|
||||||
|
/// this, the host page's own JS global is the simplest shared place to park it.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub async fn post_bytes_to_host(bytes: &[u8]) -> Result<(), String> {
|
pub async fn post_bytes_to_host(bytes: &[u8], filename: &str) -> Result<(), String> {
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
use wasm_bindgen_futures::JsFuture;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
|
|
||||||
let cfg = cmms_config().ok_or("not running inside the CMMS")?;
|
let cfg = cmms_config().ok_or("not running inside the CMMS")?;
|
||||||
|
let is_new = cfg.attachment_id.is_none();
|
||||||
|
|
||||||
let form = web_sys::FormData::new().map_err(|_| "could not build form data")?;
|
let form = web_sys::FormData::new().map_err(|_| "could not build form data")?;
|
||||||
form.append_with_str("action", "cad_save")
|
form.append_with_str("action", if is_new { "cad_save_new" } else { "cad_save" })
|
||||||
.map_err(|_| "form append failed")?;
|
|
||||||
form.append_with_str("id", &cfg.attachment_id)
|
|
||||||
.map_err(|_| "form append failed")?;
|
.map_err(|_| "form append failed")?;
|
||||||
|
if let Some(id) = &cfg.attachment_id {
|
||||||
|
form.append_with_str("id", id)
|
||||||
|
.map_err(|_| "form append failed")?;
|
||||||
|
}
|
||||||
|
if is_new {
|
||||||
|
form.append_with_str("original_name", filename)
|
||||||
|
.map_err(|_| "form append failed")?;
|
||||||
|
}
|
||||||
form.append_with_str("job_id", &cfg.job_id)
|
form.append_with_str("job_id", &cfg.job_id)
|
||||||
.map_err(|_| "form append failed")?;
|
.map_err(|_| "form append failed")?;
|
||||||
form.append_with_str("csrf_token", &cfg.csrf_token)
|
form.append_with_str("csrf_token", &cfg.csrf_token)
|
||||||
|
|
@ -385,6 +404,41 @@ pub async fn post_bytes_to_host(bytes: &[u8]) -> Result<(), String> {
|
||||||
if !response.ok() {
|
if !response.ok() {
|
||||||
return Err(format!("save failed: HTTP {}", response.status()));
|
return Err(format!("save failed: HTTP {}", response.status()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_new {
|
||||||
|
let text = JsFuture::from(response.text().map_err(|_| "text() unavailable")?)
|
||||||
|
.await
|
||||||
|
.map_err(|_| "save response body read failed")?;
|
||||||
|
let body = text.as_string().ok_or("save response body is not a string")?;
|
||||||
|
let parsed: serde_json::Value =
|
||||||
|
serde_json::from_str(&body).map_err(|_| "save response is not valid JSON")?;
|
||||||
|
let new_id = parsed
|
||||||
|
.get("id")
|
||||||
|
.and_then(|v| v.as_i64())
|
||||||
|
.ok_or("save response missing id")?;
|
||||||
|
|
||||||
|
let config_obj = js_sys::Reflect::get(&window, &JsValue::from_str("CAD_EDITOR_CONFIG"))
|
||||||
|
.map_err(|_| "could not re-read CAD_EDITOR_CONFIG")?;
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&config_obj,
|
||||||
|
&JsValue::from_str("attachmentId"),
|
||||||
|
&JsValue::from_str(&new_id.to_string()),
|
||||||
|
)
|
||||||
|
.map_err(|_| "could not update attachmentId")?;
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&config_obj,
|
||||||
|
&JsValue::from_str("loadUrl"),
|
||||||
|
&JsValue::from_str(&format!("/attachment.php?id={new_id}")),
|
||||||
|
)
|
||||||
|
.map_err(|_| "could not update loadUrl")?;
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&config_obj,
|
||||||
|
&JsValue::from_str("originalName"),
|
||||||
|
&JsValue::from_str(filename),
|
||||||
|
)
|
||||||
|
.map_err(|_| "could not update originalName")?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue