| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| { | ||
| "name": "fantasiavault", | ||
| "version": "0.0.1", | ||
| "description": "A database manager for world building", | ||
| "productName": "Fantasia archive", | ||
| "author": "Elvanos <elvanos66@gmail.com>", | ||
| "private": true, | ||
| "scripts": { | ||
| "lint": "eslint --ext .js,.ts,.vue ./", | ||
| "test": "echo \"No test specified\" && exit 0", | ||
| "dev": "quasar dev -m electron", | ||
| "build": "quasar build -m electron" | ||
| }, | ||
| "dependencies": { | ||
| "@quasar/extras": "^1.0.0", | ||
| "axios": "^0.18.1", | ||
| "core-js": "^3.6.5", | ||
| "katex": "^0.12.0", | ||
| "lodash": "^4.17.20", | ||
| "mermaid": "^8.8.4", | ||
| "pouchdb": "^7.2.2", | ||
| "pouchdb-adapter-idb": "^7.2.2", | ||
| "pouchdb-find": "^7.2.2", | ||
| "pouchdb-load": "^1.4.6", | ||
| "pouchdb-purge-on-delete": "^7.0.1", | ||
| "pouchdb-replication-stream": "^1.2.9", | ||
| "quasar": "^1.15.0", | ||
| "quasar-tiptap": "^1.9.1", | ||
| "tiptap": "^1.31.0", | ||
| "tiptap-extensions": "^1.34.0", | ||
| "vue-class-component": "^7.2.2", | ||
| "vue-codemirror": "^4.0.6", | ||
| "vue-i18n": "^8.0.0", | ||
| "vue-property-decorator": "^8.3.0", | ||
| "vuex-class": "^0.3.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@quasar/app": "^2.0.0", | ||
| "@types/lodash": "^4.14.166", | ||
| "@types/node": "^10.17.15", | ||
| "@types/pouchdb": "^6.4.0", | ||
| "@typescript-eslint/eslint-plugin": "^3.3.0", | ||
| "@typescript-eslint/parser": "^3.3.0", | ||
| "babel-eslint": "^10.0.1", | ||
| "devtron": "^1.4.0", | ||
| "electron": "^9.4.0", | ||
| "electron-debug": "^3.2.0", | ||
| "electron-devtools-installer": "^3.1.1", | ||
| "eslint": "^6.8.0", | ||
| "eslint-config-standard": "^14.1.0", | ||
| "eslint-loader": "^3.0.3", | ||
| "eslint-plugin-import": "^2.14.0", | ||
| "eslint-plugin-node": "^11.0.0", | ||
| "eslint-plugin-promise": "^4.0.1", | ||
| "eslint-plugin-standard": "^4.0.0", | ||
| "eslint-plugin-vue": "^6.1.2", | ||
| "quasar-app-extension-qdraggabletree": "0.0.5", | ||
| "typescript": "^3.8.3" | ||
| }, | ||
| "browserslist": [ | ||
| "last 10 Chrome versions", | ||
| "last 10 Firefox versions", | ||
| "last 4 Edge versions", | ||
| "last 7 Safari versions", | ||
| "last 8 Android versions", | ||
| "last 8 ChromeAndroid versions", | ||
| "last 8 FirefoxAndroid versions", | ||
| "last 10 iOS versions", | ||
| "last 5 Opera versions" | ||
| ], | ||
| "engines": { | ||
| "node": ">= 10.18.1", | ||
| "npm": ">= 6.13.4", | ||
| "yarn": ">= 1.21.1" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| /* | ||
| * This file runs in a Node context (it's NOT transpiled by Babel), so use only | ||
| * the ES6 features that are supported by your Node version. https://node.green/ | ||
| */ | ||
|
|
||
| // Configuration for your app | ||
| // https://quasar.dev/quasar-cli/quasar-conf-js | ||
| /* eslint-env node */ | ||
| /* eslint-disable @typescript-eslint/no-var-requires */ | ||
| const { configure } = require("quasar/wrappers") | ||
|
|
||
| module.exports = configure(function (ctx) { | ||
| return { | ||
| // https://quasar.dev/quasar-cli/supporting-ts | ||
| supportTS: { | ||
| tsCheckerConfig: { | ||
| eslint: true | ||
| } | ||
| }, | ||
|
|
||
| // https://quasar.dev/quasar-cli/prefetch-feature | ||
| // preFetch: true, | ||
|
|
||
| // app boot file (/src/boot) | ||
| // --> boot files are part of "main.js" | ||
| // https://quasar.dev/quasar-cli/boot-files | ||
| boot: [ | ||
| "i18n", | ||
| "axios" | ||
| ], | ||
|
|
||
| // https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css | ||
| css: [ | ||
| "app.scss" | ||
| ], | ||
|
|
||
| // https://github.com/quasarframework/quasar/tree/dev/extras | ||
| extras: [ | ||
| // 'ionicons-v4', | ||
| "mdi-v5", | ||
| // 'fontawesome-v5', | ||
| // 'eva-icons', | ||
| // 'themify', | ||
| // 'line-awesome', | ||
| // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! | ||
|
|
||
| "roboto-font", // optional, you are not bound to it | ||
| "material-icons" // optional, you are not bound to it | ||
| ], | ||
|
|
||
| // Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build | ||
| build: { | ||
| vueRouterMode: "history", // available values: 'hash', 'history' | ||
|
|
||
| // transpile: false, | ||
|
|
||
| // Add dependencies for transpiling with Babel (Array of string/regex) | ||
| // (from node_modules, which are by default not transpiled). | ||
| // Applies only if "transpile" is set to true. | ||
| // transpileDependencies: [], | ||
|
|
||
| // rtl: false, // https://quasar.dev/options/rtl-support | ||
| // preloadChunks: true, | ||
| // showProgress: false, | ||
| // gzip: true, | ||
| // analyze: true, | ||
|
|
||
| // Options below are automatically set depending on the env, set them if you want to override | ||
| // extractCSS: false, | ||
|
|
||
| // https://quasar.dev/quasar-cli/handling-webpack | ||
| extendWebpack (cfg) { | ||
| // linting is slow in TS projects, we execute it only for production builds | ||
| if (ctx.prod) { | ||
| cfg.module.rules.push({ | ||
| enforce: "pre", | ||
| test: /\.(js|vue)$/, | ||
| loader: "eslint-loader", | ||
| exclude: /node_modules/ | ||
| }) | ||
| } | ||
| } | ||
| }, | ||
|
|
||
| // Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer | ||
| devServer: { | ||
| https: false, | ||
| port: 8080, | ||
| open: true // opens browser window automatically | ||
| }, | ||
|
|
||
| // https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework | ||
| framework: { | ||
| iconSet: "material-icons", // Quasar icon set | ||
| lang: "en-us", // Quasar language pack | ||
| config: {}, | ||
|
|
||
| // Possible values for "importStrategy": | ||
| // * 'auto' - (DEFAULT) Auto-import needed Quasar components & directives | ||
| // * 'all' - Manually specify what to import | ||
| importStrategy: "auto", | ||
|
|
||
| // For special cases outside of where "auto" importStrategy can have an impact | ||
| // (like functional components as one of the examples), | ||
| // you can manually specify Quasar components/directives to be available everywhere: | ||
| // | ||
| components: [ | ||
| 'QAvatar', | ||
| 'QBtn', | ||
| 'QBtnDropdown', | ||
| 'QIcon', | ||
| 'QInput', | ||
| 'QItem', | ||
| 'QItemLabel', | ||
| 'QItemSection', | ||
| 'QList', | ||
| 'QMenu', | ||
| 'QScrollArea', | ||
| 'QScrollObserver', | ||
| 'QSeparator', | ||
| 'QSpinner', | ||
| 'QTooltip', | ||
| ], | ||
| directives: [ | ||
| 'ClosePopup' | ||
| ], | ||
|
|
||
| // Quasar plugins | ||
| plugins: ['AppFullscreen', 'Notify'] | ||
| }, | ||
|
|
||
| // animations: 'all', // --- includes all animations | ||
| // https://quasar.dev/options/animations | ||
| animations: 'all', | ||
|
|
||
| // https://quasar.dev/quasar-cli/developing-ssr/configuring-ssr | ||
| ssr: { | ||
| pwa: false | ||
| }, | ||
|
|
||
| // https://quasar.dev/quasar-cli/developing-pwa/configuring-pwa | ||
| pwa: { | ||
| workboxPluginMode: "GenerateSW", // 'GenerateSW' or 'InjectManifest' | ||
| workboxOptions: {}, // only for GenerateSW | ||
| manifest: { | ||
| name: "Fantasia archive", | ||
| short_name: "Fantasia archive", | ||
| description: "A database manager for world building", | ||
| display: "standalone", | ||
| orientation: "portrait", | ||
| background_color: "#ffffff", | ||
| theme_color: "#027be3", | ||
| icons: [ | ||
| { | ||
| src: "icons/icon-128x128.png", | ||
| sizes: "128x128", | ||
| type: "image/png" | ||
| }, | ||
| { | ||
| src: "icons/icon-192x192.png", | ||
| sizes: "192x192", | ||
| type: "image/png" | ||
| }, | ||
| { | ||
| src: "icons/icon-256x256.png", | ||
| sizes: "256x256", | ||
| type: "image/png" | ||
| }, | ||
| { | ||
| src: "icons/icon-384x384.png", | ||
| sizes: "384x384", | ||
| type: "image/png" | ||
| }, | ||
| { | ||
| src: "icons/icon-512x512.png", | ||
| sizes: "512x512", | ||
| type: "image/png" | ||
| } | ||
| ] | ||
| } | ||
| }, | ||
|
|
||
| // Full list of options: https://quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova | ||
| cordova: { | ||
| // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing | ||
| }, | ||
|
|
||
| // Full list of options: https://quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor | ||
| capacitor: { | ||
| hideSplashscreen: true | ||
| }, | ||
|
|
||
| // Full list of options: https://quasar.dev/quasar-cli/developing-electron-apps/configuring-electron | ||
| electron: { | ||
| bundler: "packager", // 'packager' or 'builder' | ||
|
|
||
| packager: { | ||
| // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options | ||
|
|
||
| // OS X / Mac App Store | ||
| // appBundleId: '', | ||
| // appCategoryType: '', | ||
| // osxSign: '', | ||
| // protocol: 'myapp://path', | ||
|
|
||
| // Windows only | ||
| // win32metadata: { ... } | ||
| }, | ||
|
|
||
| builder: { | ||
| // https://www.electron.build/configuration/configuration | ||
|
|
||
| appId: "fantasiavault" | ||
| }, | ||
|
|
||
| // More info: https://quasar.dev/quasar-cli/developing-electron-apps/node-integration | ||
| nodeIntegration: true, | ||
|
|
||
| extendWebpack (/* cfg */) { | ||
| // do something with Electron main process Webpack cfg | ||
| // chainWebpack also available besides this extendWebpack | ||
| } | ||
| } | ||
| } | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "qdraggabletree": {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // THIS FEATURE-FLAG FILE IS AUTOGENERATED, | ||
| // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING | ||
| import "quasar/dist/types/feature-flag"; | ||
|
|
||
| declare module "quasar/dist/types/feature-flag" { | ||
| interface QuasarFeatureFlags { | ||
| electron: true; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /** | ||
| * This file is used specifically and only for development. It installs | ||
| * `electron-debug` & `vue-devtools`. There shouldn't be any need to | ||
| * modify this file, but it can be used to extend your development | ||
| * environment. | ||
| */ | ||
|
|
||
| import electronDebug from 'electron-debug' | ||
| import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' | ||
| import { app, BrowserWindow } from 'electron' | ||
|
|
||
| app.whenReady().then(() => { | ||
| // allow for a small delay for mainWindow to be created | ||
| setTimeout(() => { | ||
| // Install `electron-debug` with `devtron` | ||
| electronDebug({ showDevTools: false }) | ||
|
|
||
| // Install vuejs devtools | ||
| installExtension(VUEJS_DEVTOOLS) | ||
| .then(name => { | ||
| console.log(`Added Extension: ${name}`) | ||
| // get main window | ||
| const win = BrowserWindow.getFocusedWindow() | ||
| if (win) { | ||
| win.webContents.on('did-frame-finish-load', () => { | ||
| win.webContents.once('devtools-opened', () => { | ||
| win.webContents.focus() | ||
| }) | ||
| // open electron debug | ||
| console.log('Opening dev tools') | ||
| win.webContents.openDevTools() | ||
| }) | ||
| } | ||
| }) | ||
| .catch(err => { | ||
| console.log('An error occurred: ', err) | ||
| }) | ||
| }, 250) | ||
| }) | ||
|
|
||
| import './electron-main' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import { app, BrowserWindow, nativeTheme, Menu, MenuItem } from 'electron' | ||
|
|
||
| try { | ||
| if (process.platform === 'win32' && nativeTheme.shouldUseDarkColors === true) { | ||
| require('fs').unlinkSync(require('path').join(app.getPath('userData'), 'DevTools Extensions')) | ||
| } | ||
| } catch (_) { } | ||
|
|
||
| /** | ||
| * Set `__statics` path to static files in production; | ||
| * The reason we are setting it here is that the path needs to be evaluated at runtime | ||
| */ | ||
| if (process.env.PROD) { | ||
| global.__statics = __dirname | ||
| } | ||
|
|
||
| let mainWindow | ||
|
|
||
| function createWindow () { | ||
| /** | ||
| * Initial window options | ||
| */ | ||
| mainWindow = new BrowserWindow({ | ||
| useContentSize: true, | ||
| webPreferences: { | ||
| // Change from /quasar.conf.js > electron > nodeIntegration; | ||
| // More info: https://quasar.dev/quasar-cli/developing-electron-apps/node-integration | ||
| nodeIntegration: process.env.QUASAR_NODE_INTEGRATION, | ||
| nodeIntegrationInWorker: process.env.QUASAR_NODE_INTEGRATION, | ||
| disableBlinkFeatures: "Auxclick", | ||
| enableRemoteModule: true | ||
|
|
||
| // More info: /quasar-cli/developing-electron-apps/electron-preload-script | ||
| // preload: path.resolve(__dirname, 'electron-preload.js') | ||
| } | ||
| }) | ||
|
|
||
| mainWindow.setMenu(null) | ||
| mainWindow.maximize() | ||
|
|
||
| mainWindow.loadURL(process.env.APP_URL) | ||
|
|
||
| mainWindow.on('closed', () => { | ||
| mainWindow = null | ||
| }) | ||
|
|
||
| mainWindow.webContents.on('will-navigate', event => { | ||
| event.preventDefault() | ||
| }) | ||
|
|
||
| mainWindow.webContents.on('new-window', event => { | ||
| event.preventDefault() | ||
| }) | ||
|
|
||
| mainWindow.webContents.on('context-menu', (event, params) => { | ||
| const menu = new Menu() | ||
|
|
||
| // Add each spelling suggestion | ||
| for (const suggestion of params.dictionarySuggestions) { | ||
| menu.append(new MenuItem({ | ||
| label: suggestion, | ||
| click: () => mainWindow.webContents.replaceMisspelling(suggestion) | ||
| })) | ||
| } | ||
|
|
||
| // Allow users to add the misspelled word to the dictionary | ||
| if (params.misspelledWord) { | ||
| menu.append( | ||
| new MenuItem({ | ||
| label: 'Add to dictionary', | ||
| click: () => mainWindow.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord) | ||
| }) | ||
| ) | ||
| } | ||
|
|
||
| menu.popup() | ||
| }) | ||
| } | ||
|
|
||
| app.on('ready', createWindow) | ||
|
|
||
| app.on('window-all-closed', () => { | ||
| if (process.platform !== 'darwin') { | ||
| app.quit() | ||
| } | ||
| }) | ||
|
|
||
| app.on('activate', () => { | ||
| if (mainWindow === null) { | ||
| createWindow() | ||
| } | ||
| }) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| <template> | ||
| <div id="q-app"> | ||
| <router-view /> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import BaseClass from "src/BaseClass" | ||
| import { Component } from "vue-property-decorator" | ||
| @Component | ||
| export default class App extends BaseClass { | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| import { I_OpenedDocument } from "./interfaces/I_OpenedDocument" | ||
| import { Component, Vue } from "vue-property-decorator" | ||
| import { namespace } from "vuex-class" | ||
| import { remote } from "electron" | ||
| // @ts-ignore | ||
| import replicationStream from "pouchdb-replication-stream/dist/pouchdb.replication-stream.min.js" | ||
| // @ts-ignore | ||
| import load from "pouchdb-load" | ||
| import PouchDB from "pouchdb" | ||
| import fs from "fs" | ||
| import path from "path" | ||
|
|
||
| import { I_Blueprint } from "src/interfaces/I_Blueprint" | ||
| import { I_NewObjectTrigger } from "src/interfaces/I_NewObjectTrigger" | ||
| import { uid } from "quasar" | ||
| import { I_FieldRelationship } from "src/interfaces/I_FieldRelationship" | ||
|
|
||
| const Blueprints = namespace("blueprintsModule") | ||
| const OpenedDocuments = namespace("openedDocumentsModule") | ||
|
|
||
| @Component | ||
| export default class BaseClass extends Vue { | ||
| /****************************************************************/ | ||
| // Project management | ||
| /****************************************************************/ | ||
| async createNewProject (projectName: string) { | ||
| await this.removeCurrentProject() | ||
|
|
||
| const ProjectDB = new PouchDB("project-data") | ||
| const newProject = { _id: projectName } | ||
| await ProjectDB.put(newProject) | ||
|
|
||
| this.$router.push({ path: "/project" }).catch((e: {name: string}) => { | ||
| const errorName : string = e.name | ||
| if (errorName === "NavigationDuplicated") { | ||
| return | ||
| } | ||
| console.log(e) | ||
| }) | ||
| } | ||
|
|
||
| exportProject (projectName: string) { | ||
| /*eslint-disable */ | ||
| remote.dialog.showOpenDialog({ | ||
| properties: ["openDirectory"] | ||
| }).then(async (result) => { | ||
| const folderPath = result.filePaths[0] | ||
|
|
||
| PouchDB.plugin(replicationStream.plugin) | ||
| //@ts-ignore | ||
| PouchDB.adapter("writableStream", replicationStream.adapters.writableStream) | ||
|
|
||
| //@ts-ignore | ||
| const allDBS = await indexedDB.databases() | ||
|
|
||
| const DBnames: string[] = allDBS.map((db: {name: string}) => { | ||
| return db.name.replace("_pouch_", "") | ||
| }) | ||
|
|
||
| for (const db of DBnames) { | ||
| const CurrentDB = new PouchDB(db) | ||
|
|
||
| if (!fs.existsSync(`${folderPath}/${projectName}`)) { | ||
| fs.mkdirSync(`${folderPath}/${projectName}`) | ||
| } | ||
| const ws = fs.createWriteStream(`${folderPath}/${projectName}/${db}.txt`) | ||
|
|
||
| //@ts-ignore | ||
| await CurrentDB.dump(ws) | ||
| } | ||
| }).catch(err => { | ||
| console.log(err) | ||
| }) | ||
| /* eslint-enable */ | ||
| } | ||
|
|
||
| async removeCurrentProject () { | ||
| /*eslint-disable */ | ||
| //@ts-ignore | ||
| const allDBS = await indexedDB.databases() | ||
|
|
||
| const DBnames: string[] = allDBS.map((db: {name: string}) => { | ||
| return db.name.replace("_pouch_", "") | ||
| }) | ||
|
|
||
| for (const db of DBnames) { | ||
| const CurrentDB = new PouchDB(db) | ||
| await CurrentDB.destroy() | ||
| } | ||
| /* eslint-enable */ | ||
| } | ||
|
|
||
| openExistingProject () { | ||
| /*eslint-disable */ | ||
| remote.dialog.showOpenDialog({ | ||
| properties: ["openDirectory"] | ||
| }).then(async (result) => { | ||
|
|
||
| const folderPath = result.filePaths[0] | ||
|
|
||
| if(!folderPath){return} | ||
|
|
||
| await this.removeCurrentProject() | ||
|
|
||
| //@ts-ignore | ||
| PouchDB.plugin({ | ||
| loadIt: load.load | ||
| }) | ||
|
|
||
| const allFiles = fs.readdirSync(folderPath) | ||
|
|
||
| for (const file of allFiles) { | ||
| const currentDBName = path.parse(file).name | ||
| const CurrentDB = new PouchDB(currentDBName) | ||
|
|
||
| const fileContents = fs.readFileSync(`${folderPath}/${file}`, {encoding: 'utf8'}) | ||
| // @ts-ignore | ||
| await CurrentDB.loadIt(fileContents) | ||
|
|
||
| } | ||
| }).catch(err => { | ||
| console.log(err) | ||
| }) | ||
| /* eslint-enable */ | ||
| } | ||
|
|
||
| async retrieveCurrentProjectName () { | ||
| const ProjectDB = new PouchDB("project-data") | ||
| const projectData = await ProjectDB.allDocs({ include_docs: true }) | ||
| return projectData?.rows[0]?.id | ||
| } | ||
|
|
||
| /****************************************************************/ | ||
| // Blueprint management | ||
| /****************************************************************/ | ||
|
|
||
| @Blueprints.Getter("getAllBlueprints") SGET_allBlueprints !: I_Blueprint[] | ||
| @Blueprints.Getter("getBlueprint") SGET_blueprint!: (type: string) => I_Blueprint | ||
|
|
||
| @Blueprints.Mutation("setAllBlueprints") SSET_allBlueprints!: (input: I_Blueprint[]) => void | ||
|
|
||
| addNewObjectType (e: I_NewObjectTrigger) { | ||
| // console.log(e.id) | ||
|
|
||
| this.$router.push({ path: `/project/display-content/${e._id}/${uid()}` }).catch((e: {name: string}) => { | ||
| const errorName : string = e.name | ||
| if (errorName === "NavigationDuplicated") { | ||
| return | ||
| } | ||
| console.log(e) | ||
| }) | ||
| } | ||
|
|
||
| openExistingDocument (e:I_OpenedDocument | I_FieldRelationship) { | ||
| this.$router.push({ path: e.url }).catch((e: {name: string}) => { | ||
| const errorName : string = e.name | ||
| if (errorName === "NavigationDuplicated") { | ||
| return | ||
| } | ||
| console.log(e) | ||
| }) | ||
| } | ||
|
|
||
| /****************************************************************/ | ||
| // Open documents management | ||
| /****************************************************************/ | ||
|
|
||
| @OpenedDocuments.Getter("getAllDocuments") SGET_allOpenedDocuments !: {timestamp: string, docs: I_OpenedDocument[]} | ||
| @OpenedDocuments.Getter("getDocument") SGET_openedDocument!: (id: string) => I_OpenedDocument | ||
|
|
||
| @OpenedDocuments.Mutation("addDocument") SSET_addOpenedDocument!: (input: I_OpenedDocument) => void | ||
| @OpenedDocuments.Mutation("updateDocument") SSET_updateOpenedDocument!: (input: I_OpenedDocument) => void | ||
| @OpenedDocuments.Mutation("removeDocument") SSET_removeOpenedDocument!: (input: I_OpenedDocument) => void | ||
|
|
||
| retrieveFieldValue (fieldDataWrapper: I_OpenedDocument, fieldID: string) : string | [] | false | I_FieldRelationship { | ||
| const fieldData = fieldDataWrapper?.extraFields | ||
| if (!fieldData) { return false } | ||
| const fieldValue = fieldData.find(f => f.id === fieldID)?.value as unknown as string | ||
| return fieldValue | ||
| } | ||
|
|
||
| retrieveFieldLength (fieldDataWrapper: I_OpenedDocument, fieldID: string) : number | false { | ||
| /*eslint-disable */ | ||
| const fieldData = fieldDataWrapper?.extraFields | ||
| if (!fieldData) { return false } | ||
| const fieldValueLength = fieldData.find(f => f.id === fieldID)?.value.length as unknown as number | ||
| return fieldValueLength | ||
| /* eslint-enable */ | ||
| } | ||
|
|
||
| refreshRoute () { | ||
| const remainingDocuments = this.SGET_allOpenedDocuments.docs | ||
|
|
||
| if (remainingDocuments.length > 0) { | ||
| const lastDocument = remainingDocuments[remainingDocuments.length - 1] | ||
| const currentRoute = this.$router.currentRoute.path | ||
|
|
||
| const existingDocument = this.SGET_allOpenedDocuments.docs.find(e => { | ||
| return e.url === currentRoute | ||
| }) | ||
|
|
||
| // Prevent infite route cycling by checking if this actually exists in the open tabs | ||
| if (existingDocument) { return } | ||
|
|
||
| const newRoute = `/project/display-content/${lastDocument.type}/${lastDocument._id}` | ||
| if (currentRoute !== newRoute) { | ||
| this.$router.push({ path: newRoute }).catch(e => console.log(e)) | ||
| } | ||
| } else { | ||
| this.$router.push({ path: "/project" }).catch((e: {name: string}) => { | ||
| if (e.name !== "NavigationDuplicated") { console.log(e) } | ||
| } | ||
| ) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import axios, { AxiosInstance } from "axios" | ||
| import { boot } from "quasar/wrappers" | ||
|
|
||
| declare module "vue/types/vue" { | ||
| interface Vue { | ||
| $axios: AxiosInstance; | ||
| } | ||
| } | ||
|
|
||
| export default boot(({ Vue }) => { | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
| Vue.prototype.$axios = axios | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { boot } from "quasar/wrappers" | ||
| import messages from "src/i18n" | ||
| import Vue from "vue" | ||
| import VueI18n from "vue-i18n" | ||
|
|
||
| declare module "vue/types/vue" { | ||
| interface Vue { | ||
| i18n: VueI18n; | ||
| } | ||
| } | ||
|
|
||
| Vue.use(VueI18n) | ||
|
|
||
| export const i18n = new VueI18n({ | ||
| locale: "en-us", | ||
| fallbackLocale: "en-us", | ||
| messages | ||
| }) | ||
|
|
||
| export default boot(({ app }) => { | ||
| // Set i18n instance on app | ||
| app.i18n = i18n | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| <template> | ||
| <div> | ||
| <div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md"> | ||
| <q-icon v-if="inputIcon" :name="inputIcon" size="20px" class="q-mr-md"/> | ||
| {{inputDataBluePrint.name}} | ||
| </div> | ||
|
|
||
| <q-list | ||
| v-if="!editMode" | ||
| dense> | ||
| <q-item v-for="(input,index) in localInput" :key="index"> | ||
| <q-item-section side> | ||
| <q-icon name="mdi-menu-right" /> | ||
| </q-item-section> | ||
| <q-item-section> | ||
| <span> | ||
| {{input.value}} {{(inputAffix) ? inputAffix : ''}} | ||
| <span v-if="localInput[index].affix" class="inline-block q-ml-xs text-italic text-lowercase"> | ||
| ({{localInput[index].affix}}) | ||
| </span> | ||
| </span> | ||
| </q-item-section> | ||
| </q-item> | ||
| </q-list> | ||
|
|
||
| <div v-if="editMode"> | ||
| <div class="row q-mb-sm" | ||
| v-for="(singleInput,index) in localInput" | ||
| :key="index" | ||
| > | ||
| <div class="col"> | ||
| <q-input | ||
| v-model="localInput[index].value" | ||
| dense | ||
| @keyup="signalInput" | ||
| outlined | ||
| :suffix="(inputAffix) ? inputAffix : ''" | ||
| > | ||
| </q-input> | ||
| </div> | ||
|
|
||
| <div | ||
| class="q-ml-lg justify-end flex" | ||
| style="max-width: 220px; width: 220px;" | ||
| v-if="hasExtraInput"> | ||
| <q-select | ||
| style="width: 100%;" | ||
| dense | ||
| :options="localExtraInput" | ||
| use-input | ||
| :hide-dropdown-icon="!editMode" | ||
| :outlined="editMode" | ||
| :borderless="!editMode" | ||
| :readonly="!editMode" | ||
| input-debounce="0" | ||
| new-value-mode="add" | ||
| v-model="localInput[index].affix" | ||
| /> | ||
| </div> | ||
|
|
||
| <div class="col-2 justify-end flex"> | ||
| <q-btn | ||
| v-if="editMode" | ||
| color="red" | ||
| @click="removeFromList(index)" | ||
| label="Remove" /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div class="row q-mt-lg" v-if="editMode"> | ||
| <div class="col justify-start flex"> | ||
| <q-btn color="primary" :label="`Add new`" @click="addNewInput" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <q-separator color="grey q-mt-lg" /> | ||
|
|
||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Emit, Prop, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import { I_ExtraFields } from "src/interfaces/I_Blueprint" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class Field_List extends BaseClass { | ||
| @Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields | ||
| @Prop({ default: () => { return [] } }) readonly inputDataValue!: { | ||
| value: string | ||
| affix?: string | ||
| }[] | ||
| @Prop() readonly isNew!: boolean | ||
| @Prop() readonly editMode!: boolean | ||
| changedInput = false | ||
| localInput = [] as { | ||
| value: string | ||
| affix?: string | ||
| }[] | ||
| localExtraInput = [] | ||
| @Watch("inputDataValue", { deep: true, immediate: true }) | ||
| reactToInputChanges () { | ||
| this.localInput = (this.inputDataValue) ? this.inputDataValue : [] | ||
| } | ||
| get inputAffix () { | ||
| return (this.inputDataBluePrint?.predefinedListExtras?.affix) || "" | ||
| } | ||
| removeFromList (index: number) { | ||
| this.localInput.splice(index, 1) | ||
| this.signalInput() | ||
| } | ||
| get inputIcon () { | ||
| return this.inputDataBluePrint?.icon | ||
| } | ||
| get hasExtraInput () { | ||
| // @ts-ignore | ||
| this.localExtraInput = this.inputDataBluePrint?.predefinedListExtras?.extraSelectValueList | ||
| return this.inputDataBluePrint?.predefinedListExtras?.extraSelectValueList | ||
| } | ||
| @Emit() | ||
| signalInput () { | ||
| this.changedInput = true | ||
| return this.localInput | ||
| } | ||
| addNewInput () { | ||
| this.localInput.push({ | ||
| value: "", | ||
| affix: "" | ||
| }) | ||
| this.signalInput() | ||
| } | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| <template> | ||
| <div> | ||
| <div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md"> | ||
| <q-icon v-if="inputIcon" :name="inputIcon" size="20px" class="q-mr-md"/> | ||
| {{inputDataBluePrint.name}} | ||
| <q-icon v-if="isOneWayRelationship" name="mdi-arrow-right-bold" size="16px" class="q-ml-md"> | ||
| <q-tooltip> | ||
| This is a one-way relationship. <br> Editing this value <b>will not</b> have effect on the connected document/s. | ||
| </q-tooltip> | ||
| </q-icon> | ||
| <q-icon v-if="!isOneWayRelationship" name="mdi-arrow-left-right-bold" size="16px" class="q-ml-md"> | ||
| <q-tooltip> | ||
| This is a two-way relationship. <br> Editing this value <b>will</b> also effect the connected document/s. | ||
| </q-tooltip> | ||
| </q-icon> | ||
|
|
||
| </div> | ||
|
|
||
| <q-list | ||
| v-if="!editMode && localInput" | ||
| dense> | ||
| <q-item | ||
| v-for="single in localInput" | ||
| :key="single._id" | ||
| clickable | ||
| class="text-primary" | ||
| @click="openExistingDocument(single)"> | ||
| <q-item-section> | ||
| {{single.label}} | ||
| </q-item-section> | ||
| </q-item> | ||
| </q-list> | ||
|
|
||
| <div class="flex" v-if="editMode"> | ||
| <q-select | ||
| style="flex-grow: 1;" | ||
| dense | ||
| :options="filteredInput" | ||
| use-input | ||
| outlined | ||
| use-chips | ||
| multiple | ||
| input-debounce="0" | ||
| v-model="localInput" | ||
| @filter="filterSelect" | ||
| @input="signalInput" | ||
| > | ||
| <template v-slot:prepend v-if="inputIcon"> | ||
| <q-icon :name="inputIcon" /> | ||
| </template> | ||
| <template v-slot:option="{ itemProps, itemEvents, opt }"> | ||
| <q-item | ||
| v-bind="itemProps" | ||
| v-on="itemEvents" | ||
| > | ||
| <q-item-section> | ||
| <q-item-label v-html="opt.label" ></q-item-label> | ||
| </q-item-section> | ||
| <q-tooltip v-if='opt.disable'> | ||
| This option is unavailable for selection as it is already paired to another. | ||
| </q-tooltip> | ||
| </q-item> | ||
| </template> | ||
| </q-select> | ||
|
|
||
| </div> | ||
|
|
||
| <q-separator color="grey q-mt-lg" /> | ||
|
|
||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Emit, Prop, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import PouchDB from "pouchdb" | ||
| import { I_ShortenedDocument } from "src/interfaces/I_OpenedDocument" | ||
| import { I_ExtraFields } from "src/interfaces/I_Blueprint" | ||
| import { I_FieldRelationship } from "src/interfaces/I_FieldRelationship" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class Field_SingleRelationship extends BaseClass { | ||
| @Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields | ||
| @Prop({ default: [] }) readonly inputDataValue!: I_FieldRelationship[] | ||
| @Prop({ default: "" }) readonly currentId!: "" | ||
| @Prop() readonly isNew!: boolean | ||
| @Prop() readonly editMode!: boolean | ||
| changedInput = false | ||
| localInput = [] as unknown as I_FieldRelationship[] | ||
| @Watch("inputDataValue", { deep: true, immediate: true }) | ||
| reactToInputChanges () { | ||
| // @ts-ignore | ||
| this.localInput = (this.inputDataValue) ? this.inputDataValue : [] | ||
| this.reloadObjectListAndCheckIfValueExists().catch(e => console.log(e)) | ||
| } | ||
| get inputIcon () { | ||
| return this.inputDataBluePrint?.icon | ||
| } | ||
| extraInput: I_FieldRelationship[] = [] | ||
| filteredInput: I_FieldRelationship[] = [] | ||
| filterSelect (val: string, update: (e: () => void) => void) { | ||
| if (val === "") { | ||
| update(() => { | ||
| this.filteredInput = this.extraInput | ||
| }) | ||
| return | ||
| } | ||
| update(() => { | ||
| const needle = val.toLowerCase() | ||
| this.filteredInput = this.extraInput.filter(v => v.label.toLowerCase().indexOf(needle) > -1) | ||
| }) | ||
| } | ||
| @Watch("inputDataBluePrint", { deep: true, immediate: true }) | ||
| reactToBlueprintChanges () { | ||
| this.reloadObjectListAndCheckIfValueExists().catch(e => console.log(e)) | ||
| } | ||
| @Watch("currentId") | ||
| reactToIDChanges () { | ||
| this.reloadObjectListAndCheckIfValueExists().catch(e => console.log(e)) | ||
| } | ||
| get isOneWayRelationship () { | ||
| return (this.inputDataBluePrint.type === "singleToNoneRelationship" || this.inputDataBluePrint.type === "manyToNoneRelationship") | ||
| } | ||
| async reloadObjectListAndCheckIfValueExists () { | ||
| if (this.inputDataBluePrint?.relationshipSettings && this.currentId.length > 0) { | ||
| const CurrentObjectDB = new PouchDB(this.inputDataBluePrint.relationshipSettings.connectedObjectType) | ||
| const allObjects = (await CurrentObjectDB.allDocs({ include_docs: true })).rows.map((doc) => { | ||
| const objectDoc = doc.doc as unknown as I_ShortenedDocument | ||
| const pairedField = (this.inputDataBluePrint?.relationshipSettings?.connectedField) || "" | ||
| let isDisabled = false | ||
| if (pairedField.length > 0) { | ||
| const pairedFieldObject = objectDoc.extraFields.find(f => f.id === pairedField) | ||
| const pairingType = this.inputDataBluePrint.type | ||
| if (typeof pairedFieldObject?.value !== "string" && pairedFieldObject?.value !== null && pairingType === "manyToSingleRelationship") { | ||
| isDisabled = true | ||
| } | ||
| } | ||
| return { | ||
| _id: objectDoc._id, | ||
| value: objectDoc._id, | ||
| type: objectDoc.type, | ||
| disable: isDisabled, | ||
| url: `/project/display-content/${objectDoc.type}/${objectDoc._id}`, | ||
| label: objectDoc.extraFields.find(e => e.id === "name")?.value, | ||
| pairedField: pairedField | ||
| } | ||
| }) as unknown as I_FieldRelationship[] | ||
| const allObjectsWithoutCurrent: I_FieldRelationship[] = allObjects.filter((obj) => obj._id !== this.currentId) | ||
| this.localInput = (Array.isArray(this.localInput)) ? this.localInput : [] | ||
| this.localInput.forEach((s, index) => { | ||
| if (s._id) { | ||
| if (!allObjectsWithoutCurrent.find(e => e._id === s._id)) { | ||
| // @ts-ignore | ||
| this.localInput.splice(index, 1) | ||
| } | ||
| } | ||
| }) | ||
| this.extraInput = allObjectsWithoutCurrent | ||
| } | ||
| } | ||
| @Emit() | ||
| signalInput () { | ||
| this.changedInput = true | ||
| return this.localInput | ||
| } | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| <template> | ||
| <div> | ||
| <div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md"> | ||
| <q-icon v-if="inputIcon" :name="inputIcon" size="20px" class="q-mr-md"/> | ||
| {{inputDataBluePrint.name}} | ||
| </div> | ||
|
|
||
| <q-list | ||
| v-if="!editMode" | ||
| dense> | ||
| <q-item v-for="(input,index) in localInput" :key="index"> | ||
| <q-item-section side> | ||
| <q-icon name="mdi-menu-right" /> | ||
| </q-item-section> | ||
| <q-item-section> | ||
| {{input}} | ||
| </q-item-section> | ||
| </q-item> | ||
| </q-list> | ||
|
|
||
| <q-select | ||
| v-if="editMode" | ||
| style="width: 100%;" | ||
| dense | ||
| :options="extraInput" | ||
| use-input | ||
| outlined | ||
| use-chips | ||
| input-debounce="0" | ||
| new-value-mode="add" | ||
| multiple | ||
| v-model="localInput" | ||
| @input="signalInput" | ||
| @keydown="signalInput" | ||
| > | ||
| <template v-slot:prepend v-if="inputIcon"> | ||
| <q-icon :name="inputIcon" /> | ||
| </template> | ||
| </q-select> | ||
|
|
||
| <q-separator color="grey q-mt-lg" /> | ||
|
|
||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Emit, Prop, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import { I_ExtraFields } from "src/interfaces/I_Blueprint" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class Field_MultiSelect extends BaseClass { | ||
| @Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields | ||
| @Prop({ default: [] }) readonly inputDataValue!: [] | ||
| @Prop() readonly isNew!: boolean | ||
| @Prop() readonly editMode!: boolean | ||
| changedInput = false | ||
| localInput = [] | ||
| @Watch("inputDataValue", { deep: true, immediate: true }) | ||
| reactToInputChanges () { | ||
| this.localInput = (this.inputDataValue) ? this.inputDataValue : [] | ||
| } | ||
| get inputIcon () { | ||
| return this.inputDataBluePrint?.icon | ||
| } | ||
| get extraInput () { | ||
| // @ts-ignore | ||
| return this.inputDataBluePrint?.predefinedSelectValues | ||
| } | ||
| @Emit() | ||
| signalInput () { | ||
| console.log("emit") | ||
| this.changedInput = true | ||
| return this.localInput | ||
| } | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| <template> | ||
| <div> | ||
| <div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md"> | ||
| <q-icon v-if="inputIcon" :name="inputIcon" size="20px" class="q-mr-md" min="1"/> | ||
| {{inputDataBluePrint.name}} | ||
| </div> | ||
|
|
||
| <q-list | ||
| v-if="!editMode" | ||
| dense> | ||
| <q-item> | ||
| <q-item-section> | ||
| {{localInput}} | ||
| </q-item-section> | ||
| </q-item> | ||
| </q-list> | ||
|
|
||
| <q-input | ||
| v-if="editMode" | ||
| v-model.number="localInput" | ||
| type="number" | ||
| @keyup="signalInput" | ||
| outlined | ||
| dense | ||
| /> | ||
|
|
||
| <q-separator color="grey q-mt-lg" /> | ||
|
|
||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Emit, Prop, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import { I_ExtraFields } from "src/interfaces/I_Blueprint" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class Field_Number extends BaseClass { | ||
| @Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields | ||
| @Prop({ default: null }) readonly inputDataValue!: null|number | ||
| @Prop() readonly editMode!: boolean | ||
| @Prop() readonly isNew!: boolean | ||
| changedInput = false | ||
| localInput: null|number = null | ||
| @Emit() | ||
| signalInput () { | ||
| this.changedInput = true | ||
| return this.localInput | ||
| } | ||
| get inputIcon () { | ||
| return this.inputDataBluePrint?.icon | ||
| } | ||
| @Watch("inputDataValue", { deep: true, immediate: true }) | ||
| reactToInputChanges () { | ||
| this.localInput = this.inputDataValue | ||
| } | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| <template> | ||
| <div> | ||
| <div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md"> | ||
| <q-icon v-if="inputIcon" :name="inputIcon" size="20px" class="q-mr-md"/> | ||
| {{inputDataBluePrint.name}} | ||
| <q-icon v-if="isOneWayRelationship" name="mdi-arrow-right-bold" size="16px" class="q-ml-md"> | ||
| <q-tooltip> | ||
| This is a one-way relationship. <br> Editing this value <b>will not</b> have effect on the connected document/s. | ||
| </q-tooltip> | ||
| </q-icon> | ||
| <q-icon v-if="!isOneWayRelationship" name="mdi-arrow-left-right-bold" size="16px" class="q-ml-md"> | ||
| <q-tooltip> | ||
| This is a two-way relationship. <br> Editing this value <b>will</b> also effect the connected document/s. | ||
| </q-tooltip> | ||
| </q-icon> | ||
|
|
||
| </div> | ||
|
|
||
| <q-list | ||
| v-if="!editMode && localInput" | ||
| dense> | ||
| <q-item | ||
| clickable | ||
| class="text-primary" | ||
| @click="openExistingDocument(localInput)"> | ||
| <q-item-section> | ||
| {{localInput.label}} | ||
| </q-item-section> | ||
| </q-item> | ||
| </q-list> | ||
|
|
||
| <div class="flex" v-if="editMode"> | ||
| <q-select | ||
| style="flex-grow: 1;" | ||
| dense | ||
| :options="filteredInput" | ||
| use-input | ||
| outlined | ||
| input-debounce="0" | ||
| v-model="localInput" | ||
| @filter="filterSelect" | ||
| @input="signalInput" | ||
| > | ||
| <template v-slot:prepend v-if="inputIcon"> | ||
| <q-icon :name="inputIcon" /> | ||
| </template> | ||
| <template v-slot:option="{ itemProps, itemEvents, opt }"> | ||
| <q-item | ||
| v-bind="itemProps" | ||
| v-on="itemEvents" | ||
| > | ||
| <q-item-section> | ||
| <q-item-label v-html="opt.label" ></q-item-label> | ||
| </q-item-section> | ||
| <q-tooltip v-if='opt.disable'> | ||
| This option is unavailable for selection as it is already paired to another. | ||
| </q-tooltip> | ||
| </q-item> | ||
| </template> | ||
| </q-select> | ||
|
|
||
| </div> | ||
|
|
||
| <q-separator color="grey q-mt-lg" /> | ||
|
|
||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Emit, Prop, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import PouchDB from "pouchdb" | ||
| import { I_ShortenedDocument } from "src/interfaces/I_OpenedDocument" | ||
| import { I_ExtraFields } from "src/interfaces/I_Blueprint" | ||
| import { I_FieldRelationship } from "src/interfaces/I_FieldRelationship" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class Field_SingleRelationship extends BaseClass { | ||
| @Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields | ||
| @Prop({ default: "" }) readonly inputDataValue!: I_FieldRelationship | ||
| @Prop({ default: "" }) readonly currentId!: "" | ||
| @Prop() readonly isNew!: boolean | ||
| @Prop() readonly editMode!: boolean | ||
| changedInput = false | ||
| localInput = "" as unknown as I_FieldRelationship | ||
| @Watch("inputDataValue", { deep: true, immediate: true }) | ||
| reactToInputChanges () { | ||
| // @ts-ignore | ||
| this.localInput = (this.inputDataValue) ? this.inputDataValue : "" | ||
| this.reloadObjectListAndCheckIfValueExists().catch(e => console.log(e)) | ||
| } | ||
| get inputIcon () { | ||
| return this.inputDataBluePrint?.icon | ||
| } | ||
| extraInput: I_FieldRelationship[] = [] | ||
| filteredInput: I_FieldRelationship[] = [] | ||
| filterSelect (val: string, update: (e: () => void) => void) { | ||
| if (val === "") { | ||
| update(() => { | ||
| this.filteredInput = this.extraInput | ||
| }) | ||
| return | ||
| } | ||
| update(() => { | ||
| const needle = val.toLowerCase() | ||
| this.filteredInput = this.extraInput.filter(v => v.label.toLowerCase().indexOf(needle) > -1) | ||
| }) | ||
| } | ||
| @Watch("inputDataBluePrint", { deep: true, immediate: true }) | ||
| reactToBlueprintChanges () { | ||
| this.reloadObjectListAndCheckIfValueExists().catch(e => console.log(e)) | ||
| } | ||
| @Watch("currentId") | ||
| reactToIDChanges () { | ||
| this.reloadObjectListAndCheckIfValueExists().catch(e => console.log(e)) | ||
| } | ||
| get isOneWayRelationship () { | ||
| return (this.inputDataBluePrint.type === "singleToNoneRelationship" || this.inputDataBluePrint.type === "manyToNoneRelationship") | ||
| } | ||
| async reloadObjectListAndCheckIfValueExists () { | ||
| if (this.inputDataBluePrint?.relationshipSettings && this.currentId.length > 0) { | ||
| const CurrentObjectDB = new PouchDB(this.inputDataBluePrint.relationshipSettings.connectedObjectType) | ||
| const allObjects = (await CurrentObjectDB.allDocs({ include_docs: true })).rows.map((doc) => { | ||
| const objectDoc = doc.doc as unknown as I_ShortenedDocument | ||
| const pairedField = (this.inputDataBluePrint?.relationshipSettings?.connectedField) || "" | ||
| let isDisabled = false | ||
| if (pairedField.length > 0) { | ||
| const pairedFieldObject = objectDoc.extraFields.find(f => f.id === pairedField) | ||
| const pairingType = this.inputDataBluePrint.type | ||
| if ( | ||
| (typeof pairedFieldObject?.value !== "string" && pairedFieldObject?.value !== null && pairingType === "singleToSingleRelationship") || | ||
| (typeof pairedFieldObject?.value !== "string" && pairedFieldObject?.value !== null && pairingType === "singleToSingleRelationship")) { | ||
| isDisabled = true | ||
| } | ||
| } | ||
| return { | ||
| _id: objectDoc._id, | ||
| value: objectDoc._id, | ||
| type: objectDoc.type, | ||
| disable: isDisabled, | ||
| url: `/project/display-content/${objectDoc.type}/${objectDoc._id}`, | ||
| label: objectDoc.extraFields.find(e => e.id === "name")?.value, | ||
| pairedField: pairedField | ||
| } | ||
| }) as unknown as I_FieldRelationship[] | ||
| const allObjectsWithoutCurrent: I_FieldRelationship[] = allObjects.filter((obj) => obj._id !== this.currentId) | ||
| if (this.localInput._id) { | ||
| if (!allObjectsWithoutCurrent.find(e => e._id === this.localInput._id)) { | ||
| // @ts-ignore | ||
| this.localInput = "" | ||
| } | ||
| } | ||
| this.extraInput = allObjectsWithoutCurrent | ||
| } | ||
| } | ||
| @Emit() | ||
| signalInput () { | ||
| this.changedInput = true | ||
| return this.localInput | ||
| } | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| <template> | ||
| <div> | ||
| <div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md"> | ||
| <q-icon v-if="inputIcon" :name="inputIcon" size="20px" class="q-mr-md"/> | ||
| {{inputDataBluePrint.name}} | ||
| </div> | ||
|
|
||
| <q-list | ||
| v-if="!editMode" | ||
| dense> | ||
| <q-item> | ||
| <q-item-section> | ||
| {{localInput}} | ||
| </q-item-section> | ||
| </q-item> | ||
| </q-list> | ||
|
|
||
| <q-select | ||
| v-if="editMode" | ||
| style="width: 100%;" | ||
| dense | ||
| :options="extraInput" | ||
| use-input | ||
| outlined | ||
| input-debounce="0" | ||
| new-value-mode="add" | ||
| v-model="localInput" | ||
| @input="signalInput" | ||
| @keydown="signalInput" | ||
| > | ||
| <template v-slot:prepend v-if="inputIcon"> | ||
| <q-icon :name="inputIcon" /> | ||
| </template> | ||
| </q-select> | ||
|
|
||
| <q-separator color="grey q-mt-lg" /> | ||
|
|
||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Emit, Prop, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import { I_ExtraFields } from "src/interfaces/I_Blueprint" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class Field_SingleSelect extends BaseClass { | ||
| @Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields | ||
| @Prop({ default: "" }) readonly inputDataValue!: "" | ||
| @Prop() readonly isNew!: boolean | ||
| @Prop() readonly editMode!: boolean | ||
| changedInput = false | ||
| localInput = "" | ||
| @Watch("inputDataValue", { deep: true, immediate: true }) | ||
| reactToInputChanges () { | ||
| this.localInput = (this.inputDataValue) ? this.inputDataValue : "" | ||
| } | ||
| get inputIcon () { | ||
| return this.inputDataBluePrint?.icon | ||
| } | ||
| get extraInput () { | ||
| // @ts-ignore | ||
| return this.inputDataBluePrint?.predefinedSelectValues | ||
| } | ||
| @Emit() | ||
| signalInput () { | ||
| console.log("emit") | ||
| this.changedInput = true | ||
| return this.localInput | ||
| } | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| <template> | ||
| <div> | ||
| <div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md"> | ||
| <q-icon v-if="inputIcon" :name="inputIcon" size="20px" class="q-mr-md"/> | ||
| {{inputDataBluePrint.name}} | ||
| </div> | ||
|
|
||
| <q-list | ||
| v-if="!editMode" | ||
| dense> | ||
| <q-item> | ||
| <q-item-section> | ||
| {{localInput}} | ||
| </q-item-section> | ||
| </q-item> | ||
| </q-list> | ||
|
|
||
| <q-input | ||
| v-if="editMode" | ||
| v-model="localInput" | ||
| @keyup="signalInput" | ||
| outlined | ||
| dense | ||
| > | ||
| <template v-slot:append v-if="isNew && !changedInput"> | ||
| <q-icon name="close" @click="deletePlaceholder()" class="cursor-pointer" /> | ||
| </template> | ||
| </q-input> | ||
|
|
||
| <q-separator color="grey q-mt-lg" /> | ||
|
|
||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Emit, Prop, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import { I_ExtraFields } from "src/interfaces/I_Blueprint" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class Field_Text extends BaseClass { | ||
| @Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields | ||
| @Prop({ default: "" }) readonly inputDataValue!: string | ||
| @Prop() readonly editMode!: boolean | ||
| @Prop() readonly isNew!: boolean | ||
| changedInput = false | ||
| localInput = "" | ||
| deletePlaceholder () { | ||
| this.localInput = "" | ||
| this.signalInput() | ||
| } | ||
| @Emit() | ||
| signalInput () { | ||
| this.changedInput = true | ||
| return this.localInput | ||
| } | ||
| get inputIcon () { | ||
| return this.inputDataBluePrint?.icon | ||
| } | ||
| @Watch("inputDataValue", { deep: true, immediate: true }) | ||
| reactToInputChanges () { | ||
| this.localInput = this.inputDataValue | ||
| } | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| <template> | ||
| <div> | ||
| <div class="flex justify-start items-center text-weight-bolder q-mb-sm q-mt-md"> | ||
| <q-icon v-if="inputIcon" :name="inputIcon" size="20px" class="q-mr-md"/> | ||
| {{inputDataBluePrint.name}} | ||
| </div> | ||
|
|
||
| <div v-if="!editMode" v-html="localInput"> | ||
| </div> | ||
|
|
||
| <q-editor | ||
| v-model="localInput" | ||
| :toolbar="wysiwygOptions" | ||
| :fonts="wysiwygFonts" | ||
| @input="signalInput" | ||
| v-if="editMode" | ||
| /> | ||
|
|
||
| <q-separator color="grey q-mt-lg" /> | ||
|
|
||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Emit, Prop, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import { I_ExtraFields } from "src/interfaces/I_Blueprint" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class Field_Wysiwyg extends BaseClass { | ||
| @Prop({ default: [] }) readonly inputDataBluePrint!: I_ExtraFields | ||
| @Prop({ default: "" }) readonly inputDataValue!: string | ||
| @Prop() readonly editMode!: boolean | ||
| @Prop() readonly isNew!: boolean | ||
| changedInput = false | ||
| localInput = "" | ||
| @Emit() | ||
| signalInput () { | ||
| this.changedInput = true | ||
| return this.localInput | ||
| } | ||
| get inputIcon () { | ||
| return this.inputDataBluePrint?.icon | ||
| } | ||
| @Watch("inputDataValue", { deep: true, immediate: true }) | ||
| reactToInputChanges () { | ||
| this.localInput = this.inputDataValue | ||
| } | ||
| wysiwygFonts = { | ||
| arial: "Arial", | ||
| arial_black: "Arial Black", | ||
| comic_sans: "Comic Sans MS", | ||
| courier_new: "Courier New", | ||
| impact: "Impact", | ||
| lucida_grande: "Lucida Grande", | ||
| times_new_roman: "Times New Roman", | ||
| verdana: "Verdana" | ||
| } | ||
| wysiwygOptions = [ | ||
| ["left", "center", "right", "justify"], | ||
| ["bold", "italic", "underline", "subscript", "superscript"], | ||
| [ | ||
| { | ||
| label: this.$q.lang.editor.formatting, | ||
| icon: this.$q.iconSet.editor.formatting, | ||
| list: "no-icons", | ||
| fixedIcon: true, | ||
| options: [ | ||
| "h1", | ||
| "h2", | ||
| "h3", | ||
| "h4", | ||
| "h5", | ||
| "h6", | ||
| "p" | ||
| ] | ||
| }, | ||
| { | ||
| label: this.$q.lang.editor.fontSize, | ||
| icon: this.$q.iconSet.editor.fontSize, | ||
| fixedIcon: true, | ||
| list: "no-icons", | ||
| options: [ | ||
| "size-1", | ||
| "size-2", | ||
| "size-3", | ||
| "size-4", | ||
| "size-5", | ||
| "size-6", | ||
| "size-7" | ||
| ] | ||
| }, | ||
| { | ||
| label: this.$q.lang.editor.defaultFont, | ||
| icon: this.$q.iconSet.editor.font, | ||
| fixedIcon: true, | ||
| list: "no-icons", | ||
| options: [ | ||
| "default_font", | ||
| "arial", | ||
| "arial_black", | ||
| "comic_sans", | ||
| "courier_new", | ||
| "impact", | ||
| "lucida_grande", | ||
| "times_new_roman", | ||
| "verdana" | ||
| ] | ||
| }, | ||
| "removeFormat" | ||
| ], | ||
| ["hr", "link", "quote", "unordered", "ordered", "outdent", "indent"], | ||
| ["undo", "redo"], | ||
| ["fullscreen"], | ||
| ["viewsource"] | ||
| ] | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,267 @@ | ||
| <template> | ||
|
|
||
| <span> | ||
| <q-input ref="treeFilter" dark filled v-model="treeFilter" label="Search..."> | ||
| <template v-slot:prepend> | ||
| <q-icon name="mdi-text-search" /> | ||
| </template> | ||
| <template v-slot:append> | ||
| <q-icon v-if="treeFilter !== ''" name="clear" class="cursor-pointer" @click="resetTreeFilter" /> | ||
| </template> | ||
| </q-input> | ||
|
|
||
| <q-tree | ||
| class="q-pa-sm" | ||
| :nodes="treeList" | ||
| node-key="label" | ||
| no-connectors | ||
| ref="tree" | ||
| dark | ||
| :filter="treeFilter" | ||
| :selected.sync="selectedTreeNode" | ||
| > | ||
| <template v-slot:default-header="prop"> | ||
| <div class="row items-center col-grow"> | ||
| <q-icon :name="prop.node.icon" class="q-mr-sm" /> | ||
| <div>{{ prop.node.label }} | ||
| <q-badge | ||
| class="q-ml-xs" | ||
| style="font-size: 12px;" | ||
| v-if="prop.node.sticker" | ||
| color="primary" | ||
| outline | ||
| align="top">{{prop.node.sticker}} | ||
| <q-tooltip> | ||
| Order priority of the document | ||
| </q-tooltip> | ||
| </q-badge> | ||
| </div> | ||
| <q-tooltip v-if="prop.node.specialLabel"> | ||
| Add new {{ prop.node.specialLabel }} | ||
| </q-tooltip> | ||
| </div> | ||
| </template> | ||
| </q-tree> | ||
|
|
||
| <!-- | ||
| <q-list> | ||
| <q-separator | ||
| color="white" | ||
| inset | ||
| class="q-mt-md" | ||
| /> | ||
| <q-item | ||
| v-ripple | ||
| clickable | ||
| class="q-mt-md" | ||
| > | ||
| <q-item-section avatar> | ||
| <q-icon :name="menuAddNewItem.icon" /> | ||
| </q-item-section> | ||
| <q-item-section> | ||
| {{ menuAddNewItem.label }} | ||
| </q-item-section> | ||
| </q-item> | ||
| </q-list> | ||
| --> | ||
| </span> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import { I_ShortenedDocument } from "src/interfaces/I_OpenedDocument" | ||
| import { I_NewObjectTrigger } from "src/interfaces/I_NewObjectTrigger" | ||
| import PouchDB from "pouchdb" | ||
| import { engageBlueprints, retrieveAllBlueprints } from "src/databaseManager/blueprintManager" | ||
| import { cleanDatabases } from "src/databaseManager/cleaner" | ||
| import { I_Blueprint } from "src/interfaces/I_Blueprint" | ||
| const menuAddNewItem = { | ||
| icon: "mdi-plus", | ||
| label: "Add new object type" | ||
| } | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class ObjectTree extends BaseClass { | ||
| menuAddNewItem = menuAddNewItem | ||
| treeList: {children: I_ShortenedDocument[], icon: string, label: string}[] = [] | ||
| firstTimeExpand = true | ||
| /** | ||
| * A resetter for the currently selected node | ||
| */ | ||
| selectedTreeNode = null | ||
| treeFilter = "" | ||
| @Watch("SGET_allBlueprints", { deep: true }) | ||
| reactToBluePrintRefresh () { | ||
| this.buildCurrentObjectTree().catch((e) => { console.log(e) }) | ||
| } | ||
| resetTreeFilter () { | ||
| this.treeFilter = "" | ||
| // @ts-ignore | ||
| const treeFilterDOM = this.$refs.treeFilter as unknown as HTMLInputElement | ||
| treeFilterDOM.focus() | ||
| } | ||
| /** | ||
| * Since we are using the object tree as URLs intead of selecting, this resets the select every time a node is clicked | ||
| */ | ||
| @Watch("selectedTreeNode") | ||
| onNodeChange (val: I_NewObjectTrigger) { | ||
| if (val !== null) { | ||
| this.selectedTreeNode = null | ||
| } | ||
| } | ||
| @Watch("SGET_allOpenedDocuments", { deep: true }) | ||
| async reactToDocumentListChange () { | ||
| await this.buildCurrentObjectTree() | ||
| } | ||
| sortDocuments (input: I_ShortenedDocument[]) { | ||
| input | ||
| .sort((a, b) => a.label.localeCompare(b.label)) | ||
| .sort((a, b) => { | ||
| const order1 = a.extraFields.find(e => e.id === "order")?.value | ||
| const order2 = b.extraFields.find(e => e.id === "order")?.value | ||
| if (order1 < order2) { return 1 } | ||
| if (order1 > order2) { return -1 } | ||
| return 0 | ||
| }) | ||
| input.forEach((e, i) => { | ||
| if (e.children.length > 0) { input[i].children = this.sortDocuments(input[i].children) } | ||
| }) | ||
| return input | ||
| } | ||
| buildTreeHierarchy (input: I_ShortenedDocument[]) { | ||
| const map: number[] = [] | ||
| let node | ||
| const roots = [] | ||
| let i | ||
| for (i = 0; i < input.length; i += 1) { | ||
| map[input[i]._id] = i // initialize the map | ||
| } | ||
| for (i = 0; i < input.length; i += 1) { | ||
| node = input[i] | ||
| if (node.parentDoc !== false) { | ||
| // if you have dangling branches check that map[node.parentId] exists | ||
| if (input[map[node.parentDoc]]) { | ||
| input[map[node.parentDoc]].children.push(node) | ||
| } else { | ||
| roots.push(node) | ||
| } | ||
| } else { | ||
| roots.push(node) | ||
| } | ||
| } | ||
| const sortedRoots = this.sortDocuments(roots) | ||
| return sortedRoots | ||
| } | ||
| async buildCurrentObjectTree () { | ||
| const allBlueprings = this.SGET_allBlueprints | ||
| const treeObject: any[] = [] | ||
| for (const blueprint of allBlueprings) { | ||
| const CurrentObjectDB = new PouchDB(blueprint._id) | ||
| const allDocuments = await CurrentObjectDB.allDocs({ include_docs: true }) | ||
| const allDocumentsRows = allDocuments.rows | ||
| .map((singleDocument) => { | ||
| const doc = singleDocument.doc as unknown as I_ShortenedDocument | ||
| const parentDocID = doc.extraFields.find(e => e.id === "parentDoc")?.value as unknown as {_id: string} | ||
| return { | ||
| label: doc.extraFields.find(e => e.id === "name")?.value, | ||
| icon: doc.icon, | ||
| sticker: doc.extraFields.find(e => e.id === "order")?.value, | ||
| parentDoc: (parentDocID) ? parentDocID._id : false, | ||
| handler: this.openExistingDocument, | ||
| expandable: true, | ||
| type: doc.type, | ||
| children: [], | ||
| hasEdits: false, | ||
| isNew: false, | ||
| url: doc.url, | ||
| extraFields: (doc?.extraFields) || [], | ||
| _id: singleDocument.id | ||
| } as I_ShortenedDocument | ||
| }) | ||
| const hierarchicalTreeContent = this.buildTreeHierarchy(allDocumentsRows) | ||
| const treeRow = { | ||
| label: blueprint.namePlural, | ||
| icon: blueprint.icon, | ||
| _id: blueprint._id, | ||
| handler: this.addNewObjectType, | ||
| specialLabel: blueprint.nameSingular.toLowerCase(), | ||
| children: [ | ||
| ...hierarchicalTreeContent, | ||
| { | ||
| label: `Add new ${blueprint.nameSingular.toLowerCase()}`, | ||
| icon: "mdi-plus", | ||
| handler: this.addNewObjectType, | ||
| _id: blueprint._id | ||
| } | ||
| ] | ||
| } | ||
| treeObject.push(treeRow) | ||
| } | ||
| this.treeList = treeObject | ||
| if (this.firstTimeExpand) { | ||
| this.firstTimeExpand = false | ||
| await this.$nextTick() | ||
| this.$refs.tree.expandAll() | ||
| } | ||
| } | ||
| async created () { | ||
| await cleanDatabases() | ||
| await this.processBluePrints() | ||
| setTimeout(() => { | ||
| this.buildCurrentObjectTree().catch((e) => { console.log(e) }) | ||
| }, 1000) | ||
| } | ||
| /** | ||
| * Processes all blueprints and redies the store for population of the app | ||
| */ | ||
| async processBluePrints (): Promise<void> { | ||
| await engageBlueprints() | ||
| const allObjectBlueprints = (await retrieveAllBlueprints()).rows.map((blueprint) => { | ||
| return blueprint.doc | ||
| }) as I_Blueprint[] | ||
| this.SSET_allBlueprints(allObjectBlueprints) | ||
| } | ||
| } | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| <template> | ||
|
|
||
| <transition | ||
| enter-active-class="animated slideInDown" | ||
| leave-active-class="animated slideOutUp" | ||
| appear | ||
| :duration="600" | ||
| > | ||
| <q-header | ||
| v-if="localDocuments.length > 0" | ||
| elevated | ||
| class="bg-dark text-cultured" | ||
| > | ||
| <q-dialog | ||
| v-if="currentlyCheckedDocument" | ||
| v-model="documentCloseDialogConfirm" | ||
| persistent> | ||
| <q-card> | ||
| <q-card-section class="row items-center"> | ||
| <span class="q-ml-sm">Discard changes to {{retrieveFieldValue(currentlyCheckedDocument,'name')}}?</span> | ||
| </q-card-section> | ||
|
|
||
| <q-card-actions align="right"> | ||
| <q-btn flat label="Cancel" color="primary" v-close-popup /> | ||
| <q-btn | ||
| flat | ||
| label="Discard changes" | ||
| color="primary" | ||
| v-close-popup | ||
| @click="closeDocument(currentlyCheckedDocument)" /> | ||
| </q-card-actions> | ||
| </q-card> | ||
| </q-dialog> | ||
|
|
||
| <q-tabs | ||
| align="left" | ||
| inline-label | ||
| no-caps> | ||
| <transition-group | ||
| name="list" | ||
| tag="div" | ||
| class="headerTransitionWrapper" | ||
| enter-active-class="animated fadeIn" | ||
| leave-active-class="animated fadeOut" | ||
| appear | ||
| :duration="300"> | ||
| <q-route-tab | ||
| :ripple="false" | ||
| v-for="document in localDocuments" | ||
| :to="`/project/display-content/${document.type}/${document._id}`" | ||
| :key="document.type+document._id" | ||
| :icon="document.icon" | ||
| :label="retrieveFieldValue(document,'name')" | ||
| :alert="document.hasEdits" | ||
| alert-icon="mdi-feather" | ||
| > | ||
| <q-btn | ||
| round | ||
| dense | ||
| class="z-max q-ml-sm" | ||
| :class="{'q-mr-sm': document.hasEdits}" | ||
| size="xs" | ||
| icon="close" | ||
| @click.stop.prevent="checkForCloseOpenedDocument(document)" | ||
| /> | ||
| </q-route-tab> | ||
| </transition-group> | ||
| </q-tabs> | ||
|
|
||
| </q-header> | ||
| </transition> | ||
|
|
||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Component, Watch } from "vue-property-decorator" | ||
| import BaseClass from "src/BaseClass" | ||
| import { I_OpenedDocument } from "src/interfaces/I_OpenedDocument" | ||
| @Component({ | ||
| components: { } | ||
| }) | ||
| export default class TppTabs extends BaseClass { | ||
| documentCloseDialogConfirm = false | ||
| currentlyCheckedDocument = null as unknown as I_OpenedDocument | ||
| checkForCloseOpenedDocument (input: I_OpenedDocument) { | ||
| this.currentlyCheckedDocument = input | ||
| if (input.hasEdits) { | ||
| this.documentCloseDialogConfirm = true | ||
| } else { | ||
| this.closeDocument(input) | ||
| } | ||
| } | ||
| closeDocument (input: I_OpenedDocument) { | ||
| this.SSET_removeOpenedDocument(input) | ||
| setTimeout(() => { | ||
| this.refreshRoute() | ||
| }, 100) | ||
| } | ||
| @Watch("SGET_allOpenedDocuments", { deep: true }) | ||
| reactToDocumentListChange (val: {docs: I_OpenedDocument[]}) { | ||
| this.localDocuments = [] | ||
| this.localDocuments = val.docs | ||
| this.refreshRoute() | ||
| } | ||
| localDocuments: I_OpenedDocument[] = [] | ||
| } | ||
| </script> | ||
|
|
||
| <style lang="scss" scoped> | ||
| .headerTransitionWrapper { | ||
| display: flex; | ||
| } | ||
| </style> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // app global css in SCSS form | ||
|
|
||
| @import './app/customColors.scss'; | ||
|
|
||
| body { | ||
| background-color: #fff; | ||
| } | ||
|
|
||
| .q-drawer-container * { | ||
| user-select: none !important; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| @import '../quasar.variables.scss'; | ||
|
|
||
| @each $name, $color in $customColors { | ||
| .text-#{$name} { | ||
| color: $color; | ||
| } | ||
|
|
||
| .bg-#{$name} { | ||
| background-color: $color; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // Quasar SCSS (& Sass) Variables | ||
| // -------------------------------------------------- | ||
| // To customize the look and feel of this app, you can override | ||
| // the Sass/SCSS variables found in Quasar's source Sass/SCSS files. | ||
|
|
||
| // Check documentation for full list of Quasar variables | ||
|
|
||
| // Your own variables (that are declared here) and Quasar's own | ||
| // ones will be available out of the box in your .vue/.scss/.sass files | ||
|
|
||
| // It's highly recommended to change the default colors | ||
| // to match your app's branding. | ||
| // Tip: Use the "Theme Builder" on Quasar's documentation website. | ||
|
|
||
| $primary : #d2a334; | ||
| $secondary : #a4031f; | ||
| $accent : #dde4e4; | ||
|
|
||
| $dark : #19323c; | ||
|
|
||
| $positive : #21ba45; | ||
| $negative : #c10015; | ||
| $info : #31ccec; | ||
| $warning : #f2c037; | ||
|
|
||
| $customColors: ( | ||
| 'gunmetal': #19323c, | ||
| 'ruby-red': #a4031f, | ||
| 'satin-sheen-gold': #d2a334, | ||
| 'gainsboro': #dde4e4, | ||
| 'cultured': #f5f5f5 | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { locationBlueprint } from "./blueprints/locations" | ||
| import { I_Blueprint } from "./../interfaces/I_Blueprint" | ||
| import { characterBlueprint } from "./blueprints/characters" | ||
| import PouchDB from "pouchdb" | ||
| import _ from "lodash" | ||
|
|
||
| /** | ||
| * Loads all the blueprints and processes them apropriatelly | ||
| */ | ||
| export const engageBlueprints = async () => { | ||
| const BlueprintsDB = new PouchDB("blueprints") | ||
|
|
||
| /** | ||
| * List of all blueprintes needed to get processed | ||
| */ | ||
| const allBluePrints: I_Blueprint[] = [ | ||
| characterBlueprint, | ||
| locationBlueprint | ||
| ] | ||
|
|
||
| /** | ||
| * Processes all blueprints | ||
| */ | ||
| for (const newBlueprint of allBluePrints) { | ||
| try { | ||
| // Try adding a brand new data blueprint | ||
| await BlueprintsDB.put(newBlueprint) | ||
| } catch (e) { | ||
| // Proceed with checking of the contents of the blueprint if it already exists | ||
| const currentBlueprint = await BlueprintsDB.get(newBlueprint._id) as I_Blueprint | ||
| const hasChanges = checkBlueprintUpdate(newBlueprint, currentBlueprint) | ||
|
|
||
| // If there are changes, overwrite the old with new | ||
| if (hasChanges) { | ||
| newBlueprint._rev = currentBlueprint._rev | ||
| await BlueprintsDB.put(newBlueprint) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Checks if there are any changes to the blueprint being processed by comparing it with the old one | ||
| * @param newBlueprint The updated blueprint freshly retrieved from the source file | ||
| * @param currentBlueprint The current blueprint existing in the database | ||
| */ | ||
| export const checkBlueprintUpdate = (newBlueprint: I_Blueprint, currentBlueprint: I_Blueprint): boolean => { | ||
| let hasChanges = false | ||
|
|
||
| // Check for naming and icon changes and compare the extra fields via Lodash | ||
| if ( | ||
| newBlueprint?.namePlural !== currentBlueprint?.namePlural || | ||
| newBlueprint?.nameSingular !== currentBlueprint?.nameSingular || | ||
| newBlueprint?.icon !== currentBlueprint?.icon || | ||
| _.isEqual(newBlueprint.extraFields, currentBlueprint.extraFields) === false | ||
| ) { hasChanges = true } | ||
|
|
||
| return hasChanges | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves all blueprints | ||
| */ | ||
| export const retrieveAllBlueprints = async () => { | ||
| const BlueprintsDB = new PouchDB("blueprints") | ||
| return await BlueprintsDB.allDocs({ include_docs: true }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| import { I_Blueprint } from "./../../interfaces/I_Blueprint" | ||
| export const characterBlueprint: I_Blueprint = { | ||
| _id: "characters", | ||
| namePlural: "Characters", | ||
| nameSingular: "Character", | ||
| icon: "mdi-account", | ||
| extraFields: [ | ||
| { | ||
| id: "name", | ||
| name: "Name", | ||
| type: "text", | ||
| icon: "mdi-account", | ||
| sizing: 6 | ||
| }, | ||
| { | ||
| id: "parentDoc", | ||
| name: "Parent document", | ||
| type: "singleToNoneRelationship", | ||
| sizing: 4, | ||
| relationshipSettings: { | ||
| connectedObjectType: "characters" | ||
| } | ||
| }, | ||
| { | ||
| id: "order", | ||
| name: "Order number", | ||
| type: "number", | ||
| icon: "mdi-file-tree", | ||
| sizing: 2 | ||
| }, | ||
| { | ||
| id: "sex", | ||
| name: "Sex", | ||
| type: "singleSelect", | ||
| icon: "mdi-gender-male-female", | ||
| sizing: 3, | ||
| predefinedSelectValues: ["Male", "Female", "Other"] | ||
| }, | ||
| { | ||
| id: "otherNames", | ||
| name: "Other names", | ||
| type: "list", | ||
| icon: "mdi-account-plus", | ||
| sizing: 6 | ||
| }, | ||
| { | ||
| id: "skills", | ||
| name: "Skills", | ||
| type: "list", | ||
| icon: "mdi-sword-cross", | ||
| sizing: 6, | ||
| predefinedListExtras: { | ||
| affix: "Level", | ||
| extraSelectValueList: [ | ||
| "Trainee", | ||
| "Good", | ||
| "Expert", | ||
| "God!" | ||
| ] | ||
| } | ||
| }, | ||
| { | ||
| id: "personalityTraits", | ||
| name: "Personality traits", | ||
| type: "multiSelect", | ||
| icon: "mdi-head-cog", | ||
| sizing: 6, | ||
| predefinedSelectValues: ["Impulsive", "Clever", "Leet", "Streetsmart", "Bashful", "CRAZY!!!"] | ||
| }, | ||
| { | ||
| id: "manyToSingle", | ||
| name: "MANY to SINGLE", | ||
| type: "manyToSingleRelationship", | ||
| sizing: 6, | ||
| relationshipSettings: { | ||
| connectedObjectType: "characters", | ||
| connectedField: "singleToMany" | ||
| } | ||
| }, | ||
| { | ||
| id: "singleToMany", | ||
| name: "SINGLE to MANY", | ||
| type: "singleToManyRelationship", | ||
| sizing: 6, | ||
| relationshipSettings: { | ||
| connectedObjectType: "characters", | ||
| connectedField: "manyToSingle" | ||
| } | ||
| }, | ||
| { | ||
| id: "singleToNone", | ||
| name: "SINGLE to NONE", | ||
| type: "singleToNoneRelationship", | ||
| sizing: 6, | ||
| relationshipSettings: { | ||
| connectedObjectType: "characters" | ||
| } | ||
| }, | ||
| { | ||
| id: "singleToSingle", | ||
| name: "SINGLE to SINGLE", | ||
| type: "singleToSingleRelationship", | ||
| sizing: 6, | ||
| relationshipSettings: { | ||
| connectedObjectType: "characters", | ||
| connectedField: "singleToSingle" | ||
| } | ||
| }, | ||
| { | ||
| id: "manyToNone", | ||
| name: "MANY to NONE", | ||
| type: "manyToNoneRelationship", | ||
| sizing: 6, | ||
| relationshipSettings: { | ||
| connectedObjectType: "characters" | ||
| } | ||
| }, | ||
| { | ||
| id: "manyToMany", | ||
| name: "MANY to MANY", | ||
| type: "manyToManyRelationship", | ||
| sizing: 6, | ||
| relationshipSettings: { | ||
| connectedObjectType: "characters", | ||
| connectedField: "manyToMany" | ||
| } | ||
| }, | ||
| { | ||
| id: "dreamAndMemes", | ||
| name: "Dreams and memes", | ||
| type: "wysiwyg", | ||
| sizing: 12 | ||
| } | ||
|
|
||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { I_Blueprint } from "../../interfaces/I_Blueprint" | ||
| export const locationBlueprint: I_Blueprint = { | ||
| _id: "locations", | ||
| namePlural: "Locations", | ||
| nameSingular: "Location", | ||
| icon: "mdi-map-marker-radius", | ||
| extraFields: [ | ||
| { | ||
| id: "name", | ||
| name: "Name", | ||
| type: "text", | ||
| sizing: 12 | ||
| }, | ||
| { | ||
| id: "otherNames", | ||
| name: "Other names", | ||
| type: "list", | ||
| sizing: 6 | ||
| }, | ||
| { | ||
| id: "connectedCharacters", | ||
| name: "Connected characters", | ||
| type: "singleToManyRelationship", | ||
| sizing: 6, | ||
| relationshipSettings: { | ||
| connectedObjectType: "characters", | ||
| connectedField: "connectedLocations" | ||
| } | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import PouchDB from "pouchdb" | ||
| import PouchDBFind from "pouchdb-find" | ||
|
|
||
| PouchDB.plugin(PouchDBFind) | ||
|
|
||
| const CharactersDB = new PouchDB("characters") | ||
|
|
||
| export const charactersStartup = async (): Promise<void> => { | ||
| // await createCharacter("Sariine") | ||
| // await createCharacter("Liarni") | ||
|
|
||
| await CharactersDB.createIndex({ | ||
| index: { fields: ["name", "age"] } | ||
| }) | ||
|
|
||
| const indexes = await CharactersDB.getIndexes() | ||
|
|
||
| console.log(indexes) | ||
|
|
||
| const foundCharacters = await CharactersDB.find({ | ||
| selector: { name: "Sariine" }, | ||
| fields: ["name", "age"], | ||
| sort: ["name"] | ||
| }) | ||
| console.log(foundCharacters) | ||
| } | ||
|
|
||
| export const createCharacter = async (name: string): Promise<void> => { | ||
| const newCharacter = { | ||
| name: name, | ||
| age: 56 | ||
| } | ||
|
|
||
| await CharactersDB.post( | ||
| newCharacter | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { retrieveAllBlueprints } from "src/databaseManager/blueprintManager" | ||
| import { I_Blueprint } from "../interfaces/I_Blueprint" | ||
| import PouchDB from "pouchdb" | ||
|
|
||
| export const cleanDatabases = async () => { | ||
| const allBlueprints = (await retrieveAllBlueprints()).rows.map((blueprint) => { | ||
| return blueprint.doc | ||
| }) as I_Blueprint[] | ||
|
|
||
| allBlueprints.forEach(blueprint => { | ||
| let activeDB = new PouchDB(blueprint._id) | ||
| const cleanedDB = new PouchDB(`${blueprint._id}Clean`) | ||
| let originalTableSize: number | ||
| let cleanedTableSize | ||
|
|
||
| activeDB.info().then((result) => { | ||
| originalTableSize = result.doc_count | ||
|
|
||
| activeDB.replicate.to(cleanedDB, { filter: function (doc: {_deleted: boolean}) { if (doc._deleted) { return false } else { return doc } } }).on("complete", function () { | ||
| cleanedDB.info().then((cleanedResult) => { | ||
| cleanedTableSize = cleanedResult.doc_count | ||
| if (cleanedTableSize === originalTableSize) { | ||
| activeDB.destroy().then(() => { | ||
| activeDB = new PouchDB(blueprint._id) | ||
| cleanedDB.replicate.to(activeDB, { filter: function (doc: {_deleted: boolean}) { if (doc._deleted) { return false } else { return doc } } }).on("complete", function () { | ||
| cleanedDB.destroy().catch((err) => { console.log(err) }) | ||
| }).catch((err) => { console.log(err) }) | ||
| }).catch((err) => { console.log(err) }) | ||
| } | ||
| }).catch((err) => { console.log(err) }) | ||
| }).catch((err) => { console.log(err) }) | ||
| }).catch((err) => { console.log(err) }) | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| import PouchDB from "pouchdb" | ||
| import { I_Blueprint } from "src/interfaces/I_Blueprint" | ||
| import { I_FieldRelationship } from "src/interfaces/I_FieldRelationship" | ||
|
|
||
| import { I_ExtraDocumentFields, I_OpenedDocument } from "./../interfaces/I_OpenedDocument" | ||
|
|
||
| export const single_changeRelationshipToAnotherObject = async ( | ||
| field: I_ExtraDocumentFields, | ||
| currentDocument:I_OpenedDocument, | ||
| previouDocument: I_OpenedDocument) => { | ||
| const currentValue = field.value | ||
| const previousValue = (previouDocument?.extraFields?.find(e => e.id === field.id))?.value || "" | ||
|
|
||
| const BlueprintsDB = new PouchDB("blueprints") | ||
| const currentBlueprint = await BlueprintsDB.get(currentDocument.type) as I_Blueprint | ||
| const fieldType = (currentBlueprint.extraFields.find(e => e.id === field.id))?.type | ||
|
|
||
| const updatedDocuments:I_OpenedDocument[] = [] | ||
|
|
||
| if (!previousValue && typeof currentValue !== "string" && currentValue) { | ||
| if (fieldType === "singleToSingleRelationship") { | ||
| updatedDocuments.push(await single_addRelationShipToAnotherObject(field, currentValue, currentDocument)) | ||
| } | ||
| if (fieldType === "singleToManyRelationship") { | ||
| updatedDocuments.push(await many_addRelationShipToAnotherObject(field, currentValue, currentDocument)) | ||
| } | ||
| } | ||
|
|
||
| if (previousValue && typeof currentValue !== "string" && currentValue) { | ||
| if (fieldType === "singleToSingleRelationship") { | ||
| updatedDocuments.push(await single_removeRelationShipFromAnotherObject(previousValue)) | ||
| updatedDocuments.push(await single_addRelationShipToAnotherObject(field, currentValue, currentDocument)) | ||
| } | ||
| if (fieldType === "singleToManyRelationship") { | ||
| const removedValued = await many_removeRelationShipFromAnotherObject(previousValue, currentDocument) | ||
| if (removedValued) { | ||
| updatedDocuments.push(removedValued) | ||
| } | ||
| updatedDocuments.push(await many_addRelationShipToAnotherObject(field, currentValue, currentDocument)) | ||
| } | ||
| } | ||
|
|
||
| if ((previousValue && typeof currentValue === "string") || (previousValue && !currentValue)) { | ||
| if (fieldType === "singleToSingleRelationship") { | ||
| updatedDocuments.push(await single_removeRelationShipFromAnotherObject(previousValue)) | ||
| } | ||
| if (fieldType === "singleToManyRelationship") { | ||
| const removedValued = await many_removeRelationShipFromAnotherObject(previousValue, currentDocument) | ||
| if (removedValued) { | ||
| updatedDocuments.push(removedValued) | ||
| } | ||
| } | ||
| } | ||
| return updatedDocuments | ||
| } | ||
|
|
||
| export const single_addRelationShipToAnotherObject = async ( | ||
| field: I_ExtraDocumentFields, | ||
| currentValue: I_FieldRelationship, | ||
| currentDocument: I_OpenedDocument | ||
| ) => { | ||
| const typeToFind = currentValue.type | ||
| const idToFind = currentValue._id | ||
|
|
||
| const PairedObjectDB = new PouchDB(typeToFind) | ||
| const pairedDocument = await PairedObjectDB.get(idToFind) as I_OpenedDocument | ||
| const pairedField = currentValue.pairedField | ||
| const pairedFieldIndex = pairedDocument.extraFields.findIndex(e => e.id === pairedField) | ||
|
|
||
| pairedDocument.extraFields[pairedFieldIndex].value = { | ||
| _id: currentDocument._id, | ||
| value: currentDocument._id, | ||
| type: currentDocument.type, | ||
| url: `/project/display-content/${currentDocument.type}/${currentDocument._id}`, | ||
| label: currentDocument.extraFields.find(e => e.id === "name")?.value, | ||
| pairedField: field.id | ||
| } | ||
|
|
||
| await PairedObjectDB.put(pairedDocument) | ||
|
|
||
| return pairedDocument | ||
| } | ||
|
|
||
| export const single_removeRelationShipFromAnotherObject = async ( | ||
| previousValue: I_FieldRelationship | ||
| ) => { | ||
| const typeToFind = previousValue.type | ||
| const idToFind = previousValue._id | ||
|
|
||
| const PairedObjectDB = new PouchDB(typeToFind) | ||
| const pairedDocument = await PairedObjectDB.get(idToFind) as I_OpenedDocument | ||
| const pairedField = previousValue.pairedField | ||
| const pairedFieldIndex = pairedDocument.extraFields.findIndex(e => e.id === pairedField) | ||
|
|
||
| pairedDocument.extraFields[pairedFieldIndex].value = "" | ||
|
|
||
| await PairedObjectDB.put(pairedDocument) | ||
|
|
||
| return pairedDocument | ||
| } | ||
|
|
||
| export const many_changeRelationshipToAnotherObject = async ( | ||
| field: I_ExtraDocumentFields, | ||
| currentDocument:I_OpenedDocument, | ||
| previouDocument: I_OpenedDocument) => { | ||
| const currentValue: I_FieldRelationship[] = (field.value && typeof field.value !== "string") ? field.value : [] | ||
| const previousValue: I_FieldRelationship[] = (previouDocument?.extraFields?.find(e => e.id === field.id))?.value || [] | ||
|
|
||
| const BlueprintsDB = new PouchDB("blueprints") | ||
| const currentBlueprint = await BlueprintsDB.get(currentDocument.type) as I_Blueprint | ||
|
|
||
| const fieldType = (currentBlueprint.extraFields.find(e => e.id === field.id))?.type | ||
|
|
||
| const addedValues = currentValue.filter(val => !previousValue.find(subVal => subVal._id === val._id)) | ||
| const removedValues = previousValue.filter(val => !currentValue.find(subVal => subVal._id === val._id)) | ||
| const updatedDocuments:I_OpenedDocument[] = [] | ||
|
|
||
| for (const addedValue of addedValues) { | ||
| if (fieldType === "manyToManyRelationship") { | ||
| updatedDocuments.push(await many_addRelationShipToAnotherObject(field, addedValue, currentDocument)) | ||
| } | ||
|
|
||
| if (fieldType === "manyToSingleRelationship") { | ||
| updatedDocuments.push(await single_addRelationShipToAnotherObject(field, addedValue, currentDocument)) | ||
| } | ||
| } | ||
|
|
||
| for (const removedValue of removedValues) { | ||
| if (fieldType === "manyToManyRelationship") { | ||
| const removedValued = await many_removeRelationShipFromAnotherObject(removedValue, currentDocument) | ||
| if (removedValued) { | ||
| updatedDocuments.push(removedValued) | ||
| } | ||
| } | ||
|
|
||
| if (fieldType === "manyToSingleRelationship") { | ||
| const removedValued = await single_removeRelationShipFromAnotherObject(removedValue) | ||
| if (removedValued) { | ||
| updatedDocuments.push(removedValued) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return updatedDocuments | ||
| } | ||
|
|
||
| export const many_addRelationShipToAnotherObject = async ( | ||
| field: I_ExtraDocumentFields, | ||
| currentValue: I_FieldRelationship, | ||
| currentDocument: I_OpenedDocument | ||
| ) => { | ||
| const typeToFind = currentValue.type | ||
| const idToFind = currentValue._id | ||
|
|
||
| const PairedObjectDB = new PouchDB(typeToFind) | ||
| const pairedDocument = await PairedObjectDB.get(idToFind) as I_OpenedDocument | ||
| const pairedField = currentValue.pairedField | ||
| const pairedFieldIndex = pairedDocument.extraFields.findIndex(e => e.id === pairedField) | ||
|
|
||
| let prairedFieldValue: I_FieldRelationship[] = pairedDocument.extraFields[pairedFieldIndex].value | ||
|
|
||
| const newValue = { | ||
| _id: currentDocument._id, | ||
| value: currentDocument._id, | ||
| type: currentDocument.type, | ||
| url: `/project/display-content/${currentDocument.type}/${currentDocument._id}`, | ||
| label: currentDocument.extraFields.find(e => e.id === "name")?.value, | ||
| pairedField: field.id | ||
| } | ||
|
|
||
| prairedFieldValue = (Array.isArray(prairedFieldValue)) ? prairedFieldValue : [] | ||
|
|
||
| const valueExistsAlready = (prairedFieldValue.find(e => e._id === newValue._id)) | ||
|
|
||
| if (!valueExistsAlready) { | ||
| prairedFieldValue.push(newValue) | ||
| } | ||
|
|
||
| pairedDocument.extraFields[pairedFieldIndex].value = prairedFieldValue | ||
|
|
||
| await PairedObjectDB.put(pairedDocument) | ||
|
|
||
| return pairedDocument | ||
| } | ||
|
|
||
| export const many_removeRelationShipFromAnotherObject = async ( | ||
| previousValue: I_FieldRelationship, | ||
| currentDocument: I_OpenedDocument | ||
| ) => { | ||
| const typeToFind = previousValue.type | ||
| const idToFind = previousValue._id | ||
|
|
||
| const PairedObjectDB = new PouchDB(typeToFind) | ||
| let pairedDocument = false as unknown as I_OpenedDocument | ||
| try { pairedDocument = await PairedObjectDB.get(idToFind) } catch (e) { return pairedDocument } | ||
|
|
||
| const pairedField = previousValue.pairedField | ||
| const pairedFieldIndex = pairedDocument.extraFields.findIndex(e => e.id === pairedField) | ||
|
|
||
| const currentValues: I_FieldRelationship[] = pairedDocument.extraFields[pairedFieldIndex].value | ||
|
|
||
| const indexToRemove = currentValues.findIndex(e => e._id === currentDocument._id) | ||
|
|
||
| currentValues.splice(indexToRemove, 1) | ||
| pairedDocument.extraFields[pairedFieldIndex].value = currentValues | ||
|
|
||
| await PairedObjectDB.put(pairedDocument) | ||
|
|
||
| return pairedDocument | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| declare namespace NodeJS { | ||
| interface ProcessEnv { | ||
| NODE_ENV: string; | ||
| VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined; | ||
| VUE_ROUTER_BASE: string | undefined; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // This is just an example, | ||
| // so you can safely delete all default props below | ||
|
|
||
| export default { | ||
| failed: "Action failed", | ||
| success: "Action was successful" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import enUS from "./en-us" | ||
|
|
||
| export default { | ||
| "en-us": enUS | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title><%= productName %></title> | ||
|
|
||
| <meta charset="utf-8"> | ||
| <meta name="description" content="<%= productDescription %>"> | ||
| <meta name="format-detection" content="telephone=no"> | ||
| <meta name="msapplication-tap-highlight" content="no"> | ||
| <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>"> | ||
|
|
||
| <link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png"> | ||
| <link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png"> | ||
| <link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png"> | ||
| <link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png"> | ||
| <link rel="icon" type="image/ico" href="favicon.ico"> | ||
| </head> | ||
| <body> | ||
| <!-- DO NOT touch the following DIV --> | ||
| <div id="q-app"></div> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| export interface I_ExtraFields { | ||
| id: string | ||
| name: string, | ||
| icon?: string, | ||
| sizing: number | ||
| type: | ||
| "text" | | ||
| "number" | | ||
| "list" | | ||
| "wysiwyg" | | ||
| "singleSelect" | | ||
| "multiSelect" | | ||
| "singleToNoneRelationship" | | ||
| "manyToNoneRelationship" | | ||
| "singleToSingleRelationship" | | ||
| "singleToManyRelationship" | | ||
| "manyToSingleRelationship" | | ||
| "manyToManyRelationship" | ||
|
|
||
| predefinedListExtras?: { | ||
| affix?: string | ||
| extraSelectValueList?: string[] | ||
| } | ||
| predefinedSelectValues?: string[] | ||
| relationshipSettings?: { | ||
| connectedObjectType: string | ||
| connectedField?: string | ||
| } | ||
| } | ||
| export interface I_Blueprint{ | ||
| _id: string | ||
| _rev?: string | ||
| namePlural: string | ||
| nameSingular: string, | ||
| icon: string | ||
| extraFields: I_ExtraFields[] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| export interface I_FieldRelationship{ | ||
| type: string | ||
| label: string | ||
| value: string | ||
| url: string | ||
| _id: string | ||
| pairedField: string | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export interface I_NewObjectTrigger { | ||
| label?: string | ||
| icon?: string | ||
| handler?: (e: I_NewObjectTrigger) => void | ||
| _id: string | ||
| } |