@mongoosejs/studio 0.0.34 → 0.0.36

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.
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const vm = require('vm');
5
+
6
+ const DeleteDashboardParams = new Archetype({
7
+ dashboardId: {
8
+ $type: 'string',
9
+ $required: true
10
+ },
11
+ }).compile('DeleteDashboardParams');
12
+
13
+ module.exports = ({ db }) => async function deleteDashboard(params) {
14
+ const { dashboardId } = new DeleteDashboardParams(params);
15
+ const Dashboard = db.model('__Studio_Dashboard');
16
+
17
+ const result = await Dashboard.deleteOne({ _id: dashboardId }).orFail();
18
+ return { result };
19
+ };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  exports.createDashboard = require('./createDashboard');
4
+ exports.deleteDashboard = require('./deleteDashboard');
4
5
  exports.getDashboard = require('./getDashboard');
5
6
  exports.getDashboards = require('./getDashboards');
6
7
  exports.updateDashboard = require('./updateDashboard');
@@ -10,16 +10,32 @@ const UpdateDashboardParams = new Archetype({
10
10
  code: {
11
11
  $type: 'string',
12
12
  $required: true
13
+ },
14
+ title: {
15
+ $type: 'string'
16
+ },
17
+ description: {
18
+ $type: 'string'
13
19
  }
14
20
  }).compile('UpdateDashboardParams');
15
21
 
16
22
  module.exports = ({ db }) => async function updateDashboard(params) {
17
- const { dashboardId, code } = new UpdateDashboardParams(params);
23
+ const { dashboardId, code, title, description } = new UpdateDashboardParams(params);
18
24
 
19
25
  const Dashboard = db.models[`__Studio_Dashboard`];
20
26
 
27
+ const updateObj = { code };
28
+
29
+ if (title) {
30
+ updateObj.title = title;
31
+ }
32
+
33
+ if (description) {
34
+ updateObj.description = description;
35
+ }
36
+
21
37
  const doc = await Dashboard.
22
- findByIdAndUpdate(dashboardId, { code }, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true });
38
+ findByIdAndUpdate(dashboardId, updateObj, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true });
23
39
 
24
40
  let result = null;
