@mongoosejs/studio 0.0.1

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 (54) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -0
  3. package/backend/actions/Model/exportQueryResults.js +66 -0
  4. package/backend/actions/Model/getDocument.js +30 -0
  5. package/backend/actions/Model/getDocuments.js +47 -0
  6. package/backend/actions/Model/index.js +7 -0
  7. package/backend/actions/Model/listModels.js +5 -0
  8. package/backend/actions/Model/updateDocument.js +32 -0
  9. package/backend/actions/index.js +3 -0
  10. package/backend/index.js +12 -0
  11. package/backend/netlify.js +28 -0
  12. package/express.js +36 -0
  13. package/frontend/index.js +36 -0
  14. package/frontend/public/app.js +4012 -0
  15. package/frontend/public/images/mongoose.svg +35 -0
  16. package/frontend/public/index.html +21 -0
  17. package/frontend/public/style.css +34 -0
  18. package/frontend/src/api.js +52 -0
  19. package/frontend/src/appendCSS.js +11 -0
  20. package/frontend/src/async-button/async-button.html +3 -0
  21. package/frontend/src/async-button/async-button.js +39 -0
  22. package/frontend/src/detail-array/detail-array.html +3 -0
  23. package/frontend/src/detail-array/detail-array.js +16 -0
  24. package/frontend/src/detail-default/detail-default.html +3 -0
  25. package/frontend/src/detail-default/detail-default.js +19 -0
  26. package/frontend/src/document/document.css +39 -0
  27. package/frontend/src/document/document.html +42 -0
  28. package/frontend/src/document/document.js +63 -0
  29. package/frontend/src/edit-default/edit-default.html +3 -0
  30. package/frontend/src/edit-default/edit-default.js +20 -0
  31. package/frontend/src/export-query-results/export-query-results.css +0 -0
  32. package/frontend/src/export-query-results/export-query-results.html +11 -0
  33. package/frontend/src/export-query-results/export-query-results.js +34 -0
  34. package/frontend/src/index.js +43 -0
  35. package/frontend/src/list-array/list-array.css +8 -0
  36. package/frontend/src/list-array/list-array.html +3 -0
  37. package/frontend/src/list-array/list-array.js +23 -0
  38. package/frontend/src/list-default/list-default.html +3 -0
  39. package/frontend/src/list-default/list-default.js +19 -0
  40. package/frontend/src/list-subdocument/list-subdocument.html +3 -0
  41. package/frontend/src/list-subdocument/list-subdocument.js +9 -0
  42. package/frontend/src/modal/modal.css +75 -0
  43. package/frontend/src/modal/modal.html +12 -0
  44. package/frontend/src/modal/modal.js +10 -0
  45. package/frontend/src/models/models.css +108 -0
  46. package/frontend/src/models/models.html +53 -0
  47. package/frontend/src/models/models.js +107 -0
  48. package/frontend/src/navbar/navbar.css +162 -0
  49. package/frontend/src/navbar/navbar.html +10 -0
  50. package/frontend/src/navbar/navbar.js +11 -0
  51. package/frontend/src/routes.js +19 -0
  52. package/frontend/webpack.config.js +36 -0
  53. package/package.json +21 -0
  54. package/test.js +264 -0
@@ -0,0 +1,75 @@
1
+ /** Vue modal */
2
+
3
+ .modal-mask {
4
+ position: fixed;
5
+ z-index: 9998;
6
+ top: 0;
7
+ left: 0;
8
+ width: 100%;
9
+ height: 100%;
10
+ background-color: rgba(0, 0, 0, 0.5);
11
+ display: table;
12
+ transition: opacity 0.3s ease;
13
+ }
14
+
15
+ .modal-wrapper {
16
+ display: table-cell;
17
+ vertical-align: middle;
18
+ }
19
+
20
+ .modal-container {
21
+ width: 600px;
22
+ margin: 0px auto;
23
+ padding: 20px 30px;
24
+ padding-bottom: 40px;
25
+ background-color: #fff;
26
+ border-radius: 2px;
27
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
28
+ transition: all 0.3s ease;
29
+ font-family: Helvetica, Arial, sans-serif;
30
+ }
31
+
32
+ .modal-header {
33
+ margin-top: 0;
34
+ font-size: 18px;
35
+ font-weight: bold
36
+ }
37
+
38
+ .modal-header-success {
39
+ color: #42b983;
40
+ }
41
+
42
+ .modal-header-error {
43
+ color: #ff0000;
44
+ }
45
+
46
+ .modal-body {
47
+ margin: 20px 0;
48
+ }
49
+
50
+ .modal__button--default {
51
+ float: right;
52
+ }
53
+
54
+ /*
55
+ * The following styles are auto-applied to elements with
56
+ * transition="modal" when their visibility is toggled
57
+ * by Vue.js.
58
+ *
59
+ * You can easily play with the modal transition by editing
60
+ * these styles.
61
+ */
62
+
63
+ .modal-enter {
64
+ opacity: 0;
65
+ }
66
+
67
+ .modal-leave-active {
68
+ opacity: 0;
69
+ }
70
+
71
+ .modal-enter .modal-container,
72
+ .modal-leave-active .modal-container {
73
+ -webkit-transform: scale(1.1);
74
+ transform: scale(1.1);
75
+ }
@@ -0,0 +1,12 @@
1
+ <transition name="modal">
2
+ <div class="modal-mask">
3
+ <div class="modal-wrapper">
4
+ <div class="modal-container">
5
+ <div class="modal-body">
6
+ <slot name="body">
7
+ </slot>
8
+ </div>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ </transition>
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ const appendCSS = require('../appendCSS');
4
+ const template = require('./modal.html');
5
+
6
+ appendCSS(require('./modal.css'));
7
+
8
+ module.exports = app => app.component('modal', {
9
+ template
10
+ });
@@ -0,0 +1,108 @@
1
+ .models {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: row;
5
+ min-height: calc(100% - 56px);
6
+ }
7
+
8
+ .models .model-selector {
9
+ background-color: #eee;
10
+ flex-grow: 0;
11
+ padding: 15px;
12
+ padding-top: 0px;
13
+ }
14
+
15
+ .models h1 {
16
+ margin-top: 0px;
17
+ }
18
+
19
+ .models .documents {
20
+ flex-grow: 1;
21
+ overflow: scroll;
22
+ max-height: calc(100vh - 56px);
23
+ }
24
+
25
+ .models .documents .documents-container {
26
+ margin-top: 40px;
27
+ }
28
+
29
+ .models .documents table {
30
+ /* max-width: -moz-fit-content;
31
+ max-width: fit-content; */
32
+ width: 100%;
33
+ table-layout: auto;
34
+ font-size: small;
35
+ padding: 0;
36
+ margin-right: 1em;
37
+ white-space: nowrap;
38
+ z-index: -1;
39
+ border-collapse: collapse;
40
+ line-height: 1.5em;
41
+ }
42
+
43
+ .models .documents table th {
44
+ position: sticky;
45
+ top: 0px;
46
+ background-color: white;
47
+ }
48
+
49
+ .models .documents table th:after {
50
+ content: '';
51
+ position: absolute;
52
+ left: 0;
53
+ width: 100%;
54
+ bottom: -1px;
55
+ border-bottom: thin solid rgba(0,0,0,.12);
56
+ }
57
+
58
+ .models .documents table tr {
59
+ color: black;
60
+ border-spacing: 0px 0px;
61
+ background-color: white;
62
+ cursor: pointer;
63
+ }
64
+
65
+ .models .documents table tr:nth-child(even) {
66
+ background-color: #f5f5f5;
67
+ }
68
+
69
+ .models .documents table tr:hover {
70
+ background-color: #55A3D4;
71
+ }
72
+
73
+ .models .documents table th, td {
74
+ border-bottom: thin solid rgba(0,0,0,.12);
75
+ text-align: left;
76
+ padding: 0 16px;
77
+ height: 48px;
78
+ }
79
+
80
+ .models textarea {
81
+ width: 100%;
82
+ height: 600px;
83
+ font-size: 1.2em;
84
+ }
85
+
86
+ .models .path-type {
87
+ color: rgba(0,0,0,.36);
88
+ font-size: 0.8em;
89
+ }
90
+
91
+ .models .documents-menu {
92
+ display: flex;
93
+ margin: 0.25em;
94
+ position: fixed;
95
+ width: calc(100vw - 200px);
96
+ }
97
+
98
+ .models .documents-menu .search-input {
99
+ flex-grow: 1;
100
+ }
101
+
102
+ .models .search-input input {
103
+ padding: 0.25em 0.5em;
104
+ font-size: 1.1em;
105
+ border: 1px solid #ddd;
106
+ border-radius: 3px;
107
+ width: calc(100% - 1em);
108
+ }
@@ -0,0 +1,53 @@
1
+ <div class="models">
2
+ <div class="model-selector">
3
+ <h1>Models</h1>
4
+ <div v-for="model in models">
5
+ <router-link :to="'/model/' + model" :class="model === currentModel ? 'bold' : ''">
6
+ {{model}}
7
+ </router-link>
8
+ </div>
9
+ </div>
10
+ <div class="documents">
11
+ <div>
12
+ <div class="documents-menu">
13
+ <div class="search-input">
14
+ <form @submit.prevent="search">
15
+ <input class="search-text" type="text" placeholder="Filter or text" v-model="searchText" />
16
+ </form>
17
+ </div>
18
+ <div class="buttons">
19
+ <button @click="shouldShowExportModal = true">Export</button>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ <div class="documents-container">
24
+ <table>
25
+ <thead>
26
+ <th v-for="path in schemaPaths">
27
+ {{path.path}}
28
+ <span class="path-type">
29
+ ({{path.instance.toLowerCase()}})
30
+ </span>
31
+ </th>
32
+ </thead>
33
+ <tbody>
34
+ <tr v-for="document in documents" @click="$router.push('/model/' + currentModel + '/document/' + document._id)" :key="document._id">
35
+ <td v-for="schemaPath in schemaPaths">
36
+ <component :is="getComponentForPath(schemaPath)" :value="document[schemaPath.path]"></component>
37
+ </td>
38
+ </tr>
39
+ </tbody>
40
+ </table>
41
+ </div>
42
+ <modal v-if="shouldShowExportModal">
43
+ <template v-slot:body>
44
+ <export-query-results
45
+ :schemaPaths="schemaPaths"
46
+ :filter="filter"
47
+ :currentModel="currentModel"
48
+ @done="shouldShowExportModal = false">
49
+ </export-query-results>
50
+ </template>
51
+ </modal>
52
+ </div>
53
+ </div>
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const api = require('../api');
4
+ const template = require('./models.html');
5
+
6
+ const appendCSS = require('../appendCSS');
7
+
8
+ appendCSS(require('./models.css'));
9
+
10
+ module.exports = app => app.component('models', {
11
+ template: template,
12
+ props: ['model'],
13
+ data: () => ({
14
+ models: [],
15
+ currentModel: null,
16
+ documents: [],
17
+ schemaPaths: [],
18
+ status: 'init',
19
+ edittingDoc: null,
20
+ docEdits: null,
21
+ filter: null,
22
+ searchText: '',
23
+ shouldShowExportModal: false,
24
+ shouldExport: {}
25
+ }),
26
+ created() {
27
+ this.currentModel = this.model;
28
+ },
29
+ async mounted() {
30
+ this.models = await api.Model.listModels().then(res => res.models);
31
+ if (this.currentModel == null && this.models.length > 0) {
32
+ this.currentModel = this.models[0];
33
+ }
34
+
35
+ if (this.currentModel != null) {
36
+ const { docs, schemaPaths } = await api.Model.getDocuments({ model: this.currentModel });
37
+ this.documents = docs;
38
+ this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
39
+ if (k1 === '_id' && k2 !== '_id') {
40
+ return -1;
41
+ }
42
+ if (k1 !== '_id' && k2 === '_id') {
43
+ return 1;
44
+ }
45
+ return 0;
46
+ }).map(key => schemaPaths[key]);
47
+
48
+ this.shouldExport = {};
49
+ for (const { path } of this.schemaPaths) {
50
+ this.shouldExport[path] = true;
51
+ }
52
+ }
53
+
54
+ this.status = 'loaded';
55
+ },
56
+ methods: {
57
+ async search() {
58
+ if (this.searchText) {
59
+ this.filter = eval(`(${this.searchText})`);
60
+ } else {
61
+ this.filter = {};
62
+ }
63
+
64
+ await this.getDocuments();
65
+ },
66
+ async getDocuments() {
67
+ const { docs, schemaPaths } = await api.Model.getDocuments({
68
+ model: this.currentModel,
69
+ filter: this.filter
70
+ });
71
+ this.documents = docs;
72
+ this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
73
+ if (k1 === '_id' && k2 !== '_id') {
74
+ return -1;
75
+ }
76
+ if (k1 !== '_id' && k2 === '_id') {
77
+ return 1;
78
+ }
79
+ return 0;
80
+ }).map(key => schemaPaths[key]);
81
+
82
+ this.shouldExport = {};
83
+ for (const { path } of this.schemaPaths) {
84
+ this.shouldExport[path] = true;
85
+ }
86
+ },
87
+ getComponentForPath(schemaPath) {
88
+ if (schemaPath.instance === 'Array') {
89
+ return 'list-array';
90
+ }
91
+ return 'list-default';
92
+ },
93
+ async saveDocEdits() {
94
+ const res = await api.Model.updateDocument({
95
+ model: this.currentModel,
96
+ _id: this.edittingDoc._id,
97
+ update: JSON.parse(this.docEdits)
98
+ });
99
+
100
+ const index = this.documents.findIndex(doc => doc === this.edittingDoc);
101
+ if (index !== -1) {
102
+ this.documents[index] = res.doc;
103
+ }
104
+ this.edittingDoc = null;
105
+ }
106
+ }
107
+ });
@@ -0,0 +1,162 @@
1
+ .navbar {
2
+ width: 100%;
3
+ background-color: #eee;
4
+ }
5
+
6
+ .navbar .nav-left {
7
+ float: left;
8
+ line-height: 54px;
9
+ font-size: 20px;
10
+ padding-left: 20px;
11
+ }
12
+
13
+ .navbar .nav-left a {
14
+ color: #232323;
15
+ }
16
+
17
+ .navbar {
18
+ border-bottom: 1px solid #ddd;
19
+ height: 55px;
20
+ }
21
+
22
+ .navbar .nav-left img {
23
+ height: 24px;
24
+ vertical-align: middle;
25
+ margin-right: 0.5em;
26
+ margin-top: 16px;
27
+ }
28
+
29
+ .navbar .nav-right {
30
+ float: right;
31
+ display: flex;
32
+ flex-direction: row;
33
+ font-size: 16px;
34
+ line-height: 54px;
35
+ padding-right: 20px;
36
+ }
37
+
38
+ .navbar .nav-right .nav-item {
39
+ flex-grow: 1;
40
+ padding: 0px 12px;
41
+ position: relative;
42
+ z-index: 21000;
43
+ }
44
+
45
+ .navbar .nav-right .nav-item:hover {
46
+ flex-grow: 1;
47
+ padding: 0px 12px;
48
+ border-bottom: 1px solid #E1B9A0;
49
+ }
50
+
51
+ .navbar .nav-right .nav-item.active {
52
+ border-bottom: 1px solid #E1B9A0;
53
+ }
54
+
55
+ .navbar .nav-action {
56
+ cursor: pointer;
57
+ color: #E1B9A0;
58
+ }
59
+
60
+ .navbar .nav-action svg {
61
+ height: 1em;
62
+ vertical-align: middle;
63
+ }
64
+
65
+ .navbar .nav-right .nav-item .flyout {
66
+ position: absolute;
67
+ top: 55px;
68
+ right: 0px;
69
+ visibility: hidden;
70
+ opacity: 0;
71
+ transition: opacity .25s,visibility .25s,transform .25s;
72
+ width: auto;
73
+ box-shadow: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);
74
+ background-color: #393944;
75
+ padding-left: 0.5em;
76
+ padding-right: 0.5em;
77
+ z-index: 1000;
78
+ min-width: 192px;
79
+ font-size: 0.9em;
80
+ }
81
+
82
+ .navbar .nav-right .nav-item:hover .flyout a {
83
+ color: #E1B9A0;
84
+ margin-top: 0.25em;
85
+ margin-bottom: 0.25em;
86
+ }
87
+
88
+ .navbar .nav-right .nav-item:hover .flyout a:hover {
89
+ color: #E1B9A0;
90
+ }
91
+
92
+ .navbar .nav-right .nav-item:hover .flyout .nav-action {
93
+ color: #E1B9A0;
94
+ margin-top: 0.25em;
95
+ margin-bottom: 0.25em;
96
+ }
97
+
98
+ .navbar .nav-right .nav-item:hover .flyout .nav-action:hover {
99
+ color: #E1B9A0;
100
+ }
101
+
102
+ .navbar .nav-right .nav-item:hover .flyout {
103
+ visibility: visible;
104
+ opacity: 1;
105
+ }
106
+
107
+ #bar-1 {
108
+ transform: translateY(-4px);
109
+ }
110
+ #bar-3 {
111
+ transform: translateY(4px);
112
+ }
113
+ .menu {
114
+ display: none;
115
+ }
116
+ .menu {
117
+ width: 35px;
118
+ height: 30px;
119
+ margin: 18px 2px 0px 0px;
120
+ cursor: pointer;
121
+ float: right;
122
+ }
123
+ .bar {
124
+ height: 5px;
125
+ width: 100%;
126
+ background-color: #fff;
127
+ display: block;
128
+ border-radius: 5px;
129
+ transition: 0.4s ease;
130
+ }
131
+ .change-icon #bar-1 {
132
+ transform: translateY(4px) rotateZ(-405deg);
133
+ }
134
+ .change-icon #bar-2 {
135
+ opacity: 0;
136
+ }
137
+ .change-icon #bar-3 {
138
+ transform: translateY(-6px) rotateZ(405deg);
139
+ }
140
+
141
+ @media (max-width: 767px) {
142
+ .menu {
143
+ display: block;
144
+ }
145
+
146
+ .change-icon ~ div.nav-right {
147
+ left: 0;
148
+ }
149
+
150
+ .navbar .nav-right {
151
+ position: fixed;
152
+ top: 55px;
153
+ left: -130%;
154
+ background: #111;
155
+ height: 100vh;
156
+ width: 100%;
157
+ text-align: center;
158
+ display: block;
159
+ transition: all 0.3s ease;
160
+ z-index: 10000;
161
+ }
162
+ }
@@ -0,0 +1,10 @@
1
+ <div class="navbar">
2
+ <div class="nav-left">
3
+ <router-link to="/">
4
+ <img src="images/mongoose.svg" alt="Mongoose Logo" />
5
+ </router-link>
6
+ </div>
7
+ <div class="nav-right">
8
+ </div>
9
+ <div style="clear: both"></div>
10
+ </div>
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const template = require('./navbar.html');
4
+
5
+ const appendCSS = require('../appendCSS');
6
+
7
+ appendCSS(require('./navbar.css'));
8
+
9
+ module.exports = app => app.component('navbar', {
10
+ template: template
11
+ });
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ module.exports = [
4
+ {
5
+ path: '/',
6
+ name: 'root',
7
+ component: 'models'
8
+ },
9
+ {
10
+ path: '/model/:model',
11
+ name: 'model',
12
+ component: 'models'
13
+ },
14
+ {
15
+ path: '/model/:model/document/:documentId',
16
+ name: 'document',
17
+ component: 'document'
18
+ }
19
+ ];
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const webpack = require('webpack');
4
+
5
+ module.exports = {
6
+ mode: 'development',
7
+ entry: {
8
+ app: `${__dirname}/src/index.js`
9
+ },
10
+ target: 'web',
11
+ devtool: false,
12
+ optimization: {
13
+ minimize: false
14
+ },
15
+ output: {
16
+ path: `${__dirname}/public`,
17
+ filename: '[name].js'
18
+ },
19
+ plugins: [
20
+ new webpack.DefinePlugin({
21
+ config__baseURL: '\'/.netlify/functions/admin\''
22
+ })
23
+ ],
24
+ module: {
25
+ rules: [
26
+ {
27
+ test: /\.html$/i,
28
+ type: 'asset/source'
29
+ },
30
+ {
31
+ test: /\.css$/i,
32
+ type: 'asset/source'
33
+ }
34
+ ]
35
+ }
36
+ };
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@mongoosejs/studio",
3
+ "version": "0.0.1",
4
+ "dependencies": {
5
+ "archetype": "0.13.0",
6
+ "csv-stringify": "6.3.0",
7
+ "extrovert": "0.0.20"
8
+ },
9
+ "peerDependencies": {
10
+ "express": "4.x",
11
+ "mongoose": "6.x || 7.x || 7.0.0-rc0"
12
+ },
13
+ "devDependencies": {
14
+ "axios": "1.2.2",
15
+ "express": "4.x",
16
+ "mocha": "10.2.0",
17
+ "mongoose": "6.x",
18
+ "webpack": "5.75.0",
19
+ "vue": "3.x"
20
+ }
21
+ }