Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Artifacts/clientdist/_githash_client_svelte.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
85ddfe2c670ad04dca6f43142948e811d198b2e5
2f7ea1549da4da7805aa990cac54f239a26eb51b
Binary file modified Artifacts/clientdist/clientpackage_svelte.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion BuildScripts/createPackageXML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ int main(int argc, char* argv[])

std::stringstream serverXMLStream;
serverXMLStream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
serverXMLStream << "<amc xmlns=\"http://schemas.autodesk.com/amc/2020/06\">\n";
serverXMLStream << "<amc xmlns=\"http://schemas.autodesk.com/amc/2020/06\" client=\"" << sDefaultClient << "\">\n";
serverXMLStream << " <server hostname=\"0.0.0.0\" port=\"8869\" " << sTempFolderXMLAttribute << "/>\n";
serverXMLStream << " <data directory=\"data/\" database=\"sqlite\" sqlitedb=\"storage.db\" />\n";
serverXMLStream << " <defaultpackage name=\""<< sDevPackagePrefix << "_package.xml\" githash=\"" << sDevPackagePrefix <<"\" sha256=\"" << sPackageSHA << "\" />\n";
Expand Down
88 changes: 83 additions & 5 deletions Client/core/common/AMCApplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import AMCApplicationDialog from "./AMCDialog.js"

const CONFIG_REQUEST_TIMEOUT_MS = 2000;
const DEFAULT_REQUEST_TIMEOUT_MS = 10000;
const EVENT_ERROR_SNACKBAR_TIMEOUT_MS = 8000;
const MAX_CONSECUTIVE_FAILURES = 5;

export default class AMCApplication extends Common.AMCObject {
Expand Down Expand Up @@ -157,7 +158,10 @@ export default class AMCApplication extends Common.AMCObject {
Timeout: -1,
Text: "",
Color: "secondary",
FontColor: "white"
FontColor: "white",
// Incremented on every showSnackBar () call, so the client can restart its auto-hide
// timer even if text and visibility have not changed.
Sequence: 0
}

}
Expand Down Expand Up @@ -920,6 +924,20 @@ export default class AMCApplication extends Common.AMCObject {
}
}
}

// Dialog content is indexed too, so synced items inside a dialog refresh
// from the same payload instead of falling back to legacy polling.
if (resultJSON.data && resultJSON.data.dialogs) {
for (let dialog of resultJSON.data.dialogs) {
if (dialog.modules) {
for (let mod of dialog.modules) {
this._indexFrontendModule(mod);
}
}
}

this._syncServerDrivenDialogs(resultJSON.data.dialogs);
}
})
.catch(err => {
this.API.unsuccessfulFrontendCounter = (this.API.unsuccessfulFrontendCounter || 0) + 1;
Expand Down Expand Up @@ -1010,6 +1028,37 @@ export default class AMCApplication extends Common.AMCObject {
return true;
}

// Applies the server-side open state of dialogs that carry an "active" flag (dialogs
// with sync:active in the machine configuration). The server is authoritative: such a
// dialog is opened while the flag is true, even if a client action closed it in between,
// and closed as soon as the flag turns false. Dialogs without the flag are untouched.
_syncServerDrivenDialogs(dialogsJSON) {
let changed = false;

for (let dialogJSON of dialogsJSON) {
if (typeof dialogJSON.active !== "boolean")
continue;

let dialog = this.AppContent.DialogMap.get(dialogJSON.name);
if (!dialog)
continue;

dialog.serverDriven = true;
dialog.closable = (dialogJSON.closable !== false);

if (dialogJSON.active && !dialog.dialogIsActive && (dialog.visible !== false)) {
// showDialog already refreshes the content items.
this.showDialog(dialog.name);
} else if (!dialogJSON.active && dialog.dialogIsActive) {
dialog.dialogIsActive = false;
changed = true;
}
}

if (changed)
this.updateContentItems();
}

// Recursively index a v2 module (and its submodules) into frontendLookup by UUID.
_indexFrontendModule(mod) {
if (!mod || !mod.uuid)
Expand Down Expand Up @@ -1487,6 +1536,9 @@ export default class AMCApplication extends Common.AMCObject {
}
}

// Only one dialog is open at a time. A server-driven dialog (see
// _syncServerDrivenDialogs) re-opens on the next frontend poll while its flag is
// set, so it wins over dialogs opened by client actions.
showDialog(dialog) {

this.closeAllDialogs();
Expand Down Expand Up @@ -1554,7 +1606,13 @@ export default class AMCApplication extends Common.AMCObject {

this.axiosPostRequest("/event", requestBody)
.then(resultHandleEvent => {


// A failed event carries no actions and must not run the success callback
if (resultHandleEvent.data.errorcode) {
this.showEventError(eventname, resultHandleEvent.data.errormessage);
return;
}

if (resultHandleEvent.data.actions) {
if (Array.isArray(resultHandleEvent.data.actions)) {
let action;
Expand Down Expand Up @@ -1584,10 +1642,27 @@ export default class AMCApplication extends Common.AMCObject {
})
.catch(err => {
console.log(err);
this.showEventError(eventname, this.extractErrorMessage(err));
});
}

triggerWidgetRequest (widgetuuid, requestType, requestJSON, executionCallback) {
// Sets every snack bar field, so a caller never inherits the color or timeout of a previous message.
// timeout <= 0 keeps the message until it is dismissed.
showSnackBar(text, color, timeout) {
this.SnackBar.Text = text;
this.SnackBar.Color = color || "secondary";
this.SnackBar.Timeout = (timeout > 0) ? timeout : -1;
this.SnackBar.Sequence++;
this.SnackBar.Visible = true;
}

// Failed events were only visible in the browser console. Report them in the snack bar.
showEventError(eventname, errormessage) {
this.showSnackBar("Event \"" + eventname + "\" failed: " + errormessage, "error", EVENT_ERROR_SNACKBAR_TIMEOUT_MS);
}

// failureCallback receives the request error, so the caller can roll back optimistic UI state.
triggerWidgetRequest (widgetuuid, requestType, requestJSON, executionCallback, failureCallback) {


this.axiosPostRequest("/widget/" + Assert.UUIDValue (widgetuuid) + "/" + Assert.StringValue (requestType), Assert.ObjectValue (requestJSON))
Expand Down Expand Up @@ -1617,11 +1692,14 @@ export default class AMCApplication extends Common.AMCObject {

if (executionCallback) {
executionCallback ();
}
}

})
.catch(err => {
console.log(err);
if (failureCallback) {
failureCallback (err);
}
});
}

Expand Down
7 changes: 7 additions & 0 deletions Client/core/common/AMCDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ export default class AMCApplicationDialog extends AMCApplicationPage {
// dialog JSON); capture it so the client can render the dialog header.
this.title = dialogJSON.title || "";
this.dialogIsActive = false;

// "closable" (default true): a non-closable dialog has no close button and ignores
// Escape and outside clicks. "active" is only sent for server-driven dialogs; their
// open state follows the server on every frontend poll instead of client actions.
// Both are initialised here so Vue 2 tracks them.
this.closable = (dialogJSON.closable !== false);
this.serverDriven = (typeof dialogJSON.active === "boolean");
}

// A dialog is never the application's active page, so the inherited page-based
Expand Down
2 changes: 0 additions & 2 deletions Client/core/common/AMCImplementation_WebGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,6 @@ class WebGLLocalizedPointsElement extends WebGLElement {
let localmouseX = (mouseX - this.position.x) / this.scale.x;
let localmouseY = (mouseY - this.position.y) / this.scale.y;

console.log (mouseX + " " + mouseY + ": " + localmouseX + " " + localmouseY);

let quadx = Math.floor ( (localmouseX - this.originx) / this.quadsizex);
let quady = Math.floor ( (localmouseY - this.originy) / this.quadsizey);

Expand Down
1 change: 1 addition & 0 deletions Client/svelte/src/lib/amcf/MdiIcon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
'mdi-clock': Clock,
'mdi-clock-outline': Clock,
'mdi-timer': Clock,
'mdi-timer-outline': Clock,
'mdi-calendar': Calendar,
'mdi-calendar-outline': Calendar,
'mdi-map': Map,
Expand Down
3 changes: 3 additions & 0 deletions Client/svelte/src/lib/amcf/Snackbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
let color = $derived.by(() => { poll.v; return app?.SnackBar?.Color || 'secondary'; });
let fontColor = $derived.by(() => { poll.v; return app?.SnackBar?.FontColor || 'white'; });
let timeout = $derived.by(() => { poll.v; return app?.SnackBar?.Timeout || -1; });
let sequence = $derived.by(() => { poll.v; return app?.SnackBar?.Sequence || 0; });

let show = $state(false);
let autoHideTimer: ReturnType<typeof setTimeout> | null = null;

$effect(() => {
// Depend on the sequence so that a repeated identical message restarts the auto-hide timer
sequence;
if (visible && text) {
show = true;
if (autoHideTimer) clearTimeout(autoHideTimer);
Expand Down
12 changes: 4 additions & 8 deletions Client/svelte/src/lib/amcf/modules/ModuleBuildList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import Image from '@lucide/svelte/icons/image';

import { usePollTick } from '$lib/amcf/poll.svelte';
// @ts-ignore — core JS has no type declarations yet
import { triggerButtonEvent } from '@core/modules/AMCModule_TableUtils.js';

let { module, app }: { module: any; app: any } = $props();
const poll = usePollTick();
Expand Down Expand Up @@ -40,15 +42,9 @@
}
}

// Shared with the Vue 2 client: selectionvalueuuid receives the build, buttonvalueuuid the button
function triggerButton (btn: any, build: any) {
if (btn.selectevent && app) {
const formvalues: Record<string, string> = {};
if (module.selectionvalueuuid)
formvalues[module.selectionvalueuuid] = build.buildUUID;
if (module.buttonvalueuuid)
formvalues[module.buttonvalueuuid] = build.buildUUID;
app.triggerUIEvent(btn.selectevent, module.uuid, formvalues);
}
triggerButtonEvent(app, module, btn, build.buildUUID);
}
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,24 @@
} catch { return ts; }
}

// Used when the configuration list defines no <button> elements. These actions have no
// server-side button UUID, so only the selected configuration is sent with the event.
const fallbackActions = [
{ uuid: 'load', caption: 'Load', selectevent: 'loadconfiguration', fallback: true },
{ uuid: 'activate', caption: 'Set Active', selectevent: 'setactiveconfiguration', fallback: true },
];

function getActions (item: any) {
if (entrybuttons.length > 0) return entrybuttons;
return [
{ uuid: 'load', caption: 'Load', selectevent: 'loadconfiguration' },
{ uuid: 'activate', caption: 'Set Active', selectevent: 'setactiveconfiguration' },
{ uuid: 'delete', caption: 'Delete', selectevent: 'deleteconfiguration' },
];
return fallbackActions;
}

function triggerAction (action: any, config: any) {
if (action.selectevent && app) {
const formvalues: Record<string, string> = {};
if (module.selectionvalueuuid)
formvalues[module.selectionvalueuuid] = config.configurationUUID;
if (module.buttonvalueuuid)
if (module.buttonvalueuuid && !action.fallback)
formvalues[module.buttonvalueuuid] = action.uuid;
app.triggerUIEvent(action.selectevent, module.uuid, formvalues);
}
Expand Down
10 changes: 4 additions & 6 deletions Client/svelte/src/lib/amcf/modules/ModuleExecutionList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import Image from '@lucide/svelte/icons/image';

import { usePollTick } from '$lib/amcf/poll.svelte';
// @ts-ignore — core JS has no type declarations yet
import { triggerButtonEvent } from '@core/modules/AMCModule_TableUtils.js';

let { module, app }: { module: any; app: any } = $props();
const poll = usePollTick();
Expand Down Expand Up @@ -58,13 +60,9 @@
}
}

// Shared with the Vue 2 client: selectionvalueuuid receives the execution, buttonvalueuuid the button
function triggerButton (btn: any, exec: any) {
if (btn.selectevent && app) {
const formvalues: Record<string, string> = {};
if (module.buttonvalueuuid)
formvalues[module.buttonvalueuuid] = exec.executionUUID;
app.triggerUIEvent(btn.selectevent, module.uuid, formvalues);
}
triggerButtonEvent(app, module, btn, exec.executionUUID);
}
</script>

Expand Down
Loading
Loading