25
41
  try {
@@ -21,9 +21,9 @@ dashboardSchema.methods.evaluate = async function evaluate() {
21
21
  const context = vm.createContext({ db: this.constructor.db });
22
22
  let result = null;
23
23
  result = await vm.runInContext(formatFunction(this.code), context);
24
- if (result.$document?.model) {
24
+ if (result.$document?.constructor?.modelName) {
25
25
  let schemaPaths = {};
26
- const Model = this.constructor.db.model(result.$document?.model);
26
+ const Model = this.constructor.db.model(result.$document?.constructor?.modelName);
27
27
  for (const path of Object.keys(Model.schema.paths)) {
28
28
  schemaPaths[path] = {
29
29
  instance: Model.schema.paths[path].instance,
@@ -43,6 +43,9 @@ if (false) {} else {
43
43
  createDashboard: function createDashboard(params) {
44
44
  return client.post('/Dashboard/createDashboard', params).then(res => res.data);
45
45
  },
46
+ deleteDashboard: function deleteDashboard(params) {
47
+ return client.post('/Dashboard/deleteDashboard', params).then(res => res.data)
48
+ },
46
49
  getDashboard: function getDashboard(params) {
47
50
  return client.put('/Dashboard/getDashboard', params).then(res => res.data);
48
51
  },
@@ -367,7 +370,7 @@ module.exports = app => app.component('dashboard-document', {
367
370
  return null;
368
371
  },
369
372
  schemaPaths() {
370
- return Object.keys(this.value.$document.schemaPaths).sort((k1, k2) => {
373
+ return Object.keys(this.value.$document?.schemaPaths || {}).sort((k1, k2) => {
371
374
  if (k1 === '_id' && k2 !== '_id') {
372
375
  return -1;
373
376
  }
@@ -477,6 +480,8 @@ module.exports = app => app.component('dashboard', {
477
480
  return {
478
481
  status: 'loading',
479
482
  code: '',
483
+ title: '',
484
+ description: '',
480
485
  showEditor: false,
481
486
  dashboard: null,
482
487
  result: null
@@ -488,6 +493,8 @@ module.exports = app => app.component('dashboard', {
488
493
  },
489
494
  async updateCode(update) {
490
495
  this.code = update.doc.code;
496
+ this.title = update.doc.title;
497
+ this.description = update.doc.description;
491
498
  this.result = update.result;
492
499
  }
493
500
  },
@@ -498,6 +505,8 @@ module.exports = app => app.component('dashboard', {
498
505
  }
499
506
  this.dashboard = dashboard;
500
507
  this.code = this.dashboard.code;
508
+ this.title = this.dashboard.title;
509
+ this.description = this.dashboard.description ?? '';
501
510
  this.result = result;
502
511
  this.status = 'loaded';
503
512
  }
@@ -520,11 +529,13 @@ const template = __webpack_require__(/*! ./edit-dashboard.html */ "./frontend/sr
520
529
 
521
530
  module.exports = app => app.component('edit-dashboard', {
522
531
  template: template,
523
- props: ['dashboardId', 'code'],
532
+ props: ['dashboardId', 'code', 'currentDescription', 'currentTitle'],
524
533
  data: function() {
525
534
  return {
526
535
  status: 'loading',
527
536
  editor: null,
537
+ title: '',
538
+ description: ''
528
539
  }
529
540
  },
530
541
  methods: {
@@ -532,9 +543,12 @@ module.exports = app => app.component('edit-dashboard', {
532
543
  this.$emit('close')
533
544
  },
534
545
  async updateCode() {
546
+ console.log('this.title', this.title, 'this.description', this.description)
535
547
  const { doc, result } = await api.Dashboard.updateDashboard({
536
548
  dashboardId: this.dashboardId,
537
- code: this.editor.getValue()
549
+ code: this.editor.getValue(),
550
+ title: this.title,
551
+ description: this.description
538
552
  });
539
553
  this.$emit('update', { doc, result });
540
554
  this.editor.setValue(doc.code);
@@ -560,8 +574,8 @@ module.exports = app => app.component('edit-dashboard', {
560
574
 
561
575
  this.editor.focus();
562
576
  // this.editor.refresh(); // if anything weird happens on load, this usually fixes it. However, this breaks it in this case.
563
-
564
-
577
+ this.description = this.currentDescription;
578
+ this.title = this.currentTitle;
565
579
  }
566
580
  });
567
581
 
@@ -586,8 +600,24 @@ module.exports = app => app.component('dashboards', {
586
600
  data: () => ({
587
601
  status: 'loading',
588
602
  dashboards: [],
589
- showCreateDashboardModal: false
603
+ showCreateDashboardModal: false,
604
+ showDeleteDashboardModal: null
590
605
  }),
606
+ methods: {
607
+ async deleteDashboard(dashboard) {
608
+ if (!dashboard) {
609
+ return;
610
+ }
611
+ await api.Dashboard.deleteDashboard({ dashboardId: dashboard._id });
612
+ const removedDashboard = this.dashboards.findIndex(x => x._id.toString() === dashboard._id.toString());
613
+ this.dashboards.splice(removedDashboard, 1);
614
+ this.showDeleteDashboardModal = null;
615
+ },
616
+ insertNewDashboard(dashboard) {
617
+ this.dashboards.push(dashboard);
618
+ this.showCreateDashboardModal = false;
619
+ }
620
+ },
591
621
  async mounted() {
592
622
  const { dashboards } = await api.Dashboard.getDashboards();
593
623
  this.dashboards = dashboards;
@@ -676,6 +706,54 @@ appendCSS(__webpack_require__(/*! ./document-details.css */ "./frontend/src/docu
676
706
  module.exports = app => app.component('document-details', {
677
707
  template,
678
708
  props: ['document', 'schemaPaths', 'editting', 'changes', 'invalid'],
709
+ computed: {
710
+ virtuals() {
711
+ if (this.schemaPaths == null) {
712
+ return [];
713
+ }
714
+ if (this.document == null) {
715
+ return [];
716
+ }
717
+ const exists = this.schemaPaths.map(x => x.path);
718
+ const docKeys = Object.keys(this.document);
719
+ const result = [];
720
+ for (let i = 0; i < docKeys.length; i++) {
721
+ if (!exists.includes(docKeys[i])) {
722
+ result.push({ name: docKeys[i], value: this.document[docKeys[i]] });
723
+ }
724
+ }
725
+
726
+ return result;
727
+ },
728
+ }
729
+ })
730
+
731
+ /***/ }),
732
+
733
+ /***/ "./frontend/src/document-details/document-property/document-property.js":
734
+ /*!******************************************************************************!*\
735
+ !*** ./frontend/src/document-details/document-property/document-property.js ***!
736
+ \******************************************************************************/
737
+ /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
738
+
739
+ "use strict";
740
+
741
+
742
+ const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
743
+ const template = __webpack_require__(/*! ./document-property.html */ "./frontend/src/document-details/document-property/document-property.html")
744
+
745
+ const appendCSS = __webpack_require__(/*! ../../appendCSS */ "./frontend/src/appendCSS.js");
746
+
747
+ appendCSS(__webpack_require__(/*! ./document-property.css */ "./frontend/src/document-details/document-property/document-property.css"));
748
+
749
+ module.exports = app => app.component('document-property', {
750
+ template,
751
+ data: function() {
752
+ return {
753
+ dateType: 'picker' // picker, iso
754
+ }
755
+ },
756
+ props: ['document', 'schemaPaths', 'editting', 'changes', 'invalid'],
679
757
  methods: {
680
758
  getComponentForPath(schemaPath) {
681
759
  if (schemaPath.instance === 'Array') {
@@ -699,30 +777,19 @@ module.exports = app => app.component('document-details', {
699
777
  return 'edit-default';
700
778
  },
701
779
  getValueForPath(path) {
780
+ if (this.document == null) {
781
+ return undefined;
782
+ }
702
783
  return mpath.get(path, this.document);
703
784
  },
704
785
  getEditValueForPath({ path }) {
705
786
  if (!this.changes) {
706
787
  return;
707
788
  }
708
- return path in this.changes ? this.changes[path] : mpath.get(path, this.document);
709
- }
710
- },
711
- computed: {
712
- virtuals() {
713
- if (this.schemaPaths == null) {
714
- return [];
715
- }
716
- const exists = this.schemaPaths.map(x => x.path);
717
- const docKeys = Object.keys(this.document);
718
- const result = [];
719
- for (let i = 0; i < docKeys.length; i++) {
720
- if (!exists.includes(docKeys[i])) {
721
- result.push({ name: docKeys[i], value: this.document[docKeys[i]] });
722
- }
789
+ if (!this.document) {
790
+ return;
723
791
  }
724
-
725
- return result;
792
+ return path in this.changes ? this.changes[path] : mpath.get(path, this.document);
726
793
  }
727
794
  }
728
795
  })
@@ -927,13 +994,11 @@ const template = __webpack_require__(/*! ./edit-date.html */ "./frontend/src/edi
927
994
 
928
995
  module.exports = app => app.component('edit-date', {
929
996
  template: template,
930
- props: ['value'],
997
+ props: ['value', 'format'],
931
998
  emits: ['input'],
932
- data: function() {
933
- return {
934
- inputType: 'picker' // picker, iso
935
- }
936
- },
999
+ data: () => ({
1000
+ inputType: ''
1001
+ }),
937
1002
  computed: {
938
1003
  valueAsLocalString() {
939
1004
  if (this.value == null) {
@@ -958,6 +1023,9 @@ module.exports = app => app.component('edit-date', {
958
1023
  }
959
1024
  const date = new Date(this.value);
960
1025
  return date.toISOString();
1026
+ },
1027
+ dateSelection() {
1028
+ return this.format;
961
1029
  }
962
1030
  }
963
1031
  });
@@ -2514,7 +2582,7 @@ module.exports = "<div>\n <div v-if=\"Array.isArray(result)\">\n <div v-for=
2514
2582
  /***/ ((module) => {
2515
2583
 
2516
2584
  "use strict";
2517
- module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"dashboard\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center w-full\">\n <h2 class=\"mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink\">{{dashboard.title}}</h2>\n <div>\n <button\n v-if=\"!showEditor\"\n @click=\"showEditor = true\"\n type=\"button\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline h-[1em]\" /> Edit\n </button>\n </div>\n </div>\n <div v-if=\"!showEditor\" class=\"mt-4 mb-4\">\n <dashboard-result :result=\"result\"></dashboard-result>\n </div>\n <div v-if=\"showEditor\">\n <edit-dashboard\n :dashboardId=\"dashboard._id\"\n :code=\"code\"\n @close=\"showEditor=false;\"\n @update=\"updateCode\"></edit-dashboard>\n </div>\n \n </div>\n <div v-if=\"!dashboard && status === 'loaded'\">\n No dashboard with the given id could be found.\n </div>\n</div>";
2585
+ module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"dashboard\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center w-full\">\n <h2 class=\"mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink\">{{title}}</h2>\n <div>\n <button\n v-if=\"!showEditor\"\n @click=\"showEditor = true\"\n type=\"button\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline h-[1em]\" /> Edit\n </button>\n </div>\n </div>\n <div v-if=\"!showEditor\" class=\"mt-4 mb-4\">\n <dashboard-result :result=\"result\"></dashboard-result>\n </div>\n <div v-if=\"showEditor\">\n <edit-dashboard\n :dashboardId=\"dashboard._id\"\n :code=\"code\"\n :currentDescription=\"description\"\n :currentTitle=\"title\"\n @close=\"showEditor=false;\"\n @update=\"updateCode\"></edit-dashboard>\n </div>\n \n </div>\n <div v-if=\"!dashboard && status === 'loaded'\">\n No dashboard with the given id could be found.\n </div>\n</div>";
2518
2586
 
2519
2587
  /***/ }),
2520
2588
 
@@ -2525,7 +2593,7 @@ module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"dashboard\" clas
2525
2593
  /***/ ((module) => {
2526
2594
 
2527
2595
  "use strict";
2528
- module.exports = "<div>\n <textarea ref=\"codeEditor\">{{code}}</textarea>\n <button @click=\"updateCode\" style=\"color: black;margin-right: 0.5em\">Submit</button>\n <button @click=\"closeEditor\" class=\"gray\" style=\"margin-right: 0.5em\">Cancel</button>\n</div>";
2596
+ module.exports = "<div class=\"p-4 bg-gray-100 rounded-lg shadow-lg\">\n <div>\n <input v-model=\"title\" class=\"w-full p-2 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500\" placeholder=\"Title\"/>\n </div>\n <div>\n <textarea v-model=\"description\" class=\"w-full p-2 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500\" rows=\"4\" placeholder=\"Description\">{{description}}</textarea>\n </div>\n <div>\n <textarea ref=\"codeEditor\" class=\"w-full p-2 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500\" rows=\"6\">{{code}}</textarea>\n </div>\n <div class=\"flex space-x-2\">\n <button @click=\"updateCode\" class=\"px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500\">Submit</button>\n <button @click=\"closeEditor\" class=\"px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500\">Cancel</button>\n </div>\n</div>";
2529
2597
 
2530
2598
  /***/ }),
2531
2599
 
@@ -2536,7 +2604,7 @@ module.exports = "<div>\n <textarea ref=\"codeEditor\">{{code}}</textarea>\n
2536
2604
  /***/ ((module) => {
2537
2605
 
2538
2606
  "use strict";
2539
- module.exports = "<div class=\"dashboards max-w-5xl mx-auto mt-8\">\n <div v-if=\"status === 'loaded' && dashboards.length === 0\">\n <div class=\"text-center\">\n <h3 class=\"mt-2 text-sm font-semibold text-gray-900\">No dashboards yet</h3>\n <p class=\"mt-1 text-sm text-gray-500\">Get started by creating a new dashboard.</p>\n <div class=\"mt-6\">\n <button type=\"button\" class=\"inline-flex items-center rounded-md bg-teal-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <svg class=\"-ml-0.5 mr-1.5 h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n New Dashboard\n </button>\n </div>\n </div>\n </div>\n\n\n <div class=\"px-4 sm:px-6 lg:px-8\">\n <div class=\"sm:flex sm:items-center\">\n <div class=\"sm:flex-auto\">\n <h1 class=\"text-base font-semibold leading-6 text-gray-900\">Dashboards</h1>\n </div>\n <div class=\"mt-4 sm:ml-16 sm:mt-0 sm:flex-none\">\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"block rounded-md bg-teal-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Create New Dashboard</button>\n </div>\n </div>\n <div class=\"mt-8 flow-root\">\n <div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\">\n <div class=\"inline-block min-w-full py-2 align-middle\">\n <table class=\"min-w-full divide-y divide-gray-300\">\n <thead>\n <tr>\n <th scope=\"col\" class=\"py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg:pl-8\">Title</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-[50%]\">Description</th>\n <th scope=\"col\" class=\"relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8\">\n </th>\n <th scope=\"col\" class=\"relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8\">\n </th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-gray-200 bg-white\">\n <tr v-for=\"dashboard in dashboards\">\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8\">{{dashboard.title}}</td>\n <td class=\"whitespace-nowrap px-3 py-4 text-sm text-gray-500 truncate w-[50%]\">{{dashboard.description}}</td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id + '?edit=true'\"\n class=\"text-teal-600 hover:text-teal-900\">\n Edit\n </router-link>\n </td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id\"\n class=\"text-teal-600 hover:text-teal-900\">\n View\n </router-link>\n </td>\n </tr>\n \n <!-- More people... -->\n </tbody>\n </table>\n </div>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showCreateDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false;\">&times;</div>\n \n <create-dashboard></create-dashboard>\n </template>\n </modal>\n</div>";
2607
+ module.exports = "<div class=\"dashboards max-w-5xl mx-auto mt-8\">\n <div v-if=\"status === 'loaded' && dashboards.length === 0\">\n <div class=\"text-center\">\n <h3 class=\"mt-2 text-sm font-semibold text-gray-900\">No dashboards yet</h3>\n <p class=\"mt-1 text-sm text-gray-500\">Get started by creating a new dashboard.</p>\n <div class=\"mt-6\">\n <button type=\"button\" class=\"inline-flex items-center rounded-md bg-teal-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <svg class=\"-ml-0.5 mr-1.5 h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n New Dashboard\n </button>\n </div>\n </div>\n </div>\n\n\n <div class=\"px-4 sm:px-6 lg:px-8\">\n <div class=\"sm:flex sm:items-center\">\n <div class=\"sm:flex-auto\">\n <h1 class=\"text-base font-semibold leading-6 text-gray-900\">Dashboards</h1>\n </div>\n <div class=\"mt-4 sm:ml-16 sm:mt-0 sm:flex-none\">\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"block rounded-md bg-teal-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Create New Dashboard</button>\n </div>\n </div>\n <div class=\"mt-8 flow-root\">\n <div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\">\n <div class=\"inline-block min-w-full py-2 align-middle\">\n <table class=\"min-w-full divide-y divide-gray-300\">\n <thead>\n <tr>\n <th scope=\"col\" class=\"py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg:pl-8\">Title</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-[50%]\">Description</th>\n <th scope=\"col\" class=\"relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8\">\n </th>\n <th scope=\"col\" class=\"relative py-3.5 pl-3 pr-4 sm:pr-6 lg:pr-8\">\n </th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-gray-200 bg-white\">\n <tr v-for=\"dashboard in dashboards\">\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8\">{{dashboard.title}}</td>\n <td class=\"whitespace-nowrap px-3 py-4 text-sm text-gray-500 truncate w-[50%]\">{{dashboard.description}}</td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id + '?edit=true'\"\n class=\"text-teal-600 hover:text-teal-900\">\n Edit\n </router-link>\n </td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id\"\n class=\"text-teal-600 hover:text-teal-900\">\n View\n </router-link>\n </td>\n <td class=\"relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8\">\n <button\n @click=\"showDeleteDashboardModal=dashboard\"\n class=\"text-teal-600 hover:text-teal-900\">\n Delete\n </button>\n </td>\n </tr>\n \n <!-- More people... -->\n </tbody>\n </table>\n </div>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showCreateDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false;\">&times;</div>\n \n <create-dashboard @close=\"insertNewDashboard\"></create-dashboard>\n </template>\n </modal>\n\n <modal v-if=\"showDeleteDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showDeleteDashboardModal = null;\">&times;</div>\n <h2>Are you sure you want to delete this dashboard titled {{showDeleteDashboardModal.title}}?</h2>\n <div class=\"flex space-x-2\">\n <button class=\"px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500\" @click=\"deleteDashboard(showDeleteDashboardModal)\">Yes, delete</button>\n <button class=\"px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-600\" @click=\"showDeleteDashboardModal=null;\">Cancel</button>\n </div>\n </template>\n </modal>\n</div>";
2540
2608
 
2541
2609
  /***/ }),
2542
2610
 
@@ -2569,7 +2637,7 @@ module.exports = "<div>\n {{value}}\n</div>";
2569
2637
  /***/ ((module) => {
2570
2638
 
2571
2639
  "use strict";
2572
- module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n}\n\n.document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n}\n\n.document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}";
2640
+ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n}\n\n.document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n}\n\n.document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.document-details .date-position {\n float: right;\n margin-top: -7px;\n}";
2573
2641
 
2574
2642
  /***/ }),
2575
2643
 
@@ -2580,7 +2648,29 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
2580
2648
  /***/ ((module) => {
2581
2649
 
2582
2650
  "use strict";
2583
- module.exports = "<div class=\"document-details\">\n <div v-for=\"path in schemaPaths\" class=\"value\">\n <div class=\"path-key p-1\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown').toLowerCase()}})\n </span> \n </div>\n <div v-if=\"editting && path.path !== '_id'\" class=\"pl-1\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n @input=\"changes[path.path] = $event; delete invalid[path.path];\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else class=\"pl-1\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n <div v-for=\"path in virtuals\" class=\"mb-2\">\n <div class=\"p-1 mb-1 bg-slate-100\">\n {{path.name}}\n <span class=\"path-type\">\n (virtual)\n </span>\n </div>\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n</div>";
2651
+ module.exports = "<div class=\"document-details\">\n <document-property\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"></document-property>\n <div v-for=\"path in virtuals\" class=\"mb-2\">\n <div class=\"p-1 mb-1 bg-slate-100\">\n {{path.name}}\n <span class=\"path-type\">\n (virtual)\n </span>\n </div>\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n</div>";
2652
+
2653
+ /***/ }),
2654
+
2655
+ /***/ "./frontend/src/document-details/document-property/document-property.css":
2656
+ /*!*******************************************************************************!*\
2657
+ !*** ./frontend/src/document-details/document-property/document-property.css ***!
2658
+ \*******************************************************************************/
2659
+ /***/ ((module) => {
2660
+
2661
+ "use strict";
2662
+ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n }\n \n .document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n }\n \n .document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n }\n \n .document-details .date-position {\n float: right;\n margin-top: -7px;\n }";
2663
+
2664
+ /***/ }),
2665
+
2666
+ /***/ "./frontend/src/document-details/document-property/document-property.html":
2667
+ /*!********************************************************************************!*\
2668
+ !*** ./frontend/src/document-details/document-property/document-property.html ***!
2669
+ \********************************************************************************/
2670
+ /***/ ((module) => {
2671
+
2672
+ "use strict";
2673
+ module.exports = "<div>\n <div v-for=\"path in schemaPaths\" class=\"value\">\n <div class=\"relative path-key p-1 flex\">\n <div class=\"grow\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown').toLowerCase()}})\n </span>\n </div>\n <div v-if=\"editting && path.instance === 'Date'\" class=\"flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-white' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-sky-600' : 'text-black'\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-white' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-sky-600' : 'text-black'\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"editting && path.path !== '_id'\" class=\"pl-1\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n @input=\"changes[path.path] = $event; delete invalid[path.path];\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else class=\"pl-1\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n</div>";
2584
2674
 
2585
2675
  /***/ }),
2586
2676
 
@@ -2646,7 +2736,7 @@ module.exports = "<div class=\"edit-array\">\n <textarea\n ref=\"arrayEditor
2646
2736
  /***/ ((module) => {
2647
2737
 
2648
2738
  "use strict";
2649
- module.exports = "<div>\n <div class=\"flex mb-[-1px] w-64 justify-end\">\n <button\n @click=\"inputType = 'picker'\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none p-1 rounded-tl-md text-sm text-gray-800 ring-1 ring-inset ring-gray-300 hover:bg-gray-200 focus:z-10\"\n :class=\"inputType === 'picker' ? 'bg-gray-200' : 'bg-white'\">\n Date Picker\n </button>\n <button\n @click=\"inputType = 'iso'\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center p-1 rounded-none rounded-tr-md text-sm text-gray-800 ring-1 ring-inset ring-gray-300 hover:bg-gray-200 focus:z-10\"\n :class=\"inputType === 'iso' ? 'bg-gray-200' : 'bg-white'\">\n ISO String Input\n </button>\n </div>\n <input v-if=\"inputType == 'picker'\" class=\"w-64 h-8 border border-gray-300 outline-0\" type=\"datetime-local\" :value=\"valueAsLocalString\" @input=\"$emit('input', $event.target.value)\">\n <input v-if=\"inputType == 'iso'\" type=\"text\" class=\"w-64 h-8 border border-gray-300 outline-0\" :value=\"valueAsISOString\" @input=\"$emit('input', $event.target.value)\">\n</div>";
2739
+ module.exports = "<div>\n <input v-if=\"dateSelection == 'picker'\" class=\"w-64 h-8 border border-gray-300 outline-0\" type=\"datetime-local\" :value=\"valueAsLocalString\" @input=\"$emit('input', $event.target.value)\">\n <input v-if=\"dateSelection == 'iso'\" type=\"text\" class=\"w-64 h-8 border border-gray-300 outline-0\" :value=\"valueAsISOString\" @input=\"$emit('input', $event.target.value)\">\n</div>";
2650
2740
 
2651
2741
  /***/ }),
2652
2742
 
@@ -10571,6 +10661,7 @@ __webpack_require__(/*! ./detail-default/detail-default */ "./frontend/src/detai
10571
10661
  __webpack_require__(/*! ./document/document */ "./frontend/src/document/document.js")(app);
10572
10662
  __webpack_require__(/*! ./document/confirm-changes/confirm-changes */ "./frontend/src/document/confirm-changes/confirm-changes.js")(app);
10573
10663
  __webpack_require__(/*! ./document-details/document-details */ "./frontend/src/document-details/document-details.js")(app);
10664
+ __webpack_require__(/*! ./document-details/document-property/document-property */ "./frontend/src/document-details/document-property/document-property.js")(app);
10574
10665
  __webpack_require__(/*! ./edit-array/edit-array */ "./frontend/src/edit-array/edit-array.js")(app);
10575
10666
  __webpack_require__(/*! ./edit-default/edit-default */ "./frontend/src/edit-default/edit-default.js")(app);
10576
10667
  __webpack_require__(/*! ./edit-number/edit-number */ "./frontend/src/edit-number/edit-number.js")(app);
@@ -638,10 +638,6 @@ video {
638
638
  margin-bottom: 1rem;
639
639
  }
640
640
 
641
- .mb-\[-1px\] {
642
- margin-bottom: -1px;
643
- }
644
-
645
641
  .ml-3 {
646
642
  margin-left: 0.75rem;
647
643
  }
@@ -774,6 +770,10 @@ video {
774
770
  flex-grow: 1;
775
771
  }
776
772
 
773
+ .cursor-pointer {
774
+ cursor: pointer;
775
+ }
776
+
777
777
  .list-disc {
778
778
  list-style-type: disc;
779
779
  }
@@ -790,8 +790,16 @@ video {
790
790
  align-items: center;
791
791
  }
792
792
 
793
- .justify-end {
794
- justify-content: flex-end;
793
+ .justify-center {
794
+ justify-content: center;
795
+ }
796
+
797
+ .gap-1 {
798
+ gap: 0.25rem;
799
+ }
800
+
801
+ .gap-1\.5 {
802
+ gap: 0.375rem;
795
803
  }
796
804
 
797
805
  .gap-2 {
@@ -806,6 +814,12 @@ video {
806
814
  row-gap: 1.75rem;
807
815
  }
808
816
 
817
+ .space-x-2 > :not([hidden]) ~ :not([hidden]) {
818
+ --tw-space-x-reverse: 0;
819
+ margin-right: calc(0.5rem * var(--tw-space-x-reverse));
820
+ margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
821
+ }
822
+
809
823
  .space-y-1 > :not([hidden]) ~ :not([hidden]) {
810
824
  --tw-space-y-reverse: 0;
811
825
  margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
@@ -828,6 +842,10 @@ video {
828
842
  border-color: rgb(209 213 219 / var(--tw-divide-opacity));
829
843
  }
830
844
 
845
+ .self-stretch {
846
+ align-self: stretch;
847
+ }
848
+
831
849
  .overflow-auto {
832
850
  overflow: auto;
833
851
  }
@@ -854,6 +872,10 @@ video {
854
872
  border-radius: 0.25rem;
855
873
  }
856
874
 
875
+ .rounded-lg {
876
+ border-radius: 0.5rem;
877
+ }
878
+
857
879
  .rounded-md {
858
880
  border-radius: 0.375rem;
859
881
  }
@@ -862,6 +884,10 @@ video {
862
884
  border-radius: 0px;
863
885
  }
864
886
 
887
+ .rounded-sm {
888
+ border-radius: 0.125rem;
889
+ }
890
+
865
891
  .rounded-l-md {
866
892
  border-top-left-radius: 0.375rem;
867
893
  border-bottom-left-radius: 0.375rem;
@@ -872,14 +898,6 @@ video {
872
898
  border-bottom-right-radius: 0.375rem;
873
899
  }
874
900
 
875
- .rounded-tl-md {
876
- border-top-left-radius: 0.375rem;
877
- }
878
-
879
- .rounded-tr-md {
880
- border-top-right-radius: 0.375rem;
881
- }
882
-
883
901
  .border {
884
902
  border-width: 1px;
885
903
  }
@@ -924,11 +942,26 @@ video {
924
942
  border-color: transparent;
925
943
  }
926
944
 
945
+ .bg-blue-500 {
946
+ --tw-bg-opacity: 1;
947
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity));
948
+ }
949
+
950
+ .bg-gray-100 {
951
+ --tw-bg-opacity: 1;
952
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
953
+ }
954
+
927
955
  .bg-gray-200 {
928
956
  --tw-bg-opacity: 1;
929
957
  background-color: rgb(229 231 235 / var(--tw-bg-opacity));
930
958
  }
931
959
 
960
+ .bg-gray-500 {
961
+ --tw-bg-opacity: 1;
962
+ background-color: rgb(107 114 128 / var(--tw-bg-opacity));
963
+ }
964
+
932
965
  .bg-green-600 {
933
966
  --tw-bg-opacity: 1;
934
967
  background-color: rgb(22 163 74 / var(--tw-bg-opacity));
@@ -939,6 +972,11 @@ video {
939
972
  background-color: rgb(254 242 242 / var(--tw-bg-opacity));
940
973
  }
941
974
 
975
+ .bg-red-500 {
976
+ --tw-bg-opacity: 1;
977
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
978
+ }
979
+
942
980
  .bg-red-600 {
943
981
  --tw-bg-opacity: 1;
944
982
  background-color: rgb(220 38 38 / var(--tw-bg-opacity));
@@ -1096,6 +1134,10 @@ video {
1096
1134
  vertical-align: middle;
1097
1135
  }
1098
1136
 
1137
+ .font-\[\'Lato\'\] {
1138
+ font-family: 'Lato';
1139
+ }
1140
+
1099
1141
  .text-base {
1100
1142
  font-size: 1rem;
1101
1143
  line-height: 1.5rem;
@@ -1116,6 +1158,11 @@ video {
1116
1158
  line-height: 1.75rem;
1117
1159
  }
1118
1160
 
1161
+ .text-xs {
1162
+ font-size: 0.75rem;
1163
+ line-height: 1rem;
1164
+ }
1165
+
1119
1166
  .font-bold {
1120
1167
  font-weight: 700;
1121
1168
  }
@@ -1128,10 +1175,18 @@ video {
1128
1175
  font-weight: 600;
1129
1176
  }
1130
1177
 
1178
+ .capitalize {
1179
+ text-transform: capitalize;
1180
+ }
1181
+
1131
1182
  .leading-6 {
1132
1183
  line-height: 1.5rem;
1133
1184
  }
1134
1185
 
1186
+ .leading-tight {
1187
+ line-height: 1.25;
1188
+ }
1189
+
1135
1190
  .text-black {
1136
1191
  --tw-text-opacity: 1;
1137
1192
  color: rgb(0 0 0 / var(--tw-text-opacity));
@@ -1152,11 +1207,6 @@ video {
1152
1207
  color: rgb(55 65 81 / var(--tw-text-opacity));
1153
1208
  }
1154
1209
 
1155
- .text-gray-800 {
1156
- --tw-text-opacity: 1;
1157
- color: rgb(31 41 55 / var(--tw-text-opacity));
1158
- }
1159
-
1160
1210
  .text-gray-900 {
1161
1211
  --tw-text-opacity: 1;
1162
1212
  color: rgb(17 24 39 / var(--tw-text-opacity));
@@ -1177,6 +1227,11 @@ video {
1177
1227
  color: rgb(153 27 27 / var(--tw-text-opacity));
1178
1228
  }
1179
1229
 
1230
+ .text-sky-600 {
1231
+ --tw-text-opacity: 1;
1232
+ color: rgb(2 132 199 / var(--tw-text-opacity));
1233
+ }
1234
+
1180
1235
  .text-sky-800 {
1181
1236
  --tw-text-opacity: 1;
1182
1237
  color: rgb(7 89 133 / var(--tw-text-opacity));
@@ -1192,6 +1247,12 @@ video {
1192
1247
  color: rgb(255 255 255 / var(--tw-text-opacity));
1193
1248
  }
1194
1249
 
1250
+ .shadow-lg {
1251
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
1252
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
1253
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1254
+ }
1255
+
1195
1256
  .shadow-sm {
1196
1257
  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
1197
1258
  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
@@ -1272,9 +1333,9 @@ video {
1272
1333
  border-color: rgb(209 213 219 / var(--tw-border-opacity));
1273
1334
  }
1274
1335
 
1275
- .hover\:bg-gray-200:hover {
1336
+ .hover\:bg-blue-600:hover {
1276
1337
  --tw-bg-opacity: 1;
1277
- background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1338
+ background-color: rgb(37 99 235 / var(--tw-bg-opacity));
1278
1339
  }
1279
1340
 
1280
1341
  .hover\:bg-gray-300:hover {
@@ -1287,6 +1348,11 @@ video {
1287
1348
  background-color: rgb(249 250 251 / var(--tw-bg-opacity));
1288
1349
  }
1289
1350
 
1351
+ .hover\:bg-gray-600:hover {
1352
+ --tw-bg-opacity: 1;
1353
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity));
1354
+ }
1355
+
1290
1356
  .hover\:bg-green-500:hover {
1291
1357
  --tw-bg-opacity: 1;
1292
1358
  background-color: rgb(34 197 94 / var(--tw-bg-opacity));
@@ -1297,6 +1363,11 @@ video {
1297
1363
  background-color: rgb(239 68 68 / var(--tw-bg-opacity));
1298
1364
  }
1299
1365
 
1366
+ .hover\:bg-red-600:hover {
1367
+ --tw-bg-opacity: 1;
1368
+ background-color: rgb(220 38 38 / var(--tw-bg-opacity));
1369
+ }
1370
+
1300
1371
  .hover\:bg-slate-500:hover {
1301
1372
  --tw-bg-opacity: 1;
1302
1373
  background-color: rgb(100 116 139 / var(--tw-bg-opacity));
@@ -1326,12 +1397,43 @@ video {
1326
1397
  z-index: 10;
1327
1398
  }
1328
1399
 
1400
+ .focus\:outline-none:focus {
1401
+ outline: 2px solid transparent;
1402
+ outline-offset: 2px;
1403
+ }
1404
+
1329
1405
  .focus\:ring-0:focus {
1330
1406
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1331
1407
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1332
1408
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1333
1409
  }
1334
1410
 
1411
+ .focus\:ring-2:focus {
1412
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1413
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1414
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1415
+ }
1416
+
1417
+ .focus\:ring-blue-500:focus {
1418
+ --tw-ring-opacity: 1;
1419
+ --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
1420
+ }
1421
+
1422
+ .focus\:ring-gray-500:focus {
1423
+ --tw-ring-opacity: 1;
1424
+ --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity));
1425
+ }
1426
+
1427
+ .focus\:ring-gray-600:focus {
1428
+ --tw-ring-opacity: 1;
1429
+ --tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity));
1430
+ }
1431
+
1432
+ .focus\:ring-red-500:focus {
1433
+ --tw-ring-opacity: 1;
1434
+ --tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity));
1435
+ }
1436
+
1335
1437
  .focus-visible\:outline:focus-visible {
1336
1438
  outline-style: solid;
1337
1439
  }
@@ -74,6 +74,9 @@ if (config__isLambda) {
74
74
  createDashboard: function createDashboard(params) {
75
75
  return client.post('/Dashboard/createDashboard', params).then(res => res.data);
76
76
  },
77
+ deleteDashboard: function deleteDashboard(params) {
78
+ return client.post('/Dashboard/deleteDashboard', params).then(res => res.data)
79
+ },
77
80
  getDashboard: function getDashboard(params) {
78
81
  return client.put('/Dashboard/getDashboard', params).then(res => res.data);
79
82
  },
@@ -1,7 +1,7 @@
1
1
  <div class="dashboard px-1">
2
2
  <div v-if="dashboard" class="max-w-5xl mx-auto">
3
3
  <div class="flex items-center w-full">
4
- <h2 class="mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink">{{dashboard.title}}</h2>
4
+ <h2 class="mt-4 mb-4 text-gray-900 font-semibold text-xl grow shrink">{{title}}</h2>
5
5
  <div>
6
6
  <button
7
7
  v-if="!showEditor"
@@ -19,6 +19,8 @@
19
19
  <edit-dashboard
20
20
  :dashboardId="dashboard._id"
21
21
  :code="code"
22
+ :currentDescription="description"
23
+ :currentTitle="title"
22
24
  @close="showEditor=false;"
23
25
  @update="updateCode"></edit-dashboard>
24
26
  </div>
@@ -10,6 +10,8 @@ module.exports = app => app.component('dashboard', {
10
10
  return {
11
11
  status: 'loading',
12
12
  code: '',
13
+ title: '',
14
+ description: '',
13
15
  showEditor: false,
14
16
  dashboard: null,
15
17
  result: null
@@ -21,6 +23,8 @@ module.exports = app => app.component('dashboard', {
21
23
  },
22
24
  async updateCode(update) {
23
25
  this.code = update.doc.code;
26
+ this.title = update.doc.title;
27
+ this.description = update.doc.description;
24
28
  this.result = update.result;
25
29
  }
26
30
  },
@@ -31,6 +35,8 @@ module.exports = app => app.component('dashboard', {
31
35
  }
32
36
  this.dashboard = dashboard;
33
37
  this.code = this.dashboard.code;
38
+ this.title = this.dashboard.title;
39
+ this.description = this.dashboard.description ?? '';
34
40
  this.result = result;
35
41
  this.status = 'loaded';
36
42
  }
@@ -1,5 +1,15 @@
1
- <div>
2
- <textarea ref="codeEditor">{{code}}</textarea>
3
- <button @click="updateCode" style="color: black;margin-right: 0.5em">Submit</button>
4
- <button @click="closeEditor" class="gray" style="margin-right: 0.5em">Cancel</button>
1
+ <div class="p-4 bg-gray-100 rounded-lg shadow-lg">
2
+ <div>
3
+ <input v-model="title" class="w-full p-2 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Title"/>
4
+ </div>
5
+ <div>
6
+ <textarea v-model="description" class="w-full p-2 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" rows="4" placeholder="Description">{{description}}</textarea>
7
+ </div>
8
+ <div>
9
+ <textarea ref="codeEditor" class="w-full p-2 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" rows="6">{{code}}</textarea>
10
+ </div>
11
+ <div class="flex space-x-2">
12
+ <button @click="updateCode" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500">Submit</button>
13
+ <button @click="closeEditor" class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500">Cancel</button>
14
+ </div>
5
15
  </div>
@@ -5,11 +5,13 @@ const template = require('./edit-dashboard.html');
5
5
 
6
6
  module.exports = app => app.component('edit-dashboard', {
7
7
  template: template,
8
- props: ['dashboardId', 'code'],
8
+ props: ['dashboardId', 'code', 'currentDescription', 'currentTitle'],
9
9
  data: function() {
10
10
  return {
11
11
  status: 'loading',
12
12
  editor: null,
13
+ title: '',
14
+ description: ''
13
15
  }
14
16
  },
15
17
  methods: {
@@ -17,9 +19,12 @@ module.exports = app => app.component('edit-dashboard', {
17
19
  this.$emit('close')
18
20
  },
19
21
  async updateCode() {
22
+ console.log('this.title', this.title, 'this.description', this.description)
20
23
  const { doc, result } = await api.Dashboard.updateDashboard({
21
24
  dashboardId: this.dashboardId,
22
- code: this.editor.getValue()
25
+ code: this.editor.getValue(),
26
+ title: this.title,
27
+ description: this.description
23
28
  });
24
29
  this.$emit('update', { doc, result });
25
30
  this.editor.setValue(doc.code);
@@ -45,7 +50,7 @@ module.exports = app => app.component('edit-dashboard', {
45
50
 
46
51
  this.editor.focus();
47
52
  // this.editor.refresh(); // if anything weird happens on load, this usually fixes it. However, this breaks it in this case.
48
-
49
-
53
+ this.description = this.currentDescription;
54
+ this.title = this.currentTitle;
50
55
  }
51
56
  });
@@ -15,7 +15,7 @@ module.exports = app => app.component('dashboard-document', {
15
15
  return null;
16
16
  },
17
17
  schemaPaths() {
18
- return Object.keys(this.value.$document.schemaPaths).sort((k1, k2) => {
18
+ return Object.keys(this.value.$document?.schemaPaths || {}).sort((k1, k2) => {
19
19
  if (k1 === '_id' && k2 !== '_id') {
20
20
  return -1;
21
21
  }
@@ -59,6 +59,13 @@
59
59
  View
60
60
  </router-link>
61
61
  </td>
62
+ <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8">
63
+ <button
64
+ @click="showDeleteDashboardModal=dashboard"
65
+ class="text-teal-600 hover:text-teal-900">
66
+ Delete
67
+ </button>
68
+ </td>
62
69
  </tr>
63
70
 
64
71
  <!-- More people... -->
@@ -73,7 +80,18 @@
73
80
  <template v-slot:body>
74
81
  <div class="modal-exit" @click="showCreateDashboardModal = false;">&times;</div>
75
82
 
76
- <create-dashboard></create-dashboard>
83
+ <create-dashboard @close="insertNewDashboard"></create-dashboard>
84
+ </template>
85
+ </modal>
86
+
87
+ <modal v-if="showDeleteDashboardModal">
88
+ <template v-slot:body>
89
+ <div class="modal-exit" @click="showDeleteDashboardModal = null;">&times;</div>
90
+ <h2>Are you sure you want to delete this dashboard titled {{showDeleteDashboardModal.title}}?</h2>
91
+ <div class="flex space-x-2">
92
+ <button class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500" @click="deleteDashboard(showDeleteDashboardModal)">Yes, delete</button>
93
+ <button class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-600" @click="showDeleteDashboardModal=null;">Cancel</button>
94
+ </div>
77
95
  </template>
78
96
  </modal>
79
97
  </div>
@@ -9,8 +9,24 @@ module.exports = app => app.component('dashboards', {
9
9
  data: () => ({
10
10
  status: 'loading',
11
11
  dashboards: [],
12
- showCreateDashboardModal: false
12
+ showCreateDashboardModal: false,
13
+ showDeleteDashboardModal: null
13
14
  }),
15
+ methods: {
16
+ async deleteDashboard(dashboard) {
17
+ if (!dashboard) {
18
+ return;
19
+ }
20
+ await api.Dashboard.deleteDashboard({ dashboardId: dashboard._id });
21
+ const removedDashboard = this.dashboards.findIndex(x => x._id.toString() === dashboard._id.toString());
22
+ this.dashboards.splice(removedDashboard, 1);
23
+ this.showDeleteDashboardModal = null;
24
+ },
25
+ insertNewDashboard(dashboard) {
26
+ this.dashboards.push(dashboard);
27
+ this.showCreateDashboardModal = false;
28
+ }
29
+ },
14
30
  async mounted() {
15
31
  const { dashboards } = await api.Dashboard.getDashboards();
16
32
  this.dashboards = dashboards;
@@ -15,4 +15,9 @@
15
15
  .document-details .path-type {
16
16
  color: rgba(0,0,0,.36);
17
17
  font-size: 0.8em;
18
+ }
19
+
20
+ .document-details .date-position {
21
+ float: right;
22
+ margin-top: -7px;
18
23
  }
@@ -1,24 +1,10 @@
1
1
  <div class="document-details">
2
- <div v-for="path in schemaPaths" class="value">
3
- <div class="path-key p-1">
4
- {{path.path}}
5
- <span class="path-type">
6
- ({{(path.instance || 'unknown').toLowerCase()}})
7
- </span>
8
- </div>
9
- <div v-if="editting && path.path !== '_id'" class="pl-1">
10
- <component
11
- :is="getEditComponentForPath(path)"
12
- :value="getEditValueForPath(path)"
13
- @input="changes[path.path] = $event; delete invalid[path.path];"
14
- @error="invalid[path.path] = $event;"
15
- >
16
- </component>
17
- </div>
18
- <div v-else class="pl-1">
19
- <component :is="getComponentForPath(path)" :value="getValueForPath(path.path)"></component>
20
- </div>
21
- </div>
2
+ <document-property
3
+ :document="document"
4
+ :schemaPaths="schemaPaths"
5
+ :editting="editting"
6
+ :changes="changes"
7
+ :invalid="invalid"></document-property>
22
8
  <div v-for="path in virtuals" class="mb-2">
23
9
  <div class="p-1 mb-1 bg-slate-100">
24
10
  {{path.name}}
@@ -10,43 +10,14 @@ appendCSS(require('./document-details.css'));
10
10
  module.exports = app => app.component('document-details', {
11
11
  template,
12
12
  props: ['document', 'schemaPaths', 'editting', 'changes', 'invalid'],
13
- methods: {
14
- getComponentForPath(schemaPath) {
15
- if (schemaPath.instance === 'Array') {
16
- return 'detail-array';
17
- }
18
- return 'detail-default';
19
- },
20
- getEditComponentForPath(path) {
21
- if (path.instance == 'Date') {
22
- return 'edit-date';
23
- }
24
- if (path.instance == 'Number') {
25
- return 'edit-number';
26
- }
27
- if (path.instance === 'Array') {
28
- return 'edit-array';
29
- }
30
- if (path.instance === 'Embedded') {
31
- return 'edit-subdocument';
32
- }
33
- return 'edit-default';
34
- },
35
- getValueForPath(path) {
36
- return mpath.get(path, this.document);
37
- },
38
- getEditValueForPath({ path }) {
39
- if (!this.changes) {
40
- return;
41
- }
42
- return path in this.changes ? this.changes[path] : mpath.get(path, this.document);
43
- }
44
- },
45
13
  computed: {
46
14
  virtuals() {
47
15
  if (this.schemaPaths == null) {
48
16
  return [];
49
17
  }
18
+ if (this.document == null) {
19
+ return [];
20
+ }
50
21
  const exists = this.schemaPaths.map(x => x.path);
51
22
  const docKeys = Object.keys(this.document);
52
23
  const result = [];
@@ -57,6 +28,6 @@ module.exports = app => app.component('document-details', {
57
28
  }
58
29
 
59
30
  return result;
60
- }
31
+ },
61
32
  }
62
33
  })
@@ -0,0 +1,23 @@
1
+ .document-details {
2
+ width: 100%;
3
+ }
4
+
5
+ .document-details .value {
6
+ padding-top: 10px;
7
+ padding-bottom: 10px;
8
+ }
9
+
10
+ .document-details .path-key {
11
+ background-color: #f0f0f0;
12
+ margin-bottom: 0.5em;
13
+ }
14
+
15
+ .document-details .path-type {
16
+ color: rgba(0,0,0,.36);
17
+ font-size: 0.8em;
18
+ }
19
+
20
+ .document-details .date-position {
21
+ float: right;
22
+ margin-top: -7px;
23
+ }
@@ -0,0 +1,47 @@
1
+ <div>
2
+ <div v-for="path in schemaPaths" class="value">
3
+ <div class="relative path-key p-1 flex">
4
+ <div class="grow">
5
+ {{path.path}}
6
+ <span class="path-type">
7
+ ({{(path.instance || 'unknown').toLowerCase()}})
8
+ </span>
9
+ </div>
10
+ <div v-if="editting && path.instance === 'Date'" class="flex gap-1.5">
11
+ <div
12
+ @click="dateType = 'picker'"
13
+ :class="dateType === 'picker' ? 'bg-white' : ''"
14
+ class="self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer">
15
+ <div
16
+ :class="dateType === 'picker' ? 'text-sky-600' : 'text-black'"
17
+ class="text-xs font-medium font-['Lato'] capitalize leading-tight">
18
+ Date Picker
19
+ </div>
20
+ </div>
21
+ <div
22
+ @click="dateType = 'iso'"
23
+ :class="dateType === 'iso' ? 'bg-white' : ''"
24
+ class="self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer">
25
+ <div
26
+ :class="dateType === 'iso' ? 'text-sky-600' : 'text-black'"
27
+ class="text-xs font-medium font-['Lato'] capitalize leading-tight">
28
+ ISO String
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div v-if="editting && path.path !== '_id'" class="pl-1">
34
+ <component
35
+ :is="getEditComponentForPath(path)"
36
+ :value="getEditValueForPath(path)"
37
+ :format="dateType"
38
+ @input="changes[path.path] = $event; delete invalid[path.path];"
39
+ @error="invalid[path.path] = $event;"
40
+ >
41
+ </component>
42
+ </div>
43
+ <div v-else class="pl-1">
44
+ <component :is="getComponentForPath(path)" :value="getValueForPath(path.path)"></component>
45
+ </div>
46
+ </div>
47
+ </div>
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const mpath = require('mpath');
4
+ const template = require('./document-property.html')
5
+
6
+ const appendCSS = require('../../appendCSS');
7
+
8
+ appendCSS(require('./document-property.css'));
9
+
10
+ module.exports = app => app.component('document-property', {
11
+ template,
12
+ data: function() {
13
+ return {
14
+ dateType: 'picker' // picker, iso
15
+ }
16
+ },
17
+ props: ['document', 'schemaPaths', 'editting', 'changes', 'invalid'],
18
+ methods: {
19
+ getComponentForPath(schemaPath) {
20
+ if (schemaPath.instance === 'Array') {
21
+ return 'detail-array';
22
+ }
23
+ return 'detail-default';
24
+ },
25
+ getEditComponentForPath(path) {
26
+ if (path.instance == 'Date') {
27
+ return 'edit-date';
28
+ }
29
+ if (path.instance == 'Number') {
30
+ return 'edit-number';
31
+ }
32
+ if (path.instance === 'Array') {
33
+ return 'edit-array';
34
+ }
35
+ if (path.instance === 'Embedded') {
36
+ return 'edit-subdocument';
37
+ }
38
+ return 'edit-default';
39
+ },
40
+ getValueForPath(path) {
41
+ if (this.document == null) {
42
+ return undefined;
43
+ }
44
+ return mpath.get(path, this.document);
45
+ },
46
+ getEditValueForPath({ path }) {
47
+ if (!this.changes) {
48
+ return;
49
+ }
50
+ if (!this.document) {
51
+ return;
52
+ }
53
+ return path in this.changes ? this.changes[path] : mpath.get(path, this.document);
54
+ }
55
+ }
56
+ })
@@ -1,20 +1,4 @@
1
1
  <div>
2
- <div class="flex mb-[-1px] w-64 justify-end">
3
- <button
4
- @click="inputType = 'picker'"
5
- type="button"
6
- class="relative inline-flex items-center rounded-none p-1 rounded-tl-md text-sm text-gray-800 ring-1 ring-inset ring-gray-300 hover:bg-gray-200 focus:z-10"
7
- :class="inputType === 'picker' ? 'bg-gray-200' : 'bg-white'">
8
- Date Picker
9
- </button>
10
- <button
11
- @click="inputType = 'iso'"
12
- type="button"
13
- class="relative -ml-px inline-flex items-center p-1 rounded-none rounded-tr-md text-sm text-gray-800 ring-1 ring-inset ring-gray-300 hover:bg-gray-200 focus:z-10"
14
- :class="inputType === 'iso' ? 'bg-gray-200' : 'bg-white'">
15
- ISO String Input
16
- </button>
17
- </div>
18
- <input v-if="inputType == 'picker'" class="w-64 h-8 border border-gray-300 outline-0" type="datetime-local" :value="valueAsLocalString" @input="$emit('input', $event.target.value)">
19
- <input v-if="inputType == 'iso'" type="text" class="w-64 h-8 border border-gray-300 outline-0" :value="valueAsISOString" @input="$emit('input', $event.target.value)">
2
+ <input v-if="dateSelection == 'picker'" class="w-64 h-8 border border-gray-300 outline-0" type="datetime-local" :value="valueAsLocalString" @input="$emit('input', $event.target.value)">
3
+ <input v-if="dateSelection == 'iso'" type="text" class="w-64 h-8 border border-gray-300 outline-0" :value="valueAsISOString" @input="$emit('input', $event.target.value)">
20
4
  </div>
@@ -4,13 +4,11 @@ const template = require('./edit-date.html');
4
4
 
5
5
  module.exports = app => app.component('edit-date', {
6
6
  template: template,
7
- props: ['value'],
7
+ props: ['value', 'format'],
8
8
  emits: ['input'],
9
- data: function() {
10
- return {
11
- inputType: 'picker' // picker, iso
12
- }
13
- },
9
+ data: () => ({
10
+ inputType: ''
11
+ }),
14
12
  computed: {
15
13
  valueAsLocalString() {
16
14
  if (this.value == null) {
@@ -35,6 +33,9 @@ module.exports = app => app.component('edit-date', {
35
33
  }
36
34
  const date = new Date(this.value);
37
35
  return date.toISOString();
36
+ },
37
+ dateSelection() {
38
+ return this.format;
38
39
  }
39
40
  }
40
41
  });
@@ -26,6 +26,7 @@ require('./detail-default/detail-default')(app);
26
26
  require('./document/document')(app);
27
27
  require('./document/confirm-changes/confirm-changes')(app);
28
28
  require('./document-details/document-details')(app);
29
+ require('./document-details/document-property/document-property')(app);
29
30
  require('./edit-array/edit-array')(app);
30
31
  require('./edit-default/edit-default')(app);
31
32
  require('./edit-number/edit-number')(app);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "dependencies": {
5
5
  "archetype": "0.13.0",
6
6
  "csv-stringify": "6.3.0",