@quvel-kit/core 1.1.0 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quvel-kit/core",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Core utilities for Quvel UI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,14 +39,6 @@
39
39
  "import": "./dist/composables/index.js",
40
40
  "types": "./dist/composables/index.d.ts"
41
41
  },
42
- "./components": {
43
- "import": "./dist/components/index.js",
44
- "types": "./dist/components/index.d.ts"
45
- },
46
- "./pages": {
47
- "import": "./dist/pages/index.js",
48
- "types": "./dist/pages/index.d.ts"
49
- },
50
42
  "./global": {
51
43
  "types": "./global.d.ts"
52
44
  }
@@ -57,8 +49,7 @@
57
49
  "README.md"
58
50
  ],
59
51
  "scripts": {
60
- "build": "tsc && npm run copy-vue && npm run copy-ambient-types",
61
- "copy-vue": "rsync -a --include='*/' --include='*.vue' --exclude='*' src/ dist/",
52
+ "build": "tsc && npm run copy-ambient-types",
62
53
  "copy-ambient-types": "rsync -a --include='*/' --include='vue.d.ts' --include='global.d.ts' --include='ssr.d.ts' --include='vue-shim.d.ts' --include='pinia.d.ts' --exclude='*' src/ dist/",
63
54
  "dev": "tsc --watch",
64
55
  "clean": "rm -rf dist",
@@ -1,47 +0,0 @@
1
- <script lang="ts" setup>
2
- /**
3
- * TaskErrors.vue
4
- *
5
- * A component to display errors from a task.
6
- *
7
- * Props:
8
- * - `taskErrors`: The errors from a task, including the main error and additional errors.
9
- */
10
- import { computed } from 'vue';
11
- import FadeInOut from '../Transitions/FadeInOut.vue';
12
- import type { ErrorBag } from '../../types/laravel.types.js';
13
-
14
- /**
15
- * Props for the component.
16
- */
17
- const props = defineProps({
18
- taskErrors: {
19
- type: Object as () => ErrorBag,
20
- default: () => ({ message: '', errors: {} }),
21
- },
22
- });
23
-
24
- /**
25
- * Extracts the most relevant error message.
26
- * - If `message` is already inside `errors`, we ignore `message` to prevent duplication.
27
- * - If `errors` exist, extract the first error.
28
- * - Otherwise, fallback to `message`.
29
- */
30
- const errorMessage = computed(() => {
31
- const { taskErrors } = props;
32
-
33
- // Get the first available error from `taskErrors`
34
- const firstError = Array.from(taskErrors.values())[0];
35
-
36
- // If firstError exists, use it. Otherwise, fallback to message.
37
- return firstError || taskErrors.get('message') || '';
38
- });
39
- </script>
40
-
41
- <template>
42
- <FadeInOut>
43
- <q-banner v-if="errorMessage" class="bg-negative text-white" dense rounded>
44
- {{ errorMessage }}
45
- </q-banner>
46
- </FadeInOut>
47
- </template>
@@ -1,88 +0,0 @@
1
- <script lang="ts" setup>
2
- import { computed } from 'vue';
3
- import type { ZodSchema } from 'zod/v4';
4
- import { useQuvel } from '@quvel-kit/core/composables';
5
-
6
- /**
7
- * Props - Only custom props, everything else forwarded via $attrs
8
- */
9
- const props = defineProps({
10
- modelValue: {
11
- type: String,
12
- required: true,
13
- },
14
- schema: {
15
- type: Object as () => ZodSchema<string> | undefined,
16
- default: undefined,
17
- },
18
- rules: {
19
- type: Array as () => ((val: string) => true | string)[],
20
- default: () => [],
21
- },
22
- });
23
-
24
- /**
25
- * Emits
26
- */
27
- const emits = defineEmits(['update:modelValue']);
28
-
29
- /**
30
- * Services
31
- */
32
- const { validation } = useQuvel();
33
-
34
- /**
35
- * Computed Property for Model Binding
36
- */
37
- const inputValue = computed({
38
- get: () => props.modelValue,
39
- set: (value) => emits('update:modelValue', value),
40
- });
41
-
42
- /**
43
- * Computed Property for Validation Rules
44
- * - If `schema` is provided, use Zod v4 validation with built-in locales
45
- * - Otherwise, use the provided `rules` array
46
- */
47
- const computedRules = computed(() => {
48
- return props.schema
49
- ? [validation.createInputRule(props.schema)]
50
- : props.rules;
51
- });
52
- </script>
53
-
54
- <template>
55
- <q-input
56
- v-model="inputValue"
57
- v-bind="$attrs"
58
- lazy-rules
59
- outlined
60
- :rules="computedRules"
61
- >
62
- <!-- Forward all QInput slots -->
63
- <template v-if="$slots.prepend" #prepend>
64
- <slot name="prepend" />
65
- </template>
66
- <template v-if="$slots.append" #append>
67
- <slot name="append" />
68
- </template>
69
- <template v-if="$slots.before" #before>
70
- <slot name="before" />
71
- </template>
72
- <template v-if="$slots.after" #after>
73
- <slot name="after" />
74
- </template>
75
- <template v-if="$slots.error" #error>
76
- <slot name="error" />
77
- </template>
78
- <template v-if="$slots.hint" #hint>
79
- <slot name="hint" />
80
- </template>
81
- <template v-if="$slots.counter" #counter>
82
- <slot name="counter" />
83
- </template>
84
- <template v-if="$slots.loading" #loading>
85
- <slot name="loading" />
86
- </template>
87
- </q-input>
88
- </template>
@@ -1,22 +0,0 @@
1
- <script lang="ts" setup>
2
- import { useClient } from '@quvel-kit/core/composables';
3
-
4
- /**
5
- * ClientOnly component
6
- *
7
- * Renders its slot content only on the client-side after hydration.
8
- * Prevents hydration mismatches for components that rely on browser-only APIs.
9
- */
10
-
11
- const { isClient } = useClient();
12
- </script>
13
-
14
- <template>
15
- <template v-if="isClient">
16
- <slot />
17
- </template>
18
-
19
- <template v-else>
20
- <slot name="fallback" />
21
- </template>
22
- </template>
@@ -1,9 +0,0 @@
1
- <template>
2
- <transition
3
- appear
4
- enter-active-class="animated fadeInUp"
5
- leave-active-class="animated fadeOutDown"
6
- >
7
- <slot></slot>
8
- </transition>
9
- </template>
@@ -1,13 +0,0 @@
1
- <template>
2
- <!--
3
- This transition gives a "growing" effect to the parent container.
4
- A great use case is dynamic form fields on a vertical form. The form doesn't
5
- just flash into its new size, offering a more aesthetically pleasing effect.
6
- -->
7
- <transition
8
- name="slow-expand"
9
- appear
10
- >
11
- <slot />
12
- </transition>
13
- </template>
@@ -1,634 +0,0 @@
1
- <script lang="ts">
2
- import { defineComponent, ref, computed, onBeforeUnmount, nextTick, onMounted } from 'vue';
3
- import { useWebSockets } from '../composables/useWebSockets';
4
- import { WebSocketChannelType, SubscribeOptions } from '../types/websocket.types';
5
- import { PublicChannelType, PrivateChannelType, PresenceChannelType, EncryptedChannelType } from '../types/websocket.types';
6
- import { QScrollArea } from 'quasar';
7
-
8
-
9
- type AnyChannel = PublicChannelType | PrivateChannelType | PresenceChannelType | EncryptedChannelType;
10
-
11
- interface ChannelConfig {
12
- name: string;
13
- type: WebSocketChannelType;
14
- event?: string | undefined;
15
- channel: AnyChannel;
16
- }
17
-
18
- interface MessageLog {
19
- channelName: string;
20
- channelType: WebSocketChannelType;
21
- event: string;
22
- data: unknown;
23
- timestamp: Date;
24
- }
25
-
26
- export default defineComponent({
27
- name: 'WebSocketChannelManager',
28
-
29
- setup() {
30
- // WebSocket connection management
31
- const { subscribe, unsubscribe } = useWebSockets();
32
-
33
- // Dialog visibility control
34
- const isDialogOpen = ref(false);
35
- const isVisible = computed(() => isDialogOpen.value);
36
-
37
- // Selected channel for filtering messages
38
- const selectedChannelIndex = ref<number | null>(null);
39
-
40
- // Register global methods to show/hide the component
41
- onMounted(() => {
42
- (window as unknown as { showWebSocketManager: () => void }).showWebSocketManager = () => {
43
- isDialogOpen.value = true;
44
-
45
- console.log('WebSocket Inspector is now open');
46
- };
47
-
48
- (window as unknown as { hideWebSocketManager: () => void }).hideWebSocketManager = () => {
49
- hideManager();
50
- };
51
-
52
- // Log instructions to console
53
- console.info(
54
- '%cWebSocket Inspector available!',
55
- 'color: #4CAF50; font-weight: bold; font-size: 14px;'
56
- );
57
- console.info(
58
- '%cUse window.showWebSocketManager() to show the inspector',
59
- 'color: #2196F3; font-size: 12px;'
60
- );
61
- });
62
-
63
- // Hide the manager and clean up
64
- const hideManager = () => {
65
- isDialogOpen.value = false;
66
- console.log('WebSocket Inspector is now closed');
67
- };
68
-
69
- // Select a channel to filter messages
70
- const selectChannel = (index: number) => {
71
- selectedChannelIndex.value = selectedChannelIndex.value === index ? null : index;
72
- };
73
-
74
- // Active channels
75
- const activeChannels = ref<ChannelConfig[]>([]);
76
-
77
- // Connection status
78
- const connectionStatus = computed(() => {
79
- const isConnected = activeChannels.value.length > 0;
80
- return {
81
- label: isConnected ? 'Connected' : 'Disconnected',
82
- color: isConnected ? 'positive' : 'negative'
83
- };
84
- });
85
-
86
- // Channel form
87
- // Channel form with partial type to allow initialization without a channel
88
- const newChannel = ref<Omit<ChannelConfig, 'channel'> & { channel?: AnyChannel }>({
89
- name: '',
90
- type: 'public',
91
- event: ''
92
- });
93
-
94
- const isAddingChannel = ref(false);
95
-
96
- // Available channel types
97
- const channelTypes = [
98
- { label: 'Public', value: 'public' },
99
- { label: 'Private', value: 'private' },
100
- { label: 'Presence', value: 'presence' },
101
- { label: 'Encrypted', value: 'encrypted' },
102
- { label: 'Public Notification', value: 'publicNotification' },
103
- { label: 'Private Notification', value: 'privateNotification' }
104
- ];
105
-
106
- // Message log
107
- const messages = ref<MessageLog[]>([]);
108
- const messageLogRef = ref<QScrollArea | null>(null);
109
-
110
- // Filtered messages based on selected channel
111
- const filteredMessages = computed(() => {
112
- if (selectedChannelIndex.value === null) {
113
- return messages.value;
114
- }
115
-
116
- const selectedChannel = activeChannels.value[selectedChannelIndex.value];
117
- return messages.value.filter(msg =>
118
- msg.channelName === selectedChannel?.name &&
119
- msg.channelType === selectedChannel?.type
120
- );
121
- });
122
-
123
- // Add a new channel
124
- const addChannel = async () => {
125
- try {
126
- isAddingChannel.value = true;
127
-
128
- // Create channel options
129
- const options: SubscribeOptions<unknown> = {
130
- channelName: newChannel.value.name,
131
- type: newChannel.value.type,
132
- // Only include event if it's defined
133
- ...(newChannel.value.event ? { event: newChannel.value.event } : {}),
134
- callback: (data: unknown) => {
135
- // Log the message
136
- messages.value.push({
137
- channelName: newChannel.value.name,
138
- channelType: newChannel.value.type,
139
- event: newChannel.value.event || 'notification',
140
- data,
141
- timestamp: new Date(),
142
- });
143
-
144
- // Scroll to bottom of message log
145
- void nextTick(() => {
146
- if (messageLogRef.value) {
147
- messageLogRef.value.setScrollPosition('vertical', 999999);
148
- }
149
- });
150
- },
151
- presenceHandlers: {
152
- onListening: {
153
- event: 'listening',
154
- callback: () => {
155
- messages.value.push({
156
- channelName: newChannel.value.name,
157
- channelType: newChannel.value.type,
158
- event: newChannel.value.event || 'notification',
159
- data: 'Listening',
160
- timestamp: new Date(),
161
- });
162
- },
163
- },
164
- onHere: (members: Record<string, unknown>) => {
165
- messages.value.push({
166
- channelName: newChannel.value.name,
167
- channelType: newChannel.value.type,
168
- event: newChannel.value.event || 'notification',
169
- data: members,
170
- timestamp: new Date(),
171
- });
172
- },
173
- onJoining: (member: Record<string, unknown>) => {
174
- messages.value.push({
175
- channelName: newChannel.value.name,
176
- channelType: newChannel.value.type,
177
- event: newChannel.value.event || 'notification',
178
- data: member,
179
- timestamp: new Date(),
180
- });
181
- },
182
- onLeaving: (member: Record<string, unknown>) => {
183
- messages.value.push({
184
- channelName: newChannel.value.name,
185
- channelType: newChannel.value.type,
186
- event: newChannel.value.event || 'notification',
187
- data: member,
188
- timestamp: new Date(),
189
- });
190
- },
191
- },
192
- };
193
-
194
- // Subscribe to the channel
195
- const channel = await subscribe(options);
196
-
197
- // Add to active channels
198
- activeChannels.value.push({
199
- name: newChannel.value.name,
200
- type: newChannel.value.type,
201
- event: newChannel.value.event || undefined,
202
- channel
203
- });
204
-
205
- // Reset form
206
- newChannel.value = {
207
- name: '',
208
- type: 'public',
209
- event: ''
210
- };
211
- } catch (error) {
212
- console.error('Failed to add channel:', error);
213
- } finally {
214
- isAddingChannel.value = false;
215
- }
216
- };
217
-
218
- // Remove a channel
219
- const removeChannel = (index: number) => {
220
- const channel = activeChannels.value[index];
221
- if (channel?.channel) {
222
- unsubscribe(channel.channel as AnyChannel);
223
- }
224
-
225
- activeChannels.value.splice(index, 1);
226
- };
227
-
228
- // Clear message log
229
- const clearMessages = () => {
230
- messages.value = [];
231
- };
232
-
233
- // Download logs as JSON
234
- const downloadLogs = () => {
235
- const data = JSON.stringify(messages.value, null, 2);
236
- const blob = new Blob([data], { type: 'application/json' });
237
- const url = URL.createObjectURL(blob);
238
- const a = document.createElement('a');
239
- a.href = url;
240
- a.download = `websocket-logs-${new Date().toISOString()}.json`;
241
- document.body.appendChild(a);
242
- a.click();
243
- document.body.removeChild(a);
244
- URL.revokeObjectURL(url);
245
- };
246
-
247
- // Format timestamp
248
- const formatTimestamp = (date: Date) => {
249
- return date.toLocaleTimeString();
250
- };
251
-
252
- // Format message data for display
253
- const formatMessageData = (data: unknown) => {
254
- try {
255
- return JSON.stringify(data, null, 2);
256
- } catch {
257
- return String(data);
258
- }
259
- };
260
-
261
- // Get color for channel type
262
- const getChannelTypeColor = (type: WebSocketChannelType) => {
263
- switch (type) {
264
- case 'public':
265
- case 'publicNotification':
266
- return 'teal';
267
- case 'private':
268
- case 'privateNotification':
269
- return 'blue';
270
- case 'presence':
271
- return 'purple';
272
- case 'encrypted':
273
- return 'deep-orange';
274
- default:
275
- return 'grey';
276
- }
277
- };
278
-
279
- // Clean up all channels when component unmounts
280
- onBeforeUnmount(() => {
281
- activeChannels.value.forEach(channelConfig => {
282
- unsubscribe(channelConfig.channel as AnyChannel);
283
- });
284
-
285
- activeChannels.value = [];
286
- });
287
-
288
- return {
289
- // Visibility control
290
- isVisible,
291
- isDialogOpen,
292
- hideManager,
293
-
294
- // Connection
295
- connectionStatus,
296
-
297
- // Channel form
298
- newChannel,
299
- channelTypes,
300
- isAddingChannel,
301
- addChannel,
302
-
303
- // Active channels
304
- activeChannels,
305
- removeChannel,
306
- getChannelTypeColor,
307
- selectedChannelIndex,
308
- selectChannel,
309
-
310
- // Message log
311
- messages,
312
- filteredMessages,
313
- messageLogRef,
314
- clearMessages,
315
- downloadLogs,
316
- formatTimestamp,
317
- formatMessageData
318
- };
319
- }
320
- });
321
- </script>
322
-
323
- <template>
324
- <q-dialog
325
- v-model="isDialogOpen"
326
- persistent
327
- maximized
328
- transition-show="slide-up"
329
- transition-hide="slide-down"
330
- >
331
- <q-card class="websocket-channel-manager">
332
- <q-card-section class="bg-dark text-white q-py-sm">
333
- <div class="flex items-center justify-between">
334
- <div class="flex items-center">
335
- <q-icon
336
- name="eva-wifi-outline"
337
- size="md"
338
- class="q-mr-sm"
339
- />
340
- <h5 class="text-h6 q-my-none">WebSocket Inspector</h5>
341
- </div>
342
- <q-chip
343
- :color="connectionStatus.color"
344
- text-color="white"
345
- size="sm"
346
- outline
347
- >
348
- {{ connectionStatus.label }}
349
- </q-chip>
350
-
351
- <q-btn
352
- flat
353
- round
354
- color="grey-5"
355
- icon="eva-close-outline"
356
- @click="hideManager"
357
- />
358
- </div>
359
- </q-card-section>
360
-
361
- <q-card-section class="q-pa-md bg-dark-subtle">
362
- <q-form
363
- @submit="addChannel"
364
- class="row q-col-gutter-sm items-end"
365
- >
366
- <div class="col-12 col-sm-4">
367
- <q-input
368
- v-model="newChannel.name"
369
- label="Channel Name"
370
- dense
371
- filled
372
- dark
373
- class="bg-dark-page"
374
- :rules="[val => !!val || 'Required']"
375
- >
376
- <template v-slot:prepend>
377
- <q-icon name="eva-hash-outline" />
378
- </template>
379
- </q-input>
380
- </div>
381
- <div class="col-12 col-sm-3">
382
- <q-select
383
- v-model="newChannel.type"
384
- :options="channelTypes"
385
- :emit-value="true"
386
- label="Channel Type"
387
- dense
388
- filled
389
- dark
390
- class="bg-dark-page"
391
- :rules="[val => !!val || 'Required']"
392
- >
393
- <template v-slot:prepend>
394
- <q-icon name="eva-eye-outline" />
395
- </template>
396
- </q-select>
397
- </div>
398
- <div class="col-12 col-sm-3">
399
- <q-input
400
- v-model="newChannel.event"
401
- label="Event Name"
402
- dense
403
- filled
404
- dark
405
- class="bg-dark-page"
406
- :rules="[val => newChannel.type !== 'presence' ? !!val || 'Required' : true]"
407
- :disable="newChannel.type === 'presence'"
408
- >
409
- <template v-slot:prepend>
410
- <q-icon name="eva-bell-outline" />
411
- </template>
412
- </q-input>
413
- </div>
414
- <div class="col-12 col-sm-2">
415
- <q-btn
416
- type="submit"
417
- color="primary"
418
- icon="eva-plus-outline"
419
- label="Subscribe"
420
- no-caps
421
- unelevated
422
- class="full-width"
423
- :loading="isAddingChannel"
424
- />
425
- </div>
426
- </q-form>
427
- </q-card-section>
428
-
429
- <q-separator dark />
430
-
431
- <q-card-section class="q-pa-none">
432
- <div class="row">
433
- <div class="col-12 col-md-3 bg-dark-subtle">
434
- <q-card-section class="q-py-sm">
435
- <div class="text-subtitle1 text-weight-medium flex items-center">
436
- <q-icon
437
- name="eva-hash-outline"
438
- class="q-mr-xs"
439
- />
440
- Active Channels
441
- <q-badge
442
- color="primary"
443
- class="q-ml-sm"
444
- >{{ activeChannels.length }}</q-badge>
445
- </div>
446
- </q-card-section>
447
-
448
- <q-separator dark />
449
-
450
- <q-list
451
- dark
452
- dense
453
- class="channel-list"
454
- >
455
- <q-item
456
- v-if="activeChannels.length === 0"
457
- class="text-grey-6"
458
- >
459
- <q-item-section>
460
- <q-item-label>No active channels</q-item-label>
461
- <q-item-label caption>Add a channel to start listening</q-item-label>
462
- </q-item-section>
463
- </q-item>
464
-
465
- <q-item
466
- v-for="(channel, index) in activeChannels"
467
- :key="index"
468
- clickable
469
- active-class="bg-primary text-white"
470
- :active="selectedChannelIndex === index"
471
- @click="selectChannel(index)"
472
- >
473
- <q-item-section avatar>
474
- <q-icon
475
- :color="getChannelTypeColor(channel.type)"
476
- name="eva-radio-outline"
477
- />
478
- </q-item-section>
479
-
480
- <q-item-section>
481
- <q-item-label lines="1">{{ channel.name }}</q-item-label>
482
- <q-item-label caption>
483
- <q-badge
484
- :color="getChannelTypeColor(channel.type)"
485
- text-color="white"
486
- size="xs"
487
- >
488
- {{ channel.type }}
489
- </q-badge>
490
- <span
491
- v-if="channel.event"
492
- class="q-ml-xs"
493
- >{{ channel.event }}</span>
494
- </q-item-label>
495
- </q-item-section>
496
-
497
- <q-item-section side>
498
- <q-btn
499
- flat
500
- round
501
- dense
502
- color="negative"
503
- icon="eva-close-outline"
504
- @click.stop="removeChannel(index)"
505
- />
506
- </q-item-section>
507
- </q-item>
508
- </q-list>
509
- </div>
510
-
511
- <div class="col-12 col-md-9">
512
- <q-card-section class="q-py-sm bg-dark-subtle">
513
- <div class="row items-center justify-between">
514
- <div class="text-subtitle1 text-weight-medium flex items-center">
515
- <q-icon
516
- name="eva-message-square-outline"
517
- class="q-mr-xs"
518
- />
519
- Message Log
520
- <q-badge
521
- color="secondary"
522
- class="q-ml-sm"
523
- >{{ filteredMessages.length }}</q-badge>
524
- </div>
525
-
526
- <div>
527
- <q-btn
528
- flat
529
- round
530
- dense
531
- color="grey"
532
- icon="eva-refresh-outline"
533
- @click="clearMessages"
534
- class="q-mr-xs"
535
- >
536
- <q-tooltip>Clear Messages</q-tooltip>
537
- </q-btn>
538
- <q-btn
539
- flat
540
- round
541
- dense
542
- color="grey"
543
- icon="eva-download-outline"
544
- @click="downloadLogs"
545
- >
546
- <q-tooltip>Download Logs</q-tooltip>
547
- </q-btn>
548
- </div>
549
- </div>
550
- </q-card-section>
551
-
552
- <q-separator dark />
553
-
554
- <q-scroll-area
555
- style="height: calc(100vh - 280px);"
556
- ref="messageLogRef"
557
- class="bg-dark-page"
558
- >
559
- <div
560
- v-if="filteredMessages.length === 0"
561
- class="text-center q-pa-md text-grey-7"
562
- >
563
- <q-icon
564
- name="eva-message-square-outline"
565
- size="2rem"
566
- />
567
- <div class="q-mt-sm">No messages received yet.</div>
568
- </div>
569
-
570
- <div
571
- v-for="(message, index) in filteredMessages"
572
- :key="index"
573
- class="message-item q-pa-md q-my-sm"
574
- >
575
- <div class="flex items-center justify-between">
576
- <div class="flex items-center">
577
- <q-badge
578
- :color="getChannelTypeColor(message.channelType)"
579
- class="q-mr-sm"
580
- >{{ message.channelType
581
- }}</q-badge>
582
- <div class="text-weight-medium">{{ message.channelName }}</div>
583
- </div>
584
- <div class="text-grey-7 text-caption">{{ formatTimestamp(message.timestamp) }}</div>
585
- </div>
586
-
587
- <div class="q-mt-xs">
588
- <q-chip
589
- size="sm"
590
- outline
591
- color="secondary"
592
- class="q-mr-sm"
593
- >{{ message.event }}</q-chip>
594
- </div>
595
-
596
- <q-card
597
- flat
598
- bordered
599
- class="q-mt-sm bg-dark-subtle"
600
- >
601
- <q-card-section class="q-pa-sm">
602
- <pre class="message-content text-grey-4">{{ formatMessageData(message.data) }}</pre>
603
- </q-card-section>
604
- </q-card>
605
- </div>
606
- </q-scroll-area>
607
- </div>
608
- </div>
609
- </q-card-section>
610
- </q-card>
611
- </q-dialog>
612
- </template>
613
-
614
- <style lang="scss">
615
- .websocket-channel-manager {
616
- width: 100%;
617
- height: 100%;
618
- display: flex;
619
- flex-direction: column;
620
- background-color: #1e1e1e;
621
- color: #e0e0e0;
622
-
623
- .channel-list {
624
- max-height: 300px;
625
- overflow-y: auto;
626
- }
627
-
628
- .message-content {
629
- overflow-x: auto;
630
- white-space: pre-wrap;
631
- word-break: break-word;
632
- }
633
- }
634
- </style>
@@ -1,12 +0,0 @@
1
- /**
2
- * Core Components
3
- *
4
- * Reusable Vue components for the Quvel UI framework
5
- */
6
- export { default as BaseInput } from './Inputs/BaseInput.vue';
7
- export { default as TaskErrors } from './Common/TaskErrors.vue';
8
- export { default as ClientOnly } from './Misc/ClientOnly.vue';
9
- export { default as FadeInOut } from './Transitions/FadeInOut.vue';
10
- export { default as SlowExpand } from './Transitions/SlowExpand.vue';
11
- export { default as WebSocketChannelManager } from './WebSocketChannelManager.vue';
12
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAG9D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAGrE,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,+BAA+B,CAAC"}
@@ -1,16 +0,0 @@
1
- /**
2
- * Core Components
3
- *
4
- * Reusable Vue components for the Quvel UI framework
5
- */
6
- // Inputs Components
7
- export { default as BaseInput } from './Inputs/BaseInput.vue';
8
- // Common Components
9
- export { default as TaskErrors } from './Common/TaskErrors.vue';
10
- // Misc Components
11
- export { default as ClientOnly } from './Misc/ClientOnly.vue';
12
- // Transitions
13
- export { default as FadeInOut } from './Transitions/FadeInOut.vue';
14
- export { default as SlowExpand } from './Transitions/SlowExpand.vue';
15
- // WebSocket
16
- export { default as WebSocketChannelManager } from './WebSocketChannelManager.vue';
@@ -1,300 +0,0 @@
1
- <template>
2
- <div class="ErrorNotFound">
3
- <div class="ErrorNotFound-Container">
4
- <div class="ErrorNotFound-Content">
5
- <!-- Gradient Background Effect -->
6
- <div class="ErrorNotFound-Glow"></div>
7
-
8
- <!-- 404 Icon with Glow Effect -->
9
- <div class="ErrorNotFound-IconContainer SmallGlow">
10
- <q-icon
11
- name="search"
12
- size="80px"
13
- class="ErrorNotFound-Icon"
14
- />
15
- </div>
16
-
17
- <!-- Error Code -->
18
- <div class="ErrorNotFound-Code">
19
- 404
20
- </div>
21
-
22
- <!-- Message -->
23
- <div class="ErrorNotFound-Title">
24
- {{ $t('common.errors.notFound.title') }}
25
- </div>
26
-
27
- <div class="ErrorNotFound-Description">
28
- {{ $t('common.errors.notFound.description') }}
29
- </div>
30
-
31
- <!-- Actions -->
32
- <div class="ErrorNotFound-Actions">
33
- <q-btn
34
- unelevated
35
- size="md"
36
- :to="homeRoute"
37
- class="PrimaryButton GenericBorder ErrorNotFound-HomeButton"
38
- no-caps
39
- >
40
- <q-icon
41
- name="home"
42
- size="18px"
43
- class="q-mr-xs"
44
- />
45
- {{ $t('common.errors.notFound.actions.home') }}
46
- </q-btn>
47
-
48
- <q-btn
49
- outline
50
- size="md"
51
- @click="goBack"
52
- class="Button GenericBorder ErrorNotFound-BackButton"
53
- no-caps
54
- >
55
- <q-icon
56
- name="arrow_back"
57
- size="18px"
58
- class="q-mr-xs"
59
- />
60
- {{ $t('common.errors.notFound.actions.back') }}
61
- </q-btn>
62
- </div>
63
-
64
- <!-- Help Text -->
65
- <div class="ErrorNotFound-Help">
66
- <p class="ErrorNotFound-HelpText">
67
- {{ $t('common.errors.notFound.help') }}
68
- </p>
69
- </div>
70
- </div>
71
- </div>
72
- </div>
73
- </template>
74
-
75
- <script setup lang="ts">
76
- import { computed } from 'vue';
77
- import { useRouter } from 'vue-router';
78
- import { useQuvel } from '../composables/useQuvel.js';
79
-
80
- const router = useRouter();
81
- const container = useQuvel();
82
-
83
- // Get home route from config with default fallback
84
- const homeRoute = computed(() => {
85
- return container.config.routes?.home || '/' as const;
86
- });
87
-
88
- const goBack = () => {
89
- if (window.history.length > 1) {
90
- router.go(-1);
91
- } else {
92
- void router.push(homeRoute.value);
93
- }
94
- };
95
- </script>
96
-
97
- <style lang="scss" scoped>
98
- .ErrorNotFound {
99
- min-height: 100vh;
100
- display: flex;
101
- align-items: center;
102
- justify-content: center;
103
- position: relative;
104
- background: linear-gradient(170deg, #f9fafb, #e6e7eb);
105
-
106
- .dark & {
107
- background: linear-gradient(150deg, #202b3b, #12171e);
108
- }
109
-
110
- &-Container {
111
- max-width: 32rem;
112
- margin-left: auto;
113
- margin-right: auto;
114
- padding-left: 2rem;
115
- padding-right: 2rem;
116
- position: relative;
117
- z-index: 10;
118
- }
119
-
120
- &-Content {
121
- text-align: center;
122
- position: relative;
123
- }
124
-
125
- &-Glow {
126
- position: absolute;
127
- top: 50%;
128
- left: 50%;
129
- width: 300px;
130
- height: 300px;
131
- transform: translate(-50%, -60%);
132
- border-radius: 50%;
133
- background: radial-gradient(circle,
134
- rgba(59, 130, 246, 0.15) 0%,
135
- rgba(243, 85, 44, 0.05) 40%,
136
- rgba(255, 255, 255, 0) 70%);
137
- opacity: 0.8;
138
- z-index: -1;
139
-
140
- .dark & {
141
- background: radial-gradient(circle,
142
- rgba(99, 102, 241, 0.2) 0%,
143
- rgba(253, 106, 42, 0.1) 40%,
144
- rgba(30, 30, 46, 0) 70%);
145
- }
146
- }
147
-
148
- &-IconContainer {
149
- margin-bottom: 2rem;
150
- display: inline-block;
151
- }
152
-
153
- &-Icon {
154
- color: #9ca3af;
155
-
156
- .dark & {
157
- color: #6b7280;
158
- }
159
- }
160
-
161
- &-Code {
162
- font-size: 6rem;
163
- line-height: 1;
164
- font-weight: 700;
165
- margin-bottom: 1.5rem;
166
- font-family: system-ui, -apple-system, sans-serif;
167
- background: linear-gradient(135deg,
168
- rgba(59, 130, 246, 0.8) 0%,
169
- rgba(243, 85, 44, 0.8) 50%,
170
- rgba(234, 83, 1, 0.8) 100%);
171
- background-clip: text;
172
- -webkit-background-clip: text;
173
- -webkit-text-fill-color: transparent;
174
- text-shadow: 0 4px 20px rgba(59, 130, 246, 0.3);
175
-
176
- .dark & {
177
- background: linear-gradient(135deg,
178
- rgba(139, 92, 246, 0.9) 0%,
179
- rgba(99, 102, 241, 0.9) 50%,
180
- rgba(59, 130, 246, 0.9) 100%);
181
- background-clip: text;
182
- -webkit-background-clip: text;
183
- -webkit-text-fill-color: transparent;
184
- text-shadow: 0 4px 20px rgba(139, 92, 246, 0.4);
185
- }
186
- }
187
-
188
- &-Title {
189
- font-size: 1.5rem;
190
- line-height: 2rem;
191
- font-weight: 600;
192
- color: #111827;
193
- margin-bottom: 0.75rem;
194
-
195
- .dark & {
196
- color: #f3f4f6;
197
- }
198
- }
199
-
200
- &-Description {
201
- color: #4b5563;
202
- margin-bottom: 2.5rem;
203
- line-height: 1.625;
204
- max-width: 28rem;
205
- margin-left: auto;
206
- margin-right: auto;
207
-
208
- .dark & {
209
- color: #d1d5db;
210
- }
211
- }
212
-
213
- &-Actions {
214
- display: flex;
215
- flex-direction: column;
216
- gap: 1rem;
217
- justify-content: center;
218
- margin-bottom: 2.5rem;
219
-
220
- @media (min-width: 640px) {
221
- flex-direction: row;
222
- }
223
- }
224
-
225
- &-HomeButton,
226
- &-BackButton {
227
- padding-left: 1.5rem;
228
- padding-right: 1.5rem;
229
- padding-top: 0.75rem;
230
- padding-bottom: 0.75rem;
231
- font-weight: 500;
232
- transition-property: all;
233
- transition-duration: 300ms;
234
- min-width: 140px;
235
-
236
- &:hover {
237
- transform: translateY(-2px);
238
- box-shadow: 0 8px 25px rgba(59, 130, 246, 0.25);
239
- }
240
-
241
- &:active {
242
- transform: translateY(0);
243
- }
244
- }
245
-
246
- &-BackButton {
247
- &:hover {
248
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
249
-
250
- .dark & {
251
- box-shadow: 0 8px 25px rgba(255, 255, 255, 0.1);
252
- }
253
- }
254
- }
255
-
256
- &-Help {
257
- font-size: 0.875rem;
258
- line-height: 1.25rem;
259
- }
260
-
261
- &-HelpText {
262
- color: #6b7280;
263
- line-height: 1.625;
264
-
265
- .dark & {
266
- color: #9ca3af;
267
- }
268
- }
269
- }
270
-
271
- @media (max-width: 640px) {
272
- .ErrorNotFound {
273
- &-Container {
274
- padding-left: 1.5rem;
275
- padding-right: 1.5rem;
276
- }
277
-
278
- &-Code {
279
- font-size: 3.75rem;
280
- line-height: 1;
281
- }
282
-
283
- &-Title {
284
- font-size: 1.25rem;
285
- line-height: 1.75rem;
286
- }
287
-
288
- &-Actions {
289
- .q-btn {
290
- width: 100%;
291
- }
292
- }
293
-
294
- &-Glow {
295
- width: 200px;
296
- height: 200px;
297
- }
298
- }
299
- }
300
- </style>
@@ -1,7 +0,0 @@
1
- /**
2
- * Core Pages
3
- *
4
- * Reusable page components for the Quvel UI framework
5
- */
6
- export { default as ErrorNotFound } from './ErrorNotFound.vue';
7
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pages/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1,6 +0,0 @@
1
- /**
2
- * Core Pages
3
- *
4
- * Reusable page components for the Quvel UI framework
5
- */
6
- export { default as ErrorNotFound } from './ErrorNotFound.vue';