@mongoosejs/studio 0.3.5 → 0.3.7
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/backend/actions/ChatThread/createChatMessage.js +12 -2
- package/backend/actions/ChatThread/streamChatMessage.js +12 -2
- package/backend/actions/Model/createChatMessage.js +12 -2
- package/backend/actions/Model/streamChatMessage.js +13 -2
- package/backend/actions/Task/getTasksOverTime.js +66 -0
- package/backend/actions/Task/index.js +1 -0
- package/frontend/public/app.js +809 -110
- package/frontend/public/tw.css +19 -16
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat.html +19 -12
- package/frontend/src/chat/chat.js +9 -3
- package/frontend/src/create-document/create-document.js +3 -1
- package/frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.html +2 -2
- package/frontend/src/dashboard-result/dashboard-primitive/dashboard-primitive.js +13 -1
- package/frontend/src/dashboard-result/dashboard-result.html +1 -1
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.html +21 -1
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.js +52 -0
- package/frontend/src/detail-date/detail-date.html +1 -0
- package/frontend/src/detail-date/detail-date.js +123 -0
- package/frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.html +26 -0
- package/frontend/src/document-details/date-view-mode-picker/date-view-mode-picker.js +41 -0
- package/frontend/src/document-details/document-property/document-property.html +13 -5
- package/frontend/src/document-details/document-property/document-property.js +14 -1
- package/frontend/src/getCurrentDateTimeContext.js +17 -0
- package/frontend/src/list-json/list-json.js +0 -7
- package/frontend/src/modal/modal.js +25 -1
- package/frontend/src/models/document-search/document-search.js +3 -0
- package/frontend/src/models/models.html +25 -5
- package/frontend/src/models/models.js +1 -1
- package/frontend/src/navbar/navbar.html +6 -1
- package/frontend/src/navbar/navbar.js +1 -6
- package/frontend/src/pro-upgrade-modal/pro-upgrade-modal.html +38 -0
- package/frontend/src/pro-upgrade-modal/pro-upgrade-modal.js +23 -0
- package/frontend/src/tasks/tasks.html +34 -20
- package/frontend/src/tasks/tasks.js +158 -5
- package/local.js +2 -1
- package/package.json +2 -1
package/frontend/public/tw.css
CHANGED
|
@@ -719,10 +719,6 @@ video {
|
|
|
719
719
|
right: 0.5rem;
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
.right-4 {
|
|
723
|
-
right: 1rem;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
722
|
.top-0 {
|
|
727
723
|
top: 0px;
|
|
728
724
|
}
|
|
@@ -1224,6 +1220,10 @@ video {
|
|
|
1224
1220
|
width: 1rem;
|
|
1225
1221
|
}
|
|
1226
1222
|
|
|
1223
|
+
.w-40 {
|
|
1224
|
+
width: 10rem;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
1227
|
.w-44 {
|
|
1228
1228
|
width: 11rem;
|
|
1229
1229
|
}
|
|
@@ -1240,6 +1240,10 @@ video {
|
|
|
1240
1240
|
width: 13rem;
|
|
1241
1241
|
}
|
|
1242
1242
|
|
|
1243
|
+
.w-56 {
|
|
1244
|
+
width: 14rem;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1243
1247
|
.w-6 {
|
|
1244
1248
|
width: 1.5rem;
|
|
1245
1249
|
}
|
|
@@ -3064,14 +3068,6 @@ video {
|
|
|
3064
3068
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
3065
3069
|
}
|
|
3066
3070
|
|
|
3067
|
-
.hover\:rounded-lg:hover {
|
|
3068
|
-
border-radius: 0.5rem;
|
|
3069
|
-
}
|
|
3070
|
-
|
|
3071
|
-
.hover\:border:hover {
|
|
3072
|
-
border-width: 1px;
|
|
3073
|
-
}
|
|
3074
|
-
|
|
3075
3071
|
.hover\:border-edge-strong:hover {
|
|
3076
3072
|
border-color: var(--color-edge-strong);
|
|
3077
3073
|
}
|
|
@@ -3328,6 +3324,11 @@ video {
|
|
|
3328
3324
|
color: var(--color-primary);
|
|
3329
3325
|
}
|
|
3330
3326
|
|
|
3327
|
+
.hover\:text-red-900:hover {
|
|
3328
|
+
--tw-text-opacity: 1;
|
|
3329
|
+
color: rgb(127 29 29 / var(--tw-text-opacity));
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3331
3332
|
.hover\:text-slate-700:hover {
|
|
3332
3333
|
--tw-text-opacity: 1;
|
|
3333
3334
|
color: rgb(51 65 85 / var(--tw-text-opacity));
|
|
@@ -3616,10 +3617,6 @@ video {
|
|
|
3616
3617
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
3617
3618
|
}
|
|
3618
3619
|
|
|
3619
|
-
.group:hover .group-hover\:text-content-secondary {
|
|
3620
|
-
color: var(--color-content-secondary);
|
|
3621
|
-
}
|
|
3622
|
-
|
|
3623
3620
|
.group:hover .group-hover\:text-primary {
|
|
3624
3621
|
color: var(--color-primary);
|
|
3625
3622
|
}
|
|
@@ -3795,6 +3792,12 @@ video {
|
|
|
3795
3792
|
}
|
|
3796
3793
|
}
|
|
3797
3794
|
|
|
3795
|
+
@media (min-width: 1280px) {
|
|
3796
|
+
.xl\:grid-cols-3 {
|
|
3797
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3798
3801
|
:is(:where(.dark) .dark\:bg-shark-800) {
|
|
3799
3802
|
--tw-bg-opacity: 1;
|
|
3800
3803
|
background-color: rgb(33 37 41 / var(--tw-bg-opacity));
|
package/frontend/src/api.js
CHANGED
|
@@ -191,6 +191,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
191
191
|
getTaskOverview: function getTaskOverview(params) {
|
|
192
192
|
return client.post('', { action: 'Task.getTaskOverview', ...params }).then(res => res.data);
|
|
193
193
|
},
|
|
194
|
+
getTasksOverTime: function getTasksOverTime(params) {
|
|
195
|
+
return client.post('', { action: 'Task.getTasksOverTime', ...params }).then(res => res.data);
|
|
196
|
+
},
|
|
194
197
|
rescheduleTask: function rescheduleTask(params) {
|
|
195
198
|
return client.post('', { action: 'Task.rescheduleTask', ...params }).then(res => res.data);
|
|
196
199
|
},
|
|
@@ -531,6 +534,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
531
534
|
getTaskOverview: function getTaskOverview(params) {
|
|
532
535
|
return client.post('/Task/getTaskOverview', params).then(res => res.data);
|
|
533
536
|
},
|
|
537
|
+
getTasksOverTime: function getTasksOverTime(params) {
|
|
538
|
+
return client.post('/Task/getTasksOverTime', params).then(res => res.data);
|
|
539
|
+
},
|
|
534
540
|
rescheduleTask: function rescheduleTask(params) {
|
|
535
541
|
return client.post('/Task/rescheduleTask', params).then(res => res.data);
|
|
536
542
|
},
|
|
@@ -10,17 +10,6 @@
|
|
|
10
10
|
<path stroke-linecap="round" stroke-linejoin="round" d="m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5" />
|
|
11
11
|
</svg>
|
|
12
12
|
</button>
|
|
13
|
-
<button
|
|
14
|
-
class="fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-surface"
|
|
15
|
-
:class="hasWorkspace ? 'text-content-secondary hover:bg-muted' : 'text-gray-300 cursor-not-allowed bg-page'"
|
|
16
|
-
@click="toggleShareThread"
|
|
17
|
-
:disabled="!hasWorkspace || !chatThreadId || sharingThread"
|
|
18
|
-
aria-label="Share thread with workspace"
|
|
19
|
-
title="Share thread with workspace"
|
|
20
|
-
>
|
|
21
|
-
<svg v-if="hasWorkspace" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z"/></svg>
|
|
22
|
-
<svg v-else xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z"/></svg>
|
|
23
|
-
</button>
|
|
24
13
|
<!-- Sidebar: Chat Threads -->
|
|
25
14
|
<aside
|
|
26
15
|
class="bg-page border-r overflow-hidden h-full transition-all duration-300 ease-in-out z-20 w-64 fixed lg:relative shrink-0 flex flex-col"
|
|
@@ -84,6 +73,18 @@
|
|
|
84
73
|
<path stroke-linecap="round" stroke-linejoin="round" d="m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5" />
|
|
85
74
|
</svg>
|
|
86
75
|
</button>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
@click="hasWorkspace ? toggleShareThread() : (showProUpgradeModal = true)"
|
|
79
|
+
class="rounded p-1.5"
|
|
80
|
+
:class="hasWorkspace && chatThreadId ? 'text-gray-400 hover:text-gray-600 hover:bg-muted' : !hasWorkspace ? 'text-gray-400 hover:text-gray-600 hover:bg-muted' : 'text-gray-300 cursor-not-allowed'"
|
|
81
|
+
:disabled="hasWorkspace && !chatThreadId"
|
|
82
|
+
aria-label="Share thread with workspace"
|
|
83
|
+
:title="'Share thread with workspace' + (!hasWorkspace ? ' (requires a pro workspace)' : !chatThreadId ? ': Open a thread first!' : '')"
|
|
84
|
+
>
|
|
85
|
+
<svg v-if="hasWorkspace" xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z"/></svg>
|
|
86
|
+
<svg v-else xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z"/></svg>
|
|
87
|
+
</button>
|
|
87
88
|
</div>
|
|
88
89
|
</aside>
|
|
89
90
|
|
|
@@ -118,7 +119,7 @@
|
|
|
118
119
|
@input="adjustTextareaHeight"
|
|
119
120
|
@keydown.enter.exact.prevent="handleEnter"
|
|
120
121
|
></textarea>
|
|
121
|
-
<button class="bg-
|
|
122
|
+
<button class="bg-primary hover:bg-primary-hover text-white px-4 h-[42px] rounded disabled:bg-gray-600" :disabled="sendingMessage">
|
|
122
123
|
<svg v-if="sendingMessage" style="height: 1em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
123
124
|
<g>
|
|
124
125
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" opacity="0.3" />
|
|
@@ -132,4 +133,10 @@
|
|
|
132
133
|
</form>
|
|
133
134
|
</div>
|
|
134
135
|
</main>
|
|
136
|
+
|
|
137
|
+
<pro-upgrade-modal
|
|
138
|
+
:show="showProUpgradeModal"
|
|
139
|
+
feature-description="Sharing threads lets you collaborate with your team by sharing chat threads within your workspace."
|
|
140
|
+
@close="showProUpgradeModal = false"
|
|
141
|
+
></pro-upgrade-modal>
|
|
135
142
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
|
+
const getCurrentDateTimeContext = require('../getCurrentDateTimeContext');
|
|
4
5
|
const template = require('./chat.html');
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
@@ -15,7 +16,8 @@ module.exports = {
|
|
|
15
16
|
chatMessages: [],
|
|
16
17
|
hideSidebar: null,
|
|
17
18
|
sharingThread: false,
|
|
18
|
-
threadSearch: ''
|
|
19
|
+
threadSearch: '',
|
|
20
|
+
showProUpgradeModal: false
|
|
19
21
|
}),
|
|
20
22
|
methods: {
|
|
21
23
|
async sendMessage() {
|
|
@@ -44,7 +46,11 @@ module.exports = {
|
|
|
44
46
|
}
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
const params = {
|
|
49
|
+
const params = {
|
|
50
|
+
chatThreadId: this.chatThreadId,
|
|
51
|
+
content,
|
|
52
|
+
currentDateTime: getCurrentDateTimeContext()
|
|
53
|
+
};
|
|
48
54
|
let userChatMessage = null;
|
|
49
55
|
let assistantChatMessage = null;
|
|
50
56
|
for await (const event of api.ChatThread.streamChatMessage(params)) {
|
|
@@ -151,7 +157,7 @@ module.exports = {
|
|
|
151
157
|
},
|
|
152
158
|
async toggleShareThread() {
|
|
153
159
|
if (!this.chatThreadId || !this.hasWorkspace) {
|
|
154
|
-
|
|
160
|
+
throw new Error('Cannot share thread: chatThreadId or hasWorkspace is missing');
|
|
155
161
|
}
|
|
156
162
|
this.sharingThread = true;
|
|
157
163
|
try {
|
|
@@ -11,6 +11,7 @@ const ObjectId = new Proxy(BSON.ObjectId, {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const appendCSS = require('../appendCSS');
|
|
14
|
+
const getCurrentDateTimeContext = require('../getCurrentDateTimeContext');
|
|
14
15
|
|
|
15
16
|
appendCSS(require('./create-document.css'));
|
|
16
17
|
|
|
@@ -49,7 +50,8 @@ module.exports = app => app.component('create-document', {
|
|
|
49
50
|
for await (const event of api.Model.streamChatMessage({
|
|
50
51
|
model: this.currentModel,
|
|
51
52
|
content: prompt,
|
|
52
|
-
documentData: this.aiOriginalDocument
|
|
53
|
+
documentData: this.aiOriginalDocument,
|
|
54
|
+
currentDateTime: getCurrentDateTimeContext()
|
|
53
55
|
})) {
|
|
54
56
|
if (event?.textPart) {
|
|
55
57
|
this.aiSuggestion += event.textPart;
|
|
@@ -9,16 +9,28 @@ module.exports = app => app.component('dashboard-primitive', {
|
|
|
9
9
|
props: ['value'],
|
|
10
10
|
computed: {
|
|
11
11
|
header() {
|
|
12
|
-
if (this.value != null && this.value.$primitive
|
|
12
|
+
if (this.value != null && this.value.$primitive?.header) {
|
|
13
13
|
return this.value.$primitive.header;
|
|
14
14
|
}
|
|
15
15
|
return null;
|
|
16
16
|
},
|
|
17
17
|
displayValue() {
|
|
18
18
|
if (this.value != null && this.value.$primitive) {
|
|
19
|
+
if (this.value.$primitive.value === null) {
|
|
20
|
+
return 'null';
|
|
21
|
+
}
|
|
19
22
|
return this.value.$primitive.value;
|
|
20
23
|
}
|
|
24
|
+
if (this.value === null) {
|
|
25
|
+
return 'null';
|
|
26
|
+
}
|
|
21
27
|
return this.value;
|
|
28
|
+
},
|
|
29
|
+
displayClass() {
|
|
30
|
+
if (this.value == null) {
|
|
31
|
+
return 'text-content-tertiary';
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
22
34
|
}
|
|
23
35
|
}
|
|
24
36
|
});
|
|
@@ -7,7 +7,27 @@
|
|
|
7
7
|
:key="'column-' + index"
|
|
8
8
|
class="bg-slate-50 p-3 text-left text-sm font-semibold text-content border-b border-edge"
|
|
9
9
|
>
|
|
10
|
-
|
|
10
|
+
<div v-if="index === columns.length - 1" class="relative flex items-center gap-2 w-full" ref="dropdown">
|
|
11
|
+
<span class="min-w-0 flex-1">{{ column }}</span>
|
|
12
|
+
<button
|
|
13
|
+
@click.stop="toggleDropdown"
|
|
14
|
+
class="ml-auto rounded p-1 text-content-secondary hover:bg-muted hover:text-content"
|
|
15
|
+
aria-label="Table actions">
|
|
16
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
17
|
+
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z" />
|
|
18
|
+
</svg>
|
|
19
|
+
</button>
|
|
20
|
+
<div
|
|
21
|
+
v-if="showDropdown"
|
|
22
|
+
class="absolute right-0 top-full z-10 mt-2 w-56 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5">
|
|
23
|
+
<button
|
|
24
|
+
class="block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted"
|
|
25
|
+
@click="downloadCsv(); showDropdown = false">
|
|
26
|
+
Download as CSV
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<template v-else>{{ column }}</template>
|
|
11
31
|
</th>
|
|
12
32
|
</tr>
|
|
13
33
|
</thead>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* global Blob, URL, document */
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
const template = require('./dashboard-table.html');
|
|
@@ -5,6 +6,11 @@ const template = require('./dashboard-table.html');
|
|
|
5
6
|
module.exports = app => app.component('dashboard-table', {
|
|
6
7
|
template,
|
|
7
8
|
props: ['value'],
|
|
9
|
+
data() {
|
|
10
|
+
return {
|
|
11
|
+
showDropdown: false
|
|
12
|
+
};
|
|
13
|
+
},
|
|
8
14
|
computed: {
|
|
9
15
|
columns() {
|
|
10
16
|
return Array.isArray(this.value?.$table?.columns) ? this.value.$table.columns : [];
|
|
@@ -20,6 +26,46 @@ module.exports = app => app.component('dashboard-table', {
|
|
|
20
26
|
}
|
|
21
27
|
},
|
|
22
28
|
methods: {
|
|
29
|
+
toggleDropdown() {
|
|
30
|
+
this.showDropdown = !this.showDropdown;
|
|
31
|
+
},
|
|
32
|
+
handleBodyClick(event) {
|
|
33
|
+
const dropdownRefs = this.$refs.dropdown;
|
|
34
|
+
const dropdowns = Array.isArray(dropdownRefs) ? dropdownRefs : [dropdownRefs];
|
|
35
|
+
const hasClickInsideDropdown = dropdowns
|
|
36
|
+
.filter(dropdown => dropdown && typeof dropdown.contains === 'function')
|
|
37
|
+
.some(dropdown => dropdown.contains(event.target));
|
|
38
|
+
|
|
39
|
+
if (!hasClickInsideDropdown) {
|
|
40
|
+
this.showDropdown = false;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
neutralizeCsvCell(cell) {
|
|
44
|
+
const value = this.displayValue(cell);
|
|
45
|
+
return /^\s*[=+\-@]/.test(value) ? `'${value}` : value;
|
|
46
|
+
},
|
|
47
|
+
escapeCsvCell(cell) {
|
|
48
|
+
const escapedCell = this.neutralizeCsvCell(cell).replaceAll('"', '""');
|
|
49
|
+
return `"${escapedCell}"`;
|
|
50
|
+
},
|
|
51
|
+
downloadCsv() {
|
|
52
|
+
const header = this.columns.map(this.escapeCsvCell).join(',');
|
|
53
|
+
const rows = this.rows
|
|
54
|
+
.map(row => row.map(this.escapeCsvCell).join(','))
|
|
55
|
+
.join('\n');
|
|
56
|
+
|
|
57
|
+
const csv = [header, rows].filter(v => v.length > 0).join('\n');
|
|
58
|
+
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
59
|
+
const url = URL.createObjectURL(blob);
|
|
60
|
+
const anchor = document.createElement('a');
|
|
61
|
+
anchor.href = url;
|
|
62
|
+
anchor.download = 'table.csv';
|
|
63
|
+
document.body.appendChild(anchor);
|
|
64
|
+
anchor.click();
|
|
65
|
+
document.body.removeChild(anchor);
|
|
66
|
+
URL.revokeObjectURL(url);
|
|
67
|
+
this.$toast.success('CSV downloaded!');
|
|
68
|
+
},
|
|
23
69
|
displayValue(cell) {
|
|
24
70
|
if (cell == null) {
|
|
25
71
|
return '';
|
|
@@ -33,5 +79,11 @@ module.exports = app => app.component('dashboard-table', {
|
|
|
33
79
|
}
|
|
34
80
|
return String(cell);
|
|
35
81
|
}
|
|
82
|
+
},
|
|
83
|
+
mounted() {
|
|
84
|
+
document.body.addEventListener('click', this.handleBodyClick);
|
|
85
|
+
},
|
|
86
|
+
unmounted() {
|
|
87
|
+
document.body.removeEventListener('click', this.handleBodyClick);
|
|
36
88
|
}
|
|
37
89
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<pre class="w-full whitespace-pre-wrap break-words font-mono text-sm text-content-secondary m-0">{{displayValue}}</pre>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./detail-date.html');
|
|
4
|
+
|
|
5
|
+
module.exports = app => app.component('detail-date', {
|
|
6
|
+
template,
|
|
7
|
+
props: ['value', 'viewMode'],
|
|
8
|
+
emits: ['updated'],
|
|
9
|
+
watch: {
|
|
10
|
+
displayValue: {
|
|
11
|
+
immediate: true,
|
|
12
|
+
handler(val) {
|
|
13
|
+
this.$emit('updated', val);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
computed: {
|
|
18
|
+
format() {
|
|
19
|
+
if (this.viewMode != null && typeof this.viewMode === 'object') {
|
|
20
|
+
return this.viewMode.format;
|
|
21
|
+
}
|
|
22
|
+
return this.viewMode;
|
|
23
|
+
},
|
|
24
|
+
timezone() {
|
|
25
|
+
if (this.viewMode != null && typeof this.viewMode === 'object') {
|
|
26
|
+
return this.viewMode.timezone || '';
|
|
27
|
+
}
|
|
28
|
+
return '';
|
|
29
|
+
},
|
|
30
|
+
parsedDate() {
|
|
31
|
+
if (this.value == null) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const date = new Date(this.value);
|
|
35
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
36
|
+
},
|
|
37
|
+
displayValue() {
|
|
38
|
+
if (this.value == null) {
|
|
39
|
+
return String(this.value);
|
|
40
|
+
}
|
|
41
|
+
if (!this.parsedDate) {
|
|
42
|
+
return 'Invalid Date';
|
|
43
|
+
}
|
|
44
|
+
return this.formatDateForDisplay(this.parsedDate);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
methods: {
|
|
48
|
+
formatDateForDisplay(date) {
|
|
49
|
+
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
|
50
|
+
return 'Invalid Date';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (this.format === 'utc_iso') {
|
|
54
|
+
return date.toISOString();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (this.format === 'local_browser') {
|
|
58
|
+
return date.toLocaleString();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (this.format === 'unix_ms') {
|
|
62
|
+
return String(date.getTime());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (this.format === 'unix_seconds') {
|
|
66
|
+
return String(Math.floor(date.getTime() / 1000));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.format === 'duration_relative') {
|
|
70
|
+
return this.formatRelativeDuration(date);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (this.format === 'custom_tz') {
|
|
74
|
+
return this.formatCustomTimezone(date);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return date.toISOString();
|
|
78
|
+
},
|
|
79
|
+
formatRelativeDuration(date) {
|
|
80
|
+
const diffMs = date.getTime() - Date.now();
|
|
81
|
+
const absMs = Math.abs(diffMs);
|
|
82
|
+
const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
|
|
83
|
+
const units = [
|
|
84
|
+
{ unit: 'year', ms: 365 * 24 * 60 * 60 * 1000 },
|
|
85
|
+
{ unit: 'month', ms: 30 * 24 * 60 * 60 * 1000 },
|
|
86
|
+
{ unit: 'day', ms: 24 * 60 * 60 * 1000 },
|
|
87
|
+
{ unit: 'hour', ms: 60 * 60 * 1000 },
|
|
88
|
+
{ unit: 'minute', ms: 60 * 1000 },
|
|
89
|
+
{ unit: 'second', ms: 1000 }
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
for (const { unit, ms } of units) {
|
|
93
|
+
if (absMs >= ms || unit === 'second') {
|
|
94
|
+
const value = Math.round(diffMs / ms);
|
|
95
|
+
return rtf.format(value, unit);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return 'now';
|
|
100
|
+
},
|
|
101
|
+
formatCustomTimezone(date) {
|
|
102
|
+
const tz = (this.timezone || '').trim();
|
|
103
|
+
if (!tz) {
|
|
104
|
+
return `${date.toISOString()} (enter an IANA timezone)`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
109
|
+
timeZone: tz,
|
|
110
|
+
year: 'numeric',
|
|
111
|
+
month: '2-digit',
|
|
112
|
+
day: '2-digit',
|
|
113
|
+
hour: '2-digit',
|
|
114
|
+
minute: '2-digit',
|
|
115
|
+
second: '2-digit',
|
|
116
|
+
timeZoneName: 'short'
|
|
117
|
+
}).format(date);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return `Invalid timezone: ${tz}`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div class="flex items-center gap-2" @click.stop>
|
|
2
|
+
<select
|
|
3
|
+
:value="format"
|
|
4
|
+
@input="onFormatChange($event.target.value)"
|
|
5
|
+
class="text-xs border border-edge rounded-md bg-surface px-2 py-1"
|
|
6
|
+
>
|
|
7
|
+
<option value="utc_iso">UTC (ISO)</option>
|
|
8
|
+
<option value="local_browser">Local (Browser)</option>
|
|
9
|
+
<option value="unix_ms">Unix (ms)</option>
|
|
10
|
+
<option value="unix_seconds">Unix (seconds)</option>
|
|
11
|
+
<option value="duration_relative">Duration relative to now</option>
|
|
12
|
+
<option value="custom_tz">Custom TZ...</option>
|
|
13
|
+
</select>
|
|
14
|
+
<input
|
|
15
|
+
v-if="format === 'custom_tz'"
|
|
16
|
+
:value="timezone"
|
|
17
|
+
@input="onTimezoneChange($event.target.value.trim())"
|
|
18
|
+
:list="timezoneDatalistId"
|
|
19
|
+
type="text"
|
|
20
|
+
placeholder="America/New_York"
|
|
21
|
+
class="text-xs border border-edge rounded-md bg-surface px-2 py-1 w-40"
|
|
22
|
+
>
|
|
23
|
+
<datalist v-if="format === 'custom_tz'" :id="timezoneDatalistId">
|
|
24
|
+
<option v-for="tz in timezones" :key="tz" :value="tz"></option>
|
|
25
|
+
</datalist>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./date-view-mode-picker.html');
|
|
4
|
+
|
|
5
|
+
module.exports = app => app.component('date-view-mode-picker', {
|
|
6
|
+
template,
|
|
7
|
+
props: ['viewMode', 'path'],
|
|
8
|
+
emits: ['update:viewMode'],
|
|
9
|
+
computed: {
|
|
10
|
+
format() {
|
|
11
|
+
if (this.viewMode != null && typeof this.viewMode === 'object') {
|
|
12
|
+
return this.viewMode.format;
|
|
13
|
+
}
|
|
14
|
+
return this.viewMode;
|
|
15
|
+
},
|
|
16
|
+
timezone() {
|
|
17
|
+
if (this.viewMode != null && typeof this.viewMode === 'object') {
|
|
18
|
+
return this.viewMode.timezone || '';
|
|
19
|
+
}
|
|
20
|
+
return '';
|
|
21
|
+
},
|
|
22
|
+
timezoneDatalistId() {
|
|
23
|
+
return `timezone-options-${String(this.path?.path || '').replace(/[^a-zA-Z0-9_-]/g, '-')}`;
|
|
24
|
+
},
|
|
25
|
+
timezones() {
|
|
26
|
+
return Intl.supportedValuesOf('timeZone');
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
methods: {
|
|
30
|
+
onFormatChange(newFormat) {
|
|
31
|
+
if (newFormat === 'custom_tz') {
|
|
32
|
+
this.$emit('update:viewMode', { format: 'custom_tz', timezone: this.timezone });
|
|
33
|
+
} else {
|
|
34
|
+
this.$emit('update:viewMode', newFormat);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
onTimezoneChange(newTimezone) {
|
|
38
|
+
this.$emit('update:viewMode', { format: 'custom_tz', timezone: newTimezone });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -70,6 +70,12 @@
|
|
|
70
70
|
</div>
|
|
71
71
|
</div>
|
|
72
72
|
<div class="flex items-center gap-2">
|
|
73
|
+
<date-view-mode-picker
|
|
74
|
+
v-if="isDatePath"
|
|
75
|
+
:viewMode="dateViewMode"
|
|
76
|
+
:path="path"
|
|
77
|
+
@update:viewMode="dateViewMode = $event"
|
|
78
|
+
></date-view-mode-picker>
|
|
73
79
|
<button
|
|
74
80
|
type="button"
|
|
75
81
|
class="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-edge-strong bg-surface"
|
|
@@ -125,7 +131,7 @@
|
|
|
125
131
|
v-if="isGeoJsonGeometry"
|
|
126
132
|
:is="getComponentForPath(path)"
|
|
127
133
|
:value="getEditValueForPath(path)"
|
|
128
|
-
:
|
|
134
|
+
:viewMode="detailViewMode"
|
|
129
135
|
:on-change="handleInputChange"
|
|
130
136
|
>
|
|
131
137
|
</component>
|
|
@@ -195,8 +201,9 @@
|
|
|
195
201
|
<div v-else-if="needsTruncation && isValueExpanded" class="relative">
|
|
196
202
|
<component
|
|
197
203
|
:is="getComponentForPath(path)"
|
|
198
|
-
:value="
|
|
199
|
-
:
|
|
204
|
+
:value="rawValue"
|
|
205
|
+
:viewMode="isDatePath ? dateViewMode : detailViewMode"
|
|
206
|
+
@updated="renderedValue = $event"></component>
|
|
200
207
|
<button
|
|
201
208
|
@click="toggleValueExpansion"
|
|
202
209
|
class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
|
|
@@ -211,8 +218,9 @@
|
|
|
211
218
|
<div v-else>
|
|
212
219
|
<component
|
|
213
220
|
:is="getComponentForPath(path)"
|
|
214
|
-
:value="
|
|
215
|
-
:
|
|
221
|
+
:value="rawValue"
|
|
222
|
+
:viewMode="isDatePath ? dateViewMode : detailViewMode"
|
|
223
|
+
@updated="renderedValue = $event"></component>
|
|
216
224
|
</div>
|
|
217
225
|
</div>
|
|
218
226
|
</div>
|
|
@@ -10,11 +10,15 @@ const appendCSS = require('../../appendCSS');
|
|
|
10
10
|
|
|
11
11
|
appendCSS(require('./document-property.css'));
|
|
12
12
|
|
|
13
|
+
const UNSET = Symbol('unset');
|
|
14
|
+
|
|
13
15
|
module.exports = app => app.component('document-property', {
|
|
14
16
|
template,
|
|
15
17
|
data: function() {
|
|
16
18
|
return {
|
|
17
19
|
dateType: 'picker', // picker, iso
|
|
20
|
+
dateViewMode: 'utc_iso',
|
|
21
|
+
renderedValue: UNSET,
|
|
18
22
|
isCollapsed: false, // Start uncollapsed by default
|
|
19
23
|
isValueExpanded: false, // Track if the value is expanded
|
|
20
24
|
detailViewMode: 'text',
|
|
@@ -31,8 +35,14 @@ module.exports = app => app.component('document-property', {
|
|
|
31
35
|
},
|
|
32
36
|
props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
|
|
33
37
|
computed: {
|
|
38
|
+
isDatePath() {
|
|
39
|
+
return this.path?.instance === 'Date';
|
|
40
|
+
},
|
|
41
|
+
rawValue() {
|
|
42
|
+
return this.getValueForPath(this.path.path);
|
|
43
|
+
},
|
|
34
44
|
valueAsString() {
|
|
35
|
-
const value = this.
|
|
45
|
+
const value = this.renderedValue !== UNSET ? this.renderedValue : this.rawValue;
|
|
36
46
|
if (value == null) {
|
|
37
47
|
return String(value);
|
|
38
48
|
}
|
|
@@ -164,6 +174,9 @@ module.exports = app => app.component('document-property', {
|
|
|
164
174
|
if (schemaPath.instance === 'Array') {
|
|
165
175
|
return 'detail-array';
|
|
166
176
|
}
|
|
177
|
+
if (schemaPath.instance === 'Date') {
|
|
178
|
+
return 'detail-date';
|
|
179
|
+
}
|
|
167
180
|
return 'detail-default';
|
|
168
181
|
},
|
|
169
182
|
getEditComponentForPath(path) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const time = require('time-commando');
|
|
4
|
+
|
|
5
|
+
module.exports = function getCurrentDateTimeContext() {
|
|
6
|
+
const date = time.now();
|
|
7
|
+
const components = [
|
|
8
|
+
date.getFullYear(),
|
|
9
|
+
date.getMonth() + 1,
|
|
10
|
+
date.getDate(),
|
|
11
|
+
date.getHours(),
|
|
12
|
+
date.getMinutes(),
|
|
13
|
+
date.getSeconds()
|
|
14
|
+
].map(num => num.toString().padStart(2, '0'));
|
|
15
|
+
const [yyyy, mm, dd, hh, mi, ss] = components;
|
|
16
|
+
return `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}`;
|
|
17
|
+
};
|