Compare commits
No commits in common. "master" and "v0.1.6a-DEV-1" have entirely different histories.
master
...
v0.1.6a-DE
|
@ -1,8 +1,8 @@
|
|||
/dist
|
||||
/src-bex/www
|
||||
/src-capacitor
|
||||
/src-cordova
|
||||
/src-electron
|
||||
/.quasar
|
||||
/node_modules
|
||||
.eslintrc.js
|
||||
/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'
|
||||
|
||||
}
|
||||
}
|
22
.github/workflows/build.yml
vendored
|
@ -1,6 +1,6 @@
|
|||
# This is a basic workflow that is manually triggered
|
||||
|
||||
name: Build App - FA Rewrite
|
||||
name: Build App
|
||||
|
||||
# Manually triggered
|
||||
on:
|
||||
|
@ -15,25 +15,22 @@ jobs:
|
|||
# Builds all major OS versions of FA
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: production
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
node: [16]
|
||||
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v2.1.5
|
||||
|
||||
- name: quasar install and build
|
||||
- name: npm install and build
|
||||
run: |
|
||||
npm install --global yarn@1.22.19
|
||||
yarn global add @quasar/cli
|
||||
yarn install
|
||||
yarn build
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Upload Linux Artifact
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
|
@ -41,9 +38,7 @@ jobs:
|
|||
with:
|
||||
# Artifact name
|
||||
name: Linux_FA ${{ github.event.inputs.version }}
|
||||
path: |
|
||||
dist/electron/Packaged/*.snap
|
||||
dist/electron/Packaged/*.AppImage
|
||||
path: dist/electron/Packaged/
|
||||
|
||||
- name: Upload MacOS Artifact
|
||||
if: ${{ matrix.os == 'macOS-latest' }}
|
||||
|
@ -51,7 +46,7 @@ jobs:
|
|||
with:
|
||||
# Artifact name
|
||||
name: Mac_FA ${{ github.event.inputs.version }}
|
||||
path: dist/electron/Packaged/*.dmg
|
||||
path: dist/electron/Packaged/
|
||||
|
||||
- name: Upload Windows Artifact
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
|
@ -59,4 +54,5 @@ jobs:
|
|||
with:
|
||||
# Artifact name
|
||||
name: Windows_FA ${{ github.event.inputs.version }}
|
||||
path: dist/electron/Packaged/*.exe
|
||||
path: dist/electron/Packaged/
|
||||
|
||||
|
|
12
.gitignore
vendored
|
@ -1,11 +1,11 @@
|
|||
.DS_Store
|
||||
.thumbs.db
|
||||
.history
|
||||
node_modules
|
||||
|
||||
# Quasar core related directories
|
||||
.quasar
|
||||
/dist
|
||||
/quasar.config.*.temporary.compiled*
|
||||
|
||||
# Cordova related directories and files
|
||||
/src-cordova/node_modules
|
||||
|
@ -28,16 +28,8 @@ yarn-error.log*
|
|||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.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
|
||||
##### Details found here: https://quasar.dev/start/quasar-cli
|
||||
## Download the program
|
||||
|
||||
##### 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.
|
||||
|
||||
```
|
||||
yarn global add @quasar/cli
|
||||
## Install the dependencies
|
||||
|
||||
In the terminal, do the following:
|
||||
|
||||
```bash
|
||||
cd fantasia-archive-master
|
||||
npm install
|
||||
```
|
||||
|
||||
## Install the dependencies and set up the project
|
||||
```
|
||||
yarn
|
||||
## Optional: Start the app in development mode (hot-code reloading, error reporting, etc.)
|
||||
|
||||
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.)
|
||||
```
|
||||
quasar dev -m electron
|
||||
## Build the app for production
|
||||
|
||||
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
|
||||
```
|
||||
#### E2E test - via Playwright
|
||||
```
|
||||
test:e2e
|
||||
I_Blueprint.ts
|
||||
```
|
||||
|
||||
### 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
116
package.json
|
@ -1,63 +1,83 @@
|
|||
{
|
||||
"name": "fantasia-archive",
|
||||
"version": "0.0.1",
|
||||
"description": "A worldbuilding database manager",
|
||||
"name": "fantasiaarchive",
|
||||
"version": "0.1.6",
|
||||
"description": "A database manager for world building",
|
||||
"productName": "Fantasia Archive",
|
||||
"author": "Elvanos <elvanos66@gmail.com>",
|
||||
"license": "GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.ts,.vue ./",
|
||||
"dev:electron": "quasar dev -m electron",
|
||||
"build": "quasar build -m electron --publish never",
|
||||
"test:component": "node \"node_modules/@playwright/test/cli.js\" test src/components",
|
||||
"test:e2e": "node \"node_modules/@playwright/test/cli.js\" test test/playwright-e2e/"
|
||||
"test": "echo \"No test specified\" && exit 0",
|
||||
"dev": "quasar dev -m electron",
|
||||
"build": "quasar build -m electron"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "^2.0.10",
|
||||
"@quasar/cli": "^2.3.0",
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"app-root-path": "^3.1.0",
|
||||
"axios": "^1.2.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^2.0.11",
|
||||
"quasar": "^2.6.0",
|
||||
"sqlite3": "^5.1.6",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.0.0",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.0.0"
|
||||
"@quasar/extras": "^1.0.0",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"apexcharts": "^3.26.0",
|
||||
"axios": "^0.18.1",
|
||||
"core-js": "^3.6.5",
|
||||
"katex": "^0.12.0",
|
||||
"lodash": "^4.17.20",
|
||||
"mermaid": "^8.8.4",
|
||||
"pouchdb": "^7.2.2",
|
||||
"pouchdb-adapter-idb": "^7.2.2",
|
||||
"pouchdb-find": "^7.2.2",
|
||||
"pouchdb-load": "^1.4.6",
|
||||
"pouchdb-purge-on-delete": "^7.0.1",
|
||||
"pouchdb-replication-stream": "^1.2.9",
|
||||
"quasar": "^1.15.0",
|
||||
"quasar-tiptap": "^1.9.1",
|
||||
"tiptap": "^1.31.0",
|
||||
"tiptap-extensions": "^1.34.0",
|
||||
"vue-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": {
|
||||
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@quasar/app-vite": "^1.3.0",
|
||||
"@quasar/quasar-app-extension-qmarkdown": "^2.0.0-beta.10",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/node": "^16.18.0",
|
||||
"@types/uuid": "^9.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"electron": "^25.5.0",
|
||||
"electron-builder": "^24.3.0",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.19.1",
|
||||
"eslint-plugin-n": "^15.0.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"playwright": "^1.37.1",
|
||||
"postcss-html": "^1.5.0",
|
||||
"sass": "1.29.0",
|
||||
"stylelint": "^15.10.3",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-config-standard-scss": "^11.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
"typescript": "^4.5.4"
|
||||
"@quasar/app": "^2.0.0",
|
||||
"@quasar/quasar-app-extension-qmarkdown": "^1.4.1",
|
||||
"@quasar/quasar-app-extension-qwindow": "^1.0.0-beta.7",
|
||||
"@types/lodash": "^4.14.166",
|
||||
"@types/node": "^10.17.15",
|
||||
"@types/pouchdb": "^6.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^3.3.0",
|
||||
"@typescript-eslint/parser": "^3.3.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^9.4.0",
|
||||
"electron-builder": "^22.9.1",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-packager": "^14.2.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-standard": "^14.1.0",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^6.1.2",
|
||||
"quasar-app-extension-qdraggabletree": "0.0.5",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 10 Chrome versions",
|
||||
"last 10 Firefox versions",
|
||||
"last 4 Edge versions",
|
||||
"last 7 Safari versions",
|
||||
"last 8 Android versions",
|
||||
"last 8 ChromeAndroid versions",
|
||||
"last 8 FirefoxAndroid versions",
|
||||
"last 10 iOS versions",
|
||||
"last 5 Opera versions"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18 || ^16 || ^14.19",
|
||||
"node": ">= 10.18.1",
|
||||
"npm": ">= 6.13.4",
|
||||
"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": {
|
||||
"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,
|
||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||
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 (_) {}
|
||||
}
|
323
src/App.vue
|
@ -1,43 +1,304 @@
|
|||
<template>
|
||||
<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="['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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { tipsTricksTriviaNotification } from 'app/src/scripts/appInfo/tipsTricksTriviaNotification'
|
||||
<script lang="ts">
|
||||
import BaseClass from "src/BaseClass"
|
||||
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"
|
||||
|
||||
/**
|
||||
* Local router variable
|
||||
*/
|
||||
const router = useRouter()
|
||||
@Component({
|
||||
components: {
|
||||
appWindowButtons: appWindowButtons
|
||||
}
|
||||
})
|
||||
export default class App extends BaseClass {
|
||||
/****************************************************************/
|
||||
// APP START & END SETUP
|
||||
/****************************************************************/
|
||||
|
||||
/**
|
||||
* Testing type currently possibly happening
|
||||
*/
|
||||
const testingType = window.extraEnvVariables.TEST_ENV
|
||||
created () {
|
||||
// Catch middle clicks
|
||||
window.addEventListener("auxclick", this.reactToMiddleClick)
|
||||
|
||||
/**
|
||||
* Name of the component being possibly tested via component testing
|
||||
*/
|
||||
const testingComponentName = window.extraEnvVariables.COMPONENT_NAME
|
||||
// Add a secondary blocker to prevent the middle-mouse button scrolling
|
||||
document.body.onmousedown = function (e) {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if some testing is happening
|
||||
*/
|
||||
const isComponentTesting = (testingType && testingType === 'components' && testingComponentName)
|
||||
// Catch normal clicks inside wysiwyg
|
||||
window.addEventListener("click", this.openWysiwygLink)
|
||||
|
||||
/**
|
||||
* In case of some testing happening:
|
||||
* - Reroute to the proper component path route assuming all is properly set.
|
||||
* - Otherwise, make sure we are on homepage on load.
|
||||
// Load settings
|
||||
this.loadSettings().catch(e => console.log(e))
|
||||
|
||||
// React to keybind presses
|
||||
window.addEventListener("keydown", this.triggerKeyPush)
|
||||
|
||||
// Load the popup hint on start
|
||||
this.loadHintPopup()
|
||||
}
|
||||
|
||||
destroyed () {
|
||||
window.removeEventListener("auxclick", this.reactToMiddleClick)
|
||||
|
||||
this.deregisterCustomKeybinds()
|
||||
this.deregisterDefaultKeybinds()
|
||||
window.removeEventListener("keydown", this.triggerKeyPush)
|
||||
}
|
||||
|
||||
/****************************************************************/
|
||||
// START NOTIFICATION
|
||||
/****************************************************************/
|
||||
|
||||
/**
|
||||
* Model for the startup notification
|
||||
*/
|
||||
if (isComponentTesting) {
|
||||
router.push({ path: `/componentTesting/${testingComponentName}` })
|
||||
} else {
|
||||
router.push({ path: '/' })
|
||||
// TODO add checking if "Did you know" popup should show
|
||||
// TODO add checking if "Did you know" popup should be showing a mascot or an icon
|
||||
tipsTricksTriviaNotification(false)
|
||||
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
|
||||
|
||||
/****************************************************************/
|
||||
// Local keybinds
|
||||
/****************************************************************/
|
||||
|
||||
@Watch("SGET_getCurrentKeyBindData", { deep: true })
|
||||
processKeyPush () {
|
||||
// Toggle the Advanced search cheatsheet
|
||||
if (this.determineKeyBind("toggleAdvSearchCheatsheet")) {
|
||||
this.advSearchWindowVisible = !this.advSearchWindowVisible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
584
src/BaseClass.ts
Normal file
|
@ -0,0 +1,584 @@
|
|||
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
|
||||
|
||||
/****************************************************************/
|
||||
// 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' {
|
||||
interface ComponentCustomProperties {
|
||||
declare module "vue/types/vue" {
|
||||
interface Vue {
|
||||
$axios: AxiosInstance;
|
||||
$api: AxiosInstance;
|
||||
}
|
||||
}
|
||||
|
||||
// Be careful when using SSR for cross-request state pollution
|
||||
// due to creating a Singleton instance here;
|
||||
// If any client changes this (global) instance, it might be a
|
||||
// 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 default boot(({ Vue }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
Vue.prototype.$axios = axios
|
||||
})
|
||||
|
||||
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 { createI18n } from 'vue-i18n'
|
||||
import { boot } from "quasar/wrappers"
|
||||
import messages from "src/i18n"
|
||||
import Vue from "vue"
|
||||
import VueI18n from "vue-i18n"
|
||||
|
||||
import messages from 'src/i18n'
|
||||
|
||||
export type MessageLanguages = keyof typeof messages;
|
||||
// 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 {}
|
||||
declare module "vue/types/vue" {
|
||||
interface Vue {
|
||||
i18n: VueI18n;
|
||||
}
|
||||
}
|
||||
/* 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 }) => {
|
||||
const i18n = createI18n({
|
||||
locale: 'en-US',
|
||||
fallbackLocale: 'en-US',
|
||||
legacy: false,
|
||||
warnHtmlMessage: false,
|
||||
messages
|
||||
})
|
||||
|
||||
// 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>
|
731
src/components/DocumentControl.vue
Normal file
|
@ -0,0 +1,731 @@
|
|||
<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-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 newDocument = copyDocument(this.documentPass, this.generateUID())
|
||||
|
||||
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>
|
1275
src/components/ObjectTree.vue
Normal file
791
src/components/appHeader/AppControl.vue
Normal file
|
@ -0,0 +1,791 @@
|
|||
<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-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-folder-open-outline" />
|
||||
</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 || SGET_allOpenedDocuments.docs.length > 0"
|
||||
>
|
||||
<q-item-section>Resume project</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="mdi-folder-open-outline" />
|
||||
</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()
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************/
|
||||
// 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>
|
571
src/components/appHeader/TopTabs.vue
Normal file
|
@ -0,0 +1,571 @@
|
|||
<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 newDocument = copyDocument(this.documentPass, this.generateUID())
|
||||
|
||||
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>
|
585
src/components/dialogs/ExistingDocument.vue
Normal file
|
@ -0,0 +1,585 @@
|
|||
<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 newDocument = copyDocument(this.documentPass, this.generateUID())
|
||||
|
||||
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>
|