@mongoosejs/studio 0.0.136 → 0.0.138
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/README.md +30 -0
- package/frontend/public/app.js +412 -68
- package/frontend/public/tw.css +111 -0
- package/frontend/src/list-json/list-json.html +17 -3
- package/frontend/src/list-json/list-json.js +350 -25
- package/frontend/src/models/models.css +1 -0
- package/frontend/src/models/models.html +19 -4
- package/frontend/src/models/models.js +58 -27
- package/next.js +97 -0
- package/package.json +1 -1
- package/frontend/src/list-json/list-json.css +0 -3
|
@@ -38,6 +38,7 @@ const QUERY_SELECTORS = [
|
|
|
38
38
|
appendCSS(require('./models.css'));
|
|
39
39
|
|
|
40
40
|
const limit = 20;
|
|
41
|
+
const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
|
|
41
42
|
|
|
42
43
|
module.exports = app => app.component('models', {
|
|
43
44
|
template: template,
|
|
@@ -80,6 +81,7 @@ module.exports = app => app.component('models', {
|
|
|
80
81
|
created() {
|
|
81
82
|
this.currentModel = this.model;
|
|
82
83
|
this.buildAutocompleteTrie();
|
|
84
|
+
this.loadOutputPreference();
|
|
83
85
|
},
|
|
84
86
|
beforeDestroy() {
|
|
85
87
|
document.removeEventListener('scroll', this.onScroll, true);
|
|
@@ -108,6 +110,24 @@ module.exports = app => app.component('models', {
|
|
|
108
110
|
this.autocompleteTrie.bulkInsert(paths, 10);
|
|
109
111
|
}
|
|
110
112
|
},
|
|
113
|
+
loadOutputPreference() {
|
|
114
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const storedPreference = window.localStorage.getItem(OUTPUT_TYPE_STORAGE_KEY);
|
|
118
|
+
if (storedPreference === 'json' || storedPreference === 'table') {
|
|
119
|
+
this.outputType = storedPreference;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
setOutputType(type) {
|
|
123
|
+
if (type !== 'json' && type !== 'table') {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.outputType = type;
|
|
127
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
128
|
+
window.localStorage.setItem(OUTPUT_TYPE_STORAGE_KEY, type);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
111
131
|
buildDocumentFetchParams(options = {}) {
|
|
112
132
|
const params = {
|
|
113
133
|
model: this.currentModel,
|
|
@@ -547,41 +567,52 @@ module.exports = app => app.component('models', {
|
|
|
547
567
|
},
|
|
548
568
|
handleDocumentClick(document, event) {
|
|
549
569
|
if (this.selectMultiple) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
570
|
+
this.handleDocumentSelection(document, event);
|
|
571
|
+
} else {
|
|
572
|
+
this.openDocument(document);
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
handleDocumentContainerClick(document, event) {
|
|
576
|
+
if (this.selectMultiple) {
|
|
577
|
+
this.handleDocumentSelection(document, event);
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
handleDocumentSelection(document, event) {
|
|
581
|
+
const documentIndex = this.documents.findIndex(doc => doc._id.toString() == document._id.toString());
|
|
582
|
+
if (event?.shiftKey && this.selectedDocuments.length > 0) {
|
|
583
|
+
const anchorIndex = this.lastSelectedIndex;
|
|
584
|
+
if (anchorIndex != null && anchorIndex !== -1 && documentIndex !== -1) {
|
|
585
|
+
const start = Math.min(anchorIndex, documentIndex);
|
|
586
|
+
const end = Math.max(anchorIndex, documentIndex);
|
|
587
|
+
const selectedDocumentIds = new Set(this.selectedDocuments.map(doc => doc._id.toString()));
|
|
588
|
+
for (let i = start; i <= end; i++) {
|
|
589
|
+
const docInRange = this.documents[i];
|
|
590
|
+
const existsInRange = selectedDocumentIds.has(docInRange._id.toString());
|
|
591
|
+
if (!existsInRange) {
|
|
592
|
+
this.selectedDocuments.push(docInRange);
|
|
563
593
|
}
|
|
564
|
-
this.lastSelectedIndex = documentIndex;
|
|
565
|
-
return;
|
|
566
594
|
}
|
|
595
|
+
this.lastSelectedIndex = documentIndex;
|
|
596
|
+
return;
|
|
567
597
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const lastDoc = this.selectedDocuments[this.selectedDocuments.length - 1];
|
|
575
|
-
this.lastSelectedIndex = this.documents.findIndex(doc => doc._id.toString() == lastDoc._id.toString());
|
|
576
|
-
}
|
|
598
|
+
}
|
|
599
|
+
const index = this.selectedDocuments.findIndex(x => x._id.toString() == document._id.toString());
|
|
600
|
+
if (index !== -1) {
|
|
601
|
+
this.selectedDocuments.splice(index, 1);
|
|
602
|
+
if (this.selectedDocuments.length === 0) {
|
|
603
|
+
this.lastSelectedIndex = null;
|
|
577
604
|
} else {
|
|
578
|
-
this.selectedDocuments.
|
|
579
|
-
this.lastSelectedIndex =
|
|
605
|
+
const lastDoc = this.selectedDocuments[this.selectedDocuments.length - 1];
|
|
606
|
+
this.lastSelectedIndex = this.documents.findIndex(doc => doc._id.toString() == lastDoc._id.toString());
|
|
580
607
|
}
|
|
581
608
|
} else {
|
|
582
|
-
this
|
|
609
|
+
this.selectedDocuments.push(document);
|
|
610
|
+
this.lastSelectedIndex = documentIndex;
|
|
583
611
|
}
|
|
584
612
|
},
|
|
613
|
+
openDocument(document) {
|
|
614
|
+
this.$router.push('/model/' + this.currentModel + '/document/' + document._id);
|
|
615
|
+
},
|
|
585
616
|
async deleteDocuments() {
|
|
586
617
|
const documentIds = this.selectedDocuments.map(x => x._id);
|
|
587
618
|
await api.Model.deleteDocuments({
|
package/next.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
module.exports = withMongooseStudio;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Copies Mongoose Studio frontend assets and injects rewrites.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} nextConfig - Existing Next.js config
|
|
12
|
+
* @param {object} [options]
|
|
13
|
+
* @param {string} [options.studioPath="/studio"] - Public base path for Studio frontend
|
|
14
|
+
*/
|
|
15
|
+
function withMongooseStudio(nextConfig = {}) {
|
|
16
|
+
const studioPath = normalizeBasePath(nextConfig.studioPath || '/studio');
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
...nextConfig,
|
|
20
|
+
|
|
21
|
+
async redirects() {
|
|
22
|
+
const userRedirects =
|
|
23
|
+
typeof nextConfig.redirects === 'function'
|
|
24
|
+
? await nextConfig.redirects()
|
|
25
|
+
: nextConfig.redirects || [];
|
|
26
|
+
|
|
27
|
+
// Permanent redirect ensures browser URL is updated
|
|
28
|
+
const studioRedirect = {
|
|
29
|
+
source: studioPath,
|
|
30
|
+
destination: `${studioPath}/index.html`,
|
|
31
|
+
permanent: true,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return [...userRedirects, studioRedirect];
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
webpack(config, { isServer }) {
|
|
38
|
+
if (isServer) {
|
|
39
|
+
try {
|
|
40
|
+
copyStudioFrontend(studioPath);
|
|
41
|
+
console.log(`✅ Mongoose Studio: copied frontend to public${studioPath}`);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('❌ Mongoose Studio: failed to copy frontend', err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Preserve user’s webpack config
|
|
48
|
+
if (typeof nextConfig.webpack === 'function') {
|
|
49
|
+
return nextConfig.webpack(config, { isServer });
|
|
50
|
+
}
|
|
51
|
+
return config;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Ensures path starts with "/" but not ends with "/" */
|
|
57
|
+
function normalizeBasePath(p) {
|
|
58
|
+
let res = p.startsWith('/') ? p : '/' + p;
|
|
59
|
+
return res.endsWith('/') ? res.slice(0, -1) : res;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Copies all built frontend assets into /public/{studioPath} */
|
|
63
|
+
function copyStudioFrontend(studioPath) {
|
|
64
|
+
const src = path.join(
|
|
65
|
+
path.dirname(require.resolve('@mongoosejs/studio/package.json')),
|
|
66
|
+
'frontend',
|
|
67
|
+
'public'
|
|
68
|
+
);
|
|
69
|
+
const dest = path.join(process.cwd(), 'public', studioPath.replace(/^\//, ''));
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(src)) {
|
|
72
|
+
throw new Error(`Frontend build not found at ${src}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
76
|
+
|
|
77
|
+
// Node 16.7+ has fs.cpSync
|
|
78
|
+
if (fs.cpSync) {
|
|
79
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
80
|
+
} else {
|
|
81
|
+
copyRecursiveSync(src, dest);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function copyRecursiveSync(src, dest) {
|
|
86
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const srcPath = path.join(src, entry.name);
|
|
89
|
+
const destPath = path.join(dest, entry.name);
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
92
|
+
copyRecursiveSync(srcPath, destPath);
|
|
93
|
+
} else {
|
|
94
|
+
fs.copyFileSync(srcPath, destPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.138",
|
|
4
4
|
"description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
|
|
5
5
|
"homepage": "https://studio.mongoosejs.io/",
|
|
6
6
|
"repository": {
|