@floless/app 0.69.0 → 0.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/floless-server.cjs +362 -283
- package/dist/templates/dummy-report.flo +27 -9
- package/dist/web/aware.js +44 -3
- package/dist/web/index.html +2 -0
- package/dist/web/panels.js +3 -2
- package/package.json +1 -1
|
@@ -4,10 +4,11 @@ display-name: Dummy Report
|
|
|
4
4
|
description: |
|
|
5
5
|
A self-contained test workflow that produces a styled HTML report — no Tekla
|
|
6
6
|
model required. Neither node touches `model`, so it runs on the host bridge with
|
|
7
|
-
nothing open. Use it to preview the in-app HTML Viewer
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
nothing open. Use it to preview the in-app HTML Viewer, the "Made with FloLess"
|
|
8
|
+
report badge, and the viewer's "Open file" / "Reveal in folder" buttons end to
|
|
9
|
+
end without a real model. Two real tekla/exec nodes: a title input → a report
|
|
10
|
+
node that builds the HTML inline, saves a copy to a file in your temp folder, and
|
|
11
|
+
is the terminal the viewer renders.
|
|
11
12
|
exposes-as-agent: false
|
|
12
13
|
inputs:
|
|
13
14
|
title:
|
|
@@ -47,11 +48,14 @@ nodes:
|
|
|
47
48
|
args:
|
|
48
49
|
title: "{{ title-input.result.title }}"
|
|
49
50
|
code: |
|
|
50
|
-
// Dummy report — builds a styled HTML report inline
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
51
|
+
// Dummy report — builds a styled HTML report inline, saves a copy to a file
|
|
52
|
+
// in your temp folder, and returns the report as this terminal node's `html`
|
|
53
|
+
// (so the floless.app HTML Viewer renders it on double-click) plus that file's
|
|
54
|
+
// path as `saved_path` (so the viewer's "Open file" / "Reveal in folder"
|
|
55
|
+
// buttons have a real file to act on). Touches NO `model`, so it runs with no
|
|
56
|
+
// Tekla open. Manual HTML-escape (no System.Net available).
|
|
54
57
|
using System;
|
|
58
|
+
using System.IO;
|
|
55
59
|
using System.Text;
|
|
56
60
|
|
|
57
61
|
string title = "FloLess Test Report";
|
|
@@ -99,6 +103,20 @@ nodes:
|
|
|
99
103
|
sb.Append("<div class=\"ft\">This is a dummy report for testing the FloLess HTML viewer.</div>");
|
|
100
104
|
sb.Append("</div></body></html>");
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
string html = sb.ToString();
|
|
107
|
+
|
|
108
|
+
// Also SAVE the report to a real file on disk so the viewer's "Open file" /
|
|
109
|
+
// "Reveal in folder" buttons have something to act on. Best-effort: a write
|
|
110
|
+
// failure just means no saved file (the report still renders inline).
|
|
111
|
+
string savedPath = null;
|
|
112
|
+
try
|
|
113
|
+
{
|
|
114
|
+
var outPath = Path.Combine(Path.GetTempPath(), "floless-dummy-report.html");
|
|
115
|
+
File.WriteAllText(outPath, html);
|
|
116
|
+
savedPath = outPath;
|
|
117
|
+
}
|
|
118
|
+
catch { savedPath = null; }
|
|
119
|
+
|
|
120
|
+
return new { ok = true, title, html, saved_path = savedPath };
|
|
103
121
|
connections:
|
|
104
122
|
- { from: title-input, to: report }
|
package/dist/web/aware.js
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
const $reportSub = document.getElementById('report-sub');
|
|
25
25
|
const $reportOpen = document.getElementById('report-open');
|
|
26
26
|
const $reportShare = document.getElementById('report-share');
|
|
27
|
+
const $reportOpenFile = document.getElementById('report-open-file');
|
|
28
|
+
const $reportRevealFile = document.getElementById('report-reveal-file');
|
|
27
29
|
const $reportClose = document.getElementById('report-close');
|
|
28
30
|
const $reportStop = document.getElementById('report-stop');
|
|
29
31
|
const $stopRunBtn = document.getElementById('stop-run-btn');
|
|
@@ -1699,12 +1701,26 @@
|
|
|
1699
1701
|
// never a disabled affordance with no path forward).
|
|
1700
1702
|
// The last HTML painted (static or 3D) — so ↗ Open works for both surfaces.
|
|
1701
1703
|
let lastPaintedHtml = '';
|
|
1704
|
+
// The workflow-written output file the current report reported (its run's
|
|
1705
|
+
// `saved_path`), or '' when the run wrote none. Drives the ⧉ Open file / ▤ Reveal
|
|
1706
|
+
// buttons — both hide together when there's no file (#209). Hidden is the default:
|
|
1707
|
+
// paintReport clears it every paint, and only a run/cached-show with a real path
|
|
1708
|
+
// re-shows it — so a trigger/3D/other paint can never leak a previous file's path.
|
|
1709
|
+
let currentSavedPath = '';
|
|
1710
|
+
function showSavedPathActions(path) {
|
|
1711
|
+
currentSavedPath = typeof path === 'string' ? path : '';
|
|
1712
|
+
const has = !!currentSavedPath;
|
|
1713
|
+
$reportOpenFile.hidden = !has;
|
|
1714
|
+
$reportRevealFile.hidden = !has;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1702
1717
|
// Paint a report into the viewer. A STATIC report uses the scriptless srcdoc frame; an
|
|
1703
1718
|
// INTERACTIVE 3D doc (viewer-3d) uses the script-enabled frame via a data: URL (opaque
|
|
1704
1719
|
// origin, cannot touch the app page) and keeps a "Loading 3D view…" overlay until the doc
|
|
1705
1720
|
// posts `viewer-ready`. Exactly one frame is shown at a time.
|
|
1706
1721
|
function paintReport(html, interactive) {
|
|
1707
1722
|
lastPaintedHtml = html || '';
|
|
1723
|
+
showSavedPathActions(''); // default off; the caller re-shows if this report saved a file
|
|
1708
1724
|
if (interactive) {
|
|
1709
1725
|
$reportFrame.hidden = true; $reportFrame.srcdoc = '';
|
|
1710
1726
|
$reportFrame3d.hidden = false;
|
|
@@ -1727,6 +1743,7 @@
|
|
|
1727
1743
|
$reportFrame.srcdoc = ''; $reportFrame.hidden = false;
|
|
1728
1744
|
$reportFrame3d.hidden = true; $reportFrame3d.removeAttribute('src');
|
|
1729
1745
|
lastPaintedHtml = '';
|
|
1746
|
+
showSavedPathActions(''); // no report shown → no saved-file actions
|
|
1730
1747
|
}
|
|
1731
1748
|
|
|
1732
1749
|
// Double-click the HTML Viewer node → LOAD the last report (no run; running is
|
|
@@ -1748,6 +1765,7 @@
|
|
|
1748
1765
|
$reportSub.textContent = `Last report${sfx} — rendered from the run's output, never composed by the UI.`;
|
|
1749
1766
|
}
|
|
1750
1767
|
paintReport(cached.html, cached.interactive);
|
|
1768
|
+
showSavedPathActions(cached.savedPath); // re-show Open file / Reveal if this run saved one
|
|
1751
1769
|
} else {
|
|
1752
1770
|
clearReportFrames();
|
|
1753
1771
|
$reportShare.hidden = true; // nothing to share — hide, don't disable
|
|
@@ -2109,8 +2127,12 @@
|
|
|
2109
2127
|
$reportSub.innerHTML = `Live 3D view from <code>${escapeHtml(nodeId)}</code>${inputBadge ? ' · ' + escapeHtml(inputBadge) : ''} — drag to orbit, scroll to zoom, click an element to inspect.`;
|
|
2110
2128
|
}
|
|
2111
2129
|
paintReport(html, interactive);
|
|
2130
|
+
// If a node wrote an output file, the run reports its path — surface the
|
|
2131
|
+
// ⧉ Open file / ▤ Reveal buttons and cache it so a later double-click restores them (#209).
|
|
2132
|
+
const savedPath = res && typeof res.savedPath === 'string' ? res.savedPath : '';
|
|
2133
|
+
showSavedPathActions(savedPath);
|
|
2112
2134
|
markFirstRunDone(app.id); // a real run rendered → this workflow is no longer "new" (app captured pre-await; a mid-run switch must not mark the wrong workflow)
|
|
2113
|
-
lastReportByApp.set(currentId, { nodeId, label: inputBadge, html, interactive });
|
|
2135
|
+
lastReportByApp.set(currentId, { nodeId, label: inputBadge, html, interactive, savedPath });
|
|
2114
2136
|
$reportModal.dataset.html = '1';
|
|
2115
2137
|
appendNarration(`Rendered the ${interactive ? '3D view' : 'report'} from <strong>${escapeHtml(nodeId)}</strong>${inputBadge ? ' (' + escapeHtml(inputBadge) + ')' : ''} — live run against the model.`);
|
|
2116
2138
|
} catch (e) {
|
|
@@ -2664,6 +2686,22 @@
|
|
|
2664
2686
|
});
|
|
2665
2687
|
// The header ■ Stop run — the always-reachable twin of the overlay Stop (#39).
|
|
2666
2688
|
if ($stopRunBtn) $stopRunBtn.onclick = () => stopRun();
|
|
2689
|
+
// ⧉ Open file / ▤ Reveal in folder — act on the run's saved output file via the OS.
|
|
2690
|
+
// The sandboxed report can't (file:// is blocked from an http page), so the local
|
|
2691
|
+
// host shells it (#209). Fire-and-forget: a 400 (file moved/deleted, or bad type)
|
|
2692
|
+
// surfaces as a toast, never a console error.
|
|
2693
|
+
async function savedFileAction(endpoint, failMsg) {
|
|
2694
|
+
if (!currentSavedPath) return; // button is hidden in this state; belt-and-braces
|
|
2695
|
+
try {
|
|
2696
|
+
await api(endpoint, { method: 'POST', body: JSON.stringify({ path: currentSavedPath }) });
|
|
2697
|
+
} catch (e) {
|
|
2698
|
+
const detail = e && e.body && e.body.error;
|
|
2699
|
+
showToast(detail ? `${failMsg} — ${detail}` : failMsg, 'warn');
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
$reportOpenFile.onclick = () => savedFileAction('/api/open-file', 'Couldn’t open the file');
|
|
2703
|
+
$reportRevealFile.onclick = () => savedFileAction('/api/reveal', 'Couldn’t reveal the file');
|
|
2704
|
+
|
|
2667
2705
|
$reportOpen.onclick = () => {
|
|
2668
2706
|
const html = lastPaintedHtml || $reportFrame.srcdoc;
|
|
2669
2707
|
if (!html) return;
|
|
@@ -6392,12 +6430,15 @@
|
|
|
6392
6430
|
window.flolessBridge = {
|
|
6393
6431
|
api,
|
|
6394
6432
|
// Open report HTML in the existing sandboxed HTML Viewer (same modal + srcdoc
|
|
6395
|
-
// iframe the canvas report node uses — no new sandbox surface).
|
|
6396
|
-
|
|
6433
|
+
// iframe the canvas report node uses — no new sandbox surface). `savedPath` is
|
|
6434
|
+
// optional: pass the run's saved_path so a dashboard-panel run surfaces the same
|
|
6435
|
+
// ⧉ Open file / ▤ Reveal buttons as a canvas run (#209); omit → they stay hidden.
|
|
6436
|
+
showHtmlReport(title, html, savedPath) {
|
|
6397
6437
|
$reportTitle.textContent = title;
|
|
6398
6438
|
$reportSub.textContent = "Rendered from the run's output — never composed by the UI.";
|
|
6399
6439
|
showModal($reportModal);
|
|
6400
6440
|
paintReport(html);
|
|
6441
|
+
showSavedPathActions(savedPath);
|
|
6401
6442
|
},
|
|
6402
6443
|
// Refresh the footer requests counter after the Customize box queues a request.
|
|
6403
6444
|
loadRequests,
|
package/dist/web/index.html
CHANGED
|
@@ -395,6 +395,8 @@
|
|
|
395
395
|
<div class="report-actions">
|
|
396
396
|
<button id="report-stop" class="ghost report-stop" hidden data-tip="Stop the live session — the report stops updating on each change">■ Stop run</button>
|
|
397
397
|
<button id="report-share" class="ghost" hidden data-tip="Share this report as a link anyone can view — hosted on floless.io">↗ Share</button>
|
|
398
|
+
<button id="report-open-file" class="ghost" hidden data-tip="Open the saved file in its default app">⧉ Open file</button>
|
|
399
|
+
<button id="report-reveal-file" class="ghost" hidden data-tip="Show the saved file in its folder">▤ Reveal in folder</button>
|
|
398
400
|
<button id="report-open" class="ghost" data-tip="Open the report in a new browser tab">↗ Open</button>
|
|
399
401
|
<button id="report-close" data-tip="Close">×</button>
|
|
400
402
|
</div>
|
package/dist/web/panels.js
CHANGED
|
@@ -398,7 +398,8 @@
|
|
|
398
398
|
const payload = payloadFor(source);
|
|
399
399
|
const html = payload && typeof payload === 'object' && typeof payload.html === 'string' ? payload.html : null;
|
|
400
400
|
if (!html) { showToast('No report yet — run the workflow first.', 'info'); return; }
|
|
401
|
-
|
|
401
|
+
const savedPath = payload && typeof payload.saved_path === 'string' ? payload.saved_path : null;
|
|
402
|
+
if (bridge.showHtmlReport) bridge.showHtmlReport(title || 'Report', html, savedPath);
|
|
402
403
|
}
|
|
403
404
|
|
|
404
405
|
async function runAction(btn) {
|
|
@@ -415,7 +416,7 @@
|
|
|
415
416
|
return;
|
|
416
417
|
}
|
|
417
418
|
const html = res.report && res.report.html;
|
|
418
|
-
if (html && bridge.showHtmlReport) bridge.showHtmlReport(`${appId} · report`, html);
|
|
419
|
+
if (html && bridge.showHtmlReport) bridge.showHtmlReport(`${appId} · report`, html, res.savedPath);
|
|
419
420
|
else showToast(`Run complete · ${appId}`, 'ok');
|
|
420
421
|
} catch (err) {
|
|
421
422
|
const cred = err && err.body && err.body.credentialIssue;
|