@gxp-dev/tools 2.0.69 → 2.0.71
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/bin/lib/commands/init.js
CHANGED
|
@@ -112,6 +112,16 @@ function copyBundleFiles(projectPath, paths, overwrite = false) {
|
|
|
112
112
|
dest: "app-manifest.json",
|
|
113
113
|
desc: "app-manifest.json",
|
|
114
114
|
},
|
|
115
|
+
{
|
|
116
|
+
src: "dev-assets/images/logo-placeholder.png",
|
|
117
|
+
dest: "src/public/logo.png",
|
|
118
|
+
desc: "src/public/logo.png (demo asset for gxp-src directive)",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
src: "dev-assets/images/banner-placeholder.jpg",
|
|
122
|
+
dest: "src/public/hero.jpg",
|
|
123
|
+
desc: "src/public/hero.jpg (demo asset for gxp-src directive)",
|
|
124
|
+
},
|
|
115
125
|
{
|
|
116
126
|
src: "vite.extend.js",
|
|
117
127
|
dest: "vite.extend.js",
|
|
@@ -220,12 +230,13 @@ function copyExtensionScripts(projectPath, paths, overwrite = false) {
|
|
|
220
230
|
* @param {string} projectPath - Target project path
|
|
221
231
|
*/
|
|
222
232
|
function createSupportingFiles(projectPath) {
|
|
223
|
-
// Create /src/
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
fs.
|
|
228
|
-
|
|
233
|
+
// Create /src/public/ directory — served by Vite from the project root
|
|
234
|
+
// and referenced as `asset_dir: "/src/public"` in app-manifest.json.
|
|
235
|
+
const publicDir = path.join(projectPath, "src", "public")
|
|
236
|
+
if (!fs.existsSync(publicDir)) {
|
|
237
|
+
fs.mkdirSync(publicDir, { recursive: true })
|
|
238
|
+
fs.writeFileSync(path.join(publicDir, ".gitkeep"), "")
|
|
239
|
+
console.log("✓ Created src/public/ directory for project assets")
|
|
229
240
|
}
|
|
230
241
|
|
|
231
242
|
// Create .env file from .env.example
|
package/package.json
CHANGED
|
@@ -650,24 +650,6 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
|
|
|
650
650
|
return permissionFlags.value.includes(flag)
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
-
// Update methods - these replace the entire object to ensure Vue reactivity triggers
|
|
654
|
-
// Used by DevTools and for programmatic updates
|
|
655
|
-
function updateString(key, value) {
|
|
656
|
-
stringsList.value = { ...stringsList.value, [key]: value }
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
function updateSetting(key, value) {
|
|
660
|
-
pluginVars.value = { ...pluginVars.value, [key]: value }
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
function updateAsset(key, value) {
|
|
664
|
-
assetList.value = { ...assetList.value, [key]: value }
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function updateState(key, value) {
|
|
668
|
-
triggerState.value = { ...triggerState.value, [key]: value }
|
|
669
|
-
}
|
|
670
|
-
|
|
671
653
|
// Convenience method to add dev assets with proper URL
|
|
672
654
|
function addDevAsset(key, filename) {
|
|
673
655
|
const appPort =
|
|
@@ -796,10 +778,6 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
|
|
|
796
778
|
findDependency,
|
|
797
779
|
|
|
798
780
|
// Update methods (for DevTools and programmatic updates)
|
|
799
|
-
updateString,
|
|
800
|
-
updateSetting,
|
|
801
|
-
updateAsset,
|
|
802
|
-
updateState,
|
|
803
781
|
addDevAsset,
|
|
804
782
|
|
|
805
783
|
// Socket methods
|
|
@@ -3,17 +3,32 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"description": "GxToolkit Plugin",
|
|
5
5
|
"manifest_version": 3,
|
|
6
|
-
"asset_dir": "/src/
|
|
6
|
+
"asset_dir": "/src/public",
|
|
7
7
|
"configurationFile": "configuration.json",
|
|
8
8
|
"appInstructionsFile": "app-instructions.md",
|
|
9
9
|
"defaultStylingFile": "default-styling.css",
|
|
10
10
|
"settings": {
|
|
11
|
-
"primary_color": "#FFD600"
|
|
11
|
+
"primary_color": "#FFD600",
|
|
12
|
+
"accent_color": "#2962FF",
|
|
13
|
+
"company_name": "Acme Corp"
|
|
12
14
|
},
|
|
13
15
|
"strings": {
|
|
14
|
-
"default": {
|
|
16
|
+
"default": {
|
|
17
|
+
"welcome_title": "Welcome to the GxP Demo",
|
|
18
|
+
"welcome_subtitle": "Edit these values live from the Dev Tools (Ctrl+Shift+D)",
|
|
19
|
+
"socket_hint": "Open this page in another window and messages broadcast on the 'primary' socket will arrive in both.",
|
|
20
|
+
"send_button_label": "Broadcast to primary socket",
|
|
21
|
+
"open_window_button_label": "Open another window"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"assets": {
|
|
25
|
+
"main_logo": "/src/public/logo.png",
|
|
26
|
+
"hero_image": "/src/public/hero.jpg"
|
|
27
|
+
},
|
|
28
|
+
"triggerState": {
|
|
29
|
+
"current_status": "idle",
|
|
30
|
+
"last_event": "none"
|
|
15
31
|
},
|
|
16
|
-
"assets": {},
|
|
17
32
|
"dependencies": [],
|
|
18
33
|
"permissions": []
|
|
19
34
|
}
|
|
@@ -1,326 +1,342 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
<!-- Example usage of the GxP Datastore -->
|
|
2
|
+
<div class="demo-container">
|
|
4
3
|
<div class="content-wrapper">
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
4
|
+
<!--
|
|
5
|
+
The GxP directives pull values from the datastore (app-manifest.json at dev-time).
|
|
6
|
+
Open Dev Tools (Ctrl+Shift+D) to edit any of these live — the page reacts.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
<!-- Hero image: gxp-src swaps `src` to the URL keyed by "hero_image" in assets -->
|
|
10
|
+
<img
|
|
11
|
+
gxp-src="hero_image"
|
|
12
|
+
src="/src/public/hero.jpg"
|
|
13
|
+
alt="Hero"
|
|
14
|
+
class="hero-image"
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<header class="hero">
|
|
18
|
+
<!-- gxp-string replaces the element's text with the value keyed "welcome_title" in strings -->
|
|
19
|
+
<h1 gxp-string="welcome_title">Welcome to the GxP Demo</h1>
|
|
20
|
+
<p gxp-string="welcome_subtitle" class="subtitle">
|
|
21
|
+
Edit these values live from the Dev Tools (Ctrl+Shift+D)
|
|
20
22
|
</p>
|
|
21
|
-
</
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<!-- gxp-settings + gxp-string: read from manifest.settings instead of strings -->
|
|
26
|
+
<section class="panel">
|
|
27
|
+
<h2>Settings <span class="tag">gxp-settings</span></h2>
|
|
28
|
+
<dl>
|
|
29
|
+
<dt>Primary color</dt>
|
|
30
|
+
<dd>
|
|
31
|
+
<span
|
|
32
|
+
class="swatch"
|
|
33
|
+
:style="{ backgroundColor: gxpStore.getSetting('primary_color') }"
|
|
34
|
+
></span>
|
|
35
|
+
<code gxp-settings gxp-string="primary_color">#FFD600</code>
|
|
36
|
+
</dd>
|
|
37
|
+
<dt>Accent color</dt>
|
|
38
|
+
<dd>
|
|
39
|
+
<span
|
|
40
|
+
class="swatch"
|
|
41
|
+
:style="{ backgroundColor: gxpStore.getSetting('accent_color') }"
|
|
42
|
+
></span>
|
|
43
|
+
<code gxp-settings gxp-string="accent_color">#2962FF</code>
|
|
44
|
+
</dd>
|
|
45
|
+
<dt>Company name</dt>
|
|
46
|
+
<dd><code gxp-settings gxp-string="company_name">Acme Corp</code></dd>
|
|
47
|
+
</dl>
|
|
48
|
+
</section>
|
|
49
|
+
|
|
50
|
+
<!-- gxp-state + gxp-string: read from triggerState (typically updated by sockets or CLI) -->
|
|
51
|
+
<section class="panel">
|
|
52
|
+
<h2>Trigger state <span class="tag">gxp-state</span></h2>
|
|
53
|
+
<dl>
|
|
54
|
+
<dt>Current status</dt>
|
|
55
|
+
<dd><code gxp-state gxp-string="current_status">idle</code></dd>
|
|
56
|
+
<dt>Last event</dt>
|
|
57
|
+
<dd><code gxp-state gxp-string="last_event">none</code></dd>
|
|
58
|
+
</dl>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<!-- Logo demo: another gxp-src -->
|
|
62
|
+
<section class="panel logo-panel">
|
|
63
|
+
<h2>Asset <span class="tag">gxp-src</span></h2>
|
|
25
64
|
<img
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
alt="
|
|
65
|
+
gxp-src="main_logo"
|
|
66
|
+
src="/src/public/logo.png"
|
|
67
|
+
alt="Logo"
|
|
29
68
|
class="logo"
|
|
30
69
|
/>
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
70
|
+
<p class="hint">
|
|
71
|
+
Change <code>assets.main_logo</code> in the Dev Tools Store Inspector
|
|
72
|
+
and watch this image swap.
|
|
73
|
+
</p>
|
|
74
|
+
</section>
|
|
75
|
+
|
|
76
|
+
<!-- Primary socket broadcast/listen demo -->
|
|
77
|
+
<section class="panel socket-panel">
|
|
78
|
+
<h2>Primary socket demo</h2>
|
|
79
|
+
<p gxp-string="socket_hint" class="hint">
|
|
80
|
+
Open this page in another window and messages broadcast on the
|
|
81
|
+
'primary' socket will arrive in both.
|
|
82
|
+
</p>
|
|
83
|
+
|
|
84
|
+
<div class="socket-controls">
|
|
85
|
+
<input
|
|
86
|
+
v-model="messageDraft"
|
|
87
|
+
type="text"
|
|
88
|
+
placeholder="Type a message…"
|
|
89
|
+
class="socket-input"
|
|
90
|
+
@keyup.enter="sendMessage"
|
|
91
|
+
/>
|
|
92
|
+
<button @click="sendMessage" class="btn primary">
|
|
93
|
+
<span gxp-string="send_button_label"
|
|
94
|
+
>Broadcast to primary socket</span
|
|
95
|
+
>
|
|
37
96
|
</button>
|
|
38
|
-
<button @click="
|
|
39
|
-
|
|
97
|
+
<button @click="openInNewWindow" class="btn secondary">
|
|
98
|
+
<span gxp-string="open_window_button_label"
|
|
99
|
+
>Open another window</span
|
|
100
|
+
>
|
|
40
101
|
</button>
|
|
41
102
|
</div>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<div
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
103
|
+
|
|
104
|
+
<div class="socket-feed">
|
|
105
|
+
<div class="feed-column">
|
|
106
|
+
<h3>Sent from this window</h3>
|
|
107
|
+
<ul v-if="sentMessages.length">
|
|
108
|
+
<li v-for="(m, i) in sentMessages" :key="`s-${i}`">
|
|
109
|
+
<span class="time">{{ m.time }}</span> {{ m.text }}
|
|
110
|
+
</li>
|
|
111
|
+
</ul>
|
|
112
|
+
<p v-else class="empty">Nothing sent yet.</p>
|
|
51
113
|
</div>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
>
|
|
61
|
-
{{ gxpStore.getString("continue_button", "Continue") }}
|
|
62
|
-
</button>
|
|
63
|
-
|
|
64
|
-
<button @click="handleSocketTest" class="action-btn secondary">
|
|
65
|
-
Test Socket
|
|
66
|
-
</button>
|
|
67
|
-
|
|
68
|
-
<button
|
|
69
|
-
@click="props.router?.visit('/start')"
|
|
70
|
-
class="action-btn secondary"
|
|
71
|
-
>
|
|
72
|
-
{{ gxpStore.getString("back_button", "Back") }}
|
|
73
|
-
</button>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<div class="permissions-section">
|
|
77
|
-
<h2>Permissions</h2>
|
|
78
|
-
<ul>
|
|
79
|
-
<li
|
|
80
|
-
v-for="permission in [
|
|
81
|
-
'can_access_camera',
|
|
82
|
-
'can_save_data',
|
|
83
|
-
'can_share_content',
|
|
84
|
-
]"
|
|
85
|
-
:key="permission"
|
|
86
|
-
>
|
|
87
|
-
{{ permission }}:
|
|
88
|
-
<span
|
|
89
|
-
:class="gxpStore.hasPermission(permission) ? 'granted' : 'denied'"
|
|
90
|
-
>
|
|
91
|
-
{{ gxpStore.hasPermission(permission) ? "Granted" : "Denied" }}
|
|
92
|
-
</span>
|
|
93
|
-
</li>
|
|
94
|
-
</ul>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<div class="dependencies-section">
|
|
98
|
-
<h2>Available Dependencies</h2>
|
|
99
|
-
<div v-if="Array.isArray(gxpStore.dependencyList)">
|
|
100
|
-
<div
|
|
101
|
-
v-for="dependency in gxpStore.dependencyList"
|
|
102
|
-
:key="dependency.identifier"
|
|
103
|
-
class="dependency-item"
|
|
104
|
-
>
|
|
105
|
-
<h3>{{ dependency.identifier }}</h3>
|
|
106
|
-
<p><strong>Model:</strong> {{ dependency.model }}</p>
|
|
107
|
-
<p>
|
|
108
|
-
<strong>Events:</strong>
|
|
109
|
-
{{ Object.keys(dependency.events || {}).join(", ") || "None" }}
|
|
110
|
-
</p>
|
|
111
|
-
<button
|
|
112
|
-
@click="testDependencyAPI(dependency.identifier)"
|
|
113
|
-
class="action-btn secondary small"
|
|
114
|
-
>
|
|
115
|
-
Test API
|
|
116
|
-
</button>
|
|
117
|
-
<button
|
|
118
|
-
v-if="
|
|
119
|
-
dependency.events && Object.keys(dependency.events).length > 0
|
|
120
|
-
"
|
|
121
|
-
@click="setupDependencyListeners(dependency)"
|
|
122
|
-
class="action-btn secondary small"
|
|
123
|
-
>
|
|
124
|
-
Listen for Events
|
|
125
|
-
</button>
|
|
114
|
+
<div class="feed-column">
|
|
115
|
+
<h3>Received from other windows</h3>
|
|
116
|
+
<ul v-if="receivedMessages.length">
|
|
117
|
+
<li v-for="(m, i) in receivedMessages" :key="`r-${i}`">
|
|
118
|
+
<span class="time">{{ m.time }}</span> {{ m.text }}
|
|
119
|
+
</li>
|
|
120
|
+
</ul>
|
|
121
|
+
<p v-else class="empty">Waiting for a message…</p>
|
|
126
122
|
</div>
|
|
127
123
|
</div>
|
|
128
|
-
|
|
129
|
-
<ul>
|
|
130
|
-
<li v-for="(id, key) in gxpStore.dependencyList" :key="key">
|
|
131
|
-
{{ key }}: {{ id }}
|
|
132
|
-
</li>
|
|
133
|
-
</ul>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
<!-- Example of how to use socket listeners -->
|
|
138
|
-
<div class="socket-section">
|
|
139
|
-
<h2>Socket Events</h2>
|
|
140
|
-
<button @click="emitTestEvent" class="action-btn secondary">
|
|
141
|
-
Emit Test Event
|
|
142
|
-
</button>
|
|
143
|
-
<div v-if="socketMessages.length > 0" class="socket-messages">
|
|
144
|
-
<h3>Received Messages:</h3>
|
|
145
|
-
<ul>
|
|
146
|
-
<li v-for="(message, index) in socketMessages" :key="index">
|
|
147
|
-
{{ message }}
|
|
148
|
-
</li>
|
|
149
|
-
</ul>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
<!-- Complete button -->
|
|
154
|
-
<div class="complete-section">
|
|
155
|
-
<button
|
|
156
|
-
@click="props.router?.visit('/final')"
|
|
157
|
-
class="action-btn complete"
|
|
158
|
-
:style="{
|
|
159
|
-
backgroundColor: gxpStore.getSetting('final_background_color'),
|
|
160
|
-
}"
|
|
161
|
-
>
|
|
162
|
-
Complete Experience
|
|
163
|
-
</button>
|
|
164
|
-
</div>
|
|
124
|
+
</section>
|
|
165
125
|
</div>
|
|
166
126
|
</div>
|
|
167
127
|
</template>
|
|
168
128
|
|
|
169
129
|
<style scoped>
|
|
170
|
-
.
|
|
171
|
-
padding:
|
|
172
|
-
max-width:
|
|
130
|
+
.demo-container {
|
|
131
|
+
padding: 24px;
|
|
132
|
+
max-width: 880px;
|
|
173
133
|
margin: 0 auto;
|
|
174
|
-
font-family:
|
|
134
|
+
font-family:
|
|
135
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
136
|
+
color: #1f2937;
|
|
175
137
|
}
|
|
176
138
|
|
|
177
139
|
.content-wrapper {
|
|
178
140
|
background: white;
|
|
141
|
+
border-radius: 12px;
|
|
142
|
+
padding: 32px;
|
|
143
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.hero-image {
|
|
147
|
+
width: 100%;
|
|
148
|
+
height: 180px;
|
|
149
|
+
object-fit: cover;
|
|
179
150
|
border-radius: 8px;
|
|
180
|
-
|
|
181
|
-
|
|
151
|
+
margin-bottom: 20px;
|
|
152
|
+
background: #f3f4f6;
|
|
182
153
|
}
|
|
183
154
|
|
|
184
|
-
h1 {
|
|
155
|
+
.hero h1 {
|
|
156
|
+
margin: 0 0 8px 0;
|
|
185
157
|
color: v-bind('gxpStore.getSetting("primary_color")');
|
|
186
|
-
text-align: center;
|
|
187
|
-
margin-bottom: 30px;
|
|
188
158
|
}
|
|
189
159
|
|
|
190
|
-
|
|
191
|
-
color: #
|
|
192
|
-
|
|
193
|
-
padding-bottom: 10px;
|
|
194
|
-
margin: 20px 0 15px 0;
|
|
160
|
+
.subtitle {
|
|
161
|
+
color: #6b7280;
|
|
162
|
+
margin: 0 0 24px 0;
|
|
195
163
|
}
|
|
196
164
|
|
|
197
|
-
.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.
|
|
203
|
-
.complete-section {
|
|
204
|
-
margin: 20px 0;
|
|
165
|
+
.panel {
|
|
166
|
+
margin: 24px 0;
|
|
167
|
+
padding: 20px;
|
|
168
|
+
background: #f9fafb;
|
|
169
|
+
border-radius: 8px;
|
|
170
|
+
border-left: 4px solid v-bind('gxpStore.getSetting("accent_color")');
|
|
205
171
|
}
|
|
206
172
|
|
|
207
|
-
.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
display:
|
|
211
|
-
|
|
173
|
+
.panel h2 {
|
|
174
|
+
margin: 0 0 16px 0;
|
|
175
|
+
font-size: 18px;
|
|
176
|
+
display: flex;
|
|
177
|
+
align-items: center;
|
|
178
|
+
gap: 10px;
|
|
212
179
|
}
|
|
213
180
|
|
|
214
|
-
.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
181
|
+
.tag {
|
|
182
|
+
font-size: 11px;
|
|
183
|
+
font-weight: 500;
|
|
184
|
+
padding: 2px 8px;
|
|
185
|
+
background: #e5e7eb;
|
|
186
|
+
color: #4b5563;
|
|
220
187
|
border-radius: 4px;
|
|
221
|
-
|
|
222
|
-
font-size: 16px;
|
|
223
|
-
transition: background-color 0.3s;
|
|
188
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
224
189
|
}
|
|
225
190
|
|
|
226
|
-
|
|
227
|
-
|
|
191
|
+
dl {
|
|
192
|
+
display: grid;
|
|
193
|
+
grid-template-columns: max-content 1fr;
|
|
194
|
+
gap: 8px 16px;
|
|
195
|
+
margin: 0;
|
|
228
196
|
}
|
|
229
197
|
|
|
230
|
-
|
|
231
|
-
|
|
198
|
+
dt {
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
color: #374151;
|
|
232
201
|
}
|
|
233
202
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
margin: 20px auto 0;
|
|
203
|
+
dd {
|
|
204
|
+
margin: 0;
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
gap: 8px;
|
|
240
208
|
}
|
|
241
209
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
font-
|
|
210
|
+
code {
|
|
211
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
212
|
+
font-size: 14px;
|
|
213
|
+
background: white;
|
|
214
|
+
padding: 2px 6px;
|
|
215
|
+
border-radius: 4px;
|
|
216
|
+
border: 1px solid #e5e7eb;
|
|
245
217
|
}
|
|
246
218
|
|
|
247
|
-
.
|
|
248
|
-
|
|
249
|
-
|
|
219
|
+
.swatch {
|
|
220
|
+
display: inline-block;
|
|
221
|
+
width: 16px;
|
|
222
|
+
height: 16px;
|
|
223
|
+
border-radius: 3px;
|
|
224
|
+
border: 1px solid #e5e7eb;
|
|
225
|
+
vertical-align: middle;
|
|
250
226
|
}
|
|
251
227
|
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
padding: 15px;
|
|
255
|
-
border-radius: 4px;
|
|
256
|
-
margin-top: 10px;
|
|
228
|
+
.logo-panel {
|
|
229
|
+
text-align: center;
|
|
257
230
|
}
|
|
258
231
|
|
|
259
|
-
.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
margin:
|
|
263
|
-
border-radius: 4px;
|
|
264
|
-
border-left: 4px solid #007bff;
|
|
232
|
+
.logo {
|
|
233
|
+
max-width: 160px;
|
|
234
|
+
height: auto;
|
|
235
|
+
margin: 8px 0;
|
|
265
236
|
}
|
|
266
237
|
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
|
|
238
|
+
.hint {
|
|
239
|
+
color: #6b7280;
|
|
240
|
+
font-size: 14px;
|
|
241
|
+
margin: 8px 0 0 0;
|
|
270
242
|
}
|
|
271
243
|
|
|
272
|
-
.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
244
|
+
.socket-controls {
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-wrap: wrap;
|
|
247
|
+
gap: 8px;
|
|
248
|
+
margin: 16px 0;
|
|
276
249
|
}
|
|
277
250
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
padding:
|
|
251
|
+
.socket-input {
|
|
252
|
+
flex: 1 1 220px;
|
|
253
|
+
padding: 10px 12px;
|
|
254
|
+
border: 1px solid #d1d5db;
|
|
255
|
+
border-radius: 6px;
|
|
256
|
+
font-size: 14px;
|
|
281
257
|
}
|
|
282
258
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
259
|
+
.btn {
|
|
260
|
+
border: none;
|
|
261
|
+
padding: 10px 16px;
|
|
262
|
+
border-radius: 6px;
|
|
263
|
+
cursor: pointer;
|
|
264
|
+
font-size: 14px;
|
|
265
|
+
font-weight: 500;
|
|
266
|
+
transition: opacity 0.15s;
|
|
286
267
|
}
|
|
287
268
|
|
|
288
|
-
|
|
289
|
-
|
|
269
|
+
.btn:hover {
|
|
270
|
+
opacity: 0.9;
|
|
290
271
|
}
|
|
291
272
|
|
|
292
|
-
.
|
|
293
|
-
|
|
273
|
+
.btn.primary {
|
|
274
|
+
background: v-bind('gxpStore.getSetting("primary_color")');
|
|
275
|
+
color: #1f2937;
|
|
294
276
|
}
|
|
295
277
|
|
|
296
|
-
.
|
|
297
|
-
background:
|
|
298
|
-
|
|
299
|
-
border-radius: 4px;
|
|
300
|
-
margin-top: 15px;
|
|
278
|
+
.btn.secondary {
|
|
279
|
+
background: v-bind('gxpStore.getSetting("accent_color")');
|
|
280
|
+
color: white;
|
|
301
281
|
}
|
|
302
282
|
|
|
303
|
-
.
|
|
304
|
-
|
|
305
|
-
|
|
283
|
+
.socket-feed {
|
|
284
|
+
display: grid;
|
|
285
|
+
grid-template-columns: 1fr 1fr;
|
|
286
|
+
gap: 16px;
|
|
287
|
+
margin-top: 16px;
|
|
306
288
|
}
|
|
307
289
|
|
|
308
|
-
.
|
|
309
|
-
margin: 8px 0;
|
|
310
|
-
padding: 8px;
|
|
290
|
+
.feed-column {
|
|
311
291
|
background: white;
|
|
312
|
-
border-radius:
|
|
313
|
-
|
|
292
|
+
border-radius: 6px;
|
|
293
|
+
padding: 12px;
|
|
294
|
+
border: 1px solid #e5e7eb;
|
|
295
|
+
min-height: 120px;
|
|
314
296
|
}
|
|
315
297
|
|
|
316
|
-
.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
298
|
+
.feed-column h3 {
|
|
299
|
+
margin: 0 0 8px 0;
|
|
300
|
+
font-size: 13px;
|
|
301
|
+
color: #6b7280;
|
|
302
|
+
text-transform: uppercase;
|
|
303
|
+
letter-spacing: 0.05em;
|
|
320
304
|
}
|
|
321
305
|
|
|
322
|
-
.
|
|
323
|
-
|
|
306
|
+
.feed-column ul {
|
|
307
|
+
list-style: none;
|
|
308
|
+
padding: 0;
|
|
309
|
+
margin: 0;
|
|
310
|
+
font-size: 14px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.feed-column li {
|
|
314
|
+
padding: 4px 0;
|
|
315
|
+
border-bottom: 1px solid #f3f4f6;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.feed-column li:last-child {
|
|
319
|
+
border-bottom: none;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.time {
|
|
323
|
+
color: #9ca3af;
|
|
324
|
+
font-size: 12px;
|
|
325
|
+
margin-right: 6px;
|
|
326
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.empty {
|
|
330
|
+
color: #9ca3af;
|
|
331
|
+
font-size: 14px;
|
|
332
|
+
margin: 0;
|
|
333
|
+
font-style: italic;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@media (max-width: 640px) {
|
|
337
|
+
.socket-feed {
|
|
338
|
+
grid-template-columns: 1fr;
|
|
339
|
+
}
|
|
324
340
|
}
|
|
325
341
|
</style>
|
|
326
342
|
|
|
@@ -330,183 +346,53 @@ defineOptions({
|
|
|
330
346
|
})
|
|
331
347
|
|
|
332
348
|
import { ref, onMounted, onUnmounted } from "vue"
|
|
333
|
-
|
|
334
|
-
// Initialize the GxP store
|
|
335
349
|
import { useGxpStore } from "@/stores/gxpPortalConfigStore"
|
|
336
350
|
|
|
337
351
|
const gxpStore = useGxpStore()
|
|
338
352
|
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
type: Object,
|
|
343
|
-
required: false,
|
|
344
|
-
default: () => ({
|
|
345
|
-
visit: (url, options) =>
|
|
346
|
-
console.log("Router not available:", url, options),
|
|
347
|
-
}),
|
|
348
|
-
},
|
|
349
|
-
})
|
|
353
|
+
const messageDraft = ref("")
|
|
354
|
+
const sentMessages = ref([])
|
|
355
|
+
const receivedMessages = ref([])
|
|
350
356
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// Local state
|
|
354
|
-
const socketMessages = ref([])
|
|
355
|
-
const socketUnsubscribers = ref([])
|
|
356
|
-
const currentAssets = ref(null)
|
|
357
|
-
|
|
358
|
-
// Example API call using the store
|
|
359
|
-
async function handleApiCall() {
|
|
360
|
-
try {
|
|
361
|
-
console.log("Making API call...")
|
|
362
|
-
// Example API call - this would work with your actual API
|
|
363
|
-
// const result = await gxpStore.apiGet('/some-endpoint');
|
|
364
|
-
// console.log('API Result:', result);
|
|
365
|
-
|
|
366
|
-
// For demo purposes, simulate API call
|
|
367
|
-
setTimeout(() => {
|
|
368
|
-
console.log("Simulated API call completed")
|
|
369
|
-
}, 1000)
|
|
370
|
-
} catch (error) {
|
|
371
|
-
console.error("API call failed:", error)
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Example dependency API call using new methods
|
|
376
|
-
async function testDependencyAPI(identifier) {
|
|
377
|
-
try {
|
|
378
|
-
console.log(`Testing API for dependency: ${identifier}`)
|
|
379
|
-
|
|
380
|
-
// Example of the new getList method
|
|
381
|
-
// const result = await gxpStore.getList(identifier, { page: 1, limit: 10 });
|
|
382
|
-
// console.log(`API Result for ${identifier}:`, result);
|
|
383
|
-
|
|
384
|
-
// For demo purposes, simulate API call
|
|
385
|
-
socketMessages.value.unshift(`API call simulated for ${identifier}`)
|
|
386
|
-
} catch (error) {
|
|
387
|
-
console.error(`API call failed for ${identifier}:`, error)
|
|
388
|
-
socketMessages.value.unshift(
|
|
389
|
-
`API call failed for ${identifier}: ${error.message}`,
|
|
390
|
-
)
|
|
391
|
-
}
|
|
392
|
-
}
|
|
357
|
+
let unsubscribe = null
|
|
393
358
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
console.log(`Setting up listeners for ${dependency.identifier}`)
|
|
397
|
-
|
|
398
|
-
// Set up listeners for each event type
|
|
399
|
-
Object.keys(dependency.events || {}).forEach((eventType) => {
|
|
400
|
-
const eventName = dependency.events[eventType]
|
|
401
|
-
|
|
402
|
-
if (
|
|
403
|
-
gxpStore.sockets[dependency.identifier] &&
|
|
404
|
-
gxpStore.sockets[dependency.identifier][eventType]
|
|
405
|
-
) {
|
|
406
|
-
const unsubscribe = gxpStore.sockets[dependency.identifier][
|
|
407
|
-
eventType
|
|
408
|
-
].listen((data) => {
|
|
409
|
-
console.log(
|
|
410
|
-
`Received ${eventType} event for ${dependency.identifier}:`,
|
|
411
|
-
data,
|
|
412
|
-
)
|
|
413
|
-
socketMessages.value.unshift(
|
|
414
|
-
`${dependency.identifier}.${eventType}: ${data.message || JSON.stringify(data).substring(0, 50)}...`,
|
|
415
|
-
)
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
socketUnsubscribers.value.push(unsubscribe)
|
|
419
|
-
}
|
|
420
|
-
})
|
|
359
|
+
const PRIMARY_SOCKET = "primary"
|
|
360
|
+
const DEMO_EVENT = "demo-message"
|
|
421
361
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
)
|
|
362
|
+
function timestamp() {
|
|
363
|
+
return new Date().toLocaleTimeString()
|
|
425
364
|
}
|
|
426
365
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
gxpStore.emitSocket("primary", "test-event", {
|
|
431
|
-
message: "Hello from plugin!",
|
|
432
|
-
})
|
|
433
|
-
console.log("Emitted test event via socket")
|
|
434
|
-
}
|
|
366
|
+
function sendMessage() {
|
|
367
|
+
const text = messageDraft.value.trim()
|
|
368
|
+
if (!text) return
|
|
435
369
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
message: `Test message at ${timestamp}`,
|
|
440
|
-
timestamp: Date.now(),
|
|
370
|
+
gxpStore.broadcast(PRIMARY_SOCKET, DEMO_EVENT, {
|
|
371
|
+
text,
|
|
372
|
+
sentAt: Date.now(),
|
|
441
373
|
})
|
|
442
374
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
// Asset management methods
|
|
447
|
-
function addDevAssets() {
|
|
448
|
-
// Add some development assets using the convenience method
|
|
449
|
-
gxpStore.addDevAsset("main_logo", "logo-placeholder.png")
|
|
450
|
-
gxpStore.addDevAsset("background_image", "background-placeholder.jpg")
|
|
451
|
-
gxpStore.addDevAsset("product_image", "product-placeholder.jpg")
|
|
452
|
-
gxpStore.addDevAsset("avatar_placeholder", "avatar-placeholder.png")
|
|
453
|
-
|
|
454
|
-
console.log("Added development assets")
|
|
455
|
-
listAllAssets()
|
|
375
|
+
sentMessages.value.unshift({ text, time: timestamp() })
|
|
376
|
+
messageDraft.value = ""
|
|
456
377
|
}
|
|
457
378
|
|
|
458
|
-
function
|
|
459
|
-
|
|
460
|
-
|
|
379
|
+
function openInNewWindow() {
|
|
380
|
+
// Open a new window of the current page so two peers can exchange socket
|
|
381
|
+
// messages via the primary socket.
|
|
382
|
+
window.open(window.location.href, "_blank", "noopener,width=900,height=760")
|
|
461
383
|
}
|
|
462
384
|
|
|
463
|
-
function updateLogo() {
|
|
464
|
-
// Example of updating a specific asset
|
|
465
|
-
const appPort = window.location.port || 3000
|
|
466
|
-
const appProtocol = window.location.protocol || "http"
|
|
467
|
-
const newLogoUrl = `${appProtocol}://localhost:${appPort}/dev-assets/images/logo-placeholder.png`
|
|
468
|
-
gxpStore.updateAsset("main_logo", newLogoUrl)
|
|
469
|
-
console.log("Updated logo asset")
|
|
470
|
-
listAllAssets()
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Set up socket listeners when component mounts
|
|
474
385
|
onMounted(() => {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
console.log("Received test response:", data)
|
|
481
|
-
socketMessages.value.unshift(`Received: ${JSON.stringify(data)}`)
|
|
482
|
-
},
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
// Listen for any incoming messages
|
|
486
|
-
const unsubscribe2 = gxpStore.useSocketListener(
|
|
487
|
-
"primary",
|
|
488
|
-
"incoming-message",
|
|
489
|
-
(data) => {
|
|
490
|
-
console.log("Received incoming message:", data)
|
|
491
|
-
socketMessages.value.unshift(
|
|
492
|
-
`Incoming: ${data.message || JSON.stringify(data)}`,
|
|
493
|
-
)
|
|
494
|
-
},
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
// Store unsubscribers for cleanup
|
|
498
|
-
socketUnsubscribers.value = [unsubscribe1, unsubscribe2]
|
|
499
|
-
|
|
500
|
-
console.log("Plugin component mounted with GxP Datastore")
|
|
501
|
-
console.log("Available store methods:", Object.keys(gxpStore))
|
|
386
|
+
unsubscribe = gxpStore.listen(PRIMARY_SOCKET, DEMO_EVENT, (data) => {
|
|
387
|
+
const text =
|
|
388
|
+
typeof data?.text === "string" ? data.text : JSON.stringify(data)
|
|
389
|
+
receivedMessages.value.unshift({ text, time: timestamp() })
|
|
390
|
+
})
|
|
502
391
|
})
|
|
503
392
|
|
|
504
|
-
// Clean up socket listeners when component unmounts
|
|
505
393
|
onUnmounted(() => {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
})
|
|
394
|
+
if (typeof unsubscribe === "function") {
|
|
395
|
+
unsubscribe()
|
|
396
|
+
}
|
|
511
397
|
})
|
|
512
398
|
</script>
|