@mongoosejs/studio 0.1.20 → 0.2.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.
Files changed (34) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +4 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +28 -22
  3. package/backend/helpers/evaluateFilter.js +38 -1
  4. package/express.js +4 -2
  5. package/frontend/public/app.js +702 -428
  6. package/frontend/public/index.html +1 -1
  7. package/frontend/public/style.css +1 -1
  8. package/frontend/public/tw.css +81 -62
  9. package/frontend/src/_util/document-search-autocomplete.js +229 -0
  10. package/frontend/src/chat/chat-message-script/chat-message-script.html +27 -20
  11. package/frontend/src/chat/chat.html +20 -17
  12. package/frontend/src/chat/chat.js +2 -0
  13. package/frontend/src/document/document.css +1 -8
  14. package/frontend/src/document/document.html +202 -164
  15. package/frontend/src/document/document.js +1 -0
  16. package/frontend/src/document-details/document-details.html +1 -11
  17. package/frontend/src/document-details/document-details.js +43 -1
  18. package/frontend/src/document-details/document-property/document-property.html +4 -4
  19. package/frontend/src/index.js +36 -15
  20. package/frontend/src/json-node/json-node.html +118 -0
  21. package/frontend/src/json-node/json-node.js +272 -0
  22. package/frontend/src/list-array/list-array.html +15 -3
  23. package/frontend/src/list-array/list-array.js +21 -3
  24. package/frontend/src/list-default/list-default.js +2 -2
  25. package/frontend/src/list-json/list-json.html +1 -1
  26. package/frontend/src/list-json/list-json.js +11 -273
  27. package/frontend/src/list-subdocument/list-subdocument.html +13 -4
  28. package/frontend/src/list-subdocument/list-subdocument.js +11 -6
  29. package/frontend/src/models/document-search/document-search.html +1 -1
  30. package/frontend/src/models/document-search/document-search.js +22 -116
  31. package/frontend/src/models/models.css +5 -15
  32. package/frontend/src/models/models.html +34 -34
  33. package/frontend/src/navbar/navbar.html +15 -6
  34. package/package.json +2 -2
@@ -85,42 +85,63 @@ app.component('app-component', {
85
85
  if (hashParams.has('code')) {
86
86
  const code = hashParams.get('code');
87
87
  const provider = hashParams.get('provider');
88
+
89
+ let user;
90
+ let accessToken;
91
+ let roles;
88
92
  try {
89
- const { accessToken, user, roles } = provider === 'github' ? await mothership.github(code) : await mothership.google(code);
93
+ ({ accessToken, user, roles } = provider === 'github' ? await mothership.github(code) : await mothership.google(code));
90
94
  if (roles == null) {
91
95
  this.authError = 'You are not authorized to access this workspace';
92
96
  this.status = 'loaded';
93
97
  return;
94
98
  }
95
- this.user = user;
96
- this.roles = roles;
97
- window.localStorage.setItem('_mongooseStudioAccessToken', accessToken._id);
98
99
  } catch (err) {
99
100
  this.authError = 'An error occurred while logging in. Please try again.';
100
101
  this.status = 'loaded';
101
102
  return;
102
- } finally {
103
- setTimeout(() => {
104
- this.$router.replace(this.$router.currentRoute.value.path);
105
- }, 0);
106
103
  }
107
104
 
108
- const { nodeEnv } = await api.status();
109
- this.nodeEnv = nodeEnv;
105
+ try {
106
+ const { nodeEnv } = await api.status();
107
+ this.nodeEnv = nodeEnv;
108
+ } catch (err) {
109
+ this.authError = 'Error connecting to Mongoose Studio API: ' + err.response?.data?.message ?? err.message;
110
+ this.status = 'loaded';
111
+ return;
112
+ }
113
+
114
+ this.user = user;
115
+ this.roles = roles;
116
+ window.localStorage.setItem('_mongooseStudioAccessToken', accessToken._id);
117
+ setTimeout(() => {
118
+ this.$router.replace(this.$router.currentRoute.value.path);
119
+ }, 0);
110
120
  } else {
111
121
  const token = window.localStorage.getItem('_mongooseStudioAccessToken');
112
122
  if (token) {
113
123
  const { user, roles } = await mothership.me();
124
+
125
+ try {
126
+ const { nodeEnv } = await api.status();
127
+ this.nodeEnv = nodeEnv;
128
+ } catch (err) {
129
+ this.authError = 'Error connecting to Mongoose Studio API: ' + (err.response?.data?.message ?? err.message);
130
+ this.status = 'loaded';
131
+ return;
132
+ }
133
+
114
134
  this.user = user;
115
135
  this.roles = roles;
116
-
117
- const { nodeEnv } = await api.status();
118
- this.nodeEnv = nodeEnv;
119
136
  }
120
137
  }
121
138
  } else {
122
- const { nodeEnv } = await api.status();
123
- this.nodeEnv = nodeEnv;
139
+ try {
140
+ const { nodeEnv } = await api.status();
141
+ this.nodeEnv = nodeEnv;
142
+ } catch (err) {
143
+ this.authError = 'Error connecting to Mongoose Studio API: ' + (err.response?.data?.message ?? err.message);
144
+ }
124
145
  }
125
146
 
126
147
  this.status = 'loaded';
@@ -0,0 +1,118 @@
1
+ <div>
2
+ <div class="flex items-baseline whitespace-pre" :style="indentStyle">
3
+ <button
4
+ v-if="showToggle"
5
+ type="button"
6
+ class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
7
+ @click.stop="handleToggle"
8
+ >
9
+ {{ isCollapsedNode ? '+' : '-' }}
10
+ </button>
11
+ <span v-else class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0"></span>
12
+ <template v-if="hasKey">
13
+ <span class="text-blue-600">"{{ nodeKey }}"</span><span>: </span>
14
+ </template>
15
+ <template v-if="isComplex">
16
+ <template v-if="hasChildren">
17
+ <span>{{ openingBracket }}</span>
18
+ <span v-if="isCollapsedNode" class="mx-1">…</span>
19
+ <span v-if="isCollapsedNode">{{ closingBracket }}{{ comma }}</span>
20
+ </template>
21
+ <template v-else>
22
+ <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>
23
+ </template>
24
+ </template>
25
+ <template v-else>
26
+ <!--
27
+ If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.
28
+ This is done via CSS ellipsis strategy.
29
+ -->
30
+ <span
31
+ v-if="shouldShowReferenceLink"
32
+ class="inline-flex items-baseline group"
33
+ >
34
+ <span
35
+ :class="[...valueClasses, 'underline', 'decoration-dotted', 'underline-offset-2']"
36
+ :style="typeof value === 'string'
37
+ ? {
38
+ display: 'inline-block',
39
+ maxWidth: '100%',
40
+ overflow: 'hidden',
41
+ textOverflow: 'ellipsis',
42
+ whiteSpace: 'nowrap',
43
+ verticalAlign: 'bottom'
44
+ }
45
+ : {}"
46
+ :title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
47
+ >
48
+ {{ formattedValue }}
49
+ </span>
50
+ <span>
51
+ {{ comma }}
52
+ </span>
53
+ <a
54
+ href="#"
55
+ class="ml-1 text-sm text-sky-700 opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity"
56
+ @click.stop.prevent="goToReference()"
57
+ >
58
+ View Document
59
+ </a>
60
+ </span>
61
+ <span
62
+ v-else
63
+ :class="valueClasses"
64
+ :style="typeof value === 'string'
65
+ ? {
66
+ display: 'inline-block',
67
+ maxWidth: '100%',
68
+ overflow: 'hidden',
69
+ textOverflow: 'ellipsis',
70
+ whiteSpace: 'nowrap',
71
+ verticalAlign: 'bottom'
72
+ }
73
+ : {}"
74
+ :title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
75
+ >
76
+ {{ formattedValue }}{{ comma }}
77
+ </span>
78
+ </template>
79
+ </div>
80
+ <template v-if="isComplex && hasChildren && !isCollapsedNode">
81
+ <json-node
82
+ v-for="child in children"
83
+ :key="child.path"
84
+ :node-key="child.displayKey"
85
+ :value="child.value"
86
+ :level="level + 1"
87
+ :is-last="child.isLast"
88
+ :path="child.path"
89
+ :toggle-collapse="toggleCollapse"
90
+ :is-collapsed="isCollapsed"
91
+ :create-child-path="createChildPath"
92
+ :indent-size="indentSize"
93
+ :max-top-level-fields="maxTopLevelFields"
94
+ :top-level-expanded="topLevelExpanded"
95
+ :expand-top-level="expandTopLevel"
96
+ :references="references"
97
+ ></json-node>
98
+ <div
99
+ v-if="hasHiddenRootChildren"
100
+ class="flex items-baseline whitespace-pre"
101
+ :style="indentStyle"
102
+ >
103
+ <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
104
+ <button
105
+ type="button"
106
+ class="text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400"
107
+ :title="hiddenChildrenTooltip"
108
+ @click.stop="handleExpandTopLevel"
109
+ >
110
+ <span aria-hidden="true">{{hiddenChildrenLabel}}…</span>
111
+ </button>
112
+ </div>
113
+ <div class="flex items-baseline whitespace-pre" :style="indentStyle">
114
+ <span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
115
+ <span>{{ closingBracket }}{{ comma }}</span>
116
+ </div>
117
+ </template>
118
+ </div>
@@ -0,0 +1,272 @@
1
+ 'use strict';
2
+
3
+ const template = require('./json-node.html');
4
+
5
+ module.exports = app => app.component('json-node', {
6
+ name: 'JsonNode',
7
+ template: template,
8
+ props: {
9
+ nodeKey: {
10
+ type: [String, Number],
11
+ default: null
12
+ },
13
+ value: {
14
+ required: true
15
+ },
16
+ level: {
17
+ type: Number,
18
+ required: true
19
+ },
20
+ isLast: {
21
+ type: Boolean,
22
+ default: false
23
+ },
24
+ path: {
25
+ type: String,
26
+ required: true
27
+ },
28
+ toggleCollapse: {
29
+ type: Function,
30
+ required: true
31
+ },
32
+ isCollapsed: {
33
+ type: Function,
34
+ required: true
35
+ },
36
+ createChildPath: {
37
+ type: Function,
38
+ required: true
39
+ },
40
+ indentSize: {
41
+ type: Number,
42
+ required: true
43
+ },
44
+ maxTopLevelFields: {
45
+ type: Number,
46
+ default: null
47
+ },
48
+ topLevelExpanded: {
49
+ type: Boolean,
50
+ default: false
51
+ },
52
+ expandTopLevel: {
53
+ type: Function,
54
+ default: null
55
+ },
56
+ references: {
57
+ type: Object,
58
+ default: () => ({})
59
+ }
60
+ },
61
+ computed: {
62
+ hasKey() {
63
+ return this.nodeKey !== null && this.nodeKey !== undefined;
64
+ },
65
+ isRoot() {
66
+ return this.path === 'root';
67
+ },
68
+ isArray() {
69
+ return Array.isArray(this.value);
70
+ },
71
+ isObject() {
72
+ if (this.value === null || this.isArray) {
73
+ return false;
74
+ }
75
+ return Object.prototype.toString.call(this.value) === '[object Object]';
76
+ },
77
+ isComplex() {
78
+ return this.isArray || this.isObject;
79
+ },
80
+ children() {
81
+ if (!this.isComplex) {
82
+ return [];
83
+ }
84
+ if (this.isArray) {
85
+ return this.value.map((childValue, index) => ({
86
+ displayKey: null,
87
+ value: childValue,
88
+ isLast: index === this.value.length - 1,
89
+ path: this.createChildPath(this.path, index, true)
90
+ }));
91
+ }
92
+ const keys = Object.keys(this.value);
93
+ const visibleKeys = this.visibleObjectKeys(keys);
94
+ const hasHidden = this.hasHiddenRootChildren;
95
+ return visibleKeys.map((key, index) => ({
96
+ displayKey: key,
97
+ value: this.value[key],
98
+ isLast: !hasHidden && index === visibleKeys.length - 1,
99
+ path: this.createChildPath(this.path, key, false)
100
+ }));
101
+ },
102
+ hasChildren() {
103
+ return this.children.length > 0;
104
+ },
105
+ totalObjectChildCount() {
106
+ if (!this.isObject) {
107
+ return 0;
108
+ }
109
+ return Object.keys(this.value).length;
110
+ },
111
+ hasHiddenRootChildren() {
112
+ if (!this.isRoot || !this.isObject) {
113
+ return false;
114
+ }
115
+ if (this.topLevelExpanded) {
116
+ return false;
117
+ }
118
+ if (typeof this.maxTopLevelFields !== 'number') {
119
+ return false;
120
+ }
121
+ return this.totalObjectChildCount > this.maxTopLevelFields;
122
+ },
123
+ hiddenRootChildrenCount() {
124
+ if (!this.hasHiddenRootChildren) {
125
+ return 0;
126
+ }
127
+ return this.totalObjectChildCount - this.maxTopLevelFields;
128
+ },
129
+ showToggle() {
130
+ return this.hasChildren && !this.isRoot;
131
+ },
132
+ openingBracket() {
133
+ return this.isArray ? '[' : '{';
134
+ },
135
+ closingBracket() {
136
+ return this.isArray ? ']' : '}';
137
+ },
138
+ isCollapsedNode() {
139
+ return this.isCollapsed(this.path);
140
+ },
141
+ formattedValue() {
142
+ if (typeof this.value === 'bigint') {
143
+ return `${this.value.toString()}n`;
144
+ }
145
+ const stringified = JSON.stringify(this.value);
146
+ if (stringified === undefined) {
147
+ if (typeof this.value === 'symbol') {
148
+ return this.value.toString();
149
+ }
150
+ return String(this.value);
151
+ }
152
+ return stringified;
153
+ },
154
+ valueClasses() {
155
+ const classes = ['text-slate-700'];
156
+ if (this.value === null) {
157
+ classes.push('text-gray-500', 'italic');
158
+ return classes;
159
+ }
160
+ const type = typeof this.value;
161
+ if (type === 'string') {
162
+ classes.push('text-emerald-600');
163
+ return classes;
164
+ }
165
+ if (type === 'number' || type === 'bigint') {
166
+ classes.push('text-amber-600');
167
+ return classes;
168
+ }
169
+ if (type === 'boolean') {
170
+ classes.push('text-violet-600');
171
+ return classes;
172
+ }
173
+ if (type === 'undefined') {
174
+ classes.push('text-gray-500');
175
+ return classes;
176
+ }
177
+ return classes;
178
+ },
179
+ comma() {
180
+ return this.isLast ? '' : ',';
181
+ },
182
+ indentStyle() {
183
+ return {
184
+ paddingLeft: `${this.level * this.indentSize}px`
185
+ };
186
+ },
187
+ hiddenChildrenLabel() {
188
+ if (!this.hasHiddenRootChildren) {
189
+ return '';
190
+ }
191
+ const count = this.hiddenRootChildrenCount;
192
+ const suffix = count === 1 ? 'field' : 'fields';
193
+ return `${count} more ${suffix}`;
194
+ },
195
+ hiddenChildrenTooltip() {
196
+ return this.hiddenChildrenLabel;
197
+ },
198
+ normalizedPath() {
199
+ if (typeof this.path !== 'string') {
200
+ return '';
201
+ }
202
+ return this.path
203
+ .replace(/^root\.?/, '')
204
+ .replace(/\[\d+\]/g, '')
205
+ .replace(/^\./, '');
206
+ },
207
+ referenceModel() {
208
+ if (!this.normalizedPath || !this.references) {
209
+ return null;
210
+ }
211
+ return this.references[this.normalizedPath] || null;
212
+ },
213
+ shouldShowReferenceLink() {
214
+ return this.referenceId != null;
215
+ },
216
+ referenceId() {
217
+ if (!this.referenceModel) {
218
+ return null;
219
+ }
220
+ if (this.value == null) {
221
+ return null;
222
+ }
223
+ const type = typeof this.value;
224
+ if (type === 'string' || type === 'number' || type === 'bigint') {
225
+ return String(this.value);
226
+ }
227
+ if (type === 'object') {
228
+ if (this.value._id != null) {
229
+ return String(this.value._id);
230
+ }
231
+ if (typeof this.value.toString === 'function') {
232
+ const stringified = this.value.toString();
233
+ if (typeof stringified === 'string' && stringified !== '[object Object]') {
234
+ return stringified;
235
+ }
236
+ }
237
+ }
238
+ return null;
239
+ }
240
+ },
241
+ methods: {
242
+ visibleObjectKeys(keys) {
243
+ if (!this.isRoot || this.topLevelExpanded) {
244
+ return keys;
245
+ }
246
+ if (typeof this.maxTopLevelFields !== 'number') {
247
+ return keys;
248
+ }
249
+ if (keys.length <= this.maxTopLevelFields) {
250
+ return keys;
251
+ }
252
+ return keys.slice(0, this.maxTopLevelFields);
253
+ },
254
+ handleToggle() {
255
+ if (!this.isRoot) {
256
+ this.toggleCollapse(this.path);
257
+ }
258
+ },
259
+ handleExpandTopLevel() {
260
+ if (this.isRoot && typeof this.expandTopLevel === 'function') {
261
+ this.expandTopLevel();
262
+ }
263
+ },
264
+ goToReference() {
265
+ const id = this.referenceId;
266
+ if (!this.referenceModel || id == null) {
267
+ return;
268
+ }
269
+ this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
270
+ }
271
+ }
272
+ });
@@ -1,3 +1,15 @@
1
- <div class="list-array">
2
- <pre class="max-h-[6.5em] max-w-[30em]"><code ref="code" class="language-javascript" v-text="displayValue"></code></pre>
3
- </div>
1
+ <div class="list-array tooltip">
2
+ <div>{{displayValue}}</div>
3
+ <div class="tooltiptext" style="display:flex; width: 100%; justify-content: space-around; align-items: center; min-width: 180px;">
4
+ <div class="tooltiptextchild" @click.stop="showViewDataModal = true">View data</div>
5
+ <div class="tooltiptextchild" @click.stop="copyText(value)">Copy &#x1F4CB;</div>
6
+ </div>
7
+ <modal ref="viewDataModal" v-if="showViewDataModal" containerClass="!h-[90vh] !w-[90vw]">
8
+ <template #body>
9
+ <div @click.stop class="w-full h-full cursor-auto">
10
+ <div class="modal-exit" @click="showViewDataModal = false">&times;</div>
11
+ <list-json :value="value" :expandedFields="Object.keys(value || []).map(key => `root[${key}]`)" />
12
+ </div>
13
+ </template>
14
+ </modal>
15
+ </div>
@@ -5,12 +5,30 @@ const template = require('./list-array.html');
5
5
  module.exports = app => app.component('list-array', {
6
6
  template: template,
7
7
  props: ['value'],
8
+ data: () => ({ showViewDataModal: false }),
8
9
  computed: {
9
10
  displayValue() {
10
- return this.value;
11
+ if (this.value == null) {
12
+ return this.value;
13
+ }
14
+ const value = JSON.stringify(this.value);
15
+ if (value.length > 50) {
16
+ return `${value.slice(0, 50)}…`;
17
+ }
18
+ return value;
11
19
  }
12
20
  },
13
- mounted() {
14
- Prism.highlightElement(this.$refs.code);
21
+ methods: {
22
+ copyText(value) {
23
+ const storage = document.createElement('textarea');
24
+ storage.value = JSON.stringify(value);
25
+ const elem = this.$el;
26
+ elem.appendChild(storage);
27
+ storage.select();
28
+ storage.setSelectionRange(0, 99999);
29
+ document.execCommand('copy');
30
+ elem.removeChild(storage);
31
+ this.$toast.success('Text copied!');
32
+ }
15
33
  }
16
34
  });
@@ -32,7 +32,7 @@ module.exports = app => app.component('list-default', {
32
32
  if (this.value === undefined) {
33
33
  return 'undefined';
34
34
  }
35
- if (this.value.length > 30) {
35
+ if (this.value.length > 30 && typeof this.value === 'string') {
36
36
  return this.value.substring(0, 30) + '...';
37
37
  }
38
38
  return this.value;
@@ -41,4 +41,4 @@ module.exports = app => app.component('list-default', {
41
41
  return this.allude;
42
42
  }
43
43
  }
44
- });
44
+ });
@@ -1,4 +1,4 @@
1
- <div class="tooltip w-full font-mono text-sm py-3 text-slate-800">
1
+ <div class="tooltip w-full font-mono text-sm py-1 text-slate-800">
2
2
  <div class="w-full">
3
3
  <json-node
4
4
  :node-key="null"