@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.
@@ -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/assets/ directory
224
- const assetsDir = path.join(projectPath, "src", "assets")
225
- if (!fs.existsSync(assetsDir)) {
226
- fs.mkdirSync(assetsDir, { recursive: true })
227
- fs.writeFileSync(path.join(assetsDir, ".gitkeep"), "")
228
- console.log(" Created src/assets/ directory for project assets")
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gxp-dev/tools",
3
- "version": "2.0.69",
3
+ "version": "2.0.71",
4
4
  "description": "Dev tools to create platform plugins",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {
@@ -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/assets/",
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="plugin-container">
3
- <!-- Example usage of the GxP Datastore -->
2
+ <div class="demo-container">
4
3
  <div class="content-wrapper">
5
- <h1 gxp-string="welcome_text">Welcome!</h1>
6
-
7
- <div class="info-section">
8
- <h2>Plugin Information</h2>
9
- <p>
10
- <strong>Primary Color:</strong>
11
- <span gxp-string="primary_color"></span>
12
- </p>
13
- <p>
14
- <strong>Project ID:</strong>
15
- <span gxp-settings gxp-string="projectId"></span>
16
- </p>
17
- <p>
18
- <strong>Environment:</strong>
19
- <span gxp-settings gxp-string="environment"></span>
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
- </div>
22
-
23
- <div class="assets-section">
24
- <h2>Assets</h2>
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
- v-if="gxpStore.getAsset('main_logo')"
27
- :src="gxpStore.getAsset('main_logo')"
28
- alt="Main Logo"
65
+ gxp-src="main_logo"
66
+ src="/src/public/logo.png"
67
+ alt="Logo"
29
68
  class="logo"
30
69
  />
31
- <div class="asset-controls">
32
- <button @click="addDevAssets" class="action-btn secondary small">
33
- Add Dev Assets
34
- </button>
35
- <button @click="listAllAssets" class="action-btn secondary small">
36
- List Assets
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="updateLogo" class="action-btn secondary small">
39
- Update Logo
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
- <div v-if="currentAssets" class="asset-preview">
43
- <h3>Current Assets:</h3>
44
- <div
45
- v-for="(url, key) in currentAssets"
46
- :key="key"
47
- class="asset-item"
48
- >
49
- <strong>{{ key }}:</strong>
50
- <a :href="url" target="_blank" class="asset-link">{{ url }}</a>
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
- </div>
53
- </div>
54
-
55
- <div class="actions-section">
56
- <button
57
- @click="handleApiCall"
58
- class="action-btn"
59
- :style="{ backgroundColor: gxpStore.getSetting('primary_color') }"
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
- <div v-else>
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
- .plugin-container {
171
- padding: 20px;
172
- max-width: 800px;
130
+ .demo-container {
131
+ padding: 24px;
132
+ max-width: 880px;
173
133
  margin: 0 auto;
174
- font-family: Arial, sans-serif;
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
- padding: 30px;
181
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
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
- h2 {
191
- color: #333;
192
- border-bottom: 2px solid #eee;
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
- .info-section,
198
- .assets-section,
199
- .actions-section,
200
- .permissions-section,
201
- .dependencies-section,
202
- .socket-section,
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
- .logo {
208
- max-width: 200px;
209
- height: auto;
210
- display: block;
211
- margin: 10px 0;
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
- .action-btn {
215
- background-color: #007bff;
216
- color: white;
217
- border: none;
218
- padding: 12px 24px;
219
- margin: 5px;
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
- cursor: pointer;
222
- font-size: 16px;
223
- transition: background-color 0.3s;
188
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
224
189
  }
225
190
 
226
- .action-btn:hover {
227
- opacity: 0.9;
191
+ dl {
192
+ display: grid;
193
+ grid-template-columns: max-content 1fr;
194
+ gap: 8px 16px;
195
+ margin: 0;
228
196
  }
229
197
 
230
- .action-btn.secondary {
231
- background-color: #6c757d;
198
+ dt {
199
+ font-weight: 600;
200
+ color: #374151;
232
201
  }
233
202
 
234
- .action-btn.complete {
235
- background-color: #28a745;
236
- font-size: 18px;
237
- padding: 15px 30px;
238
- display: block;
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
- .granted {
243
- color: #28a745;
244
- font-weight: bold;
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
- .denied {
248
- color: #dc3545;
249
- font-weight: bold;
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
- .socket-messages {
253
- background: #f8f9fa;
254
- padding: 15px;
255
- border-radius: 4px;
256
- margin-top: 10px;
228
+ .logo-panel {
229
+ text-align: center;
257
230
  }
258
231
 
259
- .dependency-item {
260
- background: #f8f9fa;
261
- padding: 15px;
262
- margin: 10px 0;
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
- .dependency-item h3 {
268
- margin: 0 0 10px 0;
269
- color: #007bff;
238
+ .hint {
239
+ color: #6b7280;
240
+ font-size: 14px;
241
+ margin: 8px 0 0 0;
270
242
  }
271
243
 
272
- .action-btn.small {
273
- padding: 8px 16px;
274
- font-size: 14px;
275
- margin: 2px;
244
+ .socket-controls {
245
+ display: flex;
246
+ flex-wrap: wrap;
247
+ gap: 8px;
248
+ margin: 16px 0;
276
249
  }
277
250
 
278
- ul {
279
- list-style-type: none;
280
- padding: 0;
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
- li {
284
- padding: 5px 0;
285
- border-bottom: 1px solid #eee;
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
- li:last-child {
289
- border-bottom: none;
269
+ .btn:hover {
270
+ opacity: 0.9;
290
271
  }
291
272
 
292
- .asset-controls {
293
- margin: 15px 0;
273
+ .btn.primary {
274
+ background: v-bind('gxpStore.getSetting("primary_color")');
275
+ color: #1f2937;
294
276
  }
295
277
 
296
- .asset-preview {
297
- background: #f8f9fa;
298
- padding: 15px;
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
- .asset-preview h3 {
304
- margin: 0 0 10px 0;
305
- color: #333;
283
+ .socket-feed {
284
+ display: grid;
285
+ grid-template-columns: 1fr 1fr;
286
+ gap: 16px;
287
+ margin-top: 16px;
306
288
  }
307
289
 
308
- .asset-item {
309
- margin: 8px 0;
310
- padding: 8px;
290
+ .feed-column {
311
291
  background: white;
312
- border-radius: 4px;
313
- border-left: 3px solid #007bff;
292
+ border-radius: 6px;
293
+ padding: 12px;
294
+ border: 1px solid #e5e7eb;
295
+ min-height: 120px;
314
296
  }
315
297
 
316
- .asset-link {
317
- color: #007bff;
318
- text-decoration: none;
319
- word-break: break-all;
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
- .asset-link:hover {
323
- text-decoration: underline;
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
- // Define props (router will be passed from platform)
340
- const props = defineProps({
341
- router: {
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
- // Router is now available as props.router for navigation
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
- // Set up socket listeners for a specific dependency
395
- function setupDependencyListeners(dependency) {
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
- socketMessages.value.unshift(
423
- `Listening for events on ${dependency.identifier}`,
424
- )
362
+ function timestamp() {
363
+ return new Date().toLocaleTimeString()
425
364
  }
426
365
 
427
- // Example socket functionality
428
- function handleSocketTest() {
429
- // Emit a test event
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
- function emitTestEvent() {
437
- const timestamp = new Date().toLocaleTimeString()
438
- gxpStore.emitSocket("primary", "plugin-message", {
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
- socketMessages.value.unshift(`Sent: Test message at ${timestamp}`)
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 listAllAssets() {
459
- currentAssets.value = gxpStore.listAssets()
460
- console.log("Listed all assets")
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
- // Listen for test events
476
- const unsubscribe1 = gxpStore.useSocketListener(
477
- "primary",
478
- "test-response",
479
- (data) => {
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
- socketUnsubscribers.value.forEach((unsubscribe) => {
507
- if (typeof unsubscribe === "function") {
508
- unsubscribe()
509
- }
510
- })
394
+ if (typeof unsubscribe === "function") {
395
+ unsubscribe()
396
+ }
511
397
  })
512
398
  </script>