Compare commits
No commits in common. "master" and "v0.1.6a" have entirely different histories.
|
@ -1,8 +1,8 @@
|
||||||
/dist
|
/dist
|
||||||
|
/src-bex/www
|
||||||
/src-capacitor
|
/src-capacitor
|
||||||
/src-cordova
|
/src-cordova
|
||||||
|
/src-electron
|
||||||
/.quasar
|
/.quasar
|
||||||
/node_modules
|
/node_modules
|
||||||
.eslintrc.js
|
/src-ssr
|
||||||
/src-ssr
|
|
||||||
/quasar.config.*.temporary.compiled*
|
|
108
.eslintrc.cjs
|
@ -1,108 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
|
|
||||||
// This option interrupts the configuration hierarchy at this file
|
|
||||||
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
|
|
||||||
root: true,
|
|
||||||
|
|
||||||
// https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
|
|
||||||
// Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
|
|
||||||
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
|
|
||||||
parserOptions: {
|
|
||||||
parser: require.resolve("@typescript-eslint/parser"),
|
|
||||||
extraFileExtensions: [".vue"],
|
|
||||||
},
|
|
||||||
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2021: true,
|
|
||||||
node: true,
|
|
||||||
"vue/setup-compiler-macros": true,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Rules order is important, please avoid shuffling them
|
|
||||||
extends: [
|
|
||||||
// Base ESLint recommended rules
|
|
||||||
// 'eslint:recommended',
|
|
||||||
|
|
||||||
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
|
|
||||||
// ESLint typescript rules
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
|
|
||||||
// Uncomment any of the lines below to choose desired strictness,
|
|
||||||
// but leave only one uncommented!
|
|
||||||
// See https://eslint.vuejs.org/rules/#available-rules
|
|
||||||
"plugin:vue/vue3-essential", // Priority A: Essential (Error Prevention)
|
|
||||||
"plugin:vue/vue3-strongly-recommended", // Priority B: Strongly Recommended (Improving Readability)
|
|
||||||
"plugin:vue/vue3-recommended", // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
|
|
||||||
|
|
||||||
"standard",
|
|
||||||
],
|
|
||||||
|
|
||||||
plugins: [
|
|
||||||
// required to apply rules which need type information
|
|
||||||
"@typescript-eslint",
|
|
||||||
|
|
||||||
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
|
|
||||||
// required to lint *.vue files
|
|
||||||
"vue",
|
|
||||||
],
|
|
||||||
|
|
||||||
globals: {
|
|
||||||
ga: "readonly", // Google Analytics
|
|
||||||
cordova: "readonly",
|
|
||||||
__statics: "readonly",
|
|
||||||
__QUASAR_SSR__: "readonly",
|
|
||||||
__QUASAR_SSR_SERVER__: "readonly",
|
|
||||||
__QUASAR_SSR_CLIENT__: "readonly",
|
|
||||||
__QUASAR_SSR_PWA__: "readonly",
|
|
||||||
process: "readonly",
|
|
||||||
Capacitor: "readonly",
|
|
||||||
chrome: "readonly",
|
|
||||||
},
|
|
||||||
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
// allow async-await
|
|
||||||
"generator-star-spacing": "off",
|
|
||||||
// allow paren-less arrow functions
|
|
||||||
"arrow-parens": "off",
|
|
||||||
"one-var": "off",
|
|
||||||
"no-void": "off",
|
|
||||||
"multiline-ternary": "off",
|
|
||||||
|
|
||||||
"import/first": "off",
|
|
||||||
"import/namespace": "error",
|
|
||||||
"import/default": "error",
|
|
||||||
"import/export": "error",
|
|
||||||
"import/extensions": "off",
|
|
||||||
"import/no-unresolved": "off",
|
|
||||||
"import/no-extraneous-dependencies": "off",
|
|
||||||
|
|
||||||
// The core 'import/named' rules
|
|
||||||
// does not work with type definitions
|
|
||||||
"import/named": "off",
|
|
||||||
|
|
||||||
"prefer-promise-reject-errors": "off",
|
|
||||||
|
|
||||||
quotes: ["warn", "single", { avoidEscape: true }],
|
|
||||||
|
|
||||||
// this rule, if on, would require explicit return type on the `render` function
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
|
||||||
|
|
||||||
// in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
|
|
||||||
// The core 'no-unused-vars' rules (in the eslint:recommended ruleset)
|
|
||||||
// does not work with type definitions
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
|
|
||||||
// allow debugger during development only
|
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
|
|
||||||
|
|
||||||
// CUSTOM RULES
|
|
||||||
"object-shorthand": "off",
|
|
||||||
"quote-props": "off",
|
|
||||||
camelcase: "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": ["error", { ignoreRestArgs: true }],
|
|
||||||
},
|
|
||||||
};
|
|
110
.eslintrc.js
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
const { resolve } = require('path');
|
||||||
|
module.exports = {
|
||||||
|
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
|
||||||
|
// This option interrupts the configuration hierarchy at this file
|
||||||
|
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
|
||||||
|
root: true,
|
||||||
|
|
||||||
|
// https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser
|
||||||
|
// Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
|
||||||
|
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
|
||||||
|
parserOptions: {
|
||||||
|
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#configuration
|
||||||
|
// https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#eslint
|
||||||
|
// Needed to make the parser take into account 'vue' files
|
||||||
|
extraFileExtensions: ['.vue'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
project: resolve(__dirname, './tsconfig.json'),
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
||||||
|
sourceType: 'module' // Allows for the use of imports
|
||||||
|
},
|
||||||
|
|
||||||
|
env: {
|
||||||
|
browser: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Rules order is important, please avoid shuffling them
|
||||||
|
extends: [
|
||||||
|
// Base ESLint recommended rules
|
||||||
|
// 'eslint:recommended',
|
||||||
|
|
||||||
|
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
|
||||||
|
// ESLint typescript rules
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
// consider disabling this class of rules if linting takes too long
|
||||||
|
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||||
|
|
||||||
|
// Uncomment any of the lines below to choose desired strictness,
|
||||||
|
// but leave only one uncommented!
|
||||||
|
// See https://eslint.vuejs.org/rules/#available-rules
|
||||||
|
'plugin:vue/essential', // Priority A: Essential (Error Prevention)
|
||||||
|
// 'plugin:vue/strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
|
||||||
|
// 'plugin:vue/recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
|
||||||
|
|
||||||
|
'standard'
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
// required to apply rules which need type information
|
||||||
|
'@typescript-eslint',
|
||||||
|
|
||||||
|
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file
|
||||||
|
// required to lint *.vue files
|
||||||
|
'vue',
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
globals: {
|
||||||
|
ga: false, // Google Analytics
|
||||||
|
cordova: true,
|
||||||
|
|
||||||
|
process: true,
|
||||||
|
Capacitor: true,
|
||||||
|
chrome: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// add your custom rules here
|
||||||
|
rules: {
|
||||||
|
// allow async-await
|
||||||
|
'generator-star-spacing': 'off',
|
||||||
|
// allow paren-less arrow functions
|
||||||
|
'arrow-parens': 'off',
|
||||||
|
'one-var': 'off',
|
||||||
|
'brace-style': ["error", "stroustrup"],
|
||||||
|
'import/first': 'off',
|
||||||
|
'import/named': 'error',
|
||||||
|
'import/namespace': 'error',
|
||||||
|
'import/default': 'error',
|
||||||
|
'import/export': 'error',
|
||||||
|
'import/extensions': 'off',
|
||||||
|
'import/no-unresolved': 'off',
|
||||||
|
'import/no-extraneous-dependencies': 'off',
|
||||||
|
'prefer-promise-reject-errors': 'off',
|
||||||
|
|
||||||
|
// TypeScript
|
||||||
|
quotes: ['warn', 'single', { avoidEscape: true }],
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
|
||||||
|
// allow debugger during development only
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
|
||||||
|
"quotes": [1, "double"],
|
||||||
|
"semi": [1, "never"],
|
||||||
|
"curly": [1, "all"],
|
||||||
|
"no-trailing-spaces": [1],
|
||||||
|
//"indent": [2, 2],
|
||||||
|
"@typescript-eslint/indent": [1, 2],
|
||||||
|
"vue/no-v-html": [0],
|
||||||
|
"camelcase": 'off',
|
||||||
|
'@typescript-eslint/camelcase': 'off',
|
||||||
|
'@typescript-eslint/unbound-method': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
'@typescript-eslint/ban-types': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-member-access':'off'
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
30
.github/workflows/build.yml
vendored
|
@ -1,6 +1,6 @@
|
||||||
# This is a basic workflow that is manually triggered
|
# This is a basic workflow that is manually triggered
|
||||||
|
|
||||||
name: Build App - FA Rewrite
|
name: Build App
|
||||||
|
|
||||||
# Manually triggered
|
# Manually triggered
|
||||||
on:
|
on:
|
||||||
|
@ -15,48 +15,44 @@ jobs:
|
||||||
# Builds all major OS versions of FA
|
# Builds all major OS versions of FA
|
||||||
build:
|
build:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
environment: production
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
node: [16]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the code
|
- name: Check out the code
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.3.4
|
||||||
|
|
||||||
|
|
||||||
- name: Setup Node.js environment
|
- name: Setup Node.js environment
|
||||||
uses: actions/setup-node@v2.1.5
|
uses: actions/setup-node@v2.1.5
|
||||||
|
|
||||||
- name: quasar install and build
|
- name: npm install and build
|
||||||
run: |
|
run: |
|
||||||
npm install --global yarn@1.22.19
|
npm install
|
||||||
yarn global add @quasar/cli
|
npm run build
|
||||||
yarn install
|
|
||||||
yarn build
|
|
||||||
|
|
||||||
- name: Upload Linux Artifact
|
- name: Upload Linux Artifact
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||||
uses: actions/upload-artifact@v2.2.3
|
uses: actions/upload-artifact@v2.2.3
|
||||||
with:
|
with:
|
||||||
# Artifact name
|
# Artifact name
|
||||||
name: Linux_FA ${{ github.event.inputs.version }}
|
name: Linux_FA ${{ github.event.inputs.version }}
|
||||||
path: |
|
path: dist/electron/Packaged/
|
||||||
dist/electron/Packaged/*.snap
|
|
||||||
dist/electron/Packaged/*.AppImage
|
|
||||||
|
|
||||||
- name: Upload MacOS Artifact
|
- name: Upload MacOS Artifact
|
||||||
if: ${{ matrix.os == 'macOS-latest' }}
|
if: ${{ matrix.os == 'macOS-latest' }}
|
||||||
uses: actions/upload-artifact@v2.2.3
|
uses: actions/upload-artifact@v2.2.3
|
||||||
with:
|
with:
|
||||||
# Artifact name
|
# Artifact name
|
||||||
name: Mac_FA ${{ github.event.inputs.version }}
|
name: Mac_FA ${{ github.event.inputs.version }}
|
||||||
path: dist/electron/Packaged/*.dmg
|
path: dist/electron/Packaged/
|
||||||
|
|
||||||
- name: Upload Windows Artifact
|
- name: Upload Windows Artifact
|
||||||
if: ${{ matrix.os == 'windows-latest' }}
|
if: ${{ matrix.os == 'windows-latest' }}
|
||||||
uses: actions/upload-artifact@v2.2.3
|
uses: actions/upload-artifact@v2.2.3
|
||||||
with:
|
with:
|
||||||
# Artifact name
|
# Artifact name
|
||||||
name: Windows_FA ${{ github.event.inputs.version }}
|
name: Windows_FA ${{ github.event.inputs.version }}
|
||||||
path: dist/electron/Packaged/*.exe
|
path: dist/electron/Packaged/
|
||||||
|
|
||||||
|
|
14
.gitignore
vendored
|
@ -1,11 +1,11 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.thumbs.db
|
.thumbs.db
|
||||||
|
.history
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# Quasar core related directories
|
# Quasar core related directories
|
||||||
.quasar
|
.quasar
|
||||||
/dist
|
/dist
|
||||||
/quasar.config.*.temporary.compiled*
|
|
||||||
|
|
||||||
# Cordova related directories and files
|
# Cordova related directories and files
|
||||||
/src-cordova/node_modules
|
/src-cordova/node_modules
|
||||||
|
@ -28,16 +28,8 @@ yarn-error.log*
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
|
.vscode
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
|
|
||||||
# local .env files
|
|
||||||
.env.local*
|
|
||||||
|
|
||||||
# Local Playwright test results folder
|
|
||||||
/test-results
|
|
||||||
|
|
||||||
.nyc_output
|
|
||||||
coverage/
|
|
8
.postcssrc.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
// to edit target browsers: use "browserslist" field in package.json
|
||||||
|
require('autoprefixer')
|
||||||
|
]
|
||||||
|
}
|
14
.vscode/extensions.json
vendored
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"editorconfig.editorconfig",
|
|
||||||
"vue.volar",
|
|
||||||
"wayou.vscode-todo-highlight"
|
|
||||||
],
|
|
||||||
"unwantedRecommendations": [
|
|
||||||
"octref.vetur",
|
|
||||||
"hookyqr.beautify",
|
|
||||||
"dbaeumer.jshint",
|
|
||||||
"ms-vscode.vscode-typescript-tslint-plugin"
|
|
||||||
]
|
|
||||||
}
|
|
23
.vscode/settings.json
vendored
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"editor.bracketPairColorization.enabled": true,
|
|
||||||
"editor.guides.bracketPairs": true,
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
|
||||||
"editor.codeActionsOnSave": [
|
|
||||||
"source.fixAll.eslint"
|
|
||||||
],
|
|
||||||
"eslint.validate": [
|
|
||||||
"javascript",
|
|
||||||
"javascriptreact",
|
|
||||||
"typescript",
|
|
||||||
"vue"
|
|
||||||
],
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
||||||
},
|
|
||||||
"todo-tree.tree.scanMode": "workspace",
|
|
||||||
"[vue]": {
|
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
|
||||||
}
|
|
||||||
}
|
|
2
FUNDING.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
patreon: elvanos
|
||||||
|
github: Elvanos
|
61
README.md
|
@ -1,47 +1,56 @@
|
||||||
# Fantasia Archive (fantasia-archive)
|
# Fantasia Archive (fantasiaarchive)
|
||||||
|
|
||||||
A worldbuilding database manager
|
A database manager for world building
|
||||||
|
|
||||||
Use Yarn 1.22.19 or stuff is gonna bug out.
|
# How to Install
|
||||||
|
|
||||||
Make sure you are running this with Node v16.17.0 ("nvm" is great for these older versions)
|
## Install Node.JS
|
||||||
|
|
||||||
> Playwright tests run from built, live version of FA. Therefore, to run them, you need to localy build the app on your machine first - Both on first time using them and every time something is changed in the source code.
|
Simply install Node.JS for your system. You can get it straight from their site, or on Linux, install it through your package manager.
|
||||||
|
|
||||||
## Install Quasar CLI for smoothest experience
|
## Download the program
|
||||||
##### Details found here: https://quasar.dev/start/quasar-cli
|
|
||||||
|
|
||||||
##### Ensure that the Yarn global install location is in your PATH after install. (details in article linked above)
|
Click on the green code button above, and download the repository as a zip file. Extract the zip file and move the resulting fantasia-archive-master folder to your home directory.
|
||||||
|
|
||||||
```
|
## Install the dependencies
|
||||||
yarn global add @quasar/cli
|
|
||||||
|
In the terminal, do the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd fantasia-archive-master
|
||||||
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install the dependencies and set up the project
|
## Optional: Start the app in development mode (hot-code reloading, error reporting, etc.)
|
||||||
```
|
|
||||||
yarn
|
If you don't know what's going on, skip this step.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Start the app in Quasar development mode (hot-code reloading, error reporting, etc.)
|
## Build the app for production
|
||||||
```
|
|
||||||
quasar dev -m electron
|
Once again in the terminal, run the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build the app for production
|
After it is complete, go to /dist/electron/Packaged and select the dmg or AppImage file there and run it. You have finished installing the program.
|
||||||
|
|
||||||
|
### New object files go into
|
||||||
|
|
||||||
```
|
```
|
||||||
quasar build -m electron
|
src\databaseManager\blueprints
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing:
|
### Document configuration details can be found in the following file
|
||||||
|
|
||||||
#### Component test - via Playwright
|
|
||||||
```
|
```
|
||||||
test:component
|
I_Blueprint.ts
|
||||||
```
|
|
||||||
#### E2E test - via Playwright
|
|
||||||
```
|
|
||||||
test:e2e
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Customize the configuration
|
### Customize the configuration
|
||||||
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
|
|
||||||
|
See [Configuring quasar.conf.js](https://quasar.dev/quasar-cli/quasar-conf-js).
|
||||||
|
|
6
babel.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/* eslint-env node */
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
"@quasar/babel-preset-app"
|
||||||
|
]
|
||||||
|
}
|
57
index.html
|
@ -1,57 +0,0 @@
|
||||||
<!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">
|
|
||||||
<style>
|
|
||||||
.splashLoadingWrapper{
|
|
||||||
background-color: #18303a;
|
|
||||||
overflow: hidden;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
transition: 0.3s opacity ease-in;
|
|
||||||
transition-delay: 2s;
|
|
||||||
z-index: 9999999999999999999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.splashLoading{
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.splashLoading path{
|
|
||||||
fill: #d7ac47;
|
|
||||||
}
|
|
||||||
|
|
||||||
#q-app[data-v-app] ~ .splashLoadingWrapper{
|
|
||||||
opacity: 0 !important;
|
|
||||||
user-select: none !important;
|
|
||||||
pointer-events: none !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- quasar:entry-point -->
|
|
||||||
<div class="splashLoadingWrapper" data-test="appSplashScreen-wrapper">
|
|
||||||
<svg class="splashLoading" width="350px" height="350px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-20,-20)"><path d="M79.9,52.6C80,51.8,80,50.9,80,50s0-1.8-0.1-2.6l-5.1-0.4c-0.3-2.4-0.9-4.6-1.8-6.7l4.2-2.9c-0.7-1.6-1.6-3.1-2.6-4.5 L70,35c-1.4-1.9-3.1-3.5-4.9-4.9l2.2-4.6c-1.4-1-2.9-1.9-4.5-2.6L59.8,27c-2.1-0.9-4.4-1.5-6.7-1.8l-0.4-5.1C51.8,20,50.9,20,50,20 s-1.8,0-2.6,0.1l-0.4,5.1c-2.4,0.3-4.6,0.9-6.7,1.8l-2.9-4.1c-1.6,0.7-3.1,1.6-4.5,2.6l2.1,4.6c-1.9,1.4-3.5,3.1-5,4.9l-4.5-2.1 c-1,1.4-1.9,2.9-2.6,4.5l4.1,2.9c-0.9,2.1-1.5,4.4-1.8,6.8l-5,0.4C20,48.2,20,49.1,20,50s0,1.8,0.1,2.6l5,0.4 c0.3,2.4,0.9,4.7,1.8,6.8l-4.1,2.9c0.7,1.6,1.6,3.1,2.6,4.5l4.5-2.1c1.4,1.9,3.1,3.5,5,4.9l-2.1,4.6c1.4,1,2.9,1.9,4.5,2.6l2.9-4.1 c2.1,0.9,4.4,1.5,6.7,1.8l0.4,5.1C48.2,80,49.1,80,50,80s1.8,0,2.6-0.1l0.4-5.1c2.3-0.3,4.6-0.9,6.7-1.8l2.9,4.2 c1.6-0.7,3.1-1.6,4.5-2.6L65,69.9c1.9-1.4,3.5-3,4.9-4.9l4.6,2.2c1-1.4,1.9-2.9,2.6-4.5L73,59.8c0.9-2.1,1.5-4.4,1.8-6.7L79.9,52.6 z M50,65c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15s15,6.7,15,15C65,58.3,58.3,65,50,65z" fill="currentColor"><animateTransform attributeName="transform" type="rotate" from="90 50 50" to="0 50 50" dur="1s" repeatCount="indefinite"></animateTransform></path></g><g transform="translate(20,20) rotate(15 50 50)"><path d="M79.9,52.6C80,51.8,80,50.9,80,50s0-1.8-0.1-2.6l-5.1-0.4c-0.3-2.4-0.9-4.6-1.8-6.7l4.2-2.9c-0.7-1.6-1.6-3.1-2.6-4.5 L70,35c-1.4-1.9-3.1-3.5-4.9-4.9l2.2-4.6c-1.4-1-2.9-1.9-4.5-2.6L59.8,27c-2.1-0.9-4.4-1.5-6.7-1.8l-0.4-5.1C51.8,20,50.9,20,50,20 s-1.8,0-2.6,0.1l-0.4,5.1c-2.4,0.3-4.6,0.9-6.7,1.8l-2.9-4.1c-1.6,0.7-3.1,1.6-4.5,2.6l2.1,4.6c-1.9,1.4-3.5,3.1-5,4.9l-4.5-2.1 c-1,1.4-1.9,2.9-2.6,4.5l4.1,2.9c-0.9,2.1-1.5,4.4-1.8,6.8l-5,0.4C20,48.2,20,49.1,20,50s0,1.8,0.1,2.6l5,0.4 c0.3,2.4,0.9,4.7,1.8,6.8l-4.1,2.9c0.7,1.6,1.6,3.1,2.6,4.5l4.5-2.1c1.4,1.9,3.1,3.5,5,4.9l-2.1,4.6c1.4,1,2.9,1.9,4.5,2.6l2.9-4.1 c2.1,0.9,4.4,1.5,6.7,1.8l0.4,5.1C48.2,80,49.1,80,50,80s1.8,0,2.6-0.1l0.4-5.1c2.3-0.3,4.6-0.9,6.7-1.8l2.9,4.2 c1.6-0.7,3.1-1.6,4.5-2.6L65,69.9c1.9-1.4,3.5-3,4.9-4.9l4.6,2.2c1-1.4,1.9-2.9,2.6-4.5L73,59.8c0.9-2.1,1.5-4.4,1.8-6.7L79.9,52.6 z M50,65c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15s15,6.7,15,15C65,58.3,58.3,65,50,65z" fill="currentColor"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="90 50 50" dur="1s" repeatCount="indefinite"></animateTransform></path></g></svg>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,61 +0,0 @@
|
||||||
export interface I_appMenusDataList {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Title of the main menu button
|
|
||||||
*/
|
|
||||||
title: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data contents of the menu dropdown
|
|
||||||
*/
|
|
||||||
data:{
|
|
||||||
/**
|
|
||||||
* Determines if the item shows as a separator or full-fledged menu item
|
|
||||||
*/
|
|
||||||
mode: 'separator' | 'item'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Title of the menu item
|
|
||||||
*/
|
|
||||||
text?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon/Avatar of the menu item
|
|
||||||
*/
|
|
||||||
icon?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger functionality of the item on click
|
|
||||||
*/
|
|
||||||
trigger?: ((...args: any[]) => unknown|void)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra arguments for the trigger if need be
|
|
||||||
*/
|
|
||||||
triggerArguments?: unknown[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Conditions to show the menu item as active
|
|
||||||
*/
|
|
||||||
conditions?: boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
*Special color class for the item
|
|
||||||
*/
|
|
||||||
specialColor?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determined if the item contains a submenu and its inner data
|
|
||||||
*/
|
|
||||||
submenu?: {
|
|
||||||
mode: 'separator' | 'item'
|
|
||||||
text?: string
|
|
||||||
icon?: string
|
|
||||||
trigger?: ((...args: any[]) => unknown|void)
|
|
||||||
triggerArguments?: []
|
|
||||||
conditions?: boolean
|
|
||||||
specialColor?: string
|
|
||||||
}[]
|
|
||||||
|
|
||||||
}[]
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
export interface I_extraEnvVariablesAPI {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Full path to "electron-main.js" file in the dist, unpackaged form
|
|
||||||
*/
|
|
||||||
ELECTRON_MAIN_FILEPATH: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra rended timer buffer for tests to start after loading the app.
|
|
||||||
* - Increase if your machine isn't keeping up with the render times and tests are randomly failing.
|
|
||||||
* - Lower if your machine is quick and the tests are waiting for no reason at all.
|
|
||||||
* - Can be set manually for each component/e2e test inside the test file.
|
|
||||||
*/
|
|
||||||
FA_FRONTEND_RENDER_TIMER: number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of test environment to load.
|
|
||||||
* - 'components'
|
|
||||||
* - 'e2e'
|
|
||||||
*/
|
|
||||||
TEST_ENV?: string|false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of the component being tested.
|
|
||||||
* - MUST match the file name of the vue file being tested (including the capital letter at the start).
|
|
||||||
*/
|
|
||||||
COMPONENT_NAME?: string|false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component props, assuming they have any.
|
|
||||||
*/
|
|
||||||
COMPONENT_PROPS?: string|false
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
export interface I_faDevToolsControl {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the current state of the DevTools in the opened FA instance
|
|
||||||
*/
|
|
||||||
checkDecToolsStatus: () => boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the dev tools
|
|
||||||
* - If they are opened, close them
|
|
||||||
* - If they are closed, open them
|
|
||||||
*/
|
|
||||||
toggleDevTools: () => void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the dev tools
|
|
||||||
*/
|
|
||||||
openDevTools: () => void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the dev tools
|
|
||||||
*/
|
|
||||||
closeDevTools: () => void
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
export interface I_faExternalLinksManagerAPI {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the type of link input
|
|
||||||
* true - Is external
|
|
||||||
* false - is not external
|
|
||||||
*/
|
|
||||||
checkIfExternal: (url: string) => boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open external link in the default browser of the user
|
|
||||||
*/
|
|
||||||
openExternal: (url: string) => void
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
export interface I_faWindowControlAPI {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the current visual sizing of the current window
|
|
||||||
*/
|
|
||||||
checkWindowMaximized: () => boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimizes the current window
|
|
||||||
*/
|
|
||||||
minimizeWindow: () => void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mazimizes the current window
|
|
||||||
*/
|
|
||||||
maximizeWindow: () => void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resizes the current window.
|
|
||||||
* - If the window is maximized, smallifies it
|
|
||||||
* - If the window is smallified, maximizes it
|
|
||||||
*/
|
|
||||||
resizeWindow: () => void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the current window
|
|
||||||
*/
|
|
||||||
closeWindow: () => void
|
|
||||||
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export type T_documentList = 'advancedSearchCheatSheet'| 'advancedSearchGuide'| 'changeLog'| 'license' | 'test' | ''
|
|
18550
package-lock.json
generated
Normal file
118
package.json
|
@ -1,64 +1,84 @@
|
||||||
{
|
{
|
||||||
"name": "fantasia-archive",
|
"name": "fantasiaarchive",
|
||||||
"version": "0.0.1",
|
"version": "0.1.6",
|
||||||
"description": "A worldbuilding database manager",
|
"description": "A database manager for world building",
|
||||||
"productName": "Fantasia Archive",
|
"productName": "Fantasia Archive",
|
||||||
"author": "Elvanos <elvanos66@gmail.com>",
|
"author": "Elvanos <elvanos66@gmail.com>",
|
||||||
|
"license": "GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .js,.ts,.vue ./",
|
"lint": "eslint --ext .js,.ts,.vue ./",
|
||||||
"dev:electron": "quasar dev -m electron",
|
"test": "echo \"No test specified\" && exit 0",
|
||||||
"build": "quasar build -m electron --publish never",
|
"dev": "quasar dev -m electron",
|
||||||
"test:component": "node \"node_modules/@playwright/test/cli.js\" test src/components",
|
"build": "quasar build -m electron"
|
||||||
"test:e2e": "node \"node_modules/@playwright/test/cli.js\" test test/playwright-e2e/"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.0.10",
|
"@quasar/extras": "^1.0.0",
|
||||||
"@quasar/cli": "^2.3.0",
|
"@types/fs-extra": "^8.1.0",
|
||||||
"@quasar/extras": "^1.16.4",
|
"apexcharts": "^3.26.0",
|
||||||
"app-root-path": "^3.1.0",
|
"axios": "^0.18.1",
|
||||||
"axios": "^1.2.1",
|
"core-js": "^3.6.5",
|
||||||
"lodash-es": "^4.17.21",
|
"katex": "^0.12.0",
|
||||||
"pinia": "^2.0.11",
|
"lodash": "^4.17.20",
|
||||||
"quasar": "^2.6.0",
|
"mermaid": "^8.8.4",
|
||||||
"sqlite3": "^5.1.6",
|
"pouchdb": "^7.2.2",
|
||||||
"uuid": "^9.0.1",
|
"pouchdb-adapter-idb": "^7.2.2",
|
||||||
"vue": "^3.0.0",
|
"pouchdb-find": "^7.2.2",
|
||||||
"vue-i18n": "^9.2.2",
|
"pouchdb-load": "^1.4.6",
|
||||||
"vue-router": "^4.0.0"
|
"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-apexcharts": "^1.6.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": {
|
"devDependencies": {
|
||||||
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
|
"@quasar/app": "^2.0.0",
|
||||||
"@playwright/test": "^1.37.1",
|
"@quasar/quasar-app-extension-qmarkdown": "^1.4.1",
|
||||||
"@quasar/app-vite": "^1.3.0",
|
"@quasar/quasar-app-extension-qwindow": "^1.0.0-beta.7",
|
||||||
"@quasar/quasar-app-extension-qmarkdown": "^2.0.0-beta.10",
|
"@types/lodash": "^4.14.166",
|
||||||
"@types/lodash-es": "^4.17.10",
|
"@types/node": "^10.17.15",
|
||||||
"@types/node": "^16.18.0",
|
"@types/pouchdb": "^6.4.0",
|
||||||
"@types/uuid": "^9.0.5",
|
"@typescript-eslint/eslint-plugin": "^3.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"@typescript-eslint/parser": "^3.3.0",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
"babel-eslint": "^10.0.1",
|
||||||
"autoprefixer": "^10.4.2",
|
"devtron": "^1.4.0",
|
||||||
"electron": "^25.5.0",
|
"electron": "^9.4.0",
|
||||||
"electron-builder": "^24.3.0",
|
"electron-builder": "^22.9.1",
|
||||||
"eslint": "^8.10.0",
|
"electron-debug": "^3.2.0",
|
||||||
"eslint-config-standard": "^17.0.0",
|
"electron-devtools-installer": "^3.1.1",
|
||||||
"eslint-plugin-import": "^2.19.1",
|
"electron-packager": "^14.2.1",
|
||||||
"eslint-plugin-n": "^15.0.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-promise": "^6.0.0",
|
"eslint-config-standard": "^14.1.0",
|
||||||
"eslint-plugin-vue": "^9.0.0",
|
"eslint-loader": "^3.0.3",
|
||||||
"jsdom": "^22.1.0",
|
"eslint-plugin-import": "^2.14.0",
|
||||||
"playwright": "^1.37.1",
|
"eslint-plugin-node": "^11.0.0",
|
||||||
"postcss-html": "^1.5.0",
|
"eslint-plugin-promise": "^4.0.1",
|
||||||
"sass": "1.29.0",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"stylelint": "^15.10.3",
|
"eslint-plugin-vue": "^6.1.2",
|
||||||
"stylelint-config-standard": "^34.0.0",
|
"quasar-app-extension-qdraggabletree": "0.0.5",
|
||||||
"stylelint-config-standard-scss": "^11.0.0",
|
"typescript": "^3.8.3"
|
||||||
"stylelint-config-standard-vue": "^1.0.0",
|
|
||||||
"typescript": "^4.5.4"
|
|
||||||
},
|
},
|
||||||
|
"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": {
|
"engines": {
|
||||||
"node": "^18 || ^16 || ^14.19",
|
"node": ">= 10.18.1",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
import { defineConfig } from '@playwright/test'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
workers: 3,
|
|
||||||
fullyParallel: false,
|
|
||||||
testMatch: '**/*playwright.@(spec|test).?(c|m)[jt]s?(x)',
|
|
||||||
reporter: [
|
|
||||||
['list'],
|
|
||||||
['json', { outputFile: 'test-results/test-results.json' }]
|
|
||||||
]
|
|
||||||
})
|
|
|
@ -1,13 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
// https://github.com/michael-ciniawsky/postcss-load-config
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
plugins: [
|
|
||||||
// https://github.com/elchininet/postcss-rtlcss
|
|
||||||
// If you want to support RTL css, then
|
|
||||||
// 1. yarn/npm install postcss-rtlcss
|
|
||||||
// 2. optionally set quasar.config.js > framework > lang to an RTL language
|
|
||||||
// 3. uncomment the following line:
|
|
||||||
// require('postcss-rtlcss')
|
|
||||||
],
|
|
||||||
};
|
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9 KiB |
Before Width: | Height: | Size: 153 KiB |
Before Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 145 KiB |
234
quasar.conf.js
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
/*
|
||||||
|
* 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",
|
||||||
|
'notify-defaults',
|
||||||
|
'apex'
|
||||||
|
],
|
||||||
|
|
||||||
|
// 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-latin-ext", // 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/,
|
||||||
|
options:{
|
||||||
|
emitWarning : true,
|
||||||
|
emitError : false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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: "builder", // '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: "fantasiaarchive",
|
||||||
|
win: {
|
||||||
|
icon: 'src-electron/icons/icon.ico'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
220
quasar.config.js
|
@ -1,220 +0,0 @@
|
||||||
/* eslint-env node */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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://v2.quasar.dev/quasar-cli-vite/quasar-config-js
|
|
||||||
|
|
||||||
const { configure } = require('quasar/wrappers')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
module.exports = configure(function (/* ctx */) {
|
|
||||||
return {
|
|
||||||
eslint: {
|
|
||||||
fix: true,
|
|
||||||
// include: [],
|
|
||||||
// exclude: [],
|
|
||||||
// rawOptions: {},
|
|
||||||
warnings: true,
|
|
||||||
errors: true
|
|
||||||
},
|
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
|
|
||||||
// preFetch: true,
|
|
||||||
|
|
||||||
// app boot file (/src/boot)
|
|
||||||
// --> boot files are part of "main.js"
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
|
||||||
boot: ['i18n', 'axios', 'externalLinkManager', 'notify-defaults'],
|
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
|
||||||
css: ['app.scss'],
|
|
||||||
|
|
||||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
|
||||||
extras: [
|
|
||||||
// 'ionicons-v4',
|
|
||||||
'mdi-v5',
|
|
||||||
'fontawesome-v6',
|
|
||||||
// 'eva-icons',
|
|
||||||
// 'themify',
|
|
||||||
// 'line-awesome',
|
|
||||||
|
|
||||||
// 'roboto-font', // optional, you are not bound to it
|
|
||||||
'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
|
||||||
'material-icons' // optional, you are not bound to it
|
|
||||||
],
|
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
|
||||||
build: {
|
|
||||||
target: {
|
|
||||||
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
|
||||||
node: 'node16'
|
|
||||||
},
|
|
||||||
|
|
||||||
vueRouterMode: 'history', // available values: 'hash', 'history'
|
|
||||||
// vueRouterBase,
|
|
||||||
// vueDevtools,
|
|
||||||
// vueOptionsAPI: false,
|
|
||||||
|
|
||||||
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
|
|
||||||
|
|
||||||
// publicPath: '/',
|
|
||||||
// analyze: true,
|
|
||||||
// env: {},
|
|
||||||
// rawDefine: {}
|
|
||||||
// ignorePublicFolder: true,
|
|
||||||
// minify: false,
|
|
||||||
// polyfillModulePreload: true,
|
|
||||||
// distDir
|
|
||||||
|
|
||||||
// extendViteConf (viteConf) {},
|
|
||||||
// viteVuePluginOptions: {},
|
|
||||||
|
|
||||||
vitePlugins: [
|
|
||||||
[
|
|
||||||
'@intlify/vite-plugin-vue-i18n',
|
|
||||||
{
|
|
||||||
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
|
|
||||||
// compositionOnly: false,
|
|
||||||
|
|
||||||
// if you want to use named tokens in your Vue I18n messages, such as 'Hello {name}',
|
|
||||||
// you need to set `runtimeOnly: false`
|
|
||||||
// runtimeOnly: false,
|
|
||||||
|
|
||||||
// you need to set i18n resource including paths !
|
|
||||||
include: path.resolve(__dirname, './src/i18n/**')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
|
||||||
devServer: {
|
|
||||||
// https: true
|
|
||||||
open: true // opens browser window automatically
|
|
||||||
},
|
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
|
||||||
framework: {
|
|
||||||
config: {
|
|
||||||
ripple: false,
|
|
||||||
dark: true
|
|
||||||
},
|
|
||||||
|
|
||||||
// iconSet: 'material-icons', // Quasar icon set
|
|
||||||
// lang: 'en-US', // Quasar language pack
|
|
||||||
|
|
||||||
// For special cases outside of where the auto-import strategy can have an impact
|
|
||||||
// (like functional components as one of the examples),
|
|
||||||
// you can manually specify Quasar components/directives to be available everywhere:
|
|
||||||
//
|
|
||||||
// components: [],
|
|
||||||
// directives: [],
|
|
||||||
|
|
||||||
// Quasar plugins
|
|
||||||
plugins: ['Dialog', 'Notify']
|
|
||||||
},
|
|
||||||
|
|
||||||
// animations: 'all', // --- includes all animations
|
|
||||||
// https://v2.quasar.dev/options/animations
|
|
||||||
animations: 'all',
|
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#sourcefiles
|
|
||||||
// sourceFiles: {
|
|
||||||
// rootComponent: 'src/App.vue',
|
|
||||||
// router: 'src/router/index',
|
|
||||||
// store: 'src/store/index',
|
|
||||||
// registerServiceWorker: 'src-pwa/register-service-worker',
|
|
||||||
// serviceWorker: 'src-pwa/custom-service-worker',
|
|
||||||
// pwaManifestFile: 'src-pwa/manifest.json',
|
|
||||||
// electronMain: 'src-electron/electron-main',
|
|
||||||
// electronPreload: 'src-electron/electron-preload'
|
|
||||||
// },
|
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr
|
|
||||||
ssr: {
|
|
||||||
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
|
|
||||||
// will mess up SSR
|
|
||||||
|
|
||||||
// extendSSRWebserverConf (esbuildConf) {},
|
|
||||||
// extendPackageJson (json) {},
|
|
||||||
|
|
||||||
pwa: false,
|
|
||||||
|
|
||||||
// manualStoreHydration: true,
|
|
||||||
// manualPostHydrationTrigger: true,
|
|
||||||
|
|
||||||
prodPort: 3000, // The default port that the production server should use
|
|
||||||
// (gets superseded if process.env.PORT is specified at runtime)
|
|
||||||
|
|
||||||
middlewares: [
|
|
||||||
'render' // keep this as last one
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
|
|
||||||
pwa: {
|
|
||||||
workboxMode: 'generateSW', // or 'injectManifest'
|
|
||||||
injectPwaMetaTags: true,
|
|
||||||
swFilename: 'sw.js',
|
|
||||||
manifestFilename: 'manifest.json',
|
|
||||||
useCredentialsForManifestTag: false
|
|
||||||
// useFilenameHashes: true,
|
|
||||||
// extendGenerateSWOptions (cfg) {}
|
|
||||||
// extendInjectManifestOptions (cfg) {},
|
|
||||||
// extendManifestJson (json) {}
|
|
||||||
// extendPWACustomSWConf (esbuildConf) {}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
|
|
||||||
cordova: {
|
|
||||||
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
|
|
||||||
},
|
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
|
|
||||||
capacitor: {
|
|
||||||
hideSplashscreen: true
|
|
||||||
},
|
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
|
|
||||||
electron: {
|
|
||||||
// extendElectronMainConf (esbuildConf)
|
|
||||||
// extendElectronPreloadConf (esbuildConf)
|
|
||||||
|
|
||||||
inspectPort: 5858,
|
|
||||||
|
|
||||||
bundler: 'builder', // '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: 'fantasia-archive',
|
|
||||||
win: {
|
|
||||||
icon: 'src-electron/icons/icon.ico'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
|
|
||||||
bex: {
|
|
||||||
contentScripts: ['my-content-script']
|
|
||||||
|
|
||||||
// extendBexScriptsConf (esbuildConf) {}
|
|
||||||
// extendBexManifestJson (json) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"qdraggabletree": {},
|
||||||
"@quasar/qmarkdown": {
|
"@quasar/qmarkdown": {
|
||||||
"import_md": true
|
"import_md": true,
|
||||||
}
|
"import_vmd": true
|
||||||
}
|
},
|
||||||
|
"@quasar/qwindow": {}
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
import { I_extraEnvVariablesAPI } from 'app/interfaces/I_extraEnvVariablesAPI'
|
|
||||||
import appRoot from 'app-root-path'
|
|
||||||
|
|
||||||
export const extraEnvVariablesAPI: I_extraEnvVariablesAPI = {
|
|
||||||
ELECTRON_MAIN_FILEPATH: appRoot + '/dist/electron/UnPackaged/electron-main.js',
|
|
||||||
FA_FRONTEND_RENDER_TIMER: 3000,
|
|
||||||
TEST_ENV: (process.env.TEST_ENV) ? process.env.TEST_ENV : false,
|
|
||||||
COMPONENT_NAME: (process.env.COMPONENT_NAME) ? process.env.COMPONENT_NAME : false,
|
|
||||||
COMPONENT_PROPS: (process.env.COMPONENT_PROPS) ? JSON.parse(process.env.COMPONENT_PROPS) : false
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import { BrowserWindow } from '@electron/remote'
|
|
||||||
import { I_faDevToolsControl } from 'app/interfaces/I_faDevToolsControl'
|
|
||||||
|
|
||||||
export const faDevToolsControlAPI: I_faDevToolsControl = {
|
|
||||||
|
|
||||||
checkDecToolsStatus () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
return currentWindow.webContents.isDevToolsOpened()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleDevTools () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
const devToolsOpened = currentWindow.webContents.isDevToolsOpened()
|
|
||||||
if (devToolsOpened) {
|
|
||||||
currentWindow.webContents.closeDevTools()
|
|
||||||
} else {
|
|
||||||
currentWindow.webContents.openDevTools()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
openDevTools () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
currentWindow.webContents.openDevTools()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
closeDevTools () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
currentWindow.webContents.closeDevTools()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { shell } from 'electron'
|
|
||||||
|
|
||||||
import { I_faExternalLinksManagerAPI } from 'app/interfaces/I_faExternalLinksManagerAPI'
|
|
||||||
|
|
||||||
export const faExternalLinksManagerAPI: I_faExternalLinksManagerAPI = {
|
|
||||||
|
|
||||||
checkIfExternal (url: string) {
|
|
||||||
return (
|
|
||||||
(url.includes('http://') || url.includes('https://')) &&
|
|
||||||
!url.includes('localhost')
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
openExternal (url: string) {
|
|
||||||
shell.openExternal(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { BrowserWindow } from '@electron/remote'
|
|
||||||
import { I_faWindowControlAPI } from 'app/interfaces/I_faWindowControlAPI'
|
|
||||||
|
|
||||||
export const faWindowControlAPI: I_faWindowControlAPI = {
|
|
||||||
|
|
||||||
checkWindowMaximized () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
return currentWindow.isMaximized()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
|
|
||||||
minimizeWindow () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
currentWindow.minimize()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
maximizeWindow () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
currentWindow.maximize()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resizeWindow () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
if (currentWindow.isMaximized()) {
|
|
||||||
currentWindow.unmaximize()
|
|
||||||
} else {
|
|
||||||
currentWindow.maximize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
closeWindow () {
|
|
||||||
const currentWindow = BrowserWindow.getFocusedWindow()
|
|
||||||
if (currentWindow !== null) {
|
|
||||||
currentWindow.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
8
src-electron/electron-env.d.ts
vendored
|
@ -1,8 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
declare namespace NodeJS {
|
|
||||||
interface ProcessEnv {
|
|
||||||
QUASAR_ELECTRON_PRELOAD: string;
|
|
||||||
APP_URL: string;
|
|
||||||
}
|
|
||||||
}
|
|
1
src-electron/electron-flag.d.ts
vendored
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable */
|
|
||||||
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||||
import "quasar/dist/types/feature-flag";
|
import "quasar/dist/types/feature-flag";
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
import { fixAppName } from 'src-electron/mainScripts/fixAppName'
|
|
||||||
import { windowsDevToolsExtensionsFix } from 'src-electron/mainScripts/windowsDevToolsExtensionsFix'
|
|
||||||
import { startApp, openAppWindowManager, closeAppManager } from 'app/src-electron/mainScripts/appManagement'
|
|
||||||
import { tweakMenuRemover, tweakRetriveOS } from 'src-electron/mainScripts/tweaks'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines what platform the app is running on
|
|
||||||
* - Needed in case process is undefined under Linux
|
|
||||||
*/
|
|
||||||
const platform = tweakRetriveOS()
|
|
||||||
|
|
||||||
// Fix app name and connected pathing to it
|
|
||||||
fixAppName()
|
|
||||||
|
|
||||||
// Fix Windows-only DevTools-bug concerning dark mode
|
|
||||||
windowsDevToolsExtensionsFix(platform)
|
|
||||||
|
|
||||||
// Start a singular app instance
|
|
||||||
startApp()
|
|
||||||
|
|
||||||
// Performance improvement tweak
|
|
||||||
tweakMenuRemover()
|
|
||||||
|
|
||||||
// Set up manager for opening a singular app window
|
|
||||||
openAppWindowManager()
|
|
||||||
|
|
||||||
// Set up manager for closing app instance
|
|
||||||
closeAppManager(platform)
|
|
||||||
|
|
||||||
import * as sqlite3 from 'sqlite3'
|
|
||||||
import { app } from 'electron'
|
|
||||||
import fs from 'fs'
|
|
||||||
|
|
||||||
if (!fs.existsSync(`${app.getPath('userData')}/_faProjectTemp/`)) {
|
|
||||||
fs.mkdirSync(`${app.getPath('userData')}/_faProjectTemp/`)
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3.verbose()
|
|
||||||
const db = new sqlite3.Database(`${app.getPath('userData')}/_faProjectTemp/test.fae`)
|
|
||||||
/*
|
|
||||||
db.serialize(() => {
|
|
||||||
db.run('CREATE TABLE IF NOT EXISTS lorem (info TEXT, yeet TEXT)')
|
|
||||||
|
|
||||||
const stmt = db.prepare('INSERT INTO lorem VALUES (?,?)')
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
stmt.run(['Ipsum ' + i, 'Yeet ' + (i + 10)])
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt.finalize()
|
|
||||||
|
|
||||||
db.each('SELECT rowid AS id, info, yeet FROM lorem', (_err, row: {id:string, info:string, yeet:string}) => {
|
|
||||||
console.log(row.id + ': ' + row.info)
|
|
||||||
console.log(row.id + ': ' + row.yeet)
|
|
||||||
})
|
|
||||||
}) */
|
|
||||||
|
|
||||||
/* db.serialize(() => {
|
|
||||||
db.run('CREATE TABLE IF NOT EXISTS sqlar (name TEXT, data BLOB)')
|
|
||||||
|
|
||||||
const buffer = fs.readFileSync(`${app.getPath('userData')}/_faProjectTemp/testImg.jpg`)
|
|
||||||
|
|
||||||
db.run('INSERT INTO sqlar VALUES (?, ?)', ['test', buffer])
|
|
||||||
|
|
||||||
db.each('SELECT rowid AS id, name, data FROM sqlar', (_err, row: {id:string, name:string, data:string}) => {
|
|
||||||
fs.writeFileSync(`${app.getPath('userData')}/_faProjectTemp/testImg2.jpg`, row.data)
|
|
||||||
})
|
|
||||||
}) */
|
|
||||||
|
|
||||||
db.close()
|
|
|
@ -1,41 +0,0 @@
|
||||||
/**
|
|
||||||
* This file is used specifically for security reasons.
|
|
||||||
* Here you can access Nodejs stuff and inject functionality into
|
|
||||||
* the renderer thread (accessible there through the "window" object)
|
|
||||||
*
|
|
||||||
* WARNING!
|
|
||||||
* If you import anything from node_modules, then make sure that the package is specified
|
|
||||||
* in package.json > dependencies and NOT in devDependencies
|
|
||||||
*
|
|
||||||
* Example (injects window.myAPI.doAThing() into renderer thread):
|
|
||||||
*
|
|
||||||
* import { contextBridge } from 'electron'
|
|
||||||
*
|
|
||||||
* contextBridge.exposeInMainWorld('myAPI', {
|
|
||||||
* doAThing: () => {}
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* WARNING!
|
|
||||||
* If accessing Node functionality (like importing @electron/remote) then in your
|
|
||||||
* electron-main.ts you will need to set the following when you instantiate BrowserWindow:
|
|
||||||
*
|
|
||||||
* mainWindow = new BrowserWindow({
|
|
||||||
* // ...
|
|
||||||
* webPreferences: {
|
|
||||||
* // ...
|
|
||||||
* sandbox: false // <-- to be able to import @electron/remote in preload script
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { contextBridge } from 'electron'
|
|
||||||
|
|
||||||
import { faWindowControlAPI } from 'src-electron/customContentBridgeAPIs/faWindowControlAPI'
|
|
||||||
import { extraEnvVariablesAPI } from 'src-electron/customContentBridgeAPIs/extraEnvVariablesAPI'
|
|
||||||
import { faDevToolsControlAPI } from 'src-electron/customContentBridgeAPIs/faDevToolsControlAPI'
|
|
||||||
import { faExternalLinksManagerAPI } from 'app/src-electron/customContentBridgeAPIs/faExternalLinksManagerAPI'
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('faWindowControlAPI', faWindowControlAPI)
|
|
||||||
contextBridge.exposeInMainWorld('faDevToolsControlAPI', faDevToolsControlAPI)
|
|
||||||
contextBridge.exposeInMainWorld('faExternalLinksManagerAPI', faExternalLinksManagerAPI)
|
|
||||||
contextBridge.exposeInMainWorld('extraEnvVariables', extraEnvVariablesAPI)
|
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 13 KiB |
BIN
src-electron/icons/linux-512x512.png
Normal file
After Width: | Height: | Size: 44 KiB |
41
src-electron/main-process/electron-main.dev.js
Normal file
|
@ -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'
|
114
src-electron/main-process/electron-main.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import { app, BrowserWindow, nativeTheme, Menu, MenuItem, shell } from 'electron'
|
||||||
|
app.commandLine.appendSwitch('disable-software-rasterizer', 'true')
|
||||||
|
|
||||||
|
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,
|
||||||
|
frame: false,
|
||||||
|
|
||||||
|
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,
|
||||||
|
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, url) => {
|
||||||
|
event.preventDefault()
|
||||||
|
shell.openExternal(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if((params.dictionarySuggestions && params.dictionarySuggestions.length) || params.misspelledWord){
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock()
|
||||||
|
|
||||||
|
if (!gotTheLock) {
|
||||||
|
app.quit()
|
||||||
|
} else {
|
||||||
|
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||||
|
// Someone tried to run a second instance, we should focus our window.
|
||||||
|
if (mainWindow) {
|
||||||
|
if (mainWindow.isMinimized()) mainWindow.restore()
|
||||||
|
mainWindow.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create myWindow, load the rest of the app, etc...
|
||||||
|
app.on('ready', createWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (mainWindow === null) {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { initialize } from '@electron/remote/main'
|
|
||||||
import { mainWindowCreation } from 'app/src-electron/mainScripts/mainWindowCreation'
|
|
||||||
import { app } from 'electron'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the app's Electron instance
|
|
||||||
*/
|
|
||||||
export const startApp = () => {
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the singular app's window and make sure it is the only one
|
|
||||||
*/
|
|
||||||
export const openAppWindowManager = () => {
|
|
||||||
// Create the app window in the normal way
|
|
||||||
app.whenReady().then(mainWindowCreation)
|
|
||||||
|
|
||||||
// Create the app window, if it still doesn't exist yet
|
|
||||||
app.on('activate', () => {
|
|
||||||
mainWindowCreation()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the app's Electron instance when all windows are closed
|
|
||||||
*/
|
|
||||||
export const closeAppManager = (platform: string) => {
|
|
||||||
// Close app if we are on anything that isn't Mac
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
if (platform !== 'darwin') {
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { app } from 'electron'
|
|
||||||
import path from 'path'
|
|
||||||
import packageJSON from '../../package.json' assert {type: 'json'}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the app name will have "-dev" affix at the end for the appData.
|
|
||||||
*/
|
|
||||||
const determineAppName = () => {
|
|
||||||
if (process.env.DEBUGGING) {
|
|
||||||
return `${packageJSON.name}-dev`
|
|
||||||
}
|
|
||||||
|
|
||||||
return packageJSON.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix the name and pathing of the app.
|
|
||||||
* - This function exists mostly due to dev-mode returning "Electron" instead of the app name.
|
|
||||||
*/
|
|
||||||
export const fixAppName = () => {
|
|
||||||
const appName = determineAppName()
|
|
||||||
if (appName) {
|
|
||||||
app.setName(appName)
|
|
||||||
const appData = app.getPath('appData')
|
|
||||||
app.setPath('userData', path.join(appData, appName))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
import { BrowserWindow, app, screen } from 'electron'
|
|
||||||
import { enable } from '@electron/remote/main'
|
|
||||||
import path from 'path'
|
|
||||||
/**
|
|
||||||
* Prevent app to launch a secondary instance
|
|
||||||
*/
|
|
||||||
const preventSecondaryAppInstance = (appWindow: BrowserWindow | undefined) => {
|
|
||||||
// Do not limit the window amount if we are in auto-test mode
|
|
||||||
if (process.env.TEST_ENV && (process.env.TEST_ENV === 'components' || process.env.TEST_ENV === 'e2e')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Determines if the app is the primary instance
|
|
||||||
* - This exists as a variable due to the app bugging out if used directly from "app"
|
|
||||||
*/
|
|
||||||
const isPrimaryInstance = app.requestSingleInstanceLock()
|
|
||||||
|
|
||||||
// Check this is NOT the primary app instance
|
|
||||||
if (!isPrimaryInstance) {
|
|
||||||
app.quit()
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
Someone tried to start a second instance. That one is closed already.
|
|
||||||
Our instance here (the first instance) maximizes it's own window and refocuses it.
|
|
||||||
*/
|
|
||||||
app.on('second-instance', () => {
|
|
||||||
if (appWindow) {
|
|
||||||
if (appWindow.isMinimized()) {
|
|
||||||
appWindow.restore()
|
|
||||||
}
|
|
||||||
appWindow.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the main app window
|
|
||||||
*/
|
|
||||||
export const mainWindowCreation = () => {
|
|
||||||
/**
|
|
||||||
* Retrieve actual display size to stop flicker/debounce that happens with "maximize" function at first
|
|
||||||
*/
|
|
||||||
const displaySizes = screen.getPrimaryDisplay().workAreaSize
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial window options
|
|
||||||
*/
|
|
||||||
let appWindow: BrowserWindow | undefined = new BrowserWindow({
|
|
||||||
width: displaySizes.width,
|
|
||||||
height: displaySizes.height,
|
|
||||||
useContentSize: true,
|
|
||||||
frame: false,
|
|
||||||
show: false,
|
|
||||||
center: true,
|
|
||||||
icon: path.resolve(__dirname, '../icons/icon.png'),
|
|
||||||
webPreferences: {
|
|
||||||
sandbox: false,
|
|
||||||
contextIsolation: true,
|
|
||||||
preload: path.resolve(__dirname, process.env.QUASAR_ELECTRON_PRELOAD)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Enable actual webContents inside the created window
|
|
||||||
enable(appWindow.webContents)
|
|
||||||
|
|
||||||
// Show the windows once electron is ready to show the actual HTML content
|
|
||||||
appWindow.once('ready-to-show', () => {
|
|
||||||
if (appWindow) {
|
|
||||||
appWindow.show()
|
|
||||||
appWindow.focus()
|
|
||||||
appWindow.maximize()
|
|
||||||
|
|
||||||
// In case the windows somehow didn't maximize at first, make sure it does; this is a fix for slower machines
|
|
||||||
setTimeout(() => {
|
|
||||||
if (appWindow) {
|
|
||||||
appWindow.maximize()
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set the current window's menu as empty
|
|
||||||
appWindow.setMenu(null)
|
|
||||||
|
|
||||||
// Load the basic app URL
|
|
||||||
appWindow.loadURL(process.env.APP_URL)
|
|
||||||
|
|
||||||
// Open DevTools by default if the app is running in Dev mode or Production with debug enabled
|
|
||||||
if (process.env.DEBUGGING) {
|
|
||||||
appWindow.webContents.openDevTools()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the app window properly closes when it is closed in any way, shape or form
|
|
||||||
appWindow.on('closed', () => {
|
|
||||||
appWindow = undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check if we are on the primary or secondary instance of the app
|
|
||||||
preventSecondaryAppInstance(appWindow)
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { Menu } from 'electron'
|
|
||||||
import os from 'os'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current OS string-indetifier
|
|
||||||
*/
|
|
||||||
export const tweakRetriveOS = () => {
|
|
||||||
return process.platform || os.platform()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the default menu from all current and future windows the current app
|
|
||||||
*/
|
|
||||||
export const tweakMenuRemover = () => {
|
|
||||||
Menu.setApplicationMenu(null)
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { app, nativeTheme } from 'electron'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
/**
|
|
||||||
*Fix Windows related-bug with DevTools extensions and dark mode
|
|
||||||
*/
|
|
||||||
export const windowsDevToolsExtensionsFix = (platform: string) => {
|
|
||||||
try {
|
|
||||||
if (platform === 'win32' && nativeTheme.shouldUseDarkColors === true) {
|
|
||||||
require('fs').unlinkSync(
|
|
||||||
path.join(app.getPath('userData'), 'DevTools Extensions')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
394
src/App.vue
|
@ -1,43 +1,371 @@
|
||||||
<template>
|
<template>
|
||||||
<router-view />
|
<div id="q-app">
|
||||||
|
<appWindowButtons />
|
||||||
|
<router-view />
|
||||||
|
<q-window
|
||||||
|
v-model="advSearchWindowVisible"
|
||||||
|
no-resize
|
||||||
|
dark
|
||||||
|
title="Advanced Search Cheatsheet"
|
||||||
|
:height="510"
|
||||||
|
:width="425"
|
||||||
|
:start-x="50"
|
||||||
|
:start-y="150"
|
||||||
|
:actions="['pin', 'close']"
|
||||||
|
content-class="bg-gunmetal-light text-accent advSearchWindow"
|
||||||
|
>
|
||||||
|
<div class="q-pa-md fit">
|
||||||
|
<q-markdown no-heading-anchor-links>
|
||||||
|
{{$t('documents.advancedSearchCheatSheet')}}
|
||||||
|
</q-markdown>
|
||||||
|
</div>
|
||||||
|
</q-window>
|
||||||
|
|
||||||
|
<q-window
|
||||||
|
v-model="corkboardWindowVisible"
|
||||||
|
dark
|
||||||
|
title="Note board"
|
||||||
|
gripper-border-color="primary"
|
||||||
|
gripper-background-color="primary"
|
||||||
|
:height="600"
|
||||||
|
:width="350"
|
||||||
|
:start-x="350"
|
||||||
|
:start-y="100"
|
||||||
|
:actions="['pin', 'close']"
|
||||||
|
content-class="bg-gunmetal-light text-accent noteBoardWindow"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
class="corkboardInput"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
|
||||||
|
v-model="corkboardContent"
|
||||||
|
filled
|
||||||
|
dark
|
||||||
|
@keyup="processCorkboardInput"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</q-window>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts">
|
||||||
import { useRouter } from 'vue-router'
|
import BaseClass from "src/BaseClass"
|
||||||
import { tipsTricksTriviaNotification } from 'app/src/scripts/appInfo/tipsTricksTriviaNotification'
|
import { Component, Watch } from "vue-property-decorator"
|
||||||
|
import { defaultKeybinds } from "src/scripts/appSettings/defaultKeybinds"
|
||||||
|
import appWindowButtons from "src/components/appHeader/AppWindowButtons.vue"
|
||||||
|
import PouchDB from "pouchdb"
|
||||||
|
import { OptionsStateInteface } from "./store/module-options/state"
|
||||||
|
import { colors } from "quasar"
|
||||||
|
import { tipsTricks } from "src/scripts/utilities/tipsTricks"
|
||||||
|
import { shell } from "electron"
|
||||||
|
import { summonAllPlusheForms } from "src/scripts/utilities/plusheMascot"
|
||||||
|
import { saveCorkboard, retrieveCorkboard } from "src/scripts/projectManagement/projectManagent"
|
||||||
|
|
||||||
/**
|
@Component({
|
||||||
* Local router variable
|
components: {
|
||||||
*/
|
appWindowButtons: appWindowButtons
|
||||||
const router = useRouter()
|
}
|
||||||
|
})
|
||||||
|
export default class App extends BaseClass {
|
||||||
|
/****************************************************************/
|
||||||
|
// APP START & END SETUP
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
/**
|
created () {
|
||||||
* Testing type currently possibly happening
|
// Catch middle clicks
|
||||||
*/
|
window.addEventListener("auxclick", this.reactToMiddleClick)
|
||||||
const testingType = window.extraEnvVariables.TEST_ENV
|
|
||||||
|
|
||||||
/**
|
// Add a secondary blocker to prevent the middle-mouse button scrolling
|
||||||
* Name of the component being possibly tested via component testing
|
document.body.onmousedown = function (e) {
|
||||||
*/
|
if (e.button === 1) {
|
||||||
const testingComponentName = window.extraEnvVariables.COMPONENT_NAME
|
e.preventDefault()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// Catch normal clicks inside wysiwyg
|
||||||
* Determine if some testing is happening
|
window.addEventListener("click", this.openWysiwygLink)
|
||||||
*/
|
|
||||||
const isComponentTesting = (testingType && testingType === 'components' && testingComponentName)
|
|
||||||
|
|
||||||
/**
|
// Load settings
|
||||||
* In case of some testing happening:
|
this.loadSettings().catch(e => console.log(e))
|
||||||
* - Reroute to the proper component path route assuming all is properly set.
|
|
||||||
* - Otherwise, make sure we are on homepage on load.
|
// React to keybind presses
|
||||||
*/
|
window.addEventListener("keydown", this.triggerKeyPush)
|
||||||
if (isComponentTesting) {
|
|
||||||
router.push({ path: `/componentTesting/${testingComponentName}` })
|
// Load the popup hint on start
|
||||||
} else {
|
this.loadHintPopup()
|
||||||
router.push({ path: '/' })
|
|
||||||
// TODO add checking if "Did you know" popup should show
|
this.loadCorkboardCotent().catch(e => console.log(e))
|
||||||
// TODO add checking if "Did you know" popup should be showing a mascot or an icon
|
}
|
||||||
tipsTricksTriviaNotification(false)
|
|
||||||
|
destroyed () {
|
||||||
|
window.removeEventListener("auxclick", this.reactToMiddleClick)
|
||||||
|
|
||||||
|
this.deregisterCustomKeybinds()
|
||||||
|
this.deregisterDefaultKeybinds()
|
||||||
|
window.removeEventListener("keydown", this.triggerKeyPush)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// START NOTIFICATION
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for the startup notification
|
||||||
|
*/
|
||||||
|
starupNotif = null as any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification checker
|
||||||
|
* Can go up to 3
|
||||||
|
*/
|
||||||
|
popupCheck = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the actual popup
|
||||||
|
*/
|
||||||
|
loadHintPopup () {
|
||||||
|
const options = this.SGET_options
|
||||||
|
|
||||||
|
// Considering there is a bit of a delay between the initial load of the store DB content, we give the program 3 attempts to load the data over 3 seconds. If no is loaded in that time, we assume that the settings are not set at all and display the hint as normal.
|
||||||
|
if ((!options._id || !options._rev) && this.popupCheck < 3) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.popupCheck++
|
||||||
|
this.loadHintPopup()
|
||||||
|
}, 1000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.hideTooltipsStart) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageToShow = tipsTricks[Math.floor(Math.random() * tipsTricks.length)]
|
||||||
|
const plusheForm = summonAllPlusheForms[Math.floor(Math.random() * summonAllPlusheForms.length)]
|
||||||
|
this.starupNotif = this.$q.notify({
|
||||||
|
|
||||||
|
timeout: 15000,
|
||||||
|
icon: (this.hidePlushes) ? "mdi-help" : undefined,
|
||||||
|
color: "info",
|
||||||
|
message: "Did you know?",
|
||||||
|
avatar: (!this.hidePlushes) ? plusheForm : undefined,
|
||||||
|
caption: messageToShow,
|
||||||
|
actions: [{ icon: "mdi-close", color: "white" }]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the startup notification if the user changed the route before it disappeared
|
||||||
|
*/
|
||||||
|
@Watch("$route", { deep: true })
|
||||||
|
onUrlChange () {
|
||||||
|
if (typeof this.starupNotif === "function") {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
this.starupNotif()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// KEYBIND HANDLING
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to keybind combinations being pushed and submit them to the store
|
||||||
|
*/
|
||||||
|
triggerKeyPush (e:any) {
|
||||||
|
// console.log("")
|
||||||
|
// console.log(`Key: ${e.key}`)
|
||||||
|
// console.log(`Ctrl: ${e.ctrlKey}`)
|
||||||
|
// console.log(`Shift: ${e.shiftKey}`)
|
||||||
|
// console.log(`Alt: ${e.altKey}`)
|
||||||
|
// console.log(e)
|
||||||
|
|
||||||
|
if (e?.altKey === true || e?.ctrlKey || e?.shiftKey) {
|
||||||
|
const ouputKeycombo = {
|
||||||
|
altKey: e.altKey,
|
||||||
|
ctrlKey: e.ctrlKey,
|
||||||
|
shiftKey: e.shiftKey,
|
||||||
|
which: e.which
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SSET_updatePressedKey(ouputKeycombo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a default keybind into the store
|
||||||
|
*/
|
||||||
|
registerDefaultKeybinds () {
|
||||||
|
// @ts-ignore
|
||||||
|
defaultKeybinds.forEach(e => this.SSET_registerDefaultKeybind(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a default keybind from the store
|
||||||
|
*/
|
||||||
|
deregisterDefaultKeybinds () {
|
||||||
|
// @ts-ignore
|
||||||
|
defaultKeybinds.forEach(e => this.SSET_deregisterDefaultKeybind(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a custom keybind into the store
|
||||||
|
*/
|
||||||
|
registerCustomKeybinds () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.SGET_options.userKeybindList.forEach(e => this.SSET_registerUserKeybind(e))
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a custom keybind from the store
|
||||||
|
*/
|
||||||
|
deregisterCustomKeybinds () {
|
||||||
|
// @ts-ignore
|
||||||
|
defaultKeybinds.forEach(e => this.SSET_deregisterUserKeybind(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// VARIOUS APP FUNCTIONALITY
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open wysiwyg links in default browser window
|
||||||
|
*/
|
||||||
|
openWysiwygLink (event: MouseEvent) {
|
||||||
|
event.preventDefault()
|
||||||
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
if (event.target && event.target.tagName.toLowerCase() === "a" && event.target.closest(".fieldWysiwyg")) {
|
||||||
|
const isValidHttpUrl = (string:string) => {
|
||||||
|
let url
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(string)
|
||||||
|
}
|
||||||
|
catch (_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.protocol === "http:" || url.protocol === "https:"
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (isValidHttpUrl(event.target.href)) {
|
||||||
|
// @ts-ignore
|
||||||
|
shell.openExternal(event.target.href).catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to middle mouse button clicks
|
||||||
|
*/
|
||||||
|
reactToMiddleClick (e: {button: number, preventDefault: ()=> void}) {
|
||||||
|
if (e.button === 1) {
|
||||||
|
e.preventDefault()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load settings for the first time upon app load
|
||||||
|
*/
|
||||||
|
async loadSettings () {
|
||||||
|
const SettingsDB = new PouchDB("fa-settings")
|
||||||
|
const settingsData = await SettingsDB.allDocs({ include_docs: true })
|
||||||
|
const settings = settingsData?.rows[0]?.doc as unknown as OptionsStateInteface
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
this.SSET_options(settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.registerDefaultKeybinds()
|
||||||
|
this.registerCustomKeybinds()
|
||||||
|
|
||||||
|
await SettingsDB.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update dark/light mode across the app based on what is currently in the store
|
||||||
|
*/
|
||||||
|
@Watch("SGET_options", { deep: true })
|
||||||
|
onSettingsChange () {
|
||||||
|
const options = this.SGET_options
|
||||||
|
|
||||||
|
this.hidePlushes = options.hidePlushes
|
||||||
|
this.$q.dark.set(options.darkMode)
|
||||||
|
if (options.darkMode) {
|
||||||
|
colors.setBrand("dark", "#1b333e")
|
||||||
|
colors.setBrand("primary", "#ffd673")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
colors.setBrand("dark", "#18303a")
|
||||||
|
colors.setBrand("primary", "#e8bb50")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the mascot... nooo :(
|
||||||
|
*/
|
||||||
|
hidePlushes = false
|
||||||
|
|
||||||
|
@Watch("SGET_getAdvSearchWindowVisible")
|
||||||
|
onAdvSearchWindowOpen () {
|
||||||
|
this.advSearchWindowVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
advSearchWindowVisible = false
|
||||||
|
|
||||||
|
@Watch("SGET_getNoteCorkboardhWindowVisible")
|
||||||
|
onCorkboardWindowOpen () {
|
||||||
|
this.corkboardWindowVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
corkboardWindowVisible = false
|
||||||
|
|
||||||
|
corkboardContent = ""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce timer to prevent buggy input sync
|
||||||
|
*/
|
||||||
|
corkboardTimer = null as any
|
||||||
|
|
||||||
|
processCorkboardInput () {
|
||||||
|
clearTimeout(this.corkboardTimer)
|
||||||
|
this.corkboardTimer = setTimeout(() => {
|
||||||
|
saveCorkboard(this.corkboardContent).catch(e => console.log(e))
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadCorkboardCotent () {
|
||||||
|
this.corkboardContent = await retrieveCorkboard()
|
||||||
|
if (this.corkboardContent.length > 0) {
|
||||||
|
this.corkboardWindowVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Local keybinds
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
@Watch("SGET_getCurrentKeyBindData", { deep: true })
|
||||||
|
processKeyPush () {
|
||||||
|
// Toggle the Advanced search cheatsheet
|
||||||
|
if (this.determineKeyBind("toggleAdvSearchCheatsheet")) {
|
||||||
|
this.advSearchWindowVisible = !this.advSearchWindowVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Note Board - CTRL + ALT + SHIFT + P
|
||||||
|
if (this.determineKeyBind("toggleNoteCorkboard")) {
|
||||||
|
this.corkboardWindowVisible = !this.corkboardWindowVisible
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
587
src/BaseClass.ts
Normal file
|
@ -0,0 +1,587 @@
|
||||||
|
import { OptionsStateInteface } from "./store/module-options/state"
|
||||||
|
import { KeyManagementInterface } from "./store/module-keybinds/state"
|
||||||
|
import { I_OpenedDocument, I_ShortenedDocument } from "./interfaces/I_OpenedDocument"
|
||||||
|
import { Component, Vue } from "vue-property-decorator"
|
||||||
|
import { namespace } from "vuex-class"
|
||||||
|
import { I_Blueprint } from "src/interfaces/I_Blueprint"
|
||||||
|
import { I_NewObjectTrigger } from "src/interfaces/I_NewObjectTrigger"
|
||||||
|
import { uid, colors, extend } from "quasar"
|
||||||
|
import { I_FieldRelationship } from "src/interfaces/I_FieldRelationship"
|
||||||
|
import { I_KeyPressObject } from "src/interfaces/I_KeypressObject"
|
||||||
|
|
||||||
|
const Blueprints = namespace("blueprintsModule")
|
||||||
|
const AllDocuments = namespace("allDocumentsModule")
|
||||||
|
const OpenedDocuments = namespace("openedDocumentsModule")
|
||||||
|
const Keybinds = namespace("keybindsModule")
|
||||||
|
const Options = namespace("optionsModule")
|
||||||
|
const Dialogs = namespace("dialogsModule")
|
||||||
|
const FloatingWindows = namespace("floatingWindowsModule")
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class BaseClass extends Vue {
|
||||||
|
/****************************************************************/
|
||||||
|
// UTILITY FUNCTIONS
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates unique ID string
|
||||||
|
*/
|
||||||
|
generateUID () : string {
|
||||||
|
return uid()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves icon color for relationship searches
|
||||||
|
* If the current document has "activeTypeSearch" property, then return "primary" color instead
|
||||||
|
*/
|
||||||
|
retrieveIconColor (document: I_ShortenedDocument): string {
|
||||||
|
// @ts-ignore
|
||||||
|
return (document.activeTypeSearch) ? colors.getBrand("primary") : document.color
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async wait for XY miliseconds
|
||||||
|
*/
|
||||||
|
sleep (ms:number) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip all tags from a string
|
||||||
|
*/
|
||||||
|
stripTags (input: string) {
|
||||||
|
return (input) ? input.replace(/<[^>]+>/g, "") : input
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// FLOATING WINDOWS
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
@FloatingWindows.Getter("getAdvSearchWindowVisible") SGET_getAdvSearchWindowVisible!: string
|
||||||
|
@FloatingWindows.Mutation("setAdvSearchWindowVisible") SSET_setAdvSearchWindowVisible!: () => void
|
||||||
|
|
||||||
|
@FloatingWindows.Getter("getNoteCorkboardhWindowVisible") SGET_getNoteCorkboardhWindowVisible!: string
|
||||||
|
@FloatingWindows.Mutation("setNoteCorkboardWindowVisible") SSET_setNoteCorkboardWindowVisible!: () => void
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// KEYBINDS MANAGEMENT
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
@Keybinds.Getter("getCurrentKeyBindData") SGET_getCurrentKeyBindData!: KeyManagementInterface
|
||||||
|
|
||||||
|
@Keybinds.Mutation("registerDefaultKeybind") SSET_registerDefaultKeybind!: (input: I_KeyPressObject) => void
|
||||||
|
@Keybinds.Mutation("deregisterDefaultKeybind") SSET_deregisterDefaultKeybind!: (input: I_KeyPressObject) => void
|
||||||
|
@Keybinds.Mutation("registerUserKeybind") SSET_registerUserKeybind!: (input: I_KeyPressObject) => void
|
||||||
|
@Keybinds.Mutation("deregisterUserKeybind") SSET_deregisterUserKeybind!: (input: I_KeyPressObject) => void
|
||||||
|
@Keybinds.Mutation("updatePressedKey") SSET_updatePressedKey!: (input: I_KeyPressObject) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a humanly redable represetation of the keybind in string form
|
||||||
|
* @param keybindId - Keybind object to build the string out of
|
||||||
|
*/
|
||||||
|
retrieveKeybindString (keybind: I_KeyPressObject): string {
|
||||||
|
let keybindString = ""
|
||||||
|
|
||||||
|
if (!keybind) {
|
||||||
|
return keybindString
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keybind.ctrlKey) {
|
||||||
|
keybindString += "CTRL + "
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keybind.altKey) {
|
||||||
|
keybindString += "ALT + "
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keybind.shiftKey) {
|
||||||
|
keybindString += "SHIFT + "
|
||||||
|
}
|
||||||
|
|
||||||
|
const keybinds = [37, 38, 39, 40, 9, 32, 13, 192, 189, 187, 219, 221, 220, 186, 222, 188, 190, 191, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123]
|
||||||
|
|
||||||
|
if (keybinds.includes(keybind.which)) {
|
||||||
|
if (keybind.which === 13) {
|
||||||
|
keybindString += "ENTER"
|
||||||
|
}
|
||||||
|
if (keybind.which === 9) {
|
||||||
|
keybindString += "TAB"
|
||||||
|
}
|
||||||
|
if (keybind.which === 32) {
|
||||||
|
keybindString += "SPACE"
|
||||||
|
}
|
||||||
|
if (keybind.which === 37) {
|
||||||
|
keybindString += "LEFT ARROW"
|
||||||
|
}
|
||||||
|
if (keybind.which === 38) {
|
||||||
|
keybindString += "UP ARROW"
|
||||||
|
}
|
||||||
|
if (keybind.which === 39) {
|
||||||
|
keybindString += "RIGHT ARROW"
|
||||||
|
}
|
||||||
|
if (keybind.which === 40) {
|
||||||
|
keybindString += "DOWN ARROW"
|
||||||
|
}
|
||||||
|
if (keybind.which === 192) {
|
||||||
|
keybindString += "`"
|
||||||
|
}
|
||||||
|
if (keybind.which === 189) {
|
||||||
|
keybindString += "-"
|
||||||
|
}
|
||||||
|
if (keybind.which === 187) {
|
||||||
|
keybindString += "+"
|
||||||
|
}
|
||||||
|
if (keybind.which === 219) {
|
||||||
|
keybindString += "["
|
||||||
|
}
|
||||||
|
if (keybind.which === 221) {
|
||||||
|
keybindString += "]"
|
||||||
|
}
|
||||||
|
if (keybind.which === 220) {
|
||||||
|
keybindString += "\\"
|
||||||
|
}
|
||||||
|
if (keybind.which === 186) {
|
||||||
|
keybindString += ";"
|
||||||
|
}
|
||||||
|
if (keybind.which === 222) {
|
||||||
|
keybindString += "'"
|
||||||
|
}
|
||||||
|
if (keybind.which === 188) {
|
||||||
|
keybindString += ","
|
||||||
|
}
|
||||||
|
if (keybind.which === 190) {
|
||||||
|
keybindString += "."
|
||||||
|
}
|
||||||
|
if (keybind.which === 191) {
|
||||||
|
keybindString += "/"
|
||||||
|
}
|
||||||
|
if (keybind.which === 112) {
|
||||||
|
keybindString += "F1"
|
||||||
|
}
|
||||||
|
if (keybind.which === 113) {
|
||||||
|
keybindString += "F2"
|
||||||
|
}
|
||||||
|
if (keybind.which === 114) {
|
||||||
|
keybindString += "F3"
|
||||||
|
}
|
||||||
|
if (keybind.which === 115) {
|
||||||
|
keybindString += "F4"
|
||||||
|
}
|
||||||
|
if (keybind.which === 116) {
|
||||||
|
keybindString += "F5"
|
||||||
|
}
|
||||||
|
if (keybind.which === 117) {
|
||||||
|
keybindString += "F6"
|
||||||
|
}
|
||||||
|
if (keybind.which === 118) {
|
||||||
|
keybindString += "F7"
|
||||||
|
}
|
||||||
|
if (keybind.which === 119) {
|
||||||
|
keybindString += "F8"
|
||||||
|
}
|
||||||
|
if (keybind.which === 120) {
|
||||||
|
keybindString += "F9"
|
||||||
|
}
|
||||||
|
if (keybind.which === 121) {
|
||||||
|
keybindString += "F10"
|
||||||
|
}
|
||||||
|
if (keybind.which === 122) {
|
||||||
|
keybindString += "F11"
|
||||||
|
}
|
||||||
|
if (keybind.which === 123) {
|
||||||
|
keybindString += "F12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keybindString += String.fromCharCode(keybind.which)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keybind.note) {
|
||||||
|
keybindString += `<div class="text-italic keybindNote">${keybind.note}</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return keybindString
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the keybind triggered the proper condition
|
||||||
|
* @param keybindId - ID of the keybind to determine what to match
|
||||||
|
*/
|
||||||
|
determineKeyBind (keybindId: string): boolean {
|
||||||
|
const currentKeybindData = this.SGET_getCurrentKeyBindData
|
||||||
|
|
||||||
|
const currentKeyPress = currentKeybindData.currentKeyPress
|
||||||
|
const pairedDefaultKeybind = currentKeybindData.defaults.find(e => e.id === keybindId)
|
||||||
|
const pairedCustomKeybind = currentKeybindData.userKeybinds.find(e => e.id === keybindId)
|
||||||
|
|
||||||
|
// Kill the script if no keybind exists of this anywhere
|
||||||
|
if (!pairedDefaultKeybind) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldToCheck = (pairedCustomKeybind) || pairedDefaultKeybind
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentKeyPress.altKey === fieldToCheck.altKey &&
|
||||||
|
currentKeyPress.ctrlKey === fieldToCheck.ctrlKey &&
|
||||||
|
currentKeyPress.shiftKey === fieldToCheck.shiftKey &&
|
||||||
|
currentKeyPress.which === fieldToCheck.which
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// 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
|
||||||
|
@Blueprints.Mutation("setBlueprint") SSET_blueprint!: (input: I_Blueprint) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a brand new route for the new object with individual ID
|
||||||
|
* @param newObject A new object to be creared
|
||||||
|
*/
|
||||||
|
addNewObjectRoute (newObject: I_NewObjectTrigger) {
|
||||||
|
const parentID = (newObject?.parent) || ""
|
||||||
|
|
||||||
|
this.$router.push({
|
||||||
|
path: `/project/display-content/${newObject._id}/${uid()}`,
|
||||||
|
query: { parent: parentID }
|
||||||
|
}).catch((e: {name: string}) => {
|
||||||
|
const errorName : string = e.name
|
||||||
|
if (errorName === "NavigationDuplicated") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a new route for an already existing object
|
||||||
|
* @param existingObject An already existing object passed in
|
||||||
|
*/
|
||||||
|
openExistingDocumentRouteWithEdit (existingObject:I_OpenedDocument | I_FieldRelationship) {
|
||||||
|
const currentDoc = this.findRequestedOrActiveDocument()
|
||||||
|
|
||||||
|
if (currentDoc && existingObject._id === currentDoc._id && !currentDoc.editMode) {
|
||||||
|
const dataCopy: I_OpenedDocument = extend(true, {}, currentDoc)
|
||||||
|
dataCopy.editMode = true
|
||||||
|
const dataPass = { doc: dataCopy, treeAction: false }
|
||||||
|
this.SSET_updateOpenedDocument(dataPass)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$router.push({
|
||||||
|
path: existingObject.url,
|
||||||
|
query: { editMode: "editMode" }
|
||||||
|
}).catch((e: {name: string}) => {
|
||||||
|
const errorName : string = e.name
|
||||||
|
if (errorName === "NavigationDuplicated") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a new route for an already existing object
|
||||||
|
* @param existingObject An already existing object passed in
|
||||||
|
*/
|
||||||
|
openExistingDocumentRoute (existingObject:I_OpenedDocument | I_FieldRelationship) {
|
||||||
|
this.$router.push({ path: existingObject.url }).catch((e: {name: string}) => {
|
||||||
|
const errorName : string = e.name
|
||||||
|
if (errorName === "NavigationDuplicated") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// OPTION MANAGEMENT
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
@Options.Getter("getOptions") SGET_options!: OptionsStateInteface
|
||||||
|
|
||||||
|
@Options.Action("setOptions") SSET_options!: (input: OptionsStateInteface) => void
|
||||||
|
|
||||||
|
toggleHierarchicalTree (): void {
|
||||||
|
const optionsSnapshot: OptionsStateInteface = extend(true, {}, this.SGET_options)
|
||||||
|
|
||||||
|
optionsSnapshot.hideHierarchyTree = !optionsSnapshot.hideHierarchyTree
|
||||||
|
|
||||||
|
this.SSET_options(optionsSnapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// ALL DOCUMENTS MANAGEMENT
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
@AllDocuments.Getter("getFirstRunState") SGET_allDocumentsFirstRunState!: boolean
|
||||||
|
|
||||||
|
@AllDocuments.Action("markAsNonFirstRun") SSET_allDocumentsMarkAsNonFirstRun!: () => void
|
||||||
|
|
||||||
|
@AllDocuments.Getter("getAllDocuments") SGET_allDocuments !: {
|
||||||
|
timestamp: string,
|
||||||
|
docs: I_ShortenedDocument[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllDocuments.Getter("getAllDocumentsWithoutCategories") SGET_allDocumentsWithoutCategories !: {
|
||||||
|
timestamp: string,
|
||||||
|
docs: I_ShortenedDocument[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllDocuments.Getter("getDocumentsByType") SGET_allDocumentsByType!: (documentTypeID: string) => {
|
||||||
|
timestamp: string,
|
||||||
|
id: string,
|
||||||
|
docs: I_ShortenedDocument[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllDocuments.Getter("getDocumentsByTypeWithoutCategories") SGET_allDocumentsByTypeWithoutCategories!: (documentTypeID: string) => {
|
||||||
|
timestamp: string,
|
||||||
|
id: string,
|
||||||
|
docs: I_ShortenedDocument[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllDocuments.Getter("getDocument") SGET_document!: (id: string) => I_ShortenedDocument
|
||||||
|
|
||||||
|
@AllDocuments.Action("addDocument") SSET_addDocument!: (input: {
|
||||||
|
doc: I_ShortenedDocument
|
||||||
|
}) => void
|
||||||
|
|
||||||
|
@AllDocuments.Action("updateDocument") SSET_updateDocument!: (input: {
|
||||||
|
doc: I_ShortenedDocument
|
||||||
|
}) => void
|
||||||
|
|
||||||
|
@AllDocuments.Action("removeDocument") SSET_removeDocument!: (input: {
|
||||||
|
doc: I_ShortenedDocument
|
||||||
|
}) => void
|
||||||
|
|
||||||
|
@AllDocuments.Action("mapNewDocumentType") SSET_mapNewDocumentType!: (input: {
|
||||||
|
id: string,
|
||||||
|
docs: I_ShortenedDocument[]
|
||||||
|
}) => void
|
||||||
|
|
||||||
|
@AllDocuments.Action("resetDocuments") SSET_resetAllDocuments!: () => void
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// OPEN DOCUMENTS MANAGEMENT
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
@OpenedDocuments.Getter("getAllDocuments") SGET_allOpenedDocuments !: {
|
||||||
|
treeAction: boolean,
|
||||||
|
lastRemovedIndex: number,
|
||||||
|
timestamp: string,
|
||||||
|
docs: I_OpenedDocument[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@OpenedDocuments.Getter("getDocument") SGET_openedDocument!: (id: string) => I_OpenedDocument
|
||||||
|
|
||||||
|
@OpenedDocuments.Action("addDocument") SSET_addOpenedDocument!: (input: {
|
||||||
|
doc: I_OpenedDocument,
|
||||||
|
treeAction: boolean
|
||||||
|
}) => void
|
||||||
|
|
||||||
|
@OpenedDocuments.Action("updateDocument") SSET_updateOpenedDocument!: (input: {
|
||||||
|
doc: I_OpenedDocument,
|
||||||
|
treeAction: boolean
|
||||||
|
}) => void
|
||||||
|
|
||||||
|
@OpenedDocuments.Action("removeDocument") SSET_removeOpenedDocument!: (input: {
|
||||||
|
doc: I_OpenedDocument,
|
||||||
|
treeAction: boolean
|
||||||
|
}) => void
|
||||||
|
|
||||||
|
@OpenedDocuments.Action("closeAllDocuments") SSET_closeAllDocuments!: () => void
|
||||||
|
@OpenedDocuments.Action("forceCloseAllDocuments") SSET_forceCloseAllDocuments!: () => void
|
||||||
|
@OpenedDocuments.Action("closeAllButCurrentDocuments") SSET_closeAllButCurrentDocuments!: (input: I_OpenedDocument) => void
|
||||||
|
@OpenedDocuments.Action("forceCloseAllButCurrentDocuments") SSET_forceCloseAllButCurrentDocuments!: (input: I_OpenedDocument) => void
|
||||||
|
|
||||||
|
@OpenedDocuments.Action("triggerTreeAction") SSET_triggerTreeAction!: () => void
|
||||||
|
@OpenedDocuments.Action("resetDocuments") SSET_resetDocuments!: () => void
|
||||||
|
@OpenedDocuments.Action("resetRemoveIndex") SSET_resetRemoveIndex!: () => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If provided with an document, finds it among the opened list
|
||||||
|
* Otherwise retireves the currently opened document based on the currently active route
|
||||||
|
*/
|
||||||
|
findRequestedOrActiveDocument (doc?: I_OpenedDocument) {
|
||||||
|
if (doc) {
|
||||||
|
return (this.SGET_allOpenedDocuments.docs.find(e => e.url === doc.url)) || false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (this.SGET_allOpenedDocuments.docs.find(e => e.url === this.$route.path)) || false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves value of requested field. If the field doesn't exist, returns false instead
|
||||||
|
* @param document - Document object that is expected to contain the field
|
||||||
|
* @param fieldID - ID of the field to check
|
||||||
|
*/
|
||||||
|
retrieveFieldValue (document: I_OpenedDocument| I_ShortenedDocument, fieldID: string) : string | number | [] | false | I_FieldRelationship {
|
||||||
|
const fieldData = document?.extraFields
|
||||||
|
|
||||||
|
// Fizzle if field doesnt exist
|
||||||
|
if (!fieldData) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const fieldValue = fieldData.find(f => f.id === fieldID)?.value as unknown as string
|
||||||
|
return fieldValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves array length of requested field. If the field doesn't exist or isn't array, returns false instead
|
||||||
|
* @param document - Document object that is expected to contain the field
|
||||||
|
* @param fieldID - ID of the field to check
|
||||||
|
*/
|
||||||
|
retrieveFieldLength (document: I_OpenedDocument, fieldID: string) : number | false {
|
||||||
|
const fieldData = document?.extraFields
|
||||||
|
|
||||||
|
// Fizzle if field doesnt exist
|
||||||
|
if (!fieldData) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const fieldValue = fieldData.find(f => f.id === fieldID)?.value
|
||||||
|
|
||||||
|
// Fizzle if the value isn't an array
|
||||||
|
if (!Array.isArray(fieldValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return fieldValue.length
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dialogs.Getter("getDialogsState") SGET_getDialogsState!: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the route
|
||||||
|
*/
|
||||||
|
refreshRoute () {
|
||||||
|
if (this.SGET_options.disableCloseAftertSelectQuickSearch && this.SGET_getDialogsState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingDocuments = this.SGET_allOpenedDocuments.docs
|
||||||
|
|
||||||
|
const lastIndex = this.SGET_allOpenedDocuments.lastRemovedIndex
|
||||||
|
|
||||||
|
const newIndex = (lastIndex > -1 && lastIndex < remainingDocuments.length) ? lastIndex : remainingDocuments.length - 1
|
||||||
|
|
||||||
|
if (lastIndex > -1) {
|
||||||
|
this.SSET_resetRemoveIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming there are any documents in the current list
|
||||||
|
if (remainingDocuments.length > 0) {
|
||||||
|
const lastDocument = remainingDocuments[newIndex]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a new route if the new route isnt the one we are already on
|
||||||
|
const newRoute = `/project/display-content/${lastDocument.type}/${lastDocument._id}`
|
||||||
|
if (currentRoute !== newRoute) {
|
||||||
|
this.$router.push({ path: newRoute }).catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there are no documents inthe list, just navigate to the front page of the project
|
||||||
|
else {
|
||||||
|
this.$router.push({ path: "/project" }).catch((e: {name: string}) => {
|
||||||
|
if (e && e.name !== "NavigationDuplicated") {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// DOCUMENT LIST MANAGEMENT
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively retrieves a full hieararchical path from a full list
|
||||||
|
*/
|
||||||
|
getDocumentHieararchicalPath (document: I_OpenedDocument, list: I_OpenedDocument[]) {
|
||||||
|
let hierarchicalString = ""
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const parentDoc = (this.retrieveFieldValue(document, "parentDoc"))?.value
|
||||||
|
|
||||||
|
const parentDocInDB = (list.find(p => p._id === parentDoc?._id))
|
||||||
|
|
||||||
|
if (!parentDoc || (parentDoc && !parentDocInDB)) {
|
||||||
|
const singleBlueprintName = this.SGET_allBlueprints.find(e => e._id === document.type)?.nameSingular
|
||||||
|
hierarchicalString += singleBlueprintName
|
||||||
|
return hierarchicalString
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingDoc = list.find((doc:I_OpenedDocument) => {
|
||||||
|
return doc._id === parentDoc._id
|
||||||
|
}) as I_OpenedDocument
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
hierarchicalString += this.retrieveFieldValue(matchingDoc, "name")
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const connectedReturn = this.getDocumentHieararchicalPath(matchingDoc, list)
|
||||||
|
if (connectedReturn) {
|
||||||
|
hierarchicalString = `${connectedReturn} > ${hierarchicalString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return hierarchicalString
|
||||||
|
}
|
||||||
|
|
||||||
|
mapShortDocument (doc: I_ShortenedDocument, dbDocuments: I_OpenedDocument[]) : I_ShortenedDocument {
|
||||||
|
return {
|
||||||
|
label: doc.extraFields.find(e => e.id === "name")?.value,
|
||||||
|
icon: doc.icon,
|
||||||
|
// @ts-ignore
|
||||||
|
id: doc._id,
|
||||||
|
_id: doc._id,
|
||||||
|
url: doc.url,
|
||||||
|
type: doc.type,
|
||||||
|
extraFields: doc.extraFields,
|
||||||
|
hasEdits: false,
|
||||||
|
// @ts-ignore
|
||||||
|
hierarchicalPath: this.getDocumentHieararchicalPath(doc, dbDocuments),
|
||||||
|
tags: doc.extraFields.find(e => e.id === "tags")?.value,
|
||||||
|
color: doc.extraFields.find(e => e.id === "documentColor")?.value,
|
||||||
|
bgColor: doc.extraFields.find(e => e.id === "documentBackgroundColor")?.value,
|
||||||
|
isCategory: doc.extraFields.find(e => e.id === "categorySwitch")?.value,
|
||||||
|
isMinor: doc.extraFields.find(e => e.id === "minorSwitch")?.value,
|
||||||
|
isDead: doc.extraFields.find(e => e.id === "deadSwitch")?.value
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deepFreeze (object: object) {
|
||||||
|
// Retrieve the property names defined on object
|
||||||
|
const propNames = Object.getOwnPropertyNames(object)
|
||||||
|
|
||||||
|
// Freeze properties before freezing self
|
||||||
|
|
||||||
|
for (const name of propNames) {
|
||||||
|
const value = object[name]
|
||||||
|
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
this.deepFreeze(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.freeze(object)
|
||||||
|
}
|
||||||
|
}
|
BIN
src/assets/Digital-Patreon-Logo_White.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
src/assets/GitHub-Mark-120px-plus.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
src/assets/GitHub-Mark-Light-120px-plus.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/appLogo.png
Normal file
After Width: | Height: | Size: 103 KiB |
1
src/assets/discordLogo.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><style>.st0{fill:#FFFFFF;}</style><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/fantasiaPlushe/fantasia_didYouKnow.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
BIN
src/assets/fantasiaPlushe/fantasia_reading.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
src/assets/reddit_logo_horizontal_on_orangered.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
4
src/boot/apex.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import Vue from "vue"
|
||||||
|
import VueApexCharts from "vue-apexcharts"
|
||||||
|
|
||||||
|
Vue.component("apexchart", VueApexCharts)
|
|
@ -1,31 +1,13 @@
|
||||||
import { boot } from 'quasar/wrappers'
|
import axios, { AxiosInstance } from "axios"
|
||||||
import axios, { AxiosInstance } from 'axios'
|
import { boot } from "quasar/wrappers"
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module "vue/types/vue" {
|
||||||
interface ComponentCustomProperties {
|
interface Vue {
|
||||||
$axios: AxiosInstance;
|
$axios: AxiosInstance;
|
||||||
$api: AxiosInstance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Be careful when using SSR for cross-request state pollution
|
export default boot(({ Vue }) => {
|
||||||
// due to creating a Singleton instance here;
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
// If any client changes this (global) instance, it might be a
|
Vue.prototype.$axios = axios
|
||||||
// good idea to move this instance creation inside of the
|
|
||||||
// "export default () => {}" function below (which runs individually
|
|
||||||
// for each client)
|
|
||||||
const api = axios.create({ baseURL: 'https://api.example.com' })
|
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
|
||||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
|
||||||
|
|
||||||
app.config.globalProperties.$axios = axios
|
|
||||||
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
|
||||||
// so you won't necessarily have to import axios in each vue file
|
|
||||||
|
|
||||||
app.config.globalProperties.$api = api
|
|
||||||
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
|
|
||||||
// so you can easily perform requests against your app's API
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export { api }
|
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { boot } from 'quasar/wrappers'
|
|
||||||
|
|
||||||
export default boot(() => {
|
|
||||||
document.addEventListener('click', e => {
|
|
||||||
/**
|
|
||||||
* Target of the click event
|
|
||||||
*/
|
|
||||||
const clickTarget = e.target as HTMLAnchorElement
|
|
||||||
|
|
||||||
if (clickTarget) {
|
|
||||||
/**
|
|
||||||
* Closest anchor element
|
|
||||||
* Selector might end up being empty if the anchor itself was clicked. If so, select it instead
|
|
||||||
*/
|
|
||||||
let originLink: HTMLAnchorElement|null|false = clickTarget.closest('a')
|
|
||||||
if (originLink === null) {
|
|
||||||
originLink = (clickTarget.tagName === 'a') ? clickTarget : false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originLink) {
|
|
||||||
/**
|
|
||||||
* HREF link of the url
|
|
||||||
*/
|
|
||||||
const linkURL = originLink.href
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the URL is extenal or not
|
|
||||||
*/
|
|
||||||
const isExternal = window.faExternalLinksManagerAPI.checkIfExternal(linkURL)
|
|
||||||
|
|
||||||
if (isExternal) {
|
|
||||||
/**
|
|
||||||
* If the URL is external, prevent default and open the URL via the electron API functionality
|
|
||||||
*/
|
|
||||||
e.preventDefault()
|
|
||||||
window.faExternalLinksManagerAPI.openExternal(linkURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,35 +1,23 @@
|
||||||
import { boot } from 'quasar/wrappers'
|
import { boot } from "quasar/wrappers"
|
||||||
import { createI18n } from 'vue-i18n'
|
import messages from "src/i18n"
|
||||||
|
import Vue from "vue"
|
||||||
|
import VueI18n from "vue-i18n"
|
||||||
|
|
||||||
import messages from 'src/i18n'
|
declare module "vue/types/vue" {
|
||||||
|
interface Vue {
|
||||||
export type MessageLanguages = keyof typeof messages;
|
i18n: VueI18n;
|
||||||
// Type-define 'en-US' as the master schema for the resource
|
}
|
||||||
export type MessageSchema = typeof messages['en-US'];
|
|
||||||
|
|
||||||
// See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition
|
|
||||||
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
||||||
declare module 'vue-i18n' {
|
|
||||||
// define the locale messages schema
|
|
||||||
export interface DefineLocaleMessage extends MessageSchema {}
|
|
||||||
|
|
||||||
// define the datetime format schema
|
|
||||||
export interface DefineDateTimeFormat {}
|
|
||||||
|
|
||||||
// define the number format schema
|
|
||||||
export interface DefineNumberFormat {}
|
|
||||||
}
|
}
|
||||||
/* eslint-enable @typescript-eslint/no-empty-interface */
|
|
||||||
|
Vue.use(VueI18n)
|
||||||
|
|
||||||
|
export const i18n = new VueI18n({
|
||||||
|
locale: "en-us",
|
||||||
|
fallbackLocale: "en-us",
|
||||||
|
messages
|
||||||
|
})
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
const i18n = createI18n({
|
|
||||||
locale: 'en-US',
|
|
||||||
fallbackLocale: 'en-US',
|
|
||||||
legacy: false,
|
|
||||||
warnHtmlMessage: false,
|
|
||||||
messages
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set i18n instance on app
|
// Set i18n instance on app
|
||||||
app.use(i18n)
|
app.i18n = i18n
|
||||||
})
|
})
|
||||||
|
|
8
src/boot/notify-defaults.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { Notify } from "quasar"
|
||||||
|
|
||||||
|
Notify.setDefaults({
|
||||||
|
position: "bottom-right",
|
||||||
|
timeout: 4000,
|
||||||
|
progress: true,
|
||||||
|
textColor: "cultured"
|
||||||
|
})
|
|
@ -1,8 +0,0 @@
|
||||||
import { Notify } from 'quasar'
|
|
||||||
|
|
||||||
Notify.setDefaults({
|
|
||||||
position: 'bottom-right',
|
|
||||||
timeout: 4000,
|
|
||||||
progress: true,
|
|
||||||
textColor: 'cultured'
|
|
||||||
})
|
|
|
@ -1,79 +0,0 @@
|
||||||
import { _electron as electron } from 'playwright'
|
|
||||||
import { test, expect } from '@playwright/test'
|
|
||||||
import { extraEnvVariablesAPI } from 'app/src-electron/customContentBridgeAPIs/extraEnvVariablesAPI'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra env settings to trigger component testing via Playwright
|
|
||||||
*/
|
|
||||||
const extraEnvSettings = {
|
|
||||||
TEST_ENV: 'components',
|
|
||||||
COMPONENT_NAME: 'AppControlMenus',
|
|
||||||
COMPONENT_PROPS: JSON.stringify({})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Electron main filepath
|
|
||||||
*/
|
|
||||||
const electronMainFilePath:string = extraEnvVariablesAPI.ELECTRON_MAIN_FILEPATH
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra rended timer buffer for tests to start after loading the app
|
|
||||||
* - Change here in order manually adjust this component's wait times
|
|
||||||
*/
|
|
||||||
const faFrontendRenderTimer = extraEnvVariablesAPI.FA_FRONTEND_RENDER_TIMER
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object of string data selectors for the component
|
|
||||||
*/
|
|
||||||
const selectorList = {
|
|
||||||
testMenu: 'appControlMenus-testMenu',
|
|
||||||
anyMenu: 'appControlMenus-anyMenu'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a custom "Test Title" menu button in the menu and check if it loaded
|
|
||||||
*/
|
|
||||||
test('Load "Test Title" menu button sub-component', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const testMenu = await appWindow.$(`[data-test-test-menu="${selectorList.testMenu}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (testMenu !== null) {
|
|
||||||
await expect(true).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if we have exactly one testing menu loaded
|
|
||||||
*/
|
|
||||||
test('Check if we have exactly one testing menu loaded', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const anyMenus = await appWindow.$$(`[data-test-any-menu="${selectorList.anyMenu}"]`)
|
|
||||||
|
|
||||||
// Check for example one testing menu
|
|
||||||
if (anyMenus.length === 1) {
|
|
||||||
await expect(true).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// No menus/too many menus
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,76 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="appControlMenus bg-dark"
|
|
||||||
>
|
|
||||||
<q-btn-group
|
|
||||||
flat
|
|
||||||
class="appControlMenus__inner"
|
|
||||||
>
|
|
||||||
<!-- Test data menu - FOR COMPOENT TEST PURPOSES ONLY -->
|
|
||||||
<AppControlSingleMenu
|
|
||||||
v-if="testingType === 'components'"
|
|
||||||
:data-input="testData"
|
|
||||||
data-test-test-menu="appControlMenus-testMenu"
|
|
||||||
data-test-any-menu="appControlMenus-anyMenu"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Project menu -->
|
|
||||||
<AppControlSingleMenu
|
|
||||||
v-if="testingType !== 'components'"
|
|
||||||
:data-input="project"
|
|
||||||
data-test-any-menu="appControlMenus-anyMenu"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Documents menu -->
|
|
||||||
<AppControlSingleMenu
|
|
||||||
v-if="testingType !== 'components'"
|
|
||||||
:data-input="documents"
|
|
||||||
data-test-any-menu="appControlMenus-anyMenu"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Tools menu -->
|
|
||||||
<AppControlSingleMenu
|
|
||||||
v-if="testingType !== 'components'"
|
|
||||||
:data-input="tools"
|
|
||||||
data-test-any-menu="appControlMenus-anyMenu"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Help & Info Menu -->
|
|
||||||
<AppControlSingleMenu
|
|
||||||
v-if="testingType !== 'components'"
|
|
||||||
:data-input="helpInfo"
|
|
||||||
data-test-any-menu="appControlMenus-anyMenu"
|
|
||||||
/>
|
|
||||||
</q-btn-group>
|
|
||||||
|
|
||||||
<!-- Dialog Popups -->
|
|
||||||
<DialogMarkdownDocument />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import { testData } from 'app/src/components/AppControlMenus/_testData/test.raw.component'
|
|
||||||
|
|
||||||
import { project } from 'app/src/components/AppControlMenus/_data/project'
|
|
||||||
import { documents } from 'app/src/components/AppControlMenus/_data/documents'
|
|
||||||
import { tools } from 'app/src/components/AppControlMenus/_data/tools'
|
|
||||||
import { helpInfo } from 'app/src/components/AppControlMenus/_data/helpInfo'
|
|
||||||
|
|
||||||
import AppControlSingleMenu from 'app/src/components/AppControlMenus/AppControlSingleMenu/AppControlSingleMenu.vue'
|
|
||||||
import DialogMarkdownDocument from 'app/src/components/DialogMarkdownDocument/DialogMarkdownDocument.vue'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testing type currently possibly happening
|
|
||||||
*/
|
|
||||||
const testingType = window.extraEnvVariables.TEST_ENV
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.appControlMenus {
|
|
||||||
&__inner {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,409 +0,0 @@
|
||||||
import { _electron as electron } from 'playwright'
|
|
||||||
import { test, expect } from '@playwright/test'
|
|
||||||
import { extraEnvVariablesAPI } from 'app/src-electron/customContentBridgeAPIs/extraEnvVariablesAPI'
|
|
||||||
import { testData } from '../_testData/test.fixed.component'
|
|
||||||
import { rgbToHex } from 'src/scripts/_utilities/colorFormatConvertors'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra env settings to trigger component testing via Playwright
|
|
||||||
*/
|
|
||||||
const extraEnvSettings = {
|
|
||||||
TEST_ENV: 'components',
|
|
||||||
COMPONENT_NAME: 'AppControlSingleMenu',
|
|
||||||
COMPONENT_PROPS: JSON.stringify({})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Electron main filepath
|
|
||||||
*/
|
|
||||||
const electronMainFilePath:string = extraEnvVariablesAPI.ELECTRON_MAIN_FILEPATH
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra rended timer buffer for tests to start after loading the app
|
|
||||||
* - Change here in order manually adjust this component's wait times
|
|
||||||
*/
|
|
||||||
const faFrontendRenderTimer = extraEnvVariablesAPI.FA_FRONTEND_RENDER_TIMER
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object of string data selectors for the component
|
|
||||||
*/
|
|
||||||
const selectorList = {
|
|
||||||
menuWrapper: 'AppControlSingleMenu-wrapper',
|
|
||||||
menuTitle: 'AppControlSingleMenu-title',
|
|
||||||
menuItem: 'AppControlSingleMenu-menuItem',
|
|
||||||
menuItemText: 'AppControlSingleMenu-menuItem-text',
|
|
||||||
menuItemIcon: 'AppControlSingleMenu-menuItem-icon',
|
|
||||||
menuItemSubMenu: 'AppControlSingleMenu-menuItem-subMenu',
|
|
||||||
menuItemSubMenuItem: 'AppControlSingleMenu-menuItem-subMenu-item',
|
|
||||||
menuItemSubMenuItemText: 'AppControlSingleMenu-menuItem-subMenu-item-text',
|
|
||||||
menuItemSubMenuItemIcon: 'AppControlSingleMenu-menuItem-subMenu-item-icon'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if the component managed to load the test data
|
|
||||||
*/
|
|
||||||
test('Test if the component managed to load the test data', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const menuWrapperElement = await appWindow.$(`[data-test="${selectorList.menuWrapper}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (menuWrapperElement !== null) {
|
|
||||||
const hasProperInput = await menuWrapperElement.evaluate(el => !!el.dataset.testHasProperDataInput)
|
|
||||||
|
|
||||||
await expect(hasProperInput).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a custom "Test Title" menu button in the menu and check if it loaded
|
|
||||||
*/
|
|
||||||
test('Check if the "Menu title" element is properly loaded and has proper content in it', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const menuTitleElement = await appWindow.$(`[data-test="${selectorList.menuTitle}"]`)
|
|
||||||
const menuTitleElementText = (menuTitleElement !== null) ? await menuTitleElement.textContent() : ''
|
|
||||||
|
|
||||||
// Check if the tested element exists and has proper title content
|
|
||||||
if (menuTitleElement !== null && menuTitleElementText === testData.title) {
|
|
||||||
await expect(true).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist OR lacks proper title in it
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the main menu has a wrapper, click and check if all menu elements loaded properly
|
|
||||||
*/
|
|
||||||
test('Check if the main menu has a wrapper, click and check if all menu elements loaded properly', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const menuWrapper = await appWindow.$(`[data-test="${selectorList.menuWrapper}"]`)
|
|
||||||
|
|
||||||
// Check if wrapper exists for clicking and if so, click it
|
|
||||||
if (menuWrapper !== null) {
|
|
||||||
await menuWrapper.click()
|
|
||||||
|
|
||||||
const menuItems = await appWindow.$$(`[data-test="${selectorList.menuItem}"]`)
|
|
||||||
const dataItems = testData.data.filter(item => item.mode === 'item')
|
|
||||||
|
|
||||||
// Check if element feed matched the data feed
|
|
||||||
await expect(menuItems.length === dataItems.length).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Wrapper for opening the menu doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the first main menu item has proper text and icon
|
|
||||||
*/
|
|
||||||
test('Check if the first main menu item has proper text and icon', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const menuWrapper = await appWindow.$(`[data-test="${selectorList.menuWrapper}"]`)
|
|
||||||
|
|
||||||
// Check if wrapper exists for clicking and if so, click it
|
|
||||||
if (menuWrapper !== null) {
|
|
||||||
await menuWrapper.click()
|
|
||||||
|
|
||||||
const firstMenuItem = await appWindow.$(`[data-test="${selectorList.menuItem}"]`)
|
|
||||||
const firstDataItem = testData.data.filter(item => item.mode === 'item')[0]
|
|
||||||
|
|
||||||
// Check if the item wrapper exists
|
|
||||||
if (firstMenuItem !== null) {
|
|
||||||
const firstMenuItemTextElement = await firstMenuItem.$(`[data-test="${selectorList.menuItemText}"]`)
|
|
||||||
const firstMenuItemIconElement = await firstMenuItem.$(`[data-test="${selectorList.menuItemIcon}"]`)
|
|
||||||
|
|
||||||
// Check if the icon and text wrappers exist
|
|
||||||
if (firstMenuItemTextElement !== null && firstMenuItemIconElement !== null) {
|
|
||||||
const firstMenuItemText = await firstMenuItemTextElement.textContent()
|
|
||||||
const firstDataItemText = firstDataItem.text
|
|
||||||
|
|
||||||
const firstMenuItemIconClassObject = await firstMenuItemIconElement.evaluate(el => el.classList)
|
|
||||||
const firstMenuItemIconClassList = Object.values(firstMenuItemIconClassObject).concat()
|
|
||||||
const firstDataItemIcon = firstDataItem.icon as string
|
|
||||||
|
|
||||||
await expect(firstMenuItemText === firstDataItemText && firstMenuItemIconClassList.includes(firstDataItemIcon)).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Item text or icon wrappers don't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Item wrapper doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Wrapper for opening the menu doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if text color class applied properly to any main menu item: Secondary
|
|
||||||
*/
|
|
||||||
test('Check if text color class applied properly to any main menu item: Secondary', async () => {
|
|
||||||
const testColorString = 'secondary'
|
|
||||||
const testColorHexString = '#f75746'
|
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const menuWrapper = await appWindow.$(`[data-test="${selectorList.menuWrapper}"]`)
|
|
||||||
|
|
||||||
// Check if wrapper exists for clicking and if so, click it
|
|
||||||
if (menuWrapper !== null) {
|
|
||||||
await menuWrapper.click()
|
|
||||||
|
|
||||||
const colorMenuItem = await appWindow.$(`.text-${testColorString}[data-test="${selectorList.menuItem}"]`)
|
|
||||||
|
|
||||||
// Check if the item wrapper exists
|
|
||||||
if (colorMenuItem !== null) {
|
|
||||||
const colorMenuString = await colorMenuItem.evaluate(el => getComputedStyle(el).getPropertyValue('color'))
|
|
||||||
const colorHexString = rgbToHex(colorMenuString)
|
|
||||||
|
|
||||||
// Compare color of the string with secondary color
|
|
||||||
await expect(colorHexString).toBe(testColorHexString)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Item wrapper doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Wrapper for opening the menu doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the sub-menu opens properly on click of the main menu item and all parts are loaded properly
|
|
||||||
*/
|
|
||||||
test('Check if the sub-menu opens properly on click of the main menu item and all parts are loaded properly', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const menuWrapper = await appWindow.$(`[data-test="${selectorList.menuWrapper}"]`)
|
|
||||||
|
|
||||||
// Check if main menu wrapper exists for clicking and if so, click it
|
|
||||||
if (menuWrapper !== null) {
|
|
||||||
await menuWrapper.click()
|
|
||||||
await appWindow.waitForTimeout(600)
|
|
||||||
|
|
||||||
const menuItems = await appWindow.$$(`[data-test="${selectorList.menuItem}"]`)
|
|
||||||
const dataElement = testData.data.filter(item => item.mode === 'item').find(el => el.submenu !== undefined)
|
|
||||||
const dataIndex = testData.data.filter(item => item.mode === 'item').findIndex(el => el.submenu !== undefined)
|
|
||||||
|
|
||||||
const submenuTrigger = menuItems[dataIndex]
|
|
||||||
|
|
||||||
// Check if the submenu trigger exists and click it if it does
|
|
||||||
if (submenuTrigger !== null) {
|
|
||||||
await submenuTrigger.click()
|
|
||||||
await appWindow.waitForTimeout(600)
|
|
||||||
} else {
|
|
||||||
// Submenu trigger doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
const subMenuWrapper = await appWindow.$(`[data-test="${selectorList.menuItemSubMenu}"]`)
|
|
||||||
|
|
||||||
// Check if submenu wrapper doesn't exist
|
|
||||||
if (subMenuWrapper === null) {
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
const subMenuItems = await appWindow.$$(`[data-test="${selectorList.menuItemSubMenuItem}"]`)
|
|
||||||
const dataSubmenuItems = (dataElement?.submenu !== undefined) ? dataElement.submenu.filter(item => item.mode === 'item') : false
|
|
||||||
|
|
||||||
const subMenuItemsCount = subMenuItems.length
|
|
||||||
const dataSubmenuItemsCount = (dataSubmenuItems) ? dataSubmenuItems.length : false
|
|
||||||
|
|
||||||
await expect(subMenuItemsCount === dataSubmenuItemsCount).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Wrapper for opening the main menu doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the first sub-menu item has proper text and icon
|
|
||||||
*/
|
|
||||||
test('Check if the first sub-menu item has proper text and icon', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const menuWrapper = await appWindow.$(`[data-test="${selectorList.menuWrapper}"]`)
|
|
||||||
|
|
||||||
// Check if main menu wrapper exists for clicking and if so, click it
|
|
||||||
if (menuWrapper !== null) {
|
|
||||||
await menuWrapper.click()
|
|
||||||
await appWindow.waitForTimeout(600)
|
|
||||||
|
|
||||||
const menuItems = await appWindow.$$(`[data-test="${selectorList.menuItem}"]`)
|
|
||||||
const dataElement = testData.data.filter(item => item.mode === 'item').find(el => el.submenu !== undefined)
|
|
||||||
const dataIndex = testData.data.filter(item => item.mode === 'item').findIndex(el => el.submenu !== undefined)
|
|
||||||
|
|
||||||
const submenuTrigger = menuItems[dataIndex]
|
|
||||||
|
|
||||||
// Check if the submenu trigger exists and click it if it does
|
|
||||||
if (submenuTrigger !== null) {
|
|
||||||
await submenuTrigger.click()
|
|
||||||
await appWindow.waitForTimeout(600)
|
|
||||||
} else {
|
|
||||||
// Submenu trigger doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
const subMenuWrapper = await appWindow.$(`[data-test="${selectorList.menuItemSubMenu}"]`)
|
|
||||||
|
|
||||||
// Check if submenu wrapper doesn't exist
|
|
||||||
if (subMenuWrapper === null) {
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstSubMenuItem = await appWindow.$(`[data-test="${selectorList.menuItemSubMenuItem}"]`)
|
|
||||||
const firstDataSubmenuItem = (dataElement?.submenu !== undefined) ? dataElement.submenu.filter(item => item.mode === 'item')[0] : false
|
|
||||||
|
|
||||||
// Check if the sub-menu item wrapper exists and if the first data-item isn't false
|
|
||||||
if (firstSubMenuItem !== null && firstDataSubmenuItem) {
|
|
||||||
const firstSubmenuItemTextElement = await firstSubMenuItem.$(`[data-test="${selectorList.menuItemSubMenuItemText}"]`)
|
|
||||||
const firstSubmenuItemIconElement = await firstSubMenuItem.$(`[data-test="${selectorList.menuItemSubMenuItemIcon}"]`)
|
|
||||||
|
|
||||||
// Check if the icon and text wrappers exist
|
|
||||||
if (firstSubmenuItemTextElement !== null && firstSubmenuItemIconElement !== null) {
|
|
||||||
const firstSubmenuItemText = await firstSubmenuItemTextElement.textContent()
|
|
||||||
const firstDataItemText = firstDataSubmenuItem.text
|
|
||||||
|
|
||||||
const firstSubmenuItemIconClassObject = await firstSubmenuItemIconElement.evaluate(el => el.classList)
|
|
||||||
const firstSubmenuItemIconClassList = Object.values(firstSubmenuItemIconClassObject).concat()
|
|
||||||
const firstDataItemIcon = firstDataSubmenuItem.icon as string
|
|
||||||
|
|
||||||
await expect(firstSubmenuItemText === firstDataItemText && firstSubmenuItemIconClassList.includes(firstDataItemIcon)).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Item text or icon wrappers don't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Item wrapper doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Wrapper for opening the main menu doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if text color class applied properly to any sub-main menu item: Secondary
|
|
||||||
*/
|
|
||||||
test('Check if text color class applied properly to any sub-main menu item: Secondary', async () => {
|
|
||||||
const testColorString = 'secondary'
|
|
||||||
const testColorHexString = '#f75746'
|
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const menuWrapper = await appWindow.$(`[data-test="${selectorList.menuWrapper}"]`)
|
|
||||||
|
|
||||||
// Check if main menu wrapper exists for clicking and if so, click it
|
|
||||||
if (menuWrapper !== null) {
|
|
||||||
await menuWrapper.click()
|
|
||||||
await appWindow.waitForTimeout(600)
|
|
||||||
|
|
||||||
const menuItems = await appWindow.$$(`[data-test="${selectorList.menuItem}"]`)
|
|
||||||
const dataIndex = testData.data.filter(item => item.mode === 'item').findIndex(el => el.submenu !== undefined)
|
|
||||||
|
|
||||||
const submenuTrigger = menuItems[dataIndex]
|
|
||||||
|
|
||||||
// Check if the submenu trigger exists and click it if it does
|
|
||||||
if (submenuTrigger !== null) {
|
|
||||||
await submenuTrigger.click()
|
|
||||||
await appWindow.waitForTimeout(600)
|
|
||||||
} else {
|
|
||||||
// Submenu trigger doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
const subMenuWrapper = await appWindow.$(`[data-test="${selectorList.menuItemSubMenu}"]`)
|
|
||||||
|
|
||||||
// Check if submenu wrapper doesn't exist
|
|
||||||
if (subMenuWrapper === null) {
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
const colorSubMenuItem = await appWindow.$(`.text-${testColorString}[data-test="${selectorList.menuItemSubMenuItem}"]`)
|
|
||||||
|
|
||||||
// Check if the colored sub-menu item wrapper exists
|
|
||||||
if (colorSubMenuItem !== null) {
|
|
||||||
const colorMenuString = await colorSubMenuItem.evaluate(el => getComputedStyle(el).getPropertyValue('color'))
|
|
||||||
const colorHexString = rgbToHex(colorMenuString)
|
|
||||||
|
|
||||||
// Compare color of the string with secondary color
|
|
||||||
await expect(colorHexString).toBe(testColorHexString)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Item wrapper doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Wrapper for opening the main menu doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,212 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- Main menu - Wrapper -->
|
|
||||||
<q-btn
|
|
||||||
v-if="hasProperDataInput"
|
|
||||||
flat
|
|
||||||
class="appControlSingleMenu non-selectable"
|
|
||||||
dark
|
|
||||||
size="md"
|
|
||||||
no-caps
|
|
||||||
data-test="AppControlSingleMenu-wrapper"
|
|
||||||
:data-test-has-proper-data-input="hasProperDataInput"
|
|
||||||
>
|
|
||||||
<!-- Main menu - Title -->
|
|
||||||
<span
|
|
||||||
v-if="menuTitle"
|
|
||||||
data-test="AppControlSingleMenu-title"
|
|
||||||
>
|
|
||||||
{{ menuTitle }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<!-- Main menu - Content -->
|
|
||||||
<q-menu
|
|
||||||
anchor="bottom left"
|
|
||||||
square
|
|
||||||
dark
|
|
||||||
transition-show="jump-down"
|
|
||||||
transition-hide="jump-up"
|
|
||||||
>
|
|
||||||
<q-list
|
|
||||||
class="appControlSingleMenu__list"
|
|
||||||
dark
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
v-for="(menuItem,index) in menuData"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<q-separator
|
|
||||||
v-if="menuItem.mode === 'separator'"
|
|
||||||
class="appControlSingleMenu__separator"
|
|
||||||
dark
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-item
|
|
||||||
v-if="menuItem.mode === 'item'"
|
|
||||||
v-close-popup="menuItem.submenu === undefined ? true : false"
|
|
||||||
clickable
|
|
||||||
data-test="AppControlSingleMenu-menuItem"
|
|
||||||
:class="['appControlSingleMenu__item', `text-${menuItem.specialColor}`, 'non-selectable']"
|
|
||||||
:disable="(!menuItem.conditions)"
|
|
||||||
@click="(menuItem.trigger)
|
|
||||||
? menuItem.triggerArguments
|
|
||||||
? menuItem.trigger(...menuItem.triggerArguments)
|
|
||||||
: menuItem.trigger()
|
|
||||||
: false"
|
|
||||||
>
|
|
||||||
<q-item-section data-test="AppControlSingleMenu-menuItem-text">
|
|
||||||
{{ menuItem.text }}
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon
|
|
||||||
class="appControlSingleMenu__icon"
|
|
||||||
:name="menuItem.icon"
|
|
||||||
data-test="AppControlSingleMenu-menuItem-icon"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
|
|
||||||
<!-- Sub-menu -->
|
|
||||||
<q-menu
|
|
||||||
v-if="menuItem.submenu !== undefined"
|
|
||||||
anchor="top end"
|
|
||||||
self="top start"
|
|
||||||
square
|
|
||||||
dark
|
|
||||||
transition-show="jump-right"
|
|
||||||
transition-hide="jump-left"
|
|
||||||
class="-subMenu"
|
|
||||||
data-test="AppControlSingleMenu-menuItem-subMenu"
|
|
||||||
>
|
|
||||||
<q-list
|
|
||||||
class="appControlSingleMenu__list"
|
|
||||||
dark
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
v-for="(submenuItem,subIndex) in menuItem.submenu"
|
|
||||||
:key="subIndex"
|
|
||||||
>
|
|
||||||
<q-separator
|
|
||||||
v-if="submenuItem.mode === 'separator'"
|
|
||||||
class="appControlSingleMenu__separator"
|
|
||||||
dark
|
|
||||||
/>
|
|
||||||
<q-item
|
|
||||||
v-if="submenuItem.mode === 'item'"
|
|
||||||
v-close-popup
|
|
||||||
clickable
|
|
||||||
:class="['appControlSingleMenu__item', `text-${submenuItem.specialColor}`, 'non-selectable']"
|
|
||||||
:disable="(!submenuItem.conditions)"
|
|
||||||
data-test="AppControlSingleMenu-menuItem-subMenu-item"
|
|
||||||
@click="(submenuItem.trigger) ? submenuItem.trigger() : false"
|
|
||||||
>
|
|
||||||
<q-item-section
|
|
||||||
data-test="AppControlSingleMenu-menuItem-subMenu-item-text"
|
|
||||||
>
|
|
||||||
{{ submenuItem.text }}
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon
|
|
||||||
class="appControlSingleMenu__icon"
|
|
||||||
data-test="AppControlSingleMenu-menuItem-subMenu-item-icon"
|
|
||||||
:name="submenuItem.icon"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
<!-- Sub-menu end -->
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
<!-- Main menu end - Content -->
|
|
||||||
</q-btn>
|
|
||||||
<!-- Main menu end - Wrappper -->
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// TODO - ADD TESTS
|
|
||||||
|
|
||||||
import { I_appMenusDataList } from 'app/interfaces/I_appMenusDataList'
|
|
||||||
import { testData } from '../_testData/test.raw.component'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All component props
|
|
||||||
*/
|
|
||||||
const props = defineProps<{
|
|
||||||
/**
|
|
||||||
* Data input for the component
|
|
||||||
*/
|
|
||||||
dataInput: I_appMenusDataList
|
|
||||||
}>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testing type currently possibly happening
|
|
||||||
*/
|
|
||||||
const testingType = window.extraEnvVariables.TEST_ENV
|
|
||||||
|
|
||||||
const componentData = computed(() => {
|
|
||||||
if (testingType === 'components') {
|
|
||||||
return testData
|
|
||||||
} else {
|
|
||||||
return props.dataInput
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the input has "proper" data in it
|
|
||||||
* Checks for:
|
|
||||||
* - Title
|
|
||||||
* - Overall data feed
|
|
||||||
*/
|
|
||||||
const hasProperDataInput = !!(componentData.value.title && componentData.value.data)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Menu title from the prop
|
|
||||||
*/
|
|
||||||
const menuTitle = componentData.value.title
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Menu data content from the prop
|
|
||||||
*/
|
|
||||||
const menuData = componentData.value.data
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.appControlSingleMenu {
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: $appControlMenus_singleHover;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
font-size: $iconSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__list {
|
|
||||||
background-color: $appControlMenus_bgColor;
|
|
||||||
color: $appControlMenus_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
min-height: 42px;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: $appControlMenus_singleHover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Text legibitility fix: Red and green blend too much into each other without a little shadow */
|
|
||||||
&.text-secondary {
|
|
||||||
text-shadow: 0 0 3px black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__separator {
|
|
||||||
background-color: $appControlMenus_separatorColor;
|
|
||||||
height: 0.5px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { i18n } from 'app/src/i18n/externalFileLoader'
|
|
||||||
|
|
||||||
import { I_appMenusDataList } from 'app/interfaces/I_appMenusDataList'
|
|
||||||
|
|
||||||
// TODO - add functionality for all buttons and conditions
|
|
||||||
|
|
||||||
export const documents: I_appMenusDataList = {
|
|
||||||
title: i18n.global.t('AppControlMenus.documents.title'),
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.documents.items.quickAddNewDocument'),
|
|
||||||
icon: 'mdi-text-box-plus-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
import { i18n } from 'app/src/i18n/externalFileLoader'
|
|
||||||
|
|
||||||
import { I_appMenusDataList } from 'app/interfaces/I_appMenusDataList'
|
|
||||||
|
|
||||||
// TODO - add functionality for all buttons and conditions
|
|
||||||
import { toggleDevTools } from 'app/src/scripts/appInfo/toggleDevTools'
|
|
||||||
import { openDialogMarkdownDocument } from 'app/src/scripts/appInfo/openDialogMarkdownDocument'
|
|
||||||
|
|
||||||
export const helpInfo: I_appMenusDataList = {
|
|
||||||
title: i18n.global.t('AppControlMenus.helpInfo.title'),
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.helpInfo.items.showKeybindsCheatsheet'),
|
|
||||||
icon: 'mdi-keyboard-settings',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.helpInfo.items.advancedSearchGuide'),
|
|
||||||
icon: 'mdi-file-question',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: openDialogMarkdownDocument,
|
|
||||||
triggerArguments: ['advancedSearchGuide'],
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.helpInfo.items.tipsTricksTrivia'),
|
|
||||||
icon: 'mdi-fire-alert',
|
|
||||||
trigger: openDialogMarkdownDocument,
|
|
||||||
triggerArguments: ['tipsTricksTrivia'],
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.helpInfo.items.changelog'),
|
|
||||||
icon: 'mdi-clipboard-text',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: openDialogMarkdownDocument,
|
|
||||||
triggerArguments: ['changeLog'],
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.helpInfo.items.aboutFantsiaArchive'),
|
|
||||||
icon: 'mdi-information-variant',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.helpInfo.items.license'),
|
|
||||||
icon: 'mdi-script-text-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: openDialogMarkdownDocument,
|
|
||||||
triggerArguments: ['license'],
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.helpInfo.items.toggleDeveloperTools'),
|
|
||||||
icon: 'mdi-code-tags',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: toggleDevTools,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
import { i18n } from 'app/src/i18n/externalFileLoader'
|
|
||||||
|
|
||||||
import { I_appMenusDataList } from 'app/interfaces/I_appMenusDataList'
|
|
||||||
|
|
||||||
// TODO - add functionality for all buttons and conditions
|
|
||||||
|
|
||||||
export const project: I_appMenusDataList = {
|
|
||||||
title: i18n.global.t('AppControlMenus.project.title'),
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.newProject'),
|
|
||||||
icon: 'mdi-plus',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.saveProject'),
|
|
||||||
icon: 'mdi-package-variant-closed',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.loadProject'),
|
|
||||||
icon: 'mdi-package-variant',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.exportProjectDocuments'),
|
|
||||||
icon: 'mdi-database-export-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.showProjectDashboard'),
|
|
||||||
icon: 'mdi-chart-bar',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.projectSettings'),
|
|
||||||
icon: 'mdi-book-cog-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.closeProject'),
|
|
||||||
icon: 'mdi-exit-to-app',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.advancedProjectTools'),
|
|
||||||
icon: 'keyboard_arrow_right',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: 'grey',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.aptMerge'),
|
|
||||||
icon: 'mdi-folder-plus-outline',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.project.items.aptConvertOld'),
|
|
||||||
icon: 'mdi-wrench',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
import { i18n } from 'app/src/i18n/externalFileLoader'
|
|
||||||
|
|
||||||
import { I_appMenusDataList } from 'app/interfaces/I_appMenusDataList'
|
|
||||||
|
|
||||||
// TODO - add functionality for all buttons and conditions
|
|
||||||
|
|
||||||
export const tools: I_appMenusDataList = {
|
|
||||||
title: i18n.global.t('AppControlMenus.tools.title'),
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.tools.items.quickAddNewDocument'),
|
|
||||||
icon: 'mdi-text-box-plus-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.tools.items.quickSearchDocument'),
|
|
||||||
icon: 'mdi-database-search',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.tools.items.massDeleteDocument'),
|
|
||||||
icon: 'mdi-text-box-remove-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: 'secondary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.tools.items.toggleTree'),
|
|
||||||
icon: 'mdi-page-layout-sidebar-left',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.tools.items.showNoteBoard'),
|
|
||||||
icon: 'mdi-clipboard-text-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: i18n.global.t('AppControlMenus.tools.items.programSettings'),
|
|
||||||
icon: 'mdi-tune',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
import { I_appMenusDataList } from 'app/interfaces/I_appMenusDataList'
|
|
||||||
|
|
||||||
export const testData:I_appMenusDataList = {
|
|
||||||
title: 'Test Title',
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 1 - Open Dialog with Markdown document',
|
|
||||||
icon: 'mdi-text-box-plus-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
triggerArguments: ['changeLog'],
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 2',
|
|
||||||
icon: 'mdi-database-search',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 3 - Secondary',
|
|
||||||
icon: 'mdi-text-box-remove-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: 'secondary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 4',
|
|
||||||
icon: 'mdi-page-layout-sidebar-left',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 5',
|
|
||||||
icon: 'mdi-clipboard-text-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 6 - Grey, Submenu',
|
|
||||||
icon: 'keyboard_arrow_right',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: 'grey',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Submenu-Test Button 1',
|
|
||||||
icon: 'mdi-folder-plus-outline',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Submenu-Test Button 2 - Secondary',
|
|
||||||
icon: 'mdi-wrench',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: 'secondary'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
import { I_appMenusDataList } from 'app/interfaces/I_appMenusDataList'
|
|
||||||
import { openDialogMarkdownDocument } from 'src/scripts/appInfo/openDialogMarkdownDocument'
|
|
||||||
|
|
||||||
export const testData:I_appMenusDataList = {
|
|
||||||
title: 'Test Title',
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 1 - Open Dialog with Markdown document',
|
|
||||||
icon: 'mdi-text-box-plus-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: openDialogMarkdownDocument,
|
|
||||||
triggerArguments: ['changeLog'],
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 2',
|
|
||||||
icon: 'mdi-database-search',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 3 - Secondary',
|
|
||||||
icon: 'mdi-text-box-remove-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: 'secondary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 4',
|
|
||||||
icon: 'mdi-page-layout-sidebar-left',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 5',
|
|
||||||
icon: 'mdi-clipboard-text-outline',
|
|
||||||
submenu: undefined,
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Test Button 6 - Grey, Submenu',
|
|
||||||
icon: 'keyboard_arrow_right',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: 'grey',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Submenu-Test Button 1',
|
|
||||||
icon: 'mdi-folder-plus-outline',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'item',
|
|
||||||
text: 'Submenu-Test Button 2 - Secondary',
|
|
||||||
icon: 'mdi-wrench',
|
|
||||||
trigger: undefined,
|
|
||||||
conditions: true,
|
|
||||||
specialColor: 'secondary'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
65
src/components/AppHeader.vue
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<q-header
|
||||||
|
elevated
|
||||||
|
class="bg-dark text-cultured appHeader"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="appHeaderInner">
|
||||||
|
<appControl
|
||||||
|
class="appControl"
|
||||||
|
/>
|
||||||
|
<topTabs
|
||||||
|
class="topTabs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-header>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component } from "vue-property-decorator"
|
||||||
|
|
||||||
|
import BaseClass from "src/BaseClass"
|
||||||
|
import topTabs from "src/components/appHeader/TopTabs.vue"
|
||||||
|
import appControl from "src/components/appHeader/AppControl.vue"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
topTabs: topTabs,
|
||||||
|
appControl: appControl
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default class AppHeader extends BaseClass {
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.appHeaderInner {
|
||||||
|
z-index: 999999;
|
||||||
|
display: flex;
|
||||||
|
min-height: 40px;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
width: calc(100% - 147px);
|
||||||
|
|
||||||
|
.appControl {
|
||||||
|
width: 375px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topTabs {
|
||||||
|
max-width: calc(100% - 415px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.appWindowButtons {
|
||||||
|
//width: 200px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,95 +0,0 @@
|
||||||
import { _electron as electron } from 'playwright'
|
|
||||||
import { test, expect } from '@playwright/test'
|
|
||||||
import { extraEnvVariablesAPI } from 'app/src-electron/customContentBridgeAPIs/extraEnvVariablesAPI'
|
|
||||||
import { T_documentList } from 'app/interfaces/T_documentList'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra env settings to trigger component testing via Playwright
|
|
||||||
*/
|
|
||||||
const extraEnvSettings = {
|
|
||||||
TEST_ENV: 'components',
|
|
||||||
COMPONENT_NAME: 'DialogMarkdownDocument',
|
|
||||||
COMPONENT_PROPS: JSON.stringify({})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Electron main filepath
|
|
||||||
*/
|
|
||||||
const electronMainFilePath:string = extraEnvVariablesAPI.ELECTRON_MAIN_FILEPATH
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra rended timer buffer for tests to start after loading the app
|
|
||||||
* - Change here in order manually adjust this component's wait times
|
|
||||||
*/
|
|
||||||
const faFrontendRenderTimer = extraEnvVariablesAPI.FA_FRONTEND_RENDER_TIMER
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object of string data selectors for the component
|
|
||||||
*/
|
|
||||||
const selectorList = {
|
|
||||||
markdownWrapper: 'dialogMarkdownDocument-markdown-wrapper',
|
|
||||||
markdownContent: 'dialogMarkdownDocument-markdown-content',
|
|
||||||
closeButton: 'dialogMarkdownDocument-button-close'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Feed 'license' input as the file to open and check if the opened dialog afterwars has all the needed elements in it
|
|
||||||
*/
|
|
||||||
test('Open test "license" dialog with all elements in it', async () => {
|
|
||||||
const testString: T_documentList = 'license'
|
|
||||||
|
|
||||||
extraEnvSettings.COMPONENT_PROPS = JSON.stringify({ directInput: testString })
|
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const closeButton = await appWindow.$(`[data-test="${selectorList.closeButton}"]`)
|
|
||||||
const markdownWrapper = await appWindow.$(`[data-test="${selectorList.markdownWrapper}"]`)
|
|
||||||
const markdownContent = await appWindow.$(`[data-test="${selectorList.markdownContent}"]`)
|
|
||||||
|
|
||||||
// Check if the tested elements exists
|
|
||||||
if (closeButton !== null && markdownWrapper !== null && markdownContent !== null) {
|
|
||||||
expect(true).toBe(true)
|
|
||||||
} else {
|
|
||||||
// Elements don't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Feed 'license' input as the file to open and check if the opened dialog afterwars has all the needed elements in it
|
|
||||||
*/
|
|
||||||
test('Open test "license" dialog and try closing it', async () => {
|
|
||||||
const testString: T_documentList = 'license'
|
|
||||||
|
|
||||||
extraEnvSettings.COMPONENT_PROPS = JSON.stringify({ directInput: testString })
|
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const closeButton = await appWindow.$(`[data-test="${selectorList.closeButton}"]`)
|
|
||||||
const markdownContent = await appWindow.$(`[data-test="${selectorList.markdownContent}"]`)
|
|
||||||
|
|
||||||
// Check if the close button exists
|
|
||||||
if (closeButton !== null && markdownContent !== null) {
|
|
||||||
await closeButton.click()
|
|
||||||
await appWindow.waitForTimeout(1500)
|
|
||||||
|
|
||||||
// Check if the content is properly hidden after closing the popup
|
|
||||||
await expect(await markdownContent.isHidden()).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Close button doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,147 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- Dialog wrapper -->
|
|
||||||
<q-dialog
|
|
||||||
v-model="dialogModel"
|
|
||||||
:class="['dialogMarkdownDocument', `${documentName}`]"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<!-- Dialog contents wrapper -->
|
|
||||||
<q-card-section :class="['dialogMarkdownDocument__content', `${documentName}`, 'q-mt-xl', 'q-mb-lg', 'q-mr-lg', 'q-ml-xl', 'q-pt-none']">
|
|
||||||
<div
|
|
||||||
class="flex justify-center"
|
|
||||||
data-test="dialogMarkdownDocument-markdown-wrapper"
|
|
||||||
>
|
|
||||||
<!-- Dialog markdown -->
|
|
||||||
<q-markdown
|
|
||||||
no-heading-anchor-links
|
|
||||||
data-test="dialogMarkdownDocument-markdown-content"
|
|
||||||
:class="[`${documentName}`, 'dialogMarkdownDocument']"
|
|
||||||
:src="$t(`documents.${documentName}`)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<!-- Card actions wrapper -->
|
|
||||||
<q-card-actions
|
|
||||||
align="around"
|
|
||||||
class="q-mb-lg"
|
|
||||||
>
|
|
||||||
<!-- Close button -->
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
label="Close"
|
|
||||||
color="accent"
|
|
||||||
data-test="dialogMarkdownDocument-button-close"
|
|
||||||
/>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { QMarkdown } from '@quasar/quasar-ui-qmarkdown'
|
|
||||||
import '@quasar/quasar-ui-qmarkdown/dist/index.css'
|
|
||||||
import { T_documentList } from 'app/interfaces/T_documentList'
|
|
||||||
import { S_DialogMarkdown } from 'src/stores/S_DialogMarkdown'
|
|
||||||
import { onMounted, ref, watch } from 'vue'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All component props
|
|
||||||
*/
|
|
||||||
const props = defineProps<{
|
|
||||||
/**
|
|
||||||
* Custom input directly fed to the component in case it doesn't get triggered from the global store
|
|
||||||
*/
|
|
||||||
directInput?: T_documentList
|
|
||||||
}>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model for the current popup dialog
|
|
||||||
*/
|
|
||||||
const dialogModel = ref(false)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of the document shown inside the dialog
|
|
||||||
*/
|
|
||||||
const documentName = ref('')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the popup dialog via direct input-feed
|
|
||||||
*/
|
|
||||||
const openDialog = (input: T_documentList) => {
|
|
||||||
documentName.value = input
|
|
||||||
dialogModel.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger dialog popup via reaction to store update
|
|
||||||
*/
|
|
||||||
watch(() => S_DialogMarkdown.dialogUUID, () => {
|
|
||||||
openDialog(S_DialogMarkdown.documentToOpen)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger dialog popup via reaction to direct prop feed
|
|
||||||
*/
|
|
||||||
watch(() => props.directInput, () => {
|
|
||||||
if (props.directInput !== undefined && props.directInput !== '') {
|
|
||||||
openDialog(props.directInput)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the prop feed-status on the first mount and open the dialog if the prop is properly fed in
|
|
||||||
* This exist mostly due to component tests being flaky otherwise
|
|
||||||
*/
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.directInput !== undefined && props.directInput !== '') {
|
|
||||||
openDialog(props.directInput)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
|
|
||||||
.dialogMarkdownDocument {
|
|
||||||
.q-card {
|
|
||||||
max-width: calc(100vw - 100px) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.license .q-card {
|
|
||||||
width: 680px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.changeLog .q-card {
|
|
||||||
width: 1100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.advancedSearchGuide .q-card {
|
|
||||||
width: 1100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.tipsTricksTrivia .q-card {
|
|
||||||
width: 1100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
overflow: auto;
|
|
||||||
max-height: calc(100vh - 235px);
|
|
||||||
min-height: 650px;
|
|
||||||
|
|
||||||
&.tipsTricksTrivia {
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.changeLog {
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.advancedSearchGuide {
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
747
src/components/DocumentControl.vue
Normal file
|
@ -0,0 +1,747 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<!-- New document dialog -->
|
||||||
|
<newDocumentDialog
|
||||||
|
:dialog-trigger="newObjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="newObjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Existing document dialog -->
|
||||||
|
<existingDocumentDialog
|
||||||
|
:dialog-trigger="existingObjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="existingObjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Delele document dialog -->
|
||||||
|
<deleteDocumentCheckDialog
|
||||||
|
:dialog-trigger="deleteObjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="deleteObjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Advanced search guide dialog -->
|
||||||
|
<advancedSearchGuideDialog
|
||||||
|
:dialog-trigger="advancedSearchGuideDialogTrigger"
|
||||||
|
@trigger-dialog-close="advancedSearchGuideDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Keybind dialog -->
|
||||||
|
<keybindCheatsheetDialog
|
||||||
|
:dialog-trigger="keybindsDialogTrigger"
|
||||||
|
@trigger-dialog-close="keybindsDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Tips, Tricks & Trivia dialog -->
|
||||||
|
<tipsTricksTriviaDialog
|
||||||
|
:dialog-trigger="tipsTricksDialogTrigger"
|
||||||
|
@trigger-dialog-close="tipsTricksDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-page-sticky position="top-right"
|
||||||
|
class="documentControl bg-dark"
|
||||||
|
:class="{'fullScreen': hideHierarchyTree}"
|
||||||
|
v-if="!disableDocumentControlBar"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="documentControl__blocker"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="documentControl__wrapper"
|
||||||
|
:class="{'fullScreen': hideHierarchyTree}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="documentControl__left">
|
||||||
|
|
||||||
|
<template v-if="!disableDocumentControlBarGuides">
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-keyboard-settings"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="keybindsDialogAssignUID"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Open keybinds cheatsheet
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-file-question"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="advancedSearchGuideAssignUID"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Open advanced search guide
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-fire-alert"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="tipsTricksAssignUID"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Open Tips, Tricks & Trivia
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-separator vertical inset color="accent" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-package-variant-closed"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
:disable="!projectExists"
|
||||||
|
@click="commenceExport"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Export current project
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-separator vertical inset color="accent" />
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-page-layout-sidebar-left"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="toggleHierarchicalTree"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Toggle hierarchical tree
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-clipboard-text-outline"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="SSET_setNoteCorkboardWindowVisible"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Show note board
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-separator vertical inset color="accent" />
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-database-search"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="existingObjectAssignUID"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Quick-search an existing document
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-text-box-plus-outline"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="newObjectAssignUID"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Quick-add a new document
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="documentControl__right">
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-file-document-edit"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="toggleEditMode"
|
||||||
|
v-if="currentyEditable && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom middle"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Edit current document
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-content-save-edit"
|
||||||
|
:color="(!hasEdits) ? 'teal-14' : 'primary'"
|
||||||
|
outline
|
||||||
|
@click="saveCurrentDocument(true)"
|
||||||
|
v-if="!currentyEditable && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom left"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Save document without exiting edit mode
|
||||||
|
</q-tooltip>
|
||||||
|
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-content-save"
|
||||||
|
:color="(!hasEdits) ? 'teal-14' : 'primary'"
|
||||||
|
outline
|
||||||
|
@click="saveCurrentDocument(false)"
|
||||||
|
v-if="!currentyEditable && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom left"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Save current document
|
||||||
|
</q-tooltip>
|
||||||
|
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-file-tree"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="addNewUnderParent"
|
||||||
|
v-if="!currentlyNew && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
max-width="500px"
|
||||||
|
anchor="bottom left"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Add a new document with the currently opened one as the parent
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-content-copy"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
@click="copyTargetDocument"
|
||||||
|
v-if="!currentlyNew && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
max-width="500px"
|
||||||
|
anchor="bottom left"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Copy current document
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-separator vertical inset color="accent"
|
||||||
|
v-if="!currentlyNew && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-text-box-remove-outline"
|
||||||
|
color="secondary"
|
||||||
|
outline
|
||||||
|
@click="deleteObjectAssignUID"
|
||||||
|
v-if="!currentlyNew && SGET_allOpenedDocuments.docs.length > 0 && this.$route.path !== '/project'"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="500"
|
||||||
|
anchor="bottom left"
|
||||||
|
self="top middle"
|
||||||
|
>
|
||||||
|
Delete current document
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-page-sticky>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component, Watch } from "vue-property-decorator"
|
||||||
|
|
||||||
|
import BaseClass from "src/BaseClass"
|
||||||
|
import newDocumentDialog from "src/components/dialogs/NewDocument.vue"
|
||||||
|
import existingDocumentDialog from "src/components/dialogs/ExistingDocument.vue"
|
||||||
|
import deleteDocumentCheckDialog from "src/components/dialogs/DeleteDocumentCheck.vue"
|
||||||
|
import advancedSearchGuideDialog from "src/components/dialogs/AdvancedSearchGuide.vue"
|
||||||
|
import keybindCheatsheetDialog from "src/components/dialogs/KeybindCheatsheet.vue"
|
||||||
|
import tipsTricksTriviaDialog from "src/components/dialogs/TipsTricksTrivia.vue"
|
||||||
|
|
||||||
|
import { I_OpenedDocument } from "src/interfaces/I_OpenedDocument"
|
||||||
|
import { extend, Loading, QSpinnerGears } from "quasar"
|
||||||
|
import { saveDocument } from "src/scripts/databaseManager/documentManager"
|
||||||
|
import { createNewWithParent } from "src/scripts/documentActions/createNewWithParent"
|
||||||
|
import { copyDocument } from "src/scripts/documentActions/copyDocument"
|
||||||
|
|
||||||
|
import { retrieveCurrentProjectName, exportProject } from "src/scripts/projectManagement/projectManagent"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
newDocumentDialog,
|
||||||
|
existingDocumentDialog,
|
||||||
|
deleteDocumentCheckDialog,
|
||||||
|
advancedSearchGuideDialog,
|
||||||
|
keybindCheatsheetDialog,
|
||||||
|
tipsTricksTriviaDialog
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default class DocumentControl extends BaseClass {
|
||||||
|
projectExists: undefined | string | boolean = false
|
||||||
|
projectName = ""
|
||||||
|
|
||||||
|
disableDocumentControlBar = false
|
||||||
|
disableDocumentControlBarGuides = false
|
||||||
|
|
||||||
|
@Watch("SGET_options", { immediate: true, deep: true })
|
||||||
|
onSettingsChange () {
|
||||||
|
const options = this.SGET_options
|
||||||
|
this.disableDocumentControlBar = options.disableDocumentControlBar
|
||||||
|
this.disableDocumentControlBarGuides = options.disableDocumentControlBarGuides
|
||||||
|
this.hideHierarchyTree = options.hideHierarchyTree
|
||||||
|
}
|
||||||
|
|
||||||
|
hideHierarchyTree = false
|
||||||
|
|
||||||
|
async created () {
|
||||||
|
this.projectName = await retrieveCurrentProjectName()
|
||||||
|
this.projectExists = !!(await retrieveCurrentProjectName())
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Keybinds management
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local keybinds
|
||||||
|
*/
|
||||||
|
@Watch("SGET_getCurrentKeyBindData", { deep: true })
|
||||||
|
async processKeyPush () {
|
||||||
|
// Quick new document
|
||||||
|
if (this.determineKeyBind("quickNewDocument") && !this.SGET_getDialogsState) {
|
||||||
|
this.newObjectAssignUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick open existing document
|
||||||
|
if (this.determineKeyBind("quickExistingDocument") && !this.SGET_getDialogsState) {
|
||||||
|
this.existingObjectAssignUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete dialog - CTRL + D
|
||||||
|
if (this.determineKeyBind("deleteDocument") && !this.currentlyNew && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
|
||||||
|
this.deleteObjectAssignUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit document - CTRL + E
|
||||||
|
if (this.determineKeyBind("editDocument") && this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
|
||||||
|
this.toggleEditMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save document - CTRL + S
|
||||||
|
if (this.determineKeyBind("saveDocument") && !this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.saveCurrentDocument(false).catch(e => console.log(e))
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save document without exiting edit mode - CTRL + ALT + S
|
||||||
|
if (this.determineKeyBind("saveDocumentNoExit") && !this.currentyEditable && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.saveCurrentDocument(true).catch(e => console.log(e))
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new under parent - CTRL + SHIFT + N
|
||||||
|
if (this.determineKeyBind("addUnderParent") && !this.currentlyNew && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
|
||||||
|
await this.sleep(100)
|
||||||
|
this.addNewUnderParent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy document - CTRL + ALT + C
|
||||||
|
if (this.determineKeyBind("copyDocument") && !this.currentlyNew && this.SGET_allOpenedDocuments.docs.length > 0 && !this.SGET_getDialogsState && this.$route.path !== "/project") {
|
||||||
|
await this.sleep(100)
|
||||||
|
this.copyTargetDocument()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle hierarchical tree - CTRL + ALT + SHIFT + T
|
||||||
|
if (this.determineKeyBind("toggleHierarchicalTree")) {
|
||||||
|
// @ts-ignore
|
||||||
|
this.toggleHierarchicalTree()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Advanced search guide dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
advancedSearchGuideDialogTrigger: string | false = false
|
||||||
|
advancedSearchGuideDialogClose () {
|
||||||
|
this.advancedSearchGuideDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
advancedSearchGuideAssignUID () {
|
||||||
|
this.advancedSearchGuideDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Keybings cheatsheet dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
keybindsDialogTrigger: string | false = false
|
||||||
|
keybindsDialogClose () {
|
||||||
|
this.keybindsDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
keybindsDialogAssignUID () {
|
||||||
|
this.keybindsDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Delete dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
deleteObjectDialogTrigger: string | false = false
|
||||||
|
deleteObjectDialogClose () {
|
||||||
|
this.deleteObjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteObjectAssignUID () {
|
||||||
|
this.deleteObjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// New document dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
newObjectDialogTrigger: string | false = false
|
||||||
|
newObjectDialogClose () {
|
||||||
|
this.newObjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
newObjectAssignUID () {
|
||||||
|
this.newObjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Existing document dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
existingObjectDialogTrigger: string | false = false
|
||||||
|
existingObjectDialogClose () {
|
||||||
|
this.existingObjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
existingObjectAssignUID () {
|
||||||
|
this.existingObjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Tips, Tricka & Trivia dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
tipsTricksDialogTrigger: string | false = false
|
||||||
|
tipsTricksDialogClose () {
|
||||||
|
this.tipsTricksDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
tipsTricksAssignUID () {
|
||||||
|
this.tipsTricksDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Export project
|
||||||
|
/****************************************************************/
|
||||||
|
retrieveCurrentProjectName = retrieveCurrentProjectName
|
||||||
|
|
||||||
|
async commenceExport () {
|
||||||
|
const projectName = await retrieveCurrentProjectName()
|
||||||
|
const setup = {
|
||||||
|
message: "<h4>Exporting current project...</h4>",
|
||||||
|
spinnerColor: "primary",
|
||||||
|
messageColor: "cultured",
|
||||||
|
spinnerSize: 120,
|
||||||
|
backgroundColor: "dark",
|
||||||
|
// @ts-ignore
|
||||||
|
spinner: QSpinnerGears
|
||||||
|
}
|
||||||
|
exportProject(projectName, Loading, setup, this.$q)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Add new document under parent
|
||||||
|
/****************************************************************/
|
||||||
|
addNewUnderParent () {
|
||||||
|
const currentDoc = this.findRequestedOrActiveDocument() as I_OpenedDocument
|
||||||
|
createNewWithParent(currentDoc, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Document copy
|
||||||
|
/****************************************************************/
|
||||||
|
documentPass = null as unknown as I_OpenedDocument
|
||||||
|
|
||||||
|
copyTargetDocument () {
|
||||||
|
this.documentPass = extend(true, {}, this.findRequestedOrActiveDocument())
|
||||||
|
|
||||||
|
const blueprint = this.SGET_blueprint(this.documentPass.type)
|
||||||
|
const newDocument = copyDocument(this.documentPass, this.generateUID(), blueprint)
|
||||||
|
|
||||||
|
const dataPass = {
|
||||||
|
doc: newDocument,
|
||||||
|
treeAction: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_addOpenedDocument(dataPass)
|
||||||
|
this.$router.push({
|
||||||
|
path: newDocument.url
|
||||||
|
}).catch((e: {name: string}) => {
|
||||||
|
const errorName : string = e.name
|
||||||
|
if (errorName === "NavigationDuplicated") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Toggle edit mode & Save document
|
||||||
|
/****************************************************************/
|
||||||
|
toggleEditMode () {
|
||||||
|
const currentDoc = this.findRequestedOrActiveDocument()
|
||||||
|
if (currentDoc && !currentDoc.editMode) {
|
||||||
|
const dataCopy: I_OpenedDocument = extend(true, {}, currentDoc)
|
||||||
|
dataCopy.editMode = true
|
||||||
|
const dataPass = { doc: dataCopy, treeAction: false }
|
||||||
|
this.SSET_updateOpenedDocument(dataPass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
documentsCopy = null as unknown as I_OpenedDocument[]
|
||||||
|
|
||||||
|
async saveCurrentDocument (editMode: boolean) {
|
||||||
|
if (document.activeElement && editMode === false) {
|
||||||
|
(document.activeElement as HTMLElement).blur()
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDoc = this.findRequestedOrActiveDocument()
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const isNew = currentDoc.isNew
|
||||||
|
|
||||||
|
const allDocuments = this.SGET_allOpenedDocuments
|
||||||
|
|
||||||
|
this.documentsCopy = extend(true, [], allDocuments.docs)
|
||||||
|
|
||||||
|
if (currentDoc) {
|
||||||
|
// @ts-ignore
|
||||||
|
const savedDocument: {
|
||||||
|
documentCopy: I_OpenedDocument,
|
||||||
|
allOpenedDocuments: I_OpenedDocument[]
|
||||||
|
} = await saveDocument(currentDoc, this.documentsCopy, this.SGET_allDocuments.docs, editMode).catch(err => console.log(err))
|
||||||
|
|
||||||
|
// Update the opened document
|
||||||
|
const dataPass = { doc: savedDocument.documentCopy, treeAction: true }
|
||||||
|
this.SSET_updateOpenedDocument(dataPass)
|
||||||
|
|
||||||
|
// Update document
|
||||||
|
if (!isNew) {
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_updateDocument({ doc: this.mapShortDocument(savedDocument.documentCopy, this.SGET_allDocumentsByType(savedDocument.documentCopy.type).docs) })
|
||||||
|
}
|
||||||
|
// Add new document
|
||||||
|
else {
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_addDocument({ doc: this.mapShortDocument(savedDocument.documentCopy, this.SGET_allDocumentsByType(savedDocument.documentCopy.type).docs) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all others
|
||||||
|
for (const doc of savedDocument.allOpenedDocuments) {
|
||||||
|
// Update the opened document
|
||||||
|
const dataPass = { doc: doc, treeAction: true }
|
||||||
|
this.SSET_updateOpenedDocument(dataPass)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_updateDocument({ doc: this.mapShortDocument(doc, this.SGET_allDocumentsByType(doc.type).docs) })
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$q.notify({
|
||||||
|
group: false,
|
||||||
|
type: "positive",
|
||||||
|
message: "Document successfully saved"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch("$route", { immediate: true, deep: true })
|
||||||
|
onUrlChange () {
|
||||||
|
this.checkEditability()
|
||||||
|
this.checkNew()
|
||||||
|
this.checkHasEdits()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch("SGET_allOpenedDocuments", { deep: true })
|
||||||
|
onDocChange () {
|
||||||
|
this.checkEditability()
|
||||||
|
this.checkNew()
|
||||||
|
this.checkHasEdits()
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEdits = false
|
||||||
|
|
||||||
|
checkHasEdits () {
|
||||||
|
const currentDocument = this.findRequestedOrActiveDocument()
|
||||||
|
|
||||||
|
if (currentDocument && !currentDocument.hasEdits) {
|
||||||
|
this.hasEdits = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.hasEdits = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkEditability () {
|
||||||
|
const currentDocument = this.findRequestedOrActiveDocument()
|
||||||
|
|
||||||
|
if (currentDocument && !currentDocument.editMode) {
|
||||||
|
this.currentyEditable = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.currentyEditable = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNew () {
|
||||||
|
const currentDocument = this.findRequestedOrActiveDocument()
|
||||||
|
|
||||||
|
if (currentDocument && currentDocument.isNew) {
|
||||||
|
this.currentlyNew = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.currentlyNew = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentyEditable = false
|
||||||
|
currentlyNew = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.documentControl {
|
||||||
|
z-index: 999;
|
||||||
|
width: calc(100vw - 380px);
|
||||||
|
margin-top: 2.5px;
|
||||||
|
|
||||||
|
&.fullScreen {
|
||||||
|
width: calc(100vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__blocker {
|
||||||
|
position: absolute;
|
||||||
|
top: -7.5px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: darken($dark, 0.5);
|
||||||
|
z-index: 999;
|
||||||
|
height: 7.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
width: calc(100vw - 385px);
|
||||||
|
padding: 8.5px 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.fullScreen {
|
||||||
|
width: calc(100vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: " ";
|
||||||
|
bottom: 1px;
|
||||||
|
right: -5px;
|
||||||
|
left: -5px;
|
||||||
|
position: absolute;
|
||||||
|
height: 1px;
|
||||||
|
background-color: rgba($accent, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__left,
|
||||||
|
&__right {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.q-btn,
|
||||||
|
.q-separator {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.q-btn,
|
||||||
|
.q-separator {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html body {
|
||||||
|
&.q-body--prevent-scroll {
|
||||||
|
.documentControl {
|
||||||
|
min-width: calc(100vw - 375px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,204 +0,0 @@
|
||||||
import { _electron as electron } from 'playwright'
|
|
||||||
import { test, expect } from '@playwright/test'
|
|
||||||
import { extraEnvVariablesAPI } from 'app/src-electron/customContentBridgeAPIs/extraEnvVariablesAPI'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra env settings to trigger component testing via Playwright
|
|
||||||
*/
|
|
||||||
const extraEnvSettings = {
|
|
||||||
TEST_ENV: 'components',
|
|
||||||
COMPONENT_NAME: 'FantasiaMascotImage',
|
|
||||||
COMPONENT_PROPS: JSON.stringify({})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Electron main filepath
|
|
||||||
*/
|
|
||||||
const electronMainFilePath:string = extraEnvVariablesAPI.ELECTRON_MAIN_FILEPATH
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra rended timer buffer for tests to start after loading the app
|
|
||||||
* - Change here in order manually adjust this component's wait times
|
|
||||||
*/
|
|
||||||
const faFrontendRenderTimer:number = extraEnvVariablesAPI.FA_FRONTEND_RENDER_TIMER
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object of string data selectors for the component
|
|
||||||
*/
|
|
||||||
const selectorList = {
|
|
||||||
image: 'fantasiaMascotImage-image'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the wrapper contains 'IMG' element
|
|
||||||
*/
|
|
||||||
test('Check if the wrapper contains "IMG" element', async () => {
|
|
||||||
const testString = 'IMG'
|
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const imageElement = await appWindow.$(`[data-test="${selectorList.image}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (imageElement !== null) {
|
|
||||||
const elementType = await imageElement.evaluate(el => el.tagName)
|
|
||||||
|
|
||||||
await expect(elementType).toBe(testString)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to pass "width" prop to the component and check the result
|
|
||||||
*/
|
|
||||||
test('Visually check "width" prop', async () => {
|
|
||||||
const testString = '300px'
|
|
||||||
|
|
||||||
extraEnvSettings.COMPONENT_PROPS = JSON.stringify({ width: testString })
|
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const imageElement = await appWindow.$(`[data-test="${selectorList.image}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (imageElement !== null) {
|
|
||||||
const imageBoxData = await imageElement.boundingBox()
|
|
||||||
|
|
||||||
// Test if the tested element isn't invisisble for some reason
|
|
||||||
if (imageBoxData !== null) {
|
|
||||||
const roundedFirstValue = Math.round(imageBoxData.width)
|
|
||||||
const roundedSecondValue = Math.round(parseInt(testString))
|
|
||||||
|
|
||||||
await expect(roundedFirstValue).toBe(roundedSecondValue)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element is invisible
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to pass "height" prop to the component and check the result
|
|
||||||
*/
|
|
||||||
test('Visually check "height" prop', async () => {
|
|
||||||
const testString = '300px'
|
|
||||||
|
|
||||||
extraEnvSettings.COMPONENT_PROPS = JSON.stringify({ height: testString })
|
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const imageElement = await appWindow.$(`[data-test="${selectorList.image}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (imageElement !== null) {
|
|
||||||
const imageBoxData = await imageElement.boundingBox()
|
|
||||||
|
|
||||||
// Test if the tested element isn't invisisble for some reason
|
|
||||||
if (imageBoxData !== null) {
|
|
||||||
const roundedFirstValue = Math.round(imageBoxData.height)
|
|
||||||
const roundedSecondValue = Math.round(parseInt(testString))
|
|
||||||
|
|
||||||
await expect(roundedFirstValue).toBe(roundedSecondValue)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element is invisible
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if the component properly determines if the image will be random - YES
|
|
||||||
*/
|
|
||||||
test('Check if the image is random: YES', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const imageElement = await appWindow.$(`[data-test="${selectorList.image}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (imageElement !== null) {
|
|
||||||
const isRandom = await imageElement.evaluate(el => el.dataset.testIsRandom)
|
|
||||||
|
|
||||||
await expect(isRandom).toBe('true')
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if the component properly determines if the image will be random - NO
|
|
||||||
*/
|
|
||||||
test('Check if the image is random: NO', async () => {
|
|
||||||
const testString = 'flop'
|
|
||||||
|
|
||||||
extraEnvSettings.COMPONENT_PROPS = JSON.stringify({ fantasiaImage: testString })
|
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const imageElement = await appWindow.$(`[data-test="${selectorList.image}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (imageElement !== null) {
|
|
||||||
const isRandom = await imageElement.evaluate(el => el.dataset.testIsRandom)
|
|
||||||
const imageString = await imageElement.evaluate(el => el.dataset.testImage)
|
|
||||||
|
|
||||||
await expect.soft(imageString).toBe(testString)
|
|
||||||
await expect(isRandom).toBe('false')
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
|
@ -1,79 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- Outer wrapper of the image -->
|
|
||||||
<div
|
|
||||||
class="fantasiaMascotImage"
|
|
||||||
>
|
|
||||||
<!-- Inner image of the chosen Fantasia Mascot -->
|
|
||||||
<img
|
|
||||||
:src="currentMascotImage"
|
|
||||||
class="fantasiaMascotImage__inner"
|
|
||||||
data-test="fantasiaMascotImage-image"
|
|
||||||
:data-test-image="fantasiaImage"
|
|
||||||
:data-test-is-random="isRandom"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import { fantasiaImageList, determineCurrentImage } from 'app/src/scripts/appInfo/fantasiaMascotImageManager'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All component props
|
|
||||||
*/
|
|
||||||
const props = defineProps({
|
|
||||||
/**
|
|
||||||
* Name of the object key from the list to render. Leave empty in order to generate random image.
|
|
||||||
* Available ones:
|
|
||||||
* - didYouKnow
|
|
||||||
* - flop
|
|
||||||
* - hug
|
|
||||||
* - reading
|
|
||||||
* - cooking
|
|
||||||
* - error
|
|
||||||
*/
|
|
||||||
fantasiaImage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom CSS string for the "height" attribute.
|
|
||||||
*/
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: 'initial'
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom CSS string for the "width" attribute.
|
|
||||||
*/
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: 'initial'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the image URL will be generating randomly or if it is set via the prop.
|
|
||||||
*/
|
|
||||||
const isRandom = ((imageUrl: string) => (imageUrl === ''))(props.fantasiaImage)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently selected image URL for rendering
|
|
||||||
*/
|
|
||||||
const currentMascotImage = determineCurrentImage(fantasiaImageList, isRandom, props.fantasiaImage)
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
.fantasiaMascotImage {
|
|
||||||
&__inner {
|
|
||||||
height: v-bind(height);
|
|
||||||
width: v-bind(width);
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,200 +0,0 @@
|
||||||
import { _electron as electron } from 'playwright'
|
|
||||||
import { test, expect } from '@playwright/test'
|
|
||||||
import { extraEnvVariablesAPI } from 'app/src-electron/customContentBridgeAPIs/extraEnvVariablesAPI'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra env settings to trigger component testing via Playwright
|
|
||||||
*/
|
|
||||||
const extraEnvSettings = {
|
|
||||||
TEST_ENV: 'components',
|
|
||||||
COMPONENT_NAME: 'GlobalWindowButtons',
|
|
||||||
COMPONENT_PROPS: JSON.stringify({})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Electron main filepath
|
|
||||||
*/
|
|
||||||
const electronMainFilePath:string = extraEnvVariablesAPI.ELECTRON_MAIN_FILEPATH
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra rended timer buffer for tests to start after loading the app
|
|
||||||
* - Change here in order manually adjust this component's wait times
|
|
||||||
*/
|
|
||||||
const faFrontendRenderTimer:number = extraEnvVariablesAPI.FA_FRONTEND_RENDER_TIMER
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object of string data selectors for the component
|
|
||||||
*/
|
|
||||||
const selectorList = {
|
|
||||||
buttonMinimize: 'globalWindowButtons-button-minimize',
|
|
||||||
buttonResize: 'globalWindowButtons-button-resize',
|
|
||||||
buttonClose: 'globalWindowButtons-button-close'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if the component has three specific HTML element buttons properly mounted in it:
|
|
||||||
* - Minimize button
|
|
||||||
* - Resize button
|
|
||||||
* - Close button
|
|
||||||
*/
|
|
||||||
test('Wrapper should contain three specific buttons', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const resizeButton = await appWindow.$(`[data-test="${selectorList.buttonResize}"]`)
|
|
||||||
const minimizeButton = await appWindow.$(`[data-test="${selectorList.buttonMinimize}"]`)
|
|
||||||
const closeButton = await appWindow.$(`[data-test="${selectorList.buttonClose}"]`)
|
|
||||||
|
|
||||||
// Check if the tested elements exists
|
|
||||||
if (resizeButton !== null && minimizeButton !== null && closeButton !== null) {
|
|
||||||
await expect(true).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// At least one of the tested elements doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to click the resize button
|
|
||||||
*/
|
|
||||||
test('Click resize button - "smallify"', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const resizeButton = await appWindow.$(`[data-test="${selectorList.buttonResize}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (resizeButton !== null) {
|
|
||||||
await resizeButton.click()
|
|
||||||
|
|
||||||
const isMaximized = await appWindow.evaluate(() => window.faWindowControlAPI.checkWindowMaximized())
|
|
||||||
|
|
||||||
await expect(isMaximized).toBe(false)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to click the resize button, twice
|
|
||||||
*/
|
|
||||||
test('Click resize button - "maximize"', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const resizeButton = await appWindow.$(`[data-test="${selectorList.buttonResize}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (resizeButton !== null) {
|
|
||||||
let isMaximized = await appWindow.evaluate(() => window.faWindowControlAPI.checkWindowMaximized())
|
|
||||||
|
|
||||||
// Check if the window if maximized of not, react accordingly
|
|
||||||
if (isMaximized) {
|
|
||||||
// Click twice
|
|
||||||
await resizeButton.click()
|
|
||||||
|
|
||||||
await appWindow.waitForTimeout(1500)
|
|
||||||
|
|
||||||
await resizeButton.click()
|
|
||||||
} else {
|
|
||||||
await resizeButton.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
await appWindow.waitForTimeout(1500)
|
|
||||||
isMaximized = await appWindow.evaluate(() => window.faWindowControlAPI.checkWindowMaximized())
|
|
||||||
|
|
||||||
await expect(isMaximized).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to click the minimize button
|
|
||||||
*/
|
|
||||||
test('Click minimize button', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const minimizeButton = await appWindow.$(`[data-test="${selectorList.buttonMinimize}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (minimizeButton !== null) {
|
|
||||||
await minimizeButton.click()
|
|
||||||
|
|
||||||
const isMaximized = await appWindow.evaluate(() => window.faWindowControlAPI.checkWindowMaximized())
|
|
||||||
|
|
||||||
await expect(isMaximized).toBe(false)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to click the close button
|
|
||||||
* - This test can VERY occasionally fail when the window takes too long to close on weaker PCs. Simply rerunning the tests generally fixes this.
|
|
||||||
*/
|
|
||||||
test('Click close button', async () => {
|
|
||||||
const electronApp = await electron.launch({
|
|
||||||
env: extraEnvSettings,
|
|
||||||
args: [electronMainFilePath]
|
|
||||||
})
|
|
||||||
|
|
||||||
const appWindow = await electronApp.firstWindow()
|
|
||||||
await appWindow.waitForTimeout(faFrontendRenderTimer)
|
|
||||||
|
|
||||||
const closeButton = await appWindow.$(`[data-test="${selectorList.buttonClose}"]`)
|
|
||||||
|
|
||||||
// Check if the tested element exists
|
|
||||||
if (closeButton !== null) {
|
|
||||||
let windowIsClosed = false
|
|
||||||
|
|
||||||
// Listen to window close event
|
|
||||||
appWindow.on('close', () => {
|
|
||||||
windowIsClosed = true
|
|
||||||
})
|
|
||||||
|
|
||||||
await closeButton.click()
|
|
||||||
|
|
||||||
await expect(windowIsClosed).toBe(true)
|
|
||||||
await electronApp.close()
|
|
||||||
} else {
|
|
||||||
// Element doesn't exist
|
|
||||||
test.fail()
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,183 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- App base-control button-group -->
|
|
||||||
<q-btn-group
|
|
||||||
flat
|
|
||||||
class="globalWindowButtons bg-dark"
|
|
||||||
>
|
|
||||||
<!-- Minimize button -->
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dark
|
|
||||||
size="xs"
|
|
||||||
class="globalWindowButtons__button globalWindowButtons__minimize"
|
|
||||||
data-test="globalWindowButtons-button-minimize"
|
|
||||||
@click="minimizeWindow()"
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
:delay="1000"
|
|
||||||
:offset="[0, 5]"
|
|
||||||
>
|
|
||||||
{{ $t('GlobalWindowButtons.minimizeButton') }}
|
|
||||||
</q-tooltip>
|
|
||||||
<q-icon
|
|
||||||
size="16px"
|
|
||||||
name="mdi-window-minimize"
|
|
||||||
/>
|
|
||||||
</q-btn>
|
|
||||||
|
|
||||||
<!-- MinMax button -->
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dark
|
|
||||||
size="xs"
|
|
||||||
class="globalWindowButtons__button globalWindowButtons__resize"
|
|
||||||
data-test="globalWindowButtons-button-resize"
|
|
||||||
@click="[resizeWindow(),checkIfWindowMaximized()]"
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
:delay="1000"
|
|
||||||
:offset="[0, 5]"
|
|
||||||
>
|
|
||||||
{{ isMaximized ? $t('GlobalWindowButtons.resizeButton') : $t('GlobalWindowButtons.maximizeButton') }}
|
|
||||||
</q-tooltip>
|
|
||||||
<q-icon
|
|
||||||
size="16px"
|
|
||||||
:name="(isMaximized)
|
|
||||||
? 'mdi-window-restore'
|
|
||||||
: 'mdi-window-maximize'"
|
|
||||||
/>
|
|
||||||
</q-btn>
|
|
||||||
|
|
||||||
<!-- Close button -->
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dark
|
|
||||||
size="xs"
|
|
||||||
class="globalWindowButtons__button globalWindowButtons__close"
|
|
||||||
data-test="globalWindowButtons-button-close"
|
|
||||||
@click="tryCloseWindow()"
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
:delay="1000"
|
|
||||||
:offset="[0, 5]"
|
|
||||||
>
|
|
||||||
{{ $t('GlobalWindowButtons.close') }}
|
|
||||||
</q-tooltip>
|
|
||||||
<q-icon
|
|
||||||
size="16px"
|
|
||||||
name="mdi-window-close"
|
|
||||||
/>
|
|
||||||
</q-btn>
|
|
||||||
</q-btn-group>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
|
||||||
import type { Ref } from 'vue'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers minimize of the window by the minimize button click
|
|
||||||
*/
|
|
||||||
const minimizeWindow = () => {
|
|
||||||
if (process.env.MODE === 'electron') {
|
|
||||||
window.faWindowControlAPI.minimizeWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers resize of the window by the min/max button click
|
|
||||||
*/
|
|
||||||
const resizeWindow = () => {
|
|
||||||
if (process.env.MODE === 'electron') {
|
|
||||||
window.faWindowControlAPI.resizeWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers checking of the current app state by the close button click.
|
|
||||||
* This functionality checks the following:
|
|
||||||
|
|
||||||
* 1. If the app has any projects opened to begin with at the moment
|
|
||||||
* 2. If the project has any pending chnages to it
|
|
||||||
|
|
||||||
* If both is found to be true, then an appropriate dialog is opened.
|
|
||||||
* Otherwise, the app simply closes.
|
|
||||||
*/
|
|
||||||
const tryCloseWindow = () => {
|
|
||||||
// TODO add project close checking
|
|
||||||
if (process.env.MODE === 'electron') {
|
|
||||||
window.faWindowControlAPI.closeWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Checks if the window is maximized and sets local variable accordingly
|
|
||||||
*/
|
|
||||||
const checkIfWindowMaximized = () => {
|
|
||||||
if (process.env.MODE === 'electron') {
|
|
||||||
isMaximized.value = window.faWindowControlAPI.checkWindowMaximized()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the window is currently maximized or not
|
|
||||||
*/
|
|
||||||
const isMaximized: Ref<boolean> = ref(true)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Window interval checker variable
|
|
||||||
*/
|
|
||||||
let checkerInterval: number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook up a interval timer on mount for continuous checking
|
|
||||||
* This is done due to the fact that dragging via the top header bar doesn't properly fire "drag" event
|
|
||||||
* Async due to UI render blocking
|
|
||||||
*/
|
|
||||||
onMounted(async () => {
|
|
||||||
window.clearInterval(checkerInterval)
|
|
||||||
|
|
||||||
checkIfWindowMaximized()
|
|
||||||
|
|
||||||
checkerInterval = window.setInterval(() => {
|
|
||||||
checkIfWindowMaximized()
|
|
||||||
}, 100)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
*Unhook the interval timer on unmounting in order to prevent left-over intervals ticking in the app
|
|
||||||
* Async due to UI render blocking
|
|
||||||
*/
|
|
||||||
onUnmounted(async () => {
|
|
||||||
window.clearInterval(checkerInterval)
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
.globalWindowButtons {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 99999999;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
height: $globalWindowButtons_height;
|
|
||||||
color: $globalWindowButtons_color;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
|
|
||||||
&__button {
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: $globalWindowButtons_hoverColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__close {
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: $globalWindowButtons_close_hoverColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
1276
src/components/ObjectTree.vue
Normal file
811
src/components/appHeader/AppControl.vue
Normal file
|
@ -0,0 +1,811 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:class="{'AppControl': !isFrontpage}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- New document dialog -->
|
||||||
|
<newDocumentDialog
|
||||||
|
:dialog-trigger="newObjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="newObjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Existing document dialog -->
|
||||||
|
<existingDocumentDialog
|
||||||
|
:dialog-trigger="existingObjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="existingObjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Project close dialog -->
|
||||||
|
<projectCloseCheckDialog
|
||||||
|
:dialog-trigger="projectCloseCheckDialogTrigger"
|
||||||
|
:dialog-mode="'projectClose'"
|
||||||
|
@trigger-dialog-close="projectCloseCheckDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Keybind dialog -->
|
||||||
|
<keybindCheatsheetDialog
|
||||||
|
:dialog-trigger="keybindsDialogTrigger"
|
||||||
|
@trigger-dialog-close="keybindsDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Import project dialog -->
|
||||||
|
<importProjectCheckDialog
|
||||||
|
:dialog-trigger="importProjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="importProjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Merge project dialog -->
|
||||||
|
<mergeProjectCheckDialog
|
||||||
|
:dialog-trigger="mergeProjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="mergeProjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- New project dialog -->
|
||||||
|
<newProjectCheckDialog
|
||||||
|
:dialog-trigger="newProjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="newProjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- About app dialog -->
|
||||||
|
<aboutAppDialog
|
||||||
|
:dialog-trigger="aboutAppDialogTrigger"
|
||||||
|
@trigger-dialog-close="aboutAppDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Changelog dialog -->
|
||||||
|
<changeLogDialog
|
||||||
|
:dialog-trigger="changeLogDialogTrigger"
|
||||||
|
@trigger-dialog-close="changeLogDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Program settings dialog -->
|
||||||
|
<programSettingsDialog
|
||||||
|
:dialog-trigger="programSettingsDialogTrigger"
|
||||||
|
@trigger-dialog-close="programSettingsDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Advanced search guide dialog -->
|
||||||
|
<advancedSearchGuideDialog
|
||||||
|
:dialog-trigger="advancedSearchGuideDialogTrigger"
|
||||||
|
@trigger-dialog-close="advancedSearchGuideDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Tips, Tricks & Trivia dialog -->
|
||||||
|
<tipsTricksTriviaDialog
|
||||||
|
:dialog-trigger="tipsTricksDialogTrigger"
|
||||||
|
@trigger-dialog-close="tipsTricksDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- License dialog -->
|
||||||
|
<licenseDialog
|
||||||
|
:dialog-trigger="licenseDialogTrigger"
|
||||||
|
@trigger-dialog-close="licenseDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Repair project dialog -->
|
||||||
|
<repairProjectDialog
|
||||||
|
:dialog-trigger="repairProjectDialogTrigger"
|
||||||
|
@trigger-dialog-close="repairProjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn-group
|
||||||
|
flat
|
||||||
|
class="AppControl__buttons"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- Options button -->
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
:ripple="false"
|
||||||
|
dark
|
||||||
|
size='md'
|
||||||
|
no-caps
|
||||||
|
@click="programSettingsDialogAssignUID"
|
||||||
|
>
|
||||||
|
<q-img
|
||||||
|
:src="appLogo"
|
||||||
|
style="height: 26px; width: 26px; margin: 0 -9px;"
|
||||||
|
/>
|
||||||
|
<q-tooltip anchor="center right" self="center left" :delay="500">
|
||||||
|
Program settings
|
||||||
|
</q-tooltip>
|
||||||
|
|
||||||
|
</q-btn>
|
||||||
|
<q-separator color="primary" vertical dark style="opacity: 0.1;" />
|
||||||
|
<!-- Project button-->
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
:ripple="false"
|
||||||
|
dark
|
||||||
|
size='md'
|
||||||
|
no-caps
|
||||||
|
>
|
||||||
|
Project
|
||||||
|
|
||||||
|
<q-menu
|
||||||
|
@show="checkProjectStatus"
|
||||||
|
anchor="bottom left"
|
||||||
|
class="bg-gunmetal-light"
|
||||||
|
dark
|
||||||
|
square
|
||||||
|
>
|
||||||
|
<q-list class="bg-gunmetal-light" dark>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="newObjectAssignUID"
|
||||||
|
:disable="!projectExists || isFrontpage"
|
||||||
|
>
|
||||||
|
<q-item-section>Quick-add new document</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-text-box-plus-outline" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="existingObjectAssignUID"
|
||||||
|
:disable="!projectExists || isFrontpage"
|
||||||
|
>
|
||||||
|
<q-item-section>Quick-search existing document</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-database-search" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator dark />
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="toggleHierarchicalTree"
|
||||||
|
:disable="!projectExists || isFrontpage"
|
||||||
|
>
|
||||||
|
<q-item-section>Toggle hierarchical tree</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-page-layout-sidebar-left" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="SSET_setNoteCorkboardWindowVisible"
|
||||||
|
:disable="!projectExists"
|
||||||
|
>
|
||||||
|
<q-item-section>Show note board</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-clipboard-text-outline" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator dark />
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="newProjectAssignUID"
|
||||||
|
>
|
||||||
|
<q-item-section>New project</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-plus" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator dark />
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="commenceExport"
|
||||||
|
:disable="!projectExists"
|
||||||
|
>
|
||||||
|
<q-item-section>Export current project</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-package-variant-closed" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="importProjectAssignUID"
|
||||||
|
>
|
||||||
|
<q-item-section>Import existing project</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-package-variant" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="mergeProjectAssignUID"
|
||||||
|
:disable="!projectExists"
|
||||||
|
>
|
||||||
|
<q-item-section>Merge another project into the current one</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-folder-plus-outline" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator dark />
|
||||||
|
|
||||||
|
<q-item clickable>
|
||||||
|
<q-item-section>Advanced project tools</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="keyboard_arrow_right" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-menu anchor="top end" self="top start">
|
||||||
|
<q-list class="bg-gunmetal text-accent">
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="repairProjectAssignUID"
|
||||||
|
:disable="!projectExists || isFrontpage"
|
||||||
|
>
|
||||||
|
<q-item-section>Repair legacy project</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-wrench" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator dark />
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="navigateToProjectPage"
|
||||||
|
:disable="!projectExists || isProjectPage"
|
||||||
|
>
|
||||||
|
<q-item-section>Show project overview</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-chart-bar" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
@click="projectCloseCheckDialogAssignUID"
|
||||||
|
:disable="!projectExists || isFrontpage"
|
||||||
|
>
|
||||||
|
<q-item-section>Close project</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-exit-to-app" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<!-- Help button-->
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
:ripple="false"
|
||||||
|
dark
|
||||||
|
size='md'
|
||||||
|
no-caps
|
||||||
|
>
|
||||||
|
Help, Settings & Info
|
||||||
|
<q-menu
|
||||||
|
anchor="bottom left"
|
||||||
|
class="bg-gunmetal-light"
|
||||||
|
dark
|
||||||
|
square
|
||||||
|
>
|
||||||
|
<q-list class="bg-gunmetal-light" dark>
|
||||||
|
<q-item
|
||||||
|
@click="programSettingsDialogAssignUID"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
>
|
||||||
|
<q-item-section>Program settings</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-tune" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator dark />
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
@click="keybindsDialogAssignUID"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
>
|
||||||
|
<q-item-section>Show keybind cheatsheet</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-keyboard-settings" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
@click="advancedSearchGuideAssignUID"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
>
|
||||||
|
<q-item-section>Advanced search guide</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-file-question" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
@click="tipsTricksAssignUID"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
>
|
||||||
|
<q-item-section>Tips, Tricks & Trivia</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-fire-alert" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator dark />
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
@click="changeLogDialogAssignUID"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
>
|
||||||
|
<q-item-section>Changelog</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-clipboard-text" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
@click="aboutAppDialogAssignUID"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
>
|
||||||
|
<q-item-section>About Fantasia Archive</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-information-variant" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
@click="licenseAssignUID"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
>
|
||||||
|
<q-item-section>License</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-script-text-outline" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator dark />
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
@click="toggleDevTools"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
active
|
||||||
|
active-class="bg-gunmetal-light text-cultured"
|
||||||
|
class="noHigh"
|
||||||
|
>
|
||||||
|
<q-item-section>Toggle developer tools</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-code-tags" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
|
||||||
|
</q-btn>
|
||||||
|
</q-btn-group>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component, Watch } from "vue-property-decorator"
|
||||||
|
|
||||||
|
import BaseClass from "src/BaseClass"
|
||||||
|
import projectCloseCheckDialog from "src/components/dialogs/ProjectCloseCheck.vue"
|
||||||
|
import keybindCheatsheetDialog from "src/components/dialogs/KeybindCheatsheet.vue"
|
||||||
|
import importProjectCheckDialog from "src/components/dialogs/ImportProjectCheck.vue"
|
||||||
|
import mergeProjectCheckDialog from "src/components/dialogs/MergeProjectCheck.vue"
|
||||||
|
import newProjectCheckDialog from "src/components/dialogs/NewProjectCheck.vue"
|
||||||
|
import repairProjectDialog from "src/components/dialogs/RepairProject.vue"
|
||||||
|
import aboutAppDialog from "src/components/dialogs/AboutApp.vue"
|
||||||
|
import changeLogDialog from "src/components/dialogs/ChangeLog.vue"
|
||||||
|
import programSettingsDialog from "src/components/dialogs/ProgramSettings.vue"
|
||||||
|
import advancedSearchGuideDialog from "src/components/dialogs/AdvancedSearchGuide.vue"
|
||||||
|
import newDocumentDialog from "src/components/dialogs/NewDocument.vue"
|
||||||
|
import existingDocumentDialog from "src/components/dialogs/ExistingDocument.vue"
|
||||||
|
import tipsTricksTriviaDialog from "src/components/dialogs/TipsTricksTrivia.vue"
|
||||||
|
import licenseDialog from "src/components/dialogs/License.vue"
|
||||||
|
|
||||||
|
import { Loading, QSpinnerGears } from "quasar"
|
||||||
|
import { retrieveCurrentProjectName, exportProject } from "src/scripts/projectManagement/projectManagent"
|
||||||
|
import { toggleDevTools } from "src/scripts/utilities/devTools"
|
||||||
|
|
||||||
|
import appLogo from "src/assets/appLogo.png"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
projectCloseCheckDialog,
|
||||||
|
keybindCheatsheetDialog,
|
||||||
|
importProjectCheckDialog,
|
||||||
|
mergeProjectCheckDialog,
|
||||||
|
newProjectCheckDialog,
|
||||||
|
aboutAppDialog,
|
||||||
|
changeLogDialog,
|
||||||
|
advancedSearchGuideDialog,
|
||||||
|
programSettingsDialog,
|
||||||
|
newDocumentDialog,
|
||||||
|
existingDocumentDialog,
|
||||||
|
tipsTricksTriviaDialog,
|
||||||
|
licenseDialog,
|
||||||
|
repairProjectDialog
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default class AppControl extends BaseClass {
|
||||||
|
/****************************************************************/
|
||||||
|
// Import handling
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the developer tools on and off
|
||||||
|
*/
|
||||||
|
toggleDevTools = toggleDevTools
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current project name
|
||||||
|
*/
|
||||||
|
retrieveCurrentProjectName = retrieveCurrentProjectName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just an image
|
||||||
|
*/
|
||||||
|
appLogo = appLogo
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Basic component functionality
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the project exists or not
|
||||||
|
*/
|
||||||
|
projectExists: undefined | string | boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if we are on frontpage or not
|
||||||
|
*/
|
||||||
|
isFrontpage = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if we are on project page or not
|
||||||
|
*/
|
||||||
|
isProjectPage = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current project name
|
||||||
|
*/
|
||||||
|
projectName = ""
|
||||||
|
|
||||||
|
created () {
|
||||||
|
this.checkProjectStatus().catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkProjectStatus () {
|
||||||
|
this.projectName = await retrieveCurrentProjectName()
|
||||||
|
this.projectExists = !!(await retrieveCurrentProjectName())
|
||||||
|
this.isFrontpage = (this.$route.path === "/")
|
||||||
|
this.isProjectPage = (this.$route.path === "/project")
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Local keybinds
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
@Watch("SGET_getCurrentKeyBindData", { deep: true })
|
||||||
|
processKeyPush () {
|
||||||
|
// Keybind cheatsheet
|
||||||
|
if (this.determineKeyBind("openKeybindsCheatsheet") && !this.SGET_getDialogsState) {
|
||||||
|
this.keybindsDialogAssignUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// App options
|
||||||
|
if (this.determineKeyBind("openAppOptions") && !this.SGET_getDialogsState) {
|
||||||
|
this.programSettingsDialogAssignUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// App options
|
||||||
|
if (this.determineKeyBind("navigateToProjectOverview") && this.projectExists && !this.isProjectPage) {
|
||||||
|
this.navigateToProjectPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Navigate to project page action
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
navigateToProjectPage () {
|
||||||
|
this.$router.push({ path: "/project" }).catch((e: {name: string}) => {
|
||||||
|
if (e && e.name !== "NavigationDuplicated") {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Export project action
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
async commenceExport () {
|
||||||
|
const projectName = await retrieveCurrentProjectName()
|
||||||
|
const setup = {
|
||||||
|
message: "<h4>Exporting current project...</h4>",
|
||||||
|
spinnerColor: "primary",
|
||||||
|
messageColor: "cultured",
|
||||||
|
spinnerSize: 120,
|
||||||
|
backgroundColor: "dark",
|
||||||
|
// @ts-ignore
|
||||||
|
spinner: QSpinnerGears
|
||||||
|
}
|
||||||
|
exportProject(projectName, Loading, setup, this.$q)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Close project dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
projectCloseCheckDialogTrigger: string | false = false
|
||||||
|
projectCloseCheckDialogClose () {
|
||||||
|
this.projectCloseCheckDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
projectCloseCheckDialogAssignUID () {
|
||||||
|
this.projectCloseCheckDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Import project dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
importProjectDialogTrigger: string | false = false
|
||||||
|
importProjectDialogClose () {
|
||||||
|
this.importProjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
importProjectAssignUID () {
|
||||||
|
this.importProjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Merge project dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
mergeProjectDialogTrigger: string | false = false
|
||||||
|
mergeProjectDialogClose () {
|
||||||
|
this.mergeProjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeProjectAssignUID () {
|
||||||
|
this.mergeProjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// New project dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
newProjectDialogTrigger: string | false = false
|
||||||
|
newProjectDialogClose () {
|
||||||
|
this.newProjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
newProjectAssignUID () {
|
||||||
|
this.newProjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Keybings cheatsheet dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
keybindsDialogTrigger: string | false = false
|
||||||
|
keybindsDialogClose () {
|
||||||
|
this.keybindsDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
keybindsDialogAssignUID () {
|
||||||
|
this.keybindsDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// About app dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
aboutAppDialogTrigger: string | false = false
|
||||||
|
aboutAppDialogClose () {
|
||||||
|
this.aboutAppDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
aboutAppDialogAssignUID () {
|
||||||
|
this.aboutAppDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Changelog dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
changeLogDialogTrigger: string | false = false
|
||||||
|
changeLogDialogClose () {
|
||||||
|
this.changeLogDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
changeLogDialogAssignUID () {
|
||||||
|
this.changeLogDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Program settings dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
programSettingsDialogTrigger: string | false = false
|
||||||
|
programSettingsDialogClose () {
|
||||||
|
this.programSettingsDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
programSettingsDialogAssignUID () {
|
||||||
|
this.programSettingsDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Advanced search guide dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
advancedSearchGuideDialogTrigger: string | false = false
|
||||||
|
advancedSearchGuideDialogClose () {
|
||||||
|
this.advancedSearchGuideDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
advancedSearchGuideAssignUID () {
|
||||||
|
this.advancedSearchGuideDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// New document dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
newObjectDialogTrigger: string | false = false
|
||||||
|
newObjectDialogClose () {
|
||||||
|
this.newObjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
newObjectAssignUID () {
|
||||||
|
this.newObjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Existing document dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
existingObjectDialogTrigger: string | false = false
|
||||||
|
existingObjectDialogClose () {
|
||||||
|
this.existingObjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
existingObjectAssignUID () {
|
||||||
|
this.existingObjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Tips, Tricka & Trivia dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
tipsTricksDialogTrigger: string | false = false
|
||||||
|
tipsTricksDialogClose () {
|
||||||
|
this.tipsTricksDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
tipsTricksAssignUID () {
|
||||||
|
this.tipsTricksDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// License dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
licenseDialogTrigger: string | false = false
|
||||||
|
licenseDialogClose () {
|
||||||
|
this.licenseDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
licenseAssignUID () {
|
||||||
|
this.licenseDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Repair project dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
repairProjectDialogTrigger: string | false = false
|
||||||
|
repairProjectDialogClose () {
|
||||||
|
this.repairProjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
repairProjectAssignUID () {
|
||||||
|
this.repairProjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.AppControl {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
&__buttons {
|
||||||
|
height: 40px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
148
src/components/appHeader/AppWindowButtons.vue
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<q-btn-group
|
||||||
|
flat
|
||||||
|
class="appWindowButtons bg-dark"
|
||||||
|
>
|
||||||
|
|
||||||
|
<projectCloseCheckDialog
|
||||||
|
:dialog-trigger="projectCloseCheckDialogTrigger"
|
||||||
|
:dialog-mode="'appClose'"
|
||||||
|
@trigger-dialog-close="projectCloseCheckDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Minimize button-->
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
:ripple="false"
|
||||||
|
:class="{'minimize': osSystem === 'darwin'}"
|
||||||
|
dark
|
||||||
|
size='sm'
|
||||||
|
@click="minimizeWindow">
|
||||||
|
<q-icon name="mdi-window-minimize"></q-icon>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<!-- MinMax button-->
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
:ripple="false"
|
||||||
|
:class="{'minMax': osSystem === 'darwin'}"
|
||||||
|
dark
|
||||||
|
size='sm'
|
||||||
|
@click="resizeWindow">
|
||||||
|
<q-icon :name="(isMaximized)? 'mdi-window-restore' : 'mdi-window-maximize'"></q-icon>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<!-- Close button-->
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
:ripple="false"
|
||||||
|
dark
|
||||||
|
size='sm'
|
||||||
|
@click="projectCloseCheckDialogAssignUID"
|
||||||
|
:class="[{'close': osSystem === 'darwin'}]"
|
||||||
|
>
|
||||||
|
<q-icon name="mdi-window-close"></q-icon>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
</q-btn-group>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component } from "vue-property-decorator"
|
||||||
|
import { remote } from "electron"
|
||||||
|
|
||||||
|
import BaseClass from "src/BaseClass"
|
||||||
|
import projectCloseCheckDialog from "src/components/dialogs/ProjectCloseCheck.vue"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: { projectCloseCheckDialog }
|
||||||
|
})
|
||||||
|
export default class AppWindowButtons extends BaseClass {
|
||||||
|
/****************************************************************/
|
||||||
|
// Basic component functionality
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the window is maximed or not
|
||||||
|
*/
|
||||||
|
isMaximized = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the currently used OS
|
||||||
|
*/
|
||||||
|
osSystem = remote.process.platform
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently opened window
|
||||||
|
*/
|
||||||
|
currentWindow = remote.getCurrentWindow()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the window is currently maximized or not
|
||||||
|
*/
|
||||||
|
checkIfMaximized () {
|
||||||
|
this.isMaximized = this.currentWindow.isMaximized()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimizes the current window
|
||||||
|
*/
|
||||||
|
minimizeWindow () {
|
||||||
|
this.currentWindow.minimize()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the window to either smaller or maximized
|
||||||
|
*/
|
||||||
|
resizeWindow () {
|
||||||
|
if (this.currentWindow.isMaximized()) {
|
||||||
|
this.currentWindow.unmaximize()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.currentWindow.maximize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
created () {
|
||||||
|
window.addEventListener("resize", this.checkIfMaximized)
|
||||||
|
this.checkIfMaximized()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyed () {
|
||||||
|
window.addEventListener("resize", this.checkIfMaximized)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Close project dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
projectCloseCheckDialogTrigger: string | false = false
|
||||||
|
projectCloseCheckDialogClose () {
|
||||||
|
this.projectCloseCheckDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
projectCloseCheckDialogAssignUID () {
|
||||||
|
this.projectCloseCheckDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.appWindowButtons {
|
||||||
|
border-radius: 0;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 40px;
|
||||||
|
z-index: 99999999;
|
||||||
|
color: #fff;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" >
|
||||||
|
|
||||||
|
</style>
|
572
src/components/appHeader/TopTabs.vue
Normal file
|
@ -0,0 +1,572 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
|
||||||
|
<closeDocumentCheckDialog
|
||||||
|
:dialog-trigger="closeDocumentCheckDialogTrigger"
|
||||||
|
:dialog-document="dialogDoc"
|
||||||
|
@trigger-dialog-close="closeDocumentCheckDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Delele document dialog -->
|
||||||
|
<deleteDocumentCheckDialog
|
||||||
|
:dialog-trigger="deleteObjectDialogTrigger"
|
||||||
|
:document-id="toDeleteID"
|
||||||
|
:document-type="toDeleteType"
|
||||||
|
@trigger-dialog-close="deleteObjectDialogClose"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-tabs
|
||||||
|
v-if="localDocuments.length > 0"
|
||||||
|
:class="{'hasTextShadow': textShadow}"
|
||||||
|
align="left"
|
||||||
|
inline-label
|
||||||
|
outside-arrows
|
||||||
|
mobile-arrows
|
||||||
|
class="tabsWrapper"
|
||||||
|
dense
|
||||||
|
no-caps>
|
||||||
|
<transition-group
|
||||||
|
name="list"
|
||||||
|
tag="div"
|
||||||
|
class="headerTransitionWrapper"
|
||||||
|
enter-active-class="animated fadeIn"
|
||||||
|
leave-active-class="animated fadeOut"
|
||||||
|
appear
|
||||||
|
:duration="50">
|
||||||
|
|
||||||
|
<q-route-tab
|
||||||
|
:ripple="false"
|
||||||
|
v-for="document in localDocuments"
|
||||||
|
:to="`/project/display-content/${document.type}/${document._id}`"
|
||||||
|
:key="document.type+document._id"
|
||||||
|
:icon="(retrieveFieldValue(document,'categorySwitch') ? 'fas fa-folder-open' : document.icon)"
|
||||||
|
:style="`
|
||||||
|
color: ${retrieveFieldValue(document,'documentColor')};
|
||||||
|
background-color: ${retrieveFieldValue(document,'documentBackgroundColor')};
|
||||||
|
filter: ${(retrieveFieldValue(document,'minorSwitch') ? 'grayscale(100) brightness(0.7)' : '')}`"
|
||||||
|
:class="[
|
||||||
|
{'isBold':
|
||||||
|
(
|
||||||
|
retrieveFieldValue(document,'documentColor') !== '#ffffff' &&
|
||||||
|
retrieveFieldValue(document,'documentColor') !== '#fff'
|
||||||
|
) &&
|
||||||
|
retrieveFieldValue(document,'documentColor') !== ''
|
||||||
|
}]"
|
||||||
|
:alert="document.hasEdits"
|
||||||
|
alert-icon="mdi-feather"
|
||||||
|
@click.prevent.middle="tryCloseTab(document)"
|
||||||
|
>
|
||||||
|
<span class="isDeadIndicator" v-if="retrieveFieldValue(document,'deadSwitch')">
|
||||||
|
†
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="q-tab__label"
|
||||||
|
:class="{'isDead': (retrieveFieldValue(document,'deadSwitch') && !hideDeadCrossThrough)}">
|
||||||
|
{{retrieveFieldValue(document,'name')}}
|
||||||
|
</div>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="700"
|
||||||
|
>
|
||||||
|
<span class="isDeadIndicator" v-if="retrieveFieldValue(document,'deadSwitch')">
|
||||||
|
†
|
||||||
|
</span>
|
||||||
|
{{retrieveFieldValue(document,'name')}}
|
||||||
|
<br>
|
||||||
|
<span class="text-caption">Middle mouse button to close</span>
|
||||||
|
</q-tooltip>
|
||||||
|
<q-btn
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
class="z-max q-ml-auto"
|
||||||
|
:class="{'q-mr-sm': document.hasEdits}"
|
||||||
|
size="xs"
|
||||||
|
icon="close"
|
||||||
|
style="color: #fff;"
|
||||||
|
@click.stop.prevent="tryCloseTab(document)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-menu
|
||||||
|
touch-position
|
||||||
|
context-menu
|
||||||
|
>
|
||||||
|
<q-list class="bg-gunmetal-light text-accent">
|
||||||
|
<q-item clickable>
|
||||||
|
<q-item-section>All opened tabs</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="keyboard_arrow_right" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-menu anchor="top end" self="top start">
|
||||||
|
<q-list class="bg-gunmetal text-accent">
|
||||||
|
<q-item
|
||||||
|
:to="`/project/display-content/${menuDoc.type}/${menuDoc._id}`"
|
||||||
|
v-for="menuDoc in localDocuments"
|
||||||
|
:key="menuDoc._id"
|
||||||
|
clickable
|
||||||
|
:style="`
|
||||||
|
color: ${retrieveFieldValue(menuDoc,'documentColor')};
|
||||||
|
background-color: ${retrieveFieldValue(menuDoc,'documentBackgroundColor')};
|
||||||
|
filter: ${(retrieveFieldValue(menuDoc,'minorSwitch') ? 'grayscale(100) brightness(0.7)' : '')}`"
|
||||||
|
>
|
||||||
|
<q-item-section class="isDeadIndicator grow-0" v-if="retrieveFieldValue(menuDoc,'deadSwitch')">
|
||||||
|
†
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section
|
||||||
|
:class="{'isDead': (retrieveFieldValue(menuDoc,'deadSwitch') && !hideDeadCrossThrough)}"
|
||||||
|
>{{retrieveFieldValue(menuDoc,'name')}}</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon :name="(retrieveFieldValue(menuDoc,'categorySwitch') ? 'fas fa-folder-open' : menuDoc.icon)" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-item>
|
||||||
|
<q-separator dark />
|
||||||
|
<q-item clickable v-close-popup @click="copyName(document)">
|
||||||
|
<q-item-section>Copy name</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-text-recognition" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="copyTextColor(document)">
|
||||||
|
<q-item-section>Copy text color</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-eyedropper" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="copyBackgroundColor(document)">
|
||||||
|
<q-item-section>Copy background color</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-format-color-fill" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator dark />
|
||||||
|
<q-item v-if="!document.isNew" clickable v-close-popup @click="addNewUnderParent(document)">
|
||||||
|
<q-item-section>Create new document with this document as parent</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-file-tree" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-if="!document.isNew" v-close-popup @click="copyTargetDocument(document)">
|
||||||
|
<q-item-section>Copy this document</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-content-copy" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator v-if="!document.isNew" />
|
||||||
|
<q-item clickable v-close-popup @click="tryCloseTab(document)">
|
||||||
|
<q-item-section>Close this tab</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-close" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="SSET_closeAllButCurrentDocuments(document)">
|
||||||
|
<q-item-section>Close all tabs without changes except for this</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-close-box-outline" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="SSET_closeAllDocuments">
|
||||||
|
<q-item-section>Close all tabs without changes</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-close-box-multiple-outline" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator dark />
|
||||||
|
<q-item clickable v-close-popup @click="SSET_forceCloseAllButCurrentDocuments(document)">
|
||||||
|
<q-item-section class="text-secondary"><b>Force close all tabs except for this</b></q-item-section>
|
||||||
|
<q-item-section avatar class="text-secondary">
|
||||||
|
<q-icon name="mdi-close-box" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="SSET_forceCloseAllDocuments">
|
||||||
|
<q-item-section class="text-secondary"><b>Force close all tabs</b></q-item-section>
|
||||||
|
<q-item-section avatar class="text-secondary">
|
||||||
|
<q-icon name="mdi-close-box-multiple" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator dark />
|
||||||
|
<q-item clickable v-close-popup @click="deleteTabDocument(document)">
|
||||||
|
<q-item-section class="text-secondary"><b>Delete this document</b></q-item-section>
|
||||||
|
<q-item-section avatar class="text-secondary">
|
||||||
|
<q-icon name="mdi-text-box-remove-outline" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
|
||||||
|
</q-menu>
|
||||||
|
</q-route-tab>
|
||||||
|
|
||||||
|
</transition-group>
|
||||||
|
</q-tabs>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import BaseClass from "src/BaseClass"
|
||||||
|
|
||||||
|
import { Component, Watch } from "vue-property-decorator"
|
||||||
|
import deleteDocumentCheckDialog from "src/components/dialogs/DeleteDocumentCheck.vue"
|
||||||
|
|
||||||
|
import { I_OpenedDocument } from "src/interfaces/I_OpenedDocument"
|
||||||
|
import closeDocumentCheckDialog from "src/components/dialogs/CloseDocumentCheck.vue"
|
||||||
|
import { createNewWithParent } from "src/scripts/documentActions/createNewWithParent"
|
||||||
|
import { copyDocumentName, copyDocumentTextColor, copyDocumentBackgroundColor } from "src/scripts/documentActions/uniqueFieldCopy"
|
||||||
|
import { copyDocument } from "src/scripts/documentActions/copyDocument"
|
||||||
|
import { extend } from "quasar"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
closeDocumentCheckDialog,
|
||||||
|
deleteDocumentCheckDialog
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default class TopTabs extends BaseClass {
|
||||||
|
/****************************************************************/
|
||||||
|
// App options
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the tabs have text shadow or not
|
||||||
|
*/
|
||||||
|
textShadow = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the "dead" document type should have a cross-text decoration or not
|
||||||
|
*/
|
||||||
|
hideDeadCrossThrough = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch changes on options
|
||||||
|
*/
|
||||||
|
@Watch("SGET_options", { immediate: true, deep: true })
|
||||||
|
onSettingsChange () {
|
||||||
|
const options = this.SGET_options
|
||||||
|
this.textShadow = options.textShadow
|
||||||
|
this.hideDeadCrossThrough = options.hideDeadCrossThrough
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Keybind handling
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to keypresses passed from the parent document
|
||||||
|
*/
|
||||||
|
@Watch("SGET_getCurrentKeyBindData", { deep: true })
|
||||||
|
processKeyPush () {
|
||||||
|
// Close tab dialog
|
||||||
|
if (this.determineKeyBind("closeTab") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
|
||||||
|
this.tryCloseTab()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next tab
|
||||||
|
if (this.determineKeyBind("nextTab") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
|
||||||
|
this.goToNextTab()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previous tab
|
||||||
|
if (this.determineKeyBind("previousTab") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
|
||||||
|
this.goToPreviousTab()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all tabs without changes except for this - CTRL + ALT + SHIFT + W
|
||||||
|
if (this.determineKeyBind("closeAllTabsWithoutChangesButThis") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
|
||||||
|
const currentDoc = this.findRequestedOrActiveDocument() as I_OpenedDocument
|
||||||
|
this.SSET_closeAllButCurrentDocuments(currentDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all tabs without changes - CTRL + SHIFT + W
|
||||||
|
if (this.determineKeyBind("closeAllTabsWithoutChanges") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
|
||||||
|
this.SSET_closeAllDocuments()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force close all tabs except for this - NONE
|
||||||
|
if (this.determineKeyBind("forceCloseAllTabsButThis") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
|
||||||
|
const currentDoc = this.findRequestedOrActiveDocument() as I_OpenedDocument
|
||||||
|
this.SSET_forceCloseAllButCurrentDocuments(currentDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force close all tabs - NONE
|
||||||
|
if (this.determineKeyBind("forceCloseAllTabs") && this.localDocuments.length > 0 && !this.SGET_getDialogsState) {
|
||||||
|
this.SSET_forceCloseAllDocuments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Tab management
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the local reference whenever something gets changed
|
||||||
|
*/
|
||||||
|
@Watch("SGET_allOpenedDocuments", { deep: true })
|
||||||
|
reactToDocumentListChange (val: {docs: I_OpenedDocument[]}, oldVal: {docs: I_OpenedDocument[]}) {
|
||||||
|
this.localDocuments = []
|
||||||
|
this.localDocuments = val.docs
|
||||||
|
|
||||||
|
// Re-check the route after a change
|
||||||
|
this.refreshRoute()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local reference to the opened document list
|
||||||
|
*/
|
||||||
|
localDocuments: I_OpenedDocument[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current document to be closed
|
||||||
|
*/
|
||||||
|
dialogDoc = null as unknown as I_OpenedDocument
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to close the document being closed
|
||||||
|
*/
|
||||||
|
tryCloseTab (doc?: I_OpenedDocument) {
|
||||||
|
const matchingDocument = this.findRequestedOrActiveDocument(doc)
|
||||||
|
|
||||||
|
if (matchingDocument) {
|
||||||
|
this.dialogDoc = matchingDocument
|
||||||
|
this.closeDocumentCheckDialogAssignUID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to navigate to next tab
|
||||||
|
*/
|
||||||
|
goToNextTab () {
|
||||||
|
let index = -1
|
||||||
|
const matchingDocument = this.localDocuments.find((e, i) => {
|
||||||
|
index = i
|
||||||
|
return e.url === this.$route.path
|
||||||
|
})
|
||||||
|
|
||||||
|
if (matchingDocument && index !== this.localDocuments.length - 1) {
|
||||||
|
this.$router.push({ path: this.localDocuments[index + 1].url }).catch((e: {name: string}) => {
|
||||||
|
if (e && e.name !== "NavigationDuplicated") {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (matchingDocument && index === this.localDocuments.length - 1) {
|
||||||
|
this.$router.push({ path: this.localDocuments[0].url }).catch((e: {name: string}) => {
|
||||||
|
if (e && e.name !== "NavigationDuplicated") {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to navigate to previous tab
|
||||||
|
*/
|
||||||
|
goToPreviousTab () {
|
||||||
|
let index = -1
|
||||||
|
const matchingDocument = this.localDocuments.find((e, i) => {
|
||||||
|
index = i
|
||||||
|
return e.url === this.$route.path
|
||||||
|
})
|
||||||
|
|
||||||
|
if (matchingDocument && index !== 0) {
|
||||||
|
this.$router.push({ path: this.localDocuments[index - 1].url }).catch((e: {name: string}) => {
|
||||||
|
if (e && e.name !== "NavigationDuplicated") {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingDocument && index === 0) {
|
||||||
|
this.$router.push({ path: this.localDocuments[this.localDocuments.length - 1].url }).catch((e: {name: string}) => {
|
||||||
|
if (e && e.name !== "NavigationDuplicated") {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Document field copying
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
copyName (currentDoc: I_OpenedDocument) {
|
||||||
|
copyDocumentName(currentDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTextColor (currentDoc: I_OpenedDocument) {
|
||||||
|
copyDocumentTextColor(currentDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
copyBackgroundColor (currentDoc: I_OpenedDocument) {
|
||||||
|
copyDocumentBackgroundColor(currentDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Document copy
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
documentPass = null as unknown as I_OpenedDocument
|
||||||
|
|
||||||
|
copyTargetDocument (currentDoc: I_OpenedDocument) {
|
||||||
|
this.documentPass = extend(true, {}, currentDoc)
|
||||||
|
|
||||||
|
const blueprint = this.SGET_blueprint(this.documentPass.type)
|
||||||
|
const newDocument = copyDocument(this.documentPass, this.generateUID(), blueprint)
|
||||||
|
|
||||||
|
const dataPass = {
|
||||||
|
doc: newDocument,
|
||||||
|
treeAction: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_addOpenedDocument(dataPass)
|
||||||
|
this.$router.push({
|
||||||
|
path: newDocument.url
|
||||||
|
}).catch((e: {name: string}) => {
|
||||||
|
const errorName : string = e.name
|
||||||
|
if (errorName === "NavigationDuplicated") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Add new document under parent
|
||||||
|
/****************************************************************/
|
||||||
|
addNewUnderParent (currentDoc: I_OpenedDocument) {
|
||||||
|
createNewWithParent(currentDoc, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Close document dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
closeDocumentCheckDialogTrigger: string | false = false
|
||||||
|
closeDocumentCheckDialogClose () {
|
||||||
|
this.closeDocumentCheckDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDocumentCheckDialogAssignUID () {
|
||||||
|
this.closeDocumentCheckDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Delete dialog
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
deleteObjectDialogTrigger: string | false = false
|
||||||
|
deleteObjectDialogClose () {
|
||||||
|
this.deleteObjectDialogTrigger = false
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteObjectAssignUID () {
|
||||||
|
this.deleteObjectDialogTrigger = this.generateUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
toDeleteID = ""
|
||||||
|
toDeleteType = ""
|
||||||
|
|
||||||
|
deleteTabDocument (targetDocument: I_OpenedDocument) {
|
||||||
|
this.toDeleteID = targetDocument._id
|
||||||
|
this.toDeleteType = targetDocument.type
|
||||||
|
this.deleteObjectAssignUID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.headerTransitionWrapper {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabsWrapper .fas {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabsWrapper .mdi {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.tabsWrapper {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
|
&.hasTextShadow {
|
||||||
|
.q-tab__label,
|
||||||
|
.q-tab__icon {
|
||||||
|
$shadowColorOutline: #000;
|
||||||
|
$shadowColorSurround: #000;
|
||||||
|
|
||||||
|
filter: drop-shadow(0 0 1px #000);
|
||||||
|
text-shadow:
|
||||||
|
//-2px -2px 0 $shadowColorSurround,
|
||||||
|
//2px -2px 0 $shadowColorSurround,
|
||||||
|
//-2px 2px 0 $shadowColorSurround,
|
||||||
|
//2px 2px 0 $shadowColorSurround,
|
||||||
|
-1px -1px 0 $shadowColorOutline,
|
||||||
|
1px -1px 0 $shadowColorOutline,
|
||||||
|
-1px 1px 0 $shadowColorOutline,
|
||||||
|
1px 1px 0 $shadowColorOutline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-tabs__arrow {
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isBold .q-tab__label {
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-tab {
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
min-width: 170px;
|
||||||
|
width: 170px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding-top: 2px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fas {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdi {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.q-tabs--dense .q-tab {
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-tab__alert-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
top: 4px;
|
||||||
|
right: -10px;
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.body--dark {
|
||||||
|
.topTabs {
|
||||||
|
.q-tab {
|
||||||
|
color: #dcdcdc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
145
src/components/dialogs/AboutApp.vue
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<q-dialog
|
||||||
|
v-model="dialogModel"
|
||||||
|
@hide="triggerDialogClose"
|
||||||
|
>
|
||||||
|
<q-card
|
||||||
|
class="aboutDialog"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<q-card-section>
|
||||||
|
<h6 class="text-center q-mt-lg q-mb-sm">About Fantasia Archive</h6>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section>
|
||||||
|
<div>
|
||||||
|
Currently running Fantasia Archive version: <span class="text-bold text-primary">{{appVersion}}</span>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-separator color="primary" horizonatal dark class="q-my-lg q-mx-auto" style="opacity: 0.5; width: 400px;" />
|
||||||
|
|
||||||
|
<q-card-section>
|
||||||
|
<div class="col-12 q-mx-sm q-my-md">
|
||||||
|
<div class="patreonButton shadow-1" @click="openPatreonLink">
|
||||||
|
Support Fantasia Archive on Patreon!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 q-mb-lg">
|
||||||
|
<div class="row justify-center">
|
||||||
|
|
||||||
|
<div class="q-mx-sm q-my-md">
|
||||||
|
<div class="discordButton shadow-1" @click="openDiscordInviteLink">
|
||||||
|
Discord
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mx-sm q-my-md">
|
||||||
|
<div class="redditButton shadow-1" @click="openRedditLink"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mx-sm q-my-md">
|
||||||
|
<div class="websiteButton shadow-1" @click="openWebsiteLink">
|
||||||
|
Website
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mx-sm q-my-md">
|
||||||
|
<div class="githubButton shadow-1" @click="openGithubLink">
|
||||||
|
GitHub
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="around" class="q-mb-lg q-mt-md">
|
||||||
|
<q-btn flat label="Close" color="accent" v-close-popup />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component, Watch } from "vue-property-decorator"
|
||||||
|
import { shell, remote } from "electron"
|
||||||
|
|
||||||
|
import DialogBase from "src/components/dialogs/_DialogBase"
|
||||||
|
@Component({
|
||||||
|
components: { }
|
||||||
|
})
|
||||||
|
export default class AboutApp extends DialogBase {
|
||||||
|
/**
|
||||||
|
* React to dialog opening request
|
||||||
|
*/
|
||||||
|
@Watch("dialogTrigger")
|
||||||
|
openDialog (val: string|false) {
|
||||||
|
if (val) {
|
||||||
|
if (this.SGET_getDialogsState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.SSET_setDialogState(true)
|
||||||
|
this.dialogModel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current app version
|
||||||
|
* NOTE: Show Electon version in DEV mode instead of NPM package version
|
||||||
|
*/
|
||||||
|
appVersion = remote.app.getVersion()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open Discord invite link in thw default browser window
|
||||||
|
*/
|
||||||
|
openDiscordInviteLink () {
|
||||||
|
shell.openExternal("https://discord.gg/JQDBvsN9Te").catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open Patreon link in thw default browser window
|
||||||
|
*/
|
||||||
|
openPatreonLink () {
|
||||||
|
shell.openExternal("https://www.patreon.com/elvanos").catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open Reddit link in thw default browser window
|
||||||
|
*/
|
||||||
|
openRedditLink () {
|
||||||
|
shell.openExternal("https://www.reddit.com/r/FantasiaArchive/").catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open Website link in thw default browser window
|
||||||
|
*/
|
||||||
|
openWebsiteLink () {
|
||||||
|
shell.openExternal("http://fantasiaarchive.com/").catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open GitHub link in thw default browser window
|
||||||
|
*/
|
||||||
|
openGithubLink () {
|
||||||
|
shell.openExternal("https://github.com/Elvanos/fantasia-archive").catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.aboutDialog {
|
||||||
|
text-align: center;
|
||||||
|
width: 650px;
|
||||||
|
max-width: 650px !important;
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
68
src/components/dialogs/AdvancedSearchGuide.vue
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<q-dialog
|
||||||
|
v-model="dialogModel"
|
||||||
|
@hide="triggerDialogClose"
|
||||||
|
>
|
||||||
|
<q-card
|
||||||
|
class="advancedSearchDialog"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<q-scroll-area
|
||||||
|
class="q-mx-xl q-my-lg"
|
||||||
|
visible
|
||||||
|
dark
|
||||||
|
:thumb-style="thumbStyle"
|
||||||
|
style="max-height: calc(100vh - 235px); height: 800px; width: 100%;">
|
||||||
|
<q-markdown no-heading-anchor-links>
|
||||||
|
{{$t('documents.advancedSearchGuide')}}
|
||||||
|
</q-markdown>
|
||||||
|
</q-scroll-area>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="around" class="q-mb-lg">
|
||||||
|
<q-btn flat label="Close" color="accent" v-close-popup />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component, Watch } from "vue-property-decorator"
|
||||||
|
|
||||||
|
import DialogBase from "src/components/dialogs/_DialogBase"
|
||||||
|
@Component({
|
||||||
|
components: { }
|
||||||
|
})
|
||||||
|
export default class AdvancedSearchGuide extends DialogBase {
|
||||||
|
/**
|
||||||
|
* React to dialog opening request
|
||||||
|
*/
|
||||||
|
@Watch("dialogTrigger")
|
||||||
|
openDialog (val: string|false) {
|
||||||
|
if (val) {
|
||||||
|
if (this.SGET_getDialogsState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.SSET_setDialogState(true)
|
||||||
|
this.dialogModel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.advancedSearchDialog {
|
||||||
|
width: 1000px;
|
||||||
|
max-width: calc(100vw - 100px) !important;
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
68
src/components/dialogs/ChangeLog.vue
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<q-dialog
|
||||||
|
v-model="dialogModel"
|
||||||
|
@hide="triggerDialogClose"
|
||||||
|
>
|
||||||
|
<q-card
|
||||||
|
class="changeLogDialog"
|
||||||
|
dark
|
||||||
|
>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<q-scroll-area
|
||||||
|
class="q-mx-xl q-my-lg"
|
||||||
|
visible
|
||||||
|
dark
|
||||||
|
:thumb-style="thumbStyle"
|
||||||
|
style="max-height: calc(100vh - 235px); height: 800px; width: 100%;">
|
||||||
|
<q-markdown no-heading-anchor-links>
|
||||||
|
{{$t('documents.changeLog')}}
|
||||||
|
</q-markdown>
|
||||||
|
</q-scroll-area>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="around" class="q-mb-lg">
|
||||||
|
<q-btn flat label="Close" color="accent" v-close-popup />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component, Watch } from "vue-property-decorator"
|
||||||
|
|
||||||
|
import DialogBase from "src/components/dialogs/_DialogBase"
|
||||||
|
@Component({
|
||||||
|
components: { }
|
||||||
|
})
|
||||||
|
export default class ChangeLog extends DialogBase {
|
||||||
|
/**
|
||||||
|
* React to dialog opening request
|
||||||
|
*/
|
||||||
|
@Watch("dialogTrigger")
|
||||||
|
openDialog (val: string|false) {
|
||||||
|
if (val) {
|
||||||
|
if (this.SGET_getDialogsState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.SSET_setDialogState(true)
|
||||||
|
this.dialogModel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.changeLogDialog {
|
||||||
|
width: 1000px;
|
||||||
|
max-width: calc(100vw - 100px) !important;
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
88
src/components/dialogs/CloseDocumentCheck.vue
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<q-dialog
|
||||||
|
v-model="dialogModel"
|
||||||
|
@hide="triggerDialogClose"
|
||||||
|
>
|
||||||
|
<q-card dark class="documentCloseDialog">
|
||||||
|
<q-card-section class="row justify-center">
|
||||||
|
<h6 class="text-center q-my-sm">Discard changes to <span class="text-primary">{{retrieveFieldValue(dialogDocument,'name')}}</span>?</h6>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="around" class="q-mx-xl q-mt-lg q-mb-md">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
label="Cancel"
|
||||||
|
color="accent"
|
||||||
|
v-close-popup />
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
label="Discard changes"
|
||||||
|
color="secondary"
|
||||||
|
@click="closeDocument(dialogDocument)" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component, Watch, Prop } from "vue-property-decorator"
|
||||||
|
|
||||||
|
import DialogBase from "src/components/dialogs/_DialogBase"
|
||||||
|
import { I_OpenedDocument } from "src/interfaces/I_OpenedDocument"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: { }
|
||||||
|
})
|
||||||
|
export default class CloseDocumentCheckDialog extends DialogBase {
|
||||||
|
/**
|
||||||
|
* React to dialog opening request
|
||||||
|
*/
|
||||||
|
@Watch("dialogTrigger")
|
||||||
|
openDialog (val: string|false) {
|
||||||
|
if (val) {
|
||||||
|
this.checkForCloseOpenedDocument()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current document being processed by the dialog
|
||||||
|
*/
|
||||||
|
@Prop() readonly dialogDocument!: I_OpenedDocument
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the document has edits or not. Based on this either skip this dialog altogether or show it.
|
||||||
|
*/
|
||||||
|
checkForCloseOpenedDocument () {
|
||||||
|
const input = this.dialogDocument
|
||||||
|
if (input?.hasEdits) {
|
||||||
|
if (this.SGET_getDialogsState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.SSET_setDialogState(true)
|
||||||
|
this.dialogModel = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.closeDocument(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the document and removes it from the list
|
||||||
|
*/
|
||||||
|
closeDocument (input: I_OpenedDocument) {
|
||||||
|
const dataPass = { doc: input, treeAction: false }
|
||||||
|
this.SSET_removeOpenedDocument(dataPass)
|
||||||
|
|
||||||
|
this.dialogModel = false
|
||||||
|
this.SSET_setDialogState(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.documentCloseDialog {
|
||||||
|
min-width: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
121
src/components/dialogs/DeleteDocumentCheck.vue
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<template>
|
||||||
|
<q-dialog
|
||||||
|
v-model="dialogModel"
|
||||||
|
@hide="triggerDialogClose"
|
||||||
|
>
|
||||||
|
<q-card dark class="documentCloseDialog">
|
||||||
|
<q-card-section class="row justify-center">
|
||||||
|
<h6 class="text-center q-my-sm">Delete <span class="text-primary">{{retrieveFieldValue(currentDocument, 'name')}}</span>?</h6>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section class="row justify-center q-mx-xl">
|
||||||
|
<div>
|
||||||
|
The document will be deleted <span class="text-bold text-secondary">FOREVER</span> with no way to retrieve it.
|
||||||
|
<br>
|
||||||
|
<span class="text-caption">(unless a previous export of the project exists from earlier time that cointains it)</span>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Proceed?
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="around" class="q-mx-xl q-mt-lg q-mb-md">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
label="Cancel"
|
||||||
|
color="accent"
|
||||||
|
v-close-popup />
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
:disable="!retrieveFieldValue(currentDocument, 'name')"
|
||||||
|
label="Delete document"
|
||||||
|
color="secondary"
|
||||||
|
@click="deleteDocument()" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component, Watch, Prop } from "vue-property-decorator"
|
||||||
|
|
||||||
|
import DialogBase from "src/components/dialogs/_DialogBase"
|
||||||
|
import { I_ShortenedDocument } from "src/interfaces/I_OpenedDocument"
|
||||||
|
import PouchDB from "pouchdb"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: { }
|
||||||
|
})
|
||||||
|
export default class DeleteDocumentCheckDialog extends DialogBase {
|
||||||
|
/**
|
||||||
|
* React to dialog opening request
|
||||||
|
*/
|
||||||
|
@Watch("dialogTrigger")
|
||||||
|
openDialog (val: string|false) {
|
||||||
|
if (val && (this.SGET_allOpenedDocuments.docs.length > 0 || (this.documentType.length > 0 && this.documentId.length > 0))) {
|
||||||
|
if (this.SGET_getDialogsState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.SSET_setDialogState(true)
|
||||||
|
this.dialogModel = true
|
||||||
|
|
||||||
|
const documentID = (this.documentId.length > 0) ? this.documentId : this.$route.params.id
|
||||||
|
this.currentDocument = this.SGET_document(documentID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTIONAL
|
||||||
|
* Type of the document to delete
|
||||||
|
*/
|
||||||
|
@Prop({ default: "" }) readonly documentType!: ""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTIONAL
|
||||||
|
* ID of the document to delete
|
||||||
|
*/
|
||||||
|
@Prop({ default: "" }) readonly documentId!: ""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current document for deletion
|
||||||
|
*/
|
||||||
|
currentDocument = false as unknown as I_ShortenedDocument
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the document
|
||||||
|
*/
|
||||||
|
async deleteDocument () {
|
||||||
|
const documentID = (this.documentId.length > 0) ? this.documentId : this.$route.params.id
|
||||||
|
|
||||||
|
const documentType = (this.documentType.length > 0) ? this.documentType : this.$route.params.type
|
||||||
|
window.FA_dbs[documentType] = new PouchDB(documentType)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
this.currentDocument = await window.FA_dbs[documentType].get(documentID)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
await window.FA_dbs[documentType].remove(this.currentDocument)
|
||||||
|
|
||||||
|
const dataPass = { doc: this.currentDocument, treeAction: true }
|
||||||
|
|
||||||
|
this.dialogModel = false
|
||||||
|
this.SSET_setDialogState(false)
|
||||||
|
|
||||||
|
this.currentDocument = this.SGET_document(documentID)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_removeOpenedDocument(dataPass)
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_removeDocument({ doc: this.currentDocument })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.documentCloseDialog {
|
||||||
|
min-width: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
586
src/components/dialogs/ExistingDocument.vue
Normal file
|
@ -0,0 +1,586 @@
|
||||||
|
<template>
|
||||||
|
<q-dialog
|
||||||
|
no-route-dismiss
|
||||||
|
v-model="dialogModel"
|
||||||
|
@hide="triggerDialogClose"
|
||||||
|
>
|
||||||
|
<q-card
|
||||||
|
dark
|
||||||
|
class="existingDocumentPopup"
|
||||||
|
>
|
||||||
|
|
||||||
|
<q-card-section class="row items-center">
|
||||||
|
<h6 class="text-center q-my-sm">Open existing document</h6>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section class="column items-center">
|
||||||
|
<div class="q-mb-md">
|
||||||
|
<q-checkbox dark color="primary" v-model="includeCategories" label="Include categories in the list?" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-select
|
||||||
|
style="width: 400px;"
|
||||||
|
ref="ref_existingDocument"
|
||||||
|
dense
|
||||||
|
class="existingDocumentSelect"
|
||||||
|
dark
|
||||||
|
popup-content-class="menuResizer"
|
||||||
|
menu-anchor="bottom middle"
|
||||||
|
menu-self="top middle"
|
||||||
|
:options="filteredExistingInput"
|
||||||
|
use-input
|
||||||
|
multiple
|
||||||
|
filled
|
||||||
|
input-debounce="500"
|
||||||
|
v-model="existingDocumentModel"
|
||||||
|
@filter="filterExistingSelect"
|
||||||
|
@input="openExistingInput"
|
||||||
|
>
|
||||||
|
<template v-slot:append v-if="!hideAdvSearchCheatsheetButton">
|
||||||
|
<q-btn round dense flat icon="mdi-help-rhombus" @click.stop.prevent="SSET_setAdvSearchWindowVisible"
|
||||||
|
>
|
||||||
|
<q-tooltip :delay="500">
|
||||||
|
Open search cheatsheet
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
<template v-slot:option="{ itemProps, itemEvents, opt }">
|
||||||
|
<q-item
|
||||||
|
:class="{'hasTextShadow': textShadow, 'isMinor':opt.isMinor}"
|
||||||
|
v-bind="itemProps"
|
||||||
|
v-on="itemEvents"
|
||||||
|
:key="opt.id"
|
||||||
|
:style="`color: ${opt.color}; background-color: ${opt.bgColor}`"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon
|
||||||
|
:style="`color: ${retrieveIconColor(opt)}`"
|
||||||
|
:name="(opt.isCategory) ? 'fas fa-folder-open' : opt.icon"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>
|
||||||
|
<span class="isDeadIndicator" v-if="opt.isDead">
|
||||||
|
†
|
||||||
|
</span>
|
||||||
|
<span :class="{'isDead': (opt.isDead && !hideDeadCrossThrough)}" v-html="opt.label">
|
||||||
|
</span>
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label caption class="text-cultured" v-html="opt.hierarchicalPath"></q-item-label>
|
||||||
|
<q-item-label caption class="text-cultured" v-if="opt.tags">
|
||||||
|
<q-chip
|
||||||
|
v-for="(input,index) in opt.tags" :key="index"
|
||||||
|
outline
|
||||||
|
style="opacity: 0.8;"
|
||||||
|
size="12px"
|
||||||
|
class="text-cultured"
|
||||||
|
v-html="`${input}`"
|
||||||
|
>
|
||||||
|
</q-chip>
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-btn
|
||||||
|
tabindex="-1"
|
||||||
|
round
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
color="accent"
|
||||||
|
class="z-1 q-ml-sm self-center"
|
||||||
|
icon="mdi-pencil"
|
||||||
|
size="sm"
|
||||||
|
@click.stop.prevent="editExistingInput(opt)"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="300"
|
||||||
|
>
|
||||||
|
Edit {{ stripTags(opt.label) }}
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
tabindex="-1"
|
||||||
|
round
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
color="primary"
|
||||||
|
class="z-1 q-ml-sm self-center"
|
||||||
|
icon="mdi-content-copy"
|
||||||
|
size="sm"
|
||||||
|
@click.stop.prevent="copyTargetDocument(opt)"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="300"
|
||||||
|
>
|
||||||
|
Make a copy of {{ stripTags(opt.label) }}
|
||||||
|
<br>
|
||||||
|
This action will always close the popup.
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
tabindex="-1"
|
||||||
|
round
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
color="primary"
|
||||||
|
class="z-1 q-ml-sm self-center"
|
||||||
|
icon="mdi-file-tree"
|
||||||
|
size="sm"
|
||||||
|
@click.stop.prevent="addNewUnderParent(opt)"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
:delay="300"
|
||||||
|
>
|
||||||
|
Add a new document belonging under {{ stripTags(opt.label) }}
|
||||||
|
<br>
|
||||||
|
This action will always close the popup.
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-menu
|
||||||
|
touch-position
|
||||||
|
context-menu
|
||||||
|
auto-close
|
||||||
|
separate-close-popup
|
||||||
|
>
|
||||||
|
|
||||||
|
<q-list class="bg-gunmetal-light text-accent">
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-item clickable @click="copyName(opt)">
|
||||||
|
<q-item-section>Copy name</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-text-recognition" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable @click="copyTextColor(opt)">
|
||||||
|
<q-item-section>Copy text color</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-eyedropper" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable @click="copyBackgroundColor(opt)">
|
||||||
|
<q-item-section>Copy background color</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-format-color-fill" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator dark />
|
||||||
|
<q-item clickable @click="openExistingInput(opt)">
|
||||||
|
<q-item-section>Open document</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-book-open-page-variant-outline" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable @click="editExistingInput(opt)">
|
||||||
|
<q-item-section>Edit document</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-pencil" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable @click="addNewUnderParent(opt)">
|
||||||
|
<q-item-section>Create new document with this document as parent</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon color="primary" name="mdi-file-tree" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable @click="copyTargetDocument(opt)">
|
||||||
|
<q-item-section>Copy this document</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon color="primary" name="mdi-content-copy" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</q-list>
|
||||||
|
|
||||||
|
</q-menu>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section>
|
||||||
|
<q-card-actions align="around" class="q-mb-sm">
|
||||||
|
<q-btn flat label="Close" color="accent" v-close-popup />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Component, Watch } from "vue-property-decorator"
|
||||||
|
import { I_OpenedDocument, I_ShortenedDocument } from "src/interfaces/I_OpenedDocument"
|
||||||
|
import { advancedDocumentFilter } from "src/scripts/utilities/advancedDocumentFilter"
|
||||||
|
import { extend } from "quasar"
|
||||||
|
|
||||||
|
import { createNewWithParent } from "src/scripts/documentActions/createNewWithParent"
|
||||||
|
import { copyDocumentName, copyDocumentTextColor, copyDocumentBackgroundColor } from "src/scripts/documentActions/uniqueFieldCopy"
|
||||||
|
import { copyDocument } from "src/scripts/documentActions/copyDocument"
|
||||||
|
|
||||||
|
import DialogBase from "src/components/dialogs/_DialogBase"
|
||||||
|
import { I_Blueprint } from "src/interfaces/I_Blueprint"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: { }
|
||||||
|
})
|
||||||
|
export default class ExistingDocumentDialog extends DialogBase {
|
||||||
|
/****************************************************************/
|
||||||
|
// DIALOG CONTROL
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to dialog opening request
|
||||||
|
*/
|
||||||
|
@Watch("dialogTrigger")
|
||||||
|
openDialog (val: string|false) {
|
||||||
|
if (val) {
|
||||||
|
if (this.SGET_getDialogsState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.isCloseAbleViaKeybind = false
|
||||||
|
this.SSET_setDialogState(true)
|
||||||
|
this.dialogModel = true
|
||||||
|
|
||||||
|
this.reloadOptions()
|
||||||
|
this.populateExistingObjectDialog().catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// COMPONENT SETTINGS
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch options and react to changes
|
||||||
|
*/
|
||||||
|
@Watch("SGET_options", { immediate: true, deep: true })
|
||||||
|
onSettingsChange () {
|
||||||
|
this.reloadOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads local options
|
||||||
|
*/
|
||||||
|
reloadOptions () {
|
||||||
|
this.closeWithSameClick = this.SGET_options.allowQuickPopupSameKeyClose
|
||||||
|
this.disableCloseAftertSelectQuickSearch = this.SGET_options.disableCloseAftertSelectQuickSearch
|
||||||
|
this.includeCategories = !this.SGET_options.disableQuickSearchCategoryPrecheck
|
||||||
|
this.textShadow = this.SGET_options.textShadow
|
||||||
|
this.hideDeadCrossThrough = this.SGET_options.hideDeadCrossThrough
|
||||||
|
this.hideAdvSearchCheatsheetButton = this.SGET_options.hideAdvSearchCheatsheetButton
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the advanced search cheatsheet help button in relationship type fields.
|
||||||
|
*/
|
||||||
|
hideAdvSearchCheatsheetButton = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the "dead" document type should have a cross-text decoration or not
|
||||||
|
*/
|
||||||
|
hideDeadCrossThrough = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the popup shouldnt close after a document is selected from the dropdown list
|
||||||
|
*/
|
||||||
|
disableCloseAftertSelectQuickSearch = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the popup is closeable with the same keybind that summoned it
|
||||||
|
*/
|
||||||
|
closeWithSameClick = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if text shadow will be shows for accesiblity reasons or not
|
||||||
|
*/
|
||||||
|
textShadow = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A local lock that prevents double-triggering and instant re-closing of the dialog via keybinds
|
||||||
|
*/
|
||||||
|
isCloseAbleViaKeybind = false
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// LOCAL KEYBINDS
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local keybinds
|
||||||
|
*/
|
||||||
|
@Watch("SGET_getCurrentKeyBindData", { deep: true })
|
||||||
|
processKeyPush () {
|
||||||
|
// Keybind cheatsheet
|
||||||
|
if (this.determineKeyBind("quickExistingDocument") && this.dialogModel && this.closeWithSameClick && this.isCloseAbleViaKeybind && this.SGET_getDialogsState) {
|
||||||
|
this.dialogModel = false
|
||||||
|
this.SSET_setDialogState(false)
|
||||||
|
// @ts-ignore
|
||||||
|
this.existingDocumentModel = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// PRE-FILTERING
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for pre-filtering via categories
|
||||||
|
*/
|
||||||
|
includeCategories = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to the category checkbox changes
|
||||||
|
*/
|
||||||
|
@Watch("includeCategories")
|
||||||
|
reactToCategoryCheckboxChange () {
|
||||||
|
this.preFilterDocuments()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefilter documents based on what is in the checkbox
|
||||||
|
*/
|
||||||
|
preFilterDocuments () {
|
||||||
|
this.existingObjectPrefilteredList = this.existingObjectsFullList
|
||||||
|
.filter(e => !((!this.includeCategories && e.isCategory)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// SELECT LIST MANAGEMENT
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw list of objects retrieved from the database
|
||||||
|
*/
|
||||||
|
existingObjectPrefilteredList = [] as I_ShortenedDocument[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-filtered list based on the category inclussion or exlcussion
|
||||||
|
*/
|
||||||
|
existingObjectsFullList = [] as I_ShortenedDocument[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All currently loaded blueprints
|
||||||
|
*/
|
||||||
|
allDocumentBluePrints = [] as I_Blueprint[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up up all data in to the dialog on popup load
|
||||||
|
*/
|
||||||
|
async populateExistingObjectDialog () {
|
||||||
|
this.allDocumentBluePrints = this.SGET_allBlueprints
|
||||||
|
|
||||||
|
this.existingObjectsFullList = this.SGET_allDocuments.docs
|
||||||
|
this.preFilterDocuments()
|
||||||
|
|
||||||
|
await this.$nextTick()
|
||||||
|
|
||||||
|
if (this.$refs.ref_existingDocument) {
|
||||||
|
/*eslint-disable */
|
||||||
|
await this.sleep(100)
|
||||||
|
// @ts-ignore
|
||||||
|
this.$refs.ref_existingDocument.focus()
|
||||||
|
/* eslint-enable */
|
||||||
|
}
|
||||||
|
this.isCloseAbleViaKeybind = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently being opened document
|
||||||
|
*/
|
||||||
|
existingDocumentModel = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtered list of items
|
||||||
|
*/
|
||||||
|
filteredExistingInput = null as unknown as I_ShortenedDocument[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refocuses the first value in the selct upon filtering for intuitive keyboard control
|
||||||
|
*/
|
||||||
|
async refocusSelect () {
|
||||||
|
await this.$nextTick()
|
||||||
|
/*eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
this.$refs.ref_existingDocument.setOptionIndex(-1)
|
||||||
|
// @ts-ignore
|
||||||
|
this.$refs.ref_existingDocument.moveOptionSelection(1, true)
|
||||||
|
/* eslint-enable */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local list copty for filtering in order to not mess up the original list
|
||||||
|
*/
|
||||||
|
listCopy: I_ShortenedDocument[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the pre-filtered list
|
||||||
|
*/
|
||||||
|
filterExistingSelect (val: string, update: (e: () => void) => void) {
|
||||||
|
if (val === "") {
|
||||||
|
update(() => {
|
||||||
|
this.filteredExistingInput = this.existingObjectPrefilteredList.filter((obj) => !obj.isMinor)
|
||||||
|
if (this.$refs.ref_existingDocument && this.filteredExistingInput.length > 0) {
|
||||||
|
this.refocusSelect().catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
update(() => {
|
||||||
|
const needle = val.toLowerCase()
|
||||||
|
this.listCopy = extend(true, [], this.existingObjectPrefilteredList)
|
||||||
|
this.filteredExistingInput = advancedDocumentFilter(needle, this.listCopy, this.allDocumentBluePrints, this.existingObjectsFullList)
|
||||||
|
|
||||||
|
if (this.$refs.ref_existingDocument && this.filteredExistingInput.length > 0) {
|
||||||
|
this.refocusSelect().catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// TRIGGER ACTIONS
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opened the existing input in two modes
|
||||||
|
* Either as a focus with closure of the dialog.
|
||||||
|
* Or as a background tab without closing of the dialog.
|
||||||
|
*/
|
||||||
|
openExistingInput (e: I_ShortenedDocument) {
|
||||||
|
// @ts-ignore
|
||||||
|
e = (Array.isArray(e)) ? e[0] : e
|
||||||
|
// Open document and close dialog
|
||||||
|
if (!this.disableCloseAftertSelectQuickSearch) {
|
||||||
|
this.dialogModel = false
|
||||||
|
// @ts-ignore
|
||||||
|
this.openExistingDocumentRoute(e)
|
||||||
|
this.existingDocumentModel = []
|
||||||
|
}
|
||||||
|
// Open document and DO NOT close the dialog
|
||||||
|
else {
|
||||||
|
this.existingDocumentModel = []
|
||||||
|
|
||||||
|
const retrievedObject = (this.SGET_openedDocument(e._id)) || this.SGET_document(e._id)
|
||||||
|
|
||||||
|
const dataPass = {
|
||||||
|
doc: retrievedObject,
|
||||||
|
treeAction: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_addOpenedDocument(dataPass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opened the existing input in two modes
|
||||||
|
* Either as a focus with closure of the dialog.
|
||||||
|
* Or as a background tab without closing of the dialog.
|
||||||
|
*/
|
||||||
|
editExistingInput (e: I_ShortenedDocument) {
|
||||||
|
// @ts-ignore
|
||||||
|
e = (Array.isArray(e)) ? e[0] : e
|
||||||
|
// Open document and close dialog
|
||||||
|
if (!this.disableCloseAftertSelectQuickSearch) {
|
||||||
|
this.dialogModel = false
|
||||||
|
// @ts-ignore
|
||||||
|
this.openExistingDocumentRouteWithEdit(e)
|
||||||
|
this.existingDocumentModel = []
|
||||||
|
}
|
||||||
|
// Open document and DO NOT close the dialog
|
||||||
|
else {
|
||||||
|
// @ts-ignore
|
||||||
|
this.existingDocumentModel = []
|
||||||
|
|
||||||
|
const retrievedObject = (this.SGET_openedDocument(e._id)) || this.SGET_document(e._id)
|
||||||
|
|
||||||
|
retrievedObject.hasEdits = true
|
||||||
|
|
||||||
|
const dataPass = {
|
||||||
|
doc: retrievedObject,
|
||||||
|
treeAction: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_addOpenedDocument(dataPass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
documentPass = null as unknown as I_OpenedDocument
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Add new document under parent
|
||||||
|
/****************************************************************/
|
||||||
|
addNewUnderParent (currentDoc: I_OpenedDocument) {
|
||||||
|
createNewWithParent(currentDoc, this)
|
||||||
|
this.dialogModel = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
// Document field copying
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
copyName (currentDoc: I_OpenedDocument) {
|
||||||
|
copyDocumentName(currentDoc)
|
||||||
|
|
||||||
|
this.dialogModel = false
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTextColor (currentDoc: I_OpenedDocument) {
|
||||||
|
copyDocumentTextColor(currentDoc)
|
||||||
|
|
||||||
|
this.dialogModel = false
|
||||||
|
}
|
||||||
|
|
||||||
|
copyBackgroundColor (currentDoc: I_OpenedDocument) {
|
||||||
|
copyDocumentBackgroundColor(currentDoc)
|
||||||
|
|
||||||
|
this.dialogModel = false
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTargetDocument (currentDoc: I_OpenedDocument) {
|
||||||
|
this.documentPass = extend(true, {}, currentDoc)
|
||||||
|
|
||||||
|
const blueprint = this.SGET_blueprint(this.documentPass.type)
|
||||||
|
const newDocument = copyDocument(this.documentPass, this.generateUID(), blueprint)
|
||||||
|
|
||||||
|
const dataPass = {
|
||||||
|
doc: newDocument,
|
||||||
|
treeAction: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.SSET_addOpenedDocument(dataPass)
|
||||||
|
this.$router.push({
|
||||||
|
path: newDocument.url
|
||||||
|
}).catch((e: {name: string}) => {
|
||||||
|
const errorName : string = e.name
|
||||||
|
if (errorName === "NavigationDuplicated") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.dialogModel = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.existingDocumentPopup {
|
||||||
|
min-width: 600px;
|
||||||
|
margin-top: 100px;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|