json-object-editor 0.10.633 → 0.10.638
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/CHANGELOG.md +10 -0
- package/_www/matrix.html +126 -0
- package/_www/mcp-nav.js +6 -0
- package/_www/mcp-prompt.html +116 -116
- package/_www/mcp-schemas.html +32 -2
- package/css/joe-styles.css +6 -2
- package/css/joe.css +6 -3
- package/css/joe.min.css +1 -1
- package/docs/JOE_AI_Overview.md +330 -0
- package/favicon-dev.png +0 -0
- package/js/JsonObjectEditor.jquery.craydent.js +8 -2
- package/js/favicon-env.js +50 -0
- package/js/joe-ai.js +9 -4
- package/js/joe.js +9 -3
- package/js/joe.min.js +1 -1
- package/package.json +1 -1
- package/pages/joe.html +6 -4
- package/pages/template.html +5 -4
- package/pages/template_ie.html +5 -4
- package/readme.md +8 -4
- package/server/app-config.js +2 -0
- package/server/modules/Apps.js +3 -0
- package/server/plugins/chatgpt.js +36 -11
- package/server/schemas/ai_widget_conversation.js +229 -229
- package/server/schemas/form.js +51 -7
- package/server/schemas/location.js +24 -0
- package/server/schemas/member.js +35 -0
- package/server/schemas/question.js +55 -2
- package/server/schemas/submission.js +30 -0
- package/web-components/joe-matrix.css +480 -0
- package/web-components/joe-matrix.js +1065 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 0.10.638
|
|
2
|
+
- Matrix: Added schema relationship visualization page at `/_www/matrix.html` with interactive D3.js force-directed graph showing schema-to-schema relationships. Features include app-based filtering, clickable nodes with schema properties panel, SVG icons, and link labels that appear on node selection.
|
|
3
|
+
|
|
4
|
+
## 0.10.637
|
|
5
|
+
- Favicon: Added environment-aware favicon switching - development (localhost) uses `favicon-dev.png` with red "D" indicator, production uses default `favicon.ico`. Implemented via `js/favicon-env.js` script included in all templates and MCP debug pages.
|
|
6
|
+
|
|
7
|
+
## 0.10.635
|
|
8
|
+
- AI Autofill: "Allow web search" is now supported in autofill—if a field's `ai.allowWeb` is set (schema, UI, or API), the `/API/plugin/chatgpt/autofill` endpoint will enable OpenAI's built-in `web_search` for that call. This lets the assistant fetch up-to-date info or run online research when generating field values.
|
|
9
|
+
|
|
10
|
+
|
|
1
11
|
## 0.10.632
|
|
2
12
|
- AI / Widget: Responses-based tool runner for `<joe-ai-widget>` wired to MCP tools via `chatgpt.runWithTools`, plus safer user handling (explicit user id/name/color on `ai_widget_conversation`).
|
|
3
13
|
- Auth: Updated Google OAuth token exchange to use the current `oauth2.googleapis.com/token` endpoint and surface detailed error payloads when login fails.
|
package/_www/matrix.html
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>JOE — Schema Matrix</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<link rel="stylesheet" href="/JsonObjectEditor/web-components/joe-matrix.css"/>
|
|
8
|
+
<style>
|
|
9
|
+
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:0;padding:0;overflow:hidden}
|
|
10
|
+
#matrix-container{width:100vw;height:100vh;position:relative}
|
|
11
|
+
|
|
12
|
+
</style>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div id="mcp-nav"></div>
|
|
16
|
+
<script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
|
|
17
|
+
|
|
18
|
+
<div id="matrix-container">
|
|
19
|
+
<div class="matrix-controls">
|
|
20
|
+
<label>View Mode:</label>
|
|
21
|
+
<select id="view-mode">
|
|
22
|
+
<option value="schema-to-schema">Schema to Schema (3a)</option>
|
|
23
|
+
<option value="object-relationships" disabled>Object Relationships (3b) - Coming Soon</option>
|
|
24
|
+
<option value="aggregate-relationships" disabled>Aggregate Relationships (3c) - Coming Soon</option>
|
|
25
|
+
</select>
|
|
26
|
+
<label>Filter by App:</label>
|
|
27
|
+
<select id="app-filter">
|
|
28
|
+
<option value="">All</option>
|
|
29
|
+
</select>
|
|
30
|
+
<button id="reset-view">Reset View</button>
|
|
31
|
+
<button id="fit-to-screen">Fit to Screen</button>
|
|
32
|
+
</div>
|
|
33
|
+
<joe-matrix id="joe-matrix-main"></joe-matrix>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<script src="/JsonObjectEditor/js/plugins/c3/d3.v3.min.js"></script>
|
|
37
|
+
<script src="/JsonObjectEditor/web-components/joe-matrix.js"></script>
|
|
38
|
+
<script>
|
|
39
|
+
(function(){
|
|
40
|
+
// URL parameter helpers
|
|
41
|
+
function getUrlParam(name) {
|
|
42
|
+
var params = new URLSearchParams(window.location.search);
|
|
43
|
+
return params.get(name) || '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function setUrlParam(name, value) {
|
|
47
|
+
var url = new URL(window.location);
|
|
48
|
+
if (value) {
|
|
49
|
+
url.searchParams.set(name, value);
|
|
50
|
+
} else {
|
|
51
|
+
url.searchParams.delete(name);
|
|
52
|
+
}
|
|
53
|
+
window.history.replaceState({}, '', url);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// View mode change handler
|
|
57
|
+
var viewMode = document.getElementById('view-mode');
|
|
58
|
+
var appFilter = document.getElementById('app-filter');
|
|
59
|
+
var resetBtn = document.getElementById('reset-view');
|
|
60
|
+
var fitBtn = document.getElementById('fit-to-screen');
|
|
61
|
+
var matrixEl = document.getElementById('joe-matrix-main');
|
|
62
|
+
|
|
63
|
+
// Read app parameter from URL on load
|
|
64
|
+
var initialApp = getUrlParam('app');
|
|
65
|
+
if (initialApp && appFilter) {
|
|
66
|
+
// Wait for options to be populated, then set the value
|
|
67
|
+
var checkInterval = setInterval(function() {
|
|
68
|
+
if (appFilter.options.length > 1) {
|
|
69
|
+
clearInterval(checkInterval);
|
|
70
|
+
// Check if the app exists in the options
|
|
71
|
+
var found = false;
|
|
72
|
+
for (var i = 0; i < appFilter.options.length; i++) {
|
|
73
|
+
if (appFilter.options[i].value === initialApp) {
|
|
74
|
+
appFilter.value = initialApp;
|
|
75
|
+
found = true;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (found && matrixEl && matrixEl.setAppFilter) {
|
|
80
|
+
matrixEl.setAppFilter(initialApp);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}, 100);
|
|
84
|
+
// Clear interval after 5 seconds to avoid infinite checking
|
|
85
|
+
setTimeout(function() { clearInterval(checkInterval); }, 5000);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if(viewMode){
|
|
89
|
+
viewMode.addEventListener('change', function(e){
|
|
90
|
+
if(matrixEl){
|
|
91
|
+
matrixEl.setAttribute('mode', e.target.value);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if(appFilter){
|
|
97
|
+
appFilter.addEventListener('change', function(e){
|
|
98
|
+
var selectedApp = e.target.value || '';
|
|
99
|
+
// Update URL parameter
|
|
100
|
+
setUrlParam('app', selectedApp);
|
|
101
|
+
// Update matrix filter
|
|
102
|
+
if(matrixEl && matrixEl.setAppFilter){
|
|
103
|
+
matrixEl.setAppFilter(selectedApp);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if(resetBtn){
|
|
109
|
+
resetBtn.addEventListener('click', function(){
|
|
110
|
+
if(matrixEl && matrixEl.resetView){
|
|
111
|
+
matrixEl.resetView();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if(fitBtn){
|
|
117
|
+
fitBtn.addEventListener('click', function(){
|
|
118
|
+
if(matrixEl && matrixEl.fitToScreen){
|
|
119
|
+
matrixEl.fitToScreen();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
</script>
|
|
125
|
+
</body>
|
|
126
|
+
</html>
|
package/_www/mcp-nav.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
'<a href="/mcp-test.html" target="mcp_test_win" rel="noopener">MCP Test</a>',
|
|
9
9
|
'<a href="/mcp-export.html" target="mcp_export_win" rel="noopener">MCP Export</a>',
|
|
10
10
|
'<a href="/mcp-schemas.html" target="mcp_schemas_win" rel="noopener">Schemas</a>',
|
|
11
|
+
'<a href="/matrix.html" target="matrix_win" rel="noopener">Matrix</a>',
|
|
11
12
|
'<a href="/mcp-prompt.html" target="mcp_prompt_win" rel="noopener">MCP Prompt</a>',
|
|
12
13
|
'<a href="/ai-widget-test.html" target="ai_widget_test_win" rel="noopener">AI Widget</a>',
|
|
13
14
|
'<span style="margin-left:auto"></span>',
|
|
@@ -42,6 +43,11 @@
|
|
|
42
43
|
document.addEventListener('DOMContentLoaded', insert);
|
|
43
44
|
document.addEventListener('DOMContentLoaded', updateInstance);
|
|
44
45
|
}else{ insert(); updateInstance(); }
|
|
46
|
+
|
|
47
|
+
// Load favicon environment script for production detection
|
|
48
|
+
var faviconScript = document.createElement('script');
|
|
49
|
+
faviconScript.src = '/JsonObjectEditor/js/favicon-env.js';
|
|
50
|
+
document.head.appendChild(faviconScript);
|
|
45
51
|
})();
|
|
46
52
|
|
|
47
53
|
|
package/_www/mcp-prompt.html
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<title>JOE MCP Prompt</title>
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
-
<style>
|
|
8
|
-
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;}
|
|
9
|
-
textarea{width:100%;height:360px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
10
|
-
.small{font-size:12px;color:#666}
|
|
11
|
-
</style>
|
|
12
|
-
</head>
|
|
13
|
-
<body>
|
|
14
|
-
<div id="mcp-nav"></div>
|
|
15
|
-
<script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
|
|
16
|
-
<h1>Starter Agent Instructions</h1>
|
|
17
|
-
<div class="small">Copy into your Custom GPT or Assistant system prompt. This text is loaded from docs/joe_agent_custom_gpt_instructions_v_2.md (source of truth).</div>
|
|
18
|
-
|
|
19
|
-
<h3>Downloads</h3>
|
|
20
|
-
<div class="small">Quickly export JSON helpful for agent setup and offline review.</div>
|
|
21
|
-
<div class="row" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin:8px 0 16px;">
|
|
22
|
-
<label for="base" style="margin:0">Base URL</label>
|
|
23
|
-
<input id="base" value="" placeholder="http://localhost:2025" style="min-width:280px"/>
|
|
24
|
-
<button id="downloadApps">Download apps.json</button>
|
|
25
|
-
<button id="downloadHydrate">Download hydrate-<date>.json</button>
|
|
26
|
-
<span id="dlStatus" class="small"></span>
|
|
27
|
-
</div>
|
|
28
|
-
<textarea readonly id="prompt" placeholder="Loading instructions from docs…"></textarea>
|
|
29
|
-
|
|
30
|
-
<script>
|
|
31
|
-
(function(){
|
|
32
|
-
var $ = function(id){ return document.getElementById(id); };
|
|
33
|
-
var base = $('base');
|
|
34
|
-
var dlBtn = $('downloadApps');
|
|
35
|
-
var dlHydrateBtn = $('downloadHydrate');
|
|
36
|
-
var dlStatus = $('dlStatus');
|
|
37
|
-
var promptBox = $('prompt');
|
|
38
|
-
base.value = base.value || location.origin;
|
|
39
|
-
|
|
40
|
-
function setStatus(msg, ok){
|
|
41
|
-
dlStatus.textContent = msg||'';
|
|
42
|
-
dlStatus.className = 'small ' + (ok===true?'good': ok===false?'bad':'');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function fetchJSON(url, opts){
|
|
46
|
-
const res = await fetch(url, opts);
|
|
47
|
-
const ct = res.headers.get('content-type')||'';
|
|
48
|
-
const isJSON = ct.indexOf('application/json') >= 0;
|
|
49
|
-
if(!res.ok){
|
|
50
|
-
let detail = isJSON ? await res.json().catch(function(){return {};}) : await res.text();
|
|
51
|
-
throw new Error('HTTP '+res.status+': '+(isJSON?JSON.stringify(detail):detail));
|
|
52
|
-
}
|
|
53
|
-
return isJSON ? res.json() : res.text();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function callMCP(method, params){
|
|
57
|
-
const url = base.value.replace(/\/$/,'') + '/mcp';
|
|
58
|
-
const body = { jsonrpc: '2.0', id: String(Date.now()), method: method, params: params||{} };
|
|
59
|
-
return fetchJSON(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
dlBtn.onclick = async function(){
|
|
63
|
-
setStatus('Fetching apps...', null);
|
|
64
|
-
try{
|
|
65
|
-
const resp = await callMCP('listApps', {});
|
|
66
|
-
const data = (resp && (resp.result||resp)) || {};
|
|
67
|
-
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
68
|
-
const url = URL.createObjectURL(blob);
|
|
69
|
-
const a = document.createElement('a');
|
|
70
|
-
a.href = url;
|
|
71
|
-
a.download = 'apps.json';
|
|
72
|
-
document.body.appendChild(a);
|
|
73
|
-
a.click();
|
|
74
|
-
setTimeout(function(){ URL.revokeObjectURL(url); a.remove(); }, 0);
|
|
75
|
-
setStatus('Downloaded', true);
|
|
76
|
-
} catch(e){
|
|
77
|
-
setStatus(e.message||String(e), false);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// Load system instructions from docs (source of truth)
|
|
82
|
-
(function loadDocs(){
|
|
83
|
-
var path = '/JsonObjectEditor/docs/joe_agent_custom_gpt_instructions_v_3.md';
|
|
84
|
-
fetch(path).then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.text(); })
|
|
85
|
-
.then(function(text){ promptBox.value = text; })
|
|
86
|
-
.catch(function(e){ promptBox.value = 'Failed to load instructions: '+(e.message||String(e)); });
|
|
87
|
-
})();
|
|
88
|
-
|
|
89
|
-
dlHydrateBtn.onclick = async function(){
|
|
90
|
-
setStatus('Fetching hydrate...', null);
|
|
91
|
-
try{
|
|
92
|
-
const resp = await callMCP('hydrate', {});
|
|
93
|
-
const data = (resp && (resp.result||resp)) || {};
|
|
94
|
-
const now = new Date();
|
|
95
|
-
const yyyy = now.getFullYear();
|
|
96
|
-
const mm = String(now.getMonth()+1).padStart(2,'0');
|
|
97
|
-
const dd = String(now.getDate()).padStart(2,'0');
|
|
98
|
-
const fname = `hydrate_${yyyy}-${mm}-${dd}.json`;
|
|
99
|
-
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
100
|
-
const url = URL.createObjectURL(blob);
|
|
101
|
-
const a = document.createElement('a');
|
|
102
|
-
a.href = url;
|
|
103
|
-
a.download = fname;
|
|
104
|
-
document.body.appendChild(a);
|
|
105
|
-
a.click();
|
|
106
|
-
setTimeout(function(){ URL.revokeObjectURL(url); a.remove(); }, 0);
|
|
107
|
-
setStatus('Downloaded', true);
|
|
108
|
-
} catch(e){
|
|
109
|
-
setStatus(e.message||String(e), false);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
})();
|
|
113
|
-
</script>
|
|
114
|
-
</body>
|
|
115
|
-
</html>
|
|
116
|
-
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>JOE MCP Prompt</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<style>
|
|
8
|
+
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;}
|
|
9
|
+
textarea{width:100%;height:360px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
10
|
+
.small{font-size:12px;color:#666}
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="mcp-nav"></div>
|
|
15
|
+
<script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
|
|
16
|
+
<h1>Starter Agent Instructions</h1>
|
|
17
|
+
<div class="small">Copy into your Custom GPT or Assistant system prompt. This text is loaded from docs/joe_agent_custom_gpt_instructions_v_2.md (source of truth).</div>
|
|
18
|
+
|
|
19
|
+
<h3>Downloads</h3>
|
|
20
|
+
<div class="small">Quickly export JSON helpful for agent setup and offline review.</div>
|
|
21
|
+
<div class="row" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin:8px 0 16px;">
|
|
22
|
+
<label for="base" style="margin:0">Base URL</label>
|
|
23
|
+
<input id="base" value="" placeholder="http://localhost:2025" style="min-width:280px"/>
|
|
24
|
+
<button id="downloadApps">Download apps.json</button>
|
|
25
|
+
<button id="downloadHydrate">Download hydrate-<date>.json</button>
|
|
26
|
+
<span id="dlStatus" class="small"></span>
|
|
27
|
+
</div>
|
|
28
|
+
<textarea readonly id="prompt" placeholder="Loading instructions from docs…"></textarea>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
(function(){
|
|
32
|
+
var $ = function(id){ return document.getElementById(id); };
|
|
33
|
+
var base = $('base');
|
|
34
|
+
var dlBtn = $('downloadApps');
|
|
35
|
+
var dlHydrateBtn = $('downloadHydrate');
|
|
36
|
+
var dlStatus = $('dlStatus');
|
|
37
|
+
var promptBox = $('prompt');
|
|
38
|
+
base.value = base.value || location.origin;
|
|
39
|
+
|
|
40
|
+
function setStatus(msg, ok){
|
|
41
|
+
dlStatus.textContent = msg||'';
|
|
42
|
+
dlStatus.className = 'small ' + (ok===true?'good': ok===false?'bad':'');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function fetchJSON(url, opts){
|
|
46
|
+
const res = await fetch(url, opts);
|
|
47
|
+
const ct = res.headers.get('content-type')||'';
|
|
48
|
+
const isJSON = ct.indexOf('application/json') >= 0;
|
|
49
|
+
if(!res.ok){
|
|
50
|
+
let detail = isJSON ? await res.json().catch(function(){return {};}) : await res.text();
|
|
51
|
+
throw new Error('HTTP '+res.status+': '+(isJSON?JSON.stringify(detail):detail));
|
|
52
|
+
}
|
|
53
|
+
return isJSON ? res.json() : res.text();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function callMCP(method, params){
|
|
57
|
+
const url = base.value.replace(/\/$/,'') + '/mcp';
|
|
58
|
+
const body = { jsonrpc: '2.0', id: String(Date.now()), method: method, params: params||{} };
|
|
59
|
+
return fetchJSON(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
dlBtn.onclick = async function(){
|
|
63
|
+
setStatus('Fetching apps...', null);
|
|
64
|
+
try{
|
|
65
|
+
const resp = await callMCP('listApps', {});
|
|
66
|
+
const data = (resp && (resp.result||resp)) || {};
|
|
67
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
68
|
+
const url = URL.createObjectURL(blob);
|
|
69
|
+
const a = document.createElement('a');
|
|
70
|
+
a.href = url;
|
|
71
|
+
a.download = 'apps.json';
|
|
72
|
+
document.body.appendChild(a);
|
|
73
|
+
a.click();
|
|
74
|
+
setTimeout(function(){ URL.revokeObjectURL(url); a.remove(); }, 0);
|
|
75
|
+
setStatus('Downloaded', true);
|
|
76
|
+
} catch(e){
|
|
77
|
+
setStatus(e.message||String(e), false);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Load system instructions from docs (source of truth)
|
|
82
|
+
(function loadDocs(){
|
|
83
|
+
var path = '/JsonObjectEditor/docs/joe_agent_custom_gpt_instructions_v_3.md';
|
|
84
|
+
fetch(path).then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.text(); })
|
|
85
|
+
.then(function(text){ promptBox.value = text; })
|
|
86
|
+
.catch(function(e){ promptBox.value = 'Failed to load instructions: '+(e.message||String(e)); });
|
|
87
|
+
})();
|
|
88
|
+
|
|
89
|
+
dlHydrateBtn.onclick = async function(){
|
|
90
|
+
setStatus('Fetching hydrate...', null);
|
|
91
|
+
try{
|
|
92
|
+
const resp = await callMCP('hydrate', {});
|
|
93
|
+
const data = (resp && (resp.result||resp)) || {};
|
|
94
|
+
const now = new Date();
|
|
95
|
+
const yyyy = now.getFullYear();
|
|
96
|
+
const mm = String(now.getMonth()+1).padStart(2,'0');
|
|
97
|
+
const dd = String(now.getDate()).padStart(2,'0');
|
|
98
|
+
const fname = `hydrate_${yyyy}-${mm}-${dd}.json`;
|
|
99
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
100
|
+
const url = URL.createObjectURL(blob);
|
|
101
|
+
const a = document.createElement('a');
|
|
102
|
+
a.href = url;
|
|
103
|
+
a.download = fname;
|
|
104
|
+
document.body.appendChild(a);
|
|
105
|
+
a.click();
|
|
106
|
+
setTimeout(function(){ URL.revokeObjectURL(url); a.remove(); }, 0);
|
|
107
|
+
setStatus('Downloaded', true);
|
|
108
|
+
} catch(e){
|
|
109
|
+
setStatus(e.message||String(e), false);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
})();
|
|
113
|
+
</script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
116
|
+
|
package/_www/mcp-schemas.html
CHANGED
|
@@ -180,6 +180,10 @@
|
|
|
180
180
|
return Math.round(score);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
// Read app parameter from URL
|
|
184
|
+
var urlParams = new URLSearchParams(window.location.search);
|
|
185
|
+
var urlApp = urlParams.get('app');
|
|
186
|
+
|
|
183
187
|
// Populate app filter options
|
|
184
188
|
appFilterSel = document.getElementById('appFilter');
|
|
185
189
|
if (appFilterSel){
|
|
@@ -187,6 +191,17 @@
|
|
|
187
191
|
appNames.forEach(function(a){
|
|
188
192
|
var opt=document.createElement('option'); opt.value=a; opt.textContent=a; appFilterSel.appendChild(opt);
|
|
189
193
|
});
|
|
194
|
+
|
|
195
|
+
// Set filter from URL parameter if present (before initial render)
|
|
196
|
+
if (urlApp) {
|
|
197
|
+
// Check if the app exists in the options
|
|
198
|
+
for (var i = 0; i < appFilterSel.options.length; i++) {
|
|
199
|
+
if (appFilterSel.options[i].value === urlApp) {
|
|
200
|
+
appFilterSel.value = urlApp;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
190
205
|
}
|
|
191
206
|
|
|
192
207
|
function getFilteredData(){
|
|
@@ -267,7 +282,7 @@
|
|
|
267
282
|
});
|
|
268
283
|
}
|
|
269
284
|
|
|
270
|
-
// Default sort by name asc
|
|
285
|
+
// Default sort by name asc (filter will be applied automatically if appFilterSel.value is set)
|
|
271
286
|
sortData('name');
|
|
272
287
|
updateHeaderIndicators();
|
|
273
288
|
|
|
@@ -277,7 +292,22 @@
|
|
|
277
292
|
});
|
|
278
293
|
|
|
279
294
|
// Filter handler
|
|
280
|
-
if (appFilterSel){
|
|
295
|
+
if (appFilterSel){
|
|
296
|
+
appFilterSel.onchange = function(){
|
|
297
|
+
// Update URL parameter
|
|
298
|
+
var selectedApp = appFilterSel.value || '';
|
|
299
|
+
var url = new URL(window.location);
|
|
300
|
+
if (selectedApp) {
|
|
301
|
+
url.searchParams.set('app', selectedApp);
|
|
302
|
+
} else {
|
|
303
|
+
url.searchParams.delete('app');
|
|
304
|
+
}
|
|
305
|
+
window.history.replaceState({}, '', url);
|
|
306
|
+
|
|
307
|
+
// Apply filter
|
|
308
|
+
sortData(sortState.key || 'name');
|
|
309
|
+
};
|
|
310
|
+
}
|
|
281
311
|
setStatus('Loaded '+list.length+' schemas', true);
|
|
282
312
|
}catch(e){
|
|
283
313
|
setStatus(e.message||String(e), false);
|
package/css/joe-styles.css
CHANGED
|
@@ -3128,13 +3128,17 @@ html.no-touch .joe-text-autocomplete-option:hover {
|
|
|
3128
3128
|
padding: 5px;
|
|
3129
3129
|
cursor: move;
|
|
3130
3130
|
position:relative;
|
|
3131
|
-
margin:
|
|
3131
|
+
margin: 4px 4px 4px 2px;
|
|
3132
3132
|
transition: background .1s;
|
|
3133
3133
|
background:#f9f9f9;
|
|
3134
|
+
line-height: 1em;
|
|
3135
|
+
border: 1px solid #eee;
|
|
3136
|
+
border-radius: 2px;
|
|
3134
3137
|
}
|
|
3135
3138
|
html.no-touch .joe-multisorter-bin li:hover,
|
|
3136
3139
|
html.no-touch .joe-buckets-bin li:hover{
|
|
3137
|
-
background:rgba(0,0,0,.
|
|
3140
|
+
background:rgba(0,0,0,.05);
|
|
3141
|
+
border: 1px solid #fff;
|
|
3138
3142
|
}
|
|
3139
3143
|
|
|
3140
3144
|
|
package/css/joe.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* --------------------------------------------------------
|
|
2
2
|
*
|
|
3
|
-
* json-object-editor - v0.10.
|
|
3
|
+
* json-object-editor - v0.10.636
|
|
4
4
|
* Created by: Corey Hadden
|
|
5
5
|
*
|
|
6
6
|
* -------------------------------------------------------- */
|
|
@@ -3616,13 +3616,16 @@ html.no-touch .joe-text-autocomplete-option:hover {
|
|
|
3616
3616
|
padding: 5px;
|
|
3617
3617
|
cursor: move;
|
|
3618
3618
|
position:relative;
|
|
3619
|
-
margin:
|
|
3619
|
+
margin: 4px 4px 4px 2px;
|
|
3620
3620
|
transition: background .1s;
|
|
3621
3621
|
background:#f9f9f9;
|
|
3622
|
+
line-height: 1em;
|
|
3623
|
+
border: 1px solid #eee;
|
|
3622
3624
|
}
|
|
3623
3625
|
html.no-touch .joe-multisorter-bin li:hover,
|
|
3624
3626
|
html.no-touch .joe-buckets-bin li:hover{
|
|
3625
|
-
background:rgba(0,0,0,.
|
|
3627
|
+
background:rgba(0,0,0,.05);
|
|
3628
|
+
border: 1px solid #fff;
|
|
3626
3629
|
}
|
|
3627
3630
|
|
|
3628
3631
|
|