Compare commits
24 Commits
Author | SHA1 | Date |
---|---|---|
Tim Gröger | b7735e2924 | |
Tim Gröger | d35cc8e8d1 | |
Tim Gröger | d34898e1e9 | |
Tim Gröger | 93669d66dc | |
Tim Gröger | ec5458bf7e | |
Tim Gröger | efc7c49a0b | |
Tim Gröger | b1e4879881 | |
Tim Gröger | ee7e03ce28 | |
Tim Gröger | 2928c241ad | |
Tim Gröger | fe9ec96ce1 | |
Tim Gröger | 417689b725 | |
Tim Gröger | 847e923265 | |
Tim Gröger | 4cb0362bb7 | |
Tim Gröger | a46c41cb5b | |
Tim Gröger | 3d55f2d2ae | |
Tim Gröger | 3689da810c | |
Tim Gröger | e6d9054256 | |
Tim Gröger | ab45bf3667 | |
Tim Gröger | 857d07040b | |
groegert | e07df08822 | |
Ferdinand Thiessen | ec28857af5 | |
Ferdinand Thiessen | 1c452e23fe | |
Ferdinand Thiessen | 195593ddc5 | |
Tim Gröger | 48972f84e1 |
|
@ -0,0 +1,3 @@
|
||||||
|
yarn-error.log
|
||||||
|
.woodpecker/
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
pipeline:
|
||||||
|
deploy:
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
tag: "@flaschengeist/api-v*"
|
||||||
|
image: node:lts-alpine
|
||||||
|
commands:
|
||||||
|
- cd api
|
||||||
|
- echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" > .npmrc
|
||||||
|
- yarn publish --non-interactive
|
||||||
|
secrets: [ node_auth_token ]
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
pipeline:
|
||||||
|
lint:
|
||||||
|
when:
|
||||||
|
branch: [main, develop]
|
||||||
|
image: node:lts-alpine
|
||||||
|
commands:
|
||||||
|
- yarn install
|
||||||
|
- yarn lint
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# Flaschengeist (frontend)
|
# Flaschengeist (frontend)
|
||||||
|
![status-badge](https://ci.os-sc.org/api/badges/Flaschengeist/flaschengeist-frontend/status.svg)
|
||||||
|
|
||||||
|
|
||||||
Modular student club administration system, licensed under the MIT license.
|
Modular student club administration system, licensed under the MIT license.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<q-avatar>
|
<q-avatar>
|
||||||
<slot :avatarURL="avatarURL(modelValue)">
|
<slot :avatar-u-r-l="avatarURL(modelValue)">
|
||||||
<q-img :src="avatarURL(modelValue)" style="min-width: 100%; min-height: 100%">
|
<q-img :src="avatarURL(modelValue)" style="min-width: 100%; min-height: 100%">
|
||||||
<template #error>
|
<template #error>
|
||||||
<img :src="fallback" style="height: 100%" />
|
<img :src="fallback" style="height: 100%" />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "1.0.0-alpha.7",
|
"version": "1.0.0",
|
||||||
"name": "@flaschengeist/api",
|
"name": "@flaschengeist/api",
|
||||||
"author": "Tim Gröger <flaschengeist@wu5.de>",
|
"author": "Tim Gröger <flaschengeist@wu5.de>",
|
||||||
"homepage": "https://flaschengeist.dev/Flaschengeist",
|
"homepage": "https://flaschengeist.dev/Flaschengeist",
|
||||||
|
@ -10,14 +10,14 @@
|
||||||
},
|
},
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@quasar/app": "^3.2.4",
|
"@quasar/app-webpack": "^3.7.2",
|
||||||
"flaschengeist": "^2.0.0-alpha.1",
|
"flaschengeist": "^2.0.0",
|
||||||
"pinia": "^2.0.6"
|
"pinia": "^2.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@flaschengeist/types": "^1.0.0-alpha.10",
|
"@flaschengeist/types": "^1.0.0",
|
||||||
"@types/node": "^14.18.00",
|
"@types/node": "^14.18.0",
|
||||||
"typescript": "^4.5.2"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { AxiosResponse } from 'axios';
|
||||||
import { api } from '../internal';
|
import { api } from '../internal';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { PersistentStorage } from '../utils/persistent';
|
import { PersistentStorage } from '../utils/persistent';
|
||||||
|
import { LocalStorage, SessionStorage } from 'quasar';
|
||||||
function reviveSession() {
|
function reviveSession() {
|
||||||
return PersistentStorage.get<FG.Session>('fg_session').then((s) => fixSession(s || undefined));
|
return PersistentStorage.get<FG.Session>('fg_session').then((s) => fixSession(s || undefined));
|
||||||
}
|
}
|
||||||
|
@ -154,6 +154,8 @@ export const useMainStore = defineStore({
|
||||||
handleLoggedOut() {
|
handleLoggedOut() {
|
||||||
this.$reset();
|
this.$reset();
|
||||||
void clearPersistant();
|
void clearPersistant();
|
||||||
|
LocalStorage.clear();
|
||||||
|
SessionStorage.clear();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { api } from '../internal';
|
import { api } from '../internal';
|
||||||
import { isAxiosError, useMainStore } from '.';
|
import { isAxiosError, useMainStore } from '.';
|
||||||
|
import { DisplayNameMode } from '@flaschengeist/users';
|
||||||
|
|
||||||
export function fixUser(u?: FG.User) {
|
export function fixUser(u?: FG.User) {
|
||||||
return !u ? u : Object.assign(u, { birthday: u.birthday ? new Date(u.birthday) : undefined });
|
return !u ? u : Object.assign(u, { birthday: u.birthday ? new Date(u.birthday) : undefined });
|
||||||
|
@ -22,6 +23,7 @@ export const useUserStore = defineStore({
|
||||||
state: () => ({
|
state: () => ({
|
||||||
roles: [] as FG.Role[],
|
roles: [] as FG.Role[],
|
||||||
permissions: [] as FG.Permission[],
|
permissions: [] as FG.Permission[],
|
||||||
|
userSettings: {} as FG.UserSettings,
|
||||||
// list of all users, include deleted ones, use `users` getter for list of active ones
|
// list of all users, include deleted ones, use `users` getter for list of active ones
|
||||||
_users: [] as FG.User[],
|
_users: [] as FG.User[],
|
||||||
// Internal flags for deciding if lists need to force-loaded
|
// Internal flags for deciding if lists need to force-loaded
|
||||||
|
@ -31,7 +33,42 @@ export const useUserStore = defineStore({
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
users(state) {
|
users(state) {
|
||||||
return state._users.filter((u) => !u.deleted);
|
const u = state._users.filter((u) => !u.deleted);
|
||||||
|
|
||||||
|
switch (this.userSettings['display_name']) {
|
||||||
|
case DisplayNameMode.FIRSTNAME_LASTNAME || DisplayNameMode.FIRSTNAME:
|
||||||
|
u.sort((a, b) => {
|
||||||
|
const a_lastname = a.lastname.toLowerCase();
|
||||||
|
const b_lastname = b.lastname.toLowerCase();
|
||||||
|
const a_firstname = a.firstname.toLowerCase();
|
||||||
|
const b_firstname = b.firstname.toLowerCase();
|
||||||
|
if (a_firstname === b_firstname) {
|
||||||
|
return a_lastname < b_lastname ? -1 : 1;
|
||||||
|
}
|
||||||
|
return a_firstname < b_firstname ? -1 : 1;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case <string>DisplayNameMode.DISPLAYNAME:
|
||||||
|
u.sort((a, b) => {
|
||||||
|
const a_displayname = a.display_name.toLowerCase();
|
||||||
|
const b_displayname = b.display_name.toLowerCase();
|
||||||
|
return a_displayname < b_displayname ? -1 : 1;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
u.sort((a, b) => {
|
||||||
|
const a_lastname = a.lastname.toLowerCase();
|
||||||
|
const b_lastname = b.lastname.toLowerCase();
|
||||||
|
const a_firstname = a.firstname.toLowerCase();
|
||||||
|
const b_firstname = b.firstname.toLowerCase();
|
||||||
|
if (a_lastname === b_lastname) {
|
||||||
|
return a_firstname < b_firstname ? -1 : 1;
|
||||||
|
}
|
||||||
|
return a_lastname < b_lastname ? -1 : 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return u;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -207,5 +244,35 @@ export const useUserStore = defineStore({
|
||||||
await api.delete(`/roles/${role}`);
|
await api.delete(`/roles/${role}`);
|
||||||
this.roles = this.roles.filter((r) => r.id !== role);
|
this.roles = this.roles.filter((r) => r.id !== role);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Get Settings for display name mode
|
||||||
|
* @param force If set to true a fresh list is loaded from backend even when a local copy is available
|
||||||
|
* @throws Probably an AxiosError if request failed
|
||||||
|
* @returns Settings for display name mode
|
||||||
|
*/
|
||||||
|
async getDisplayNameModeSetting(force = false): Promise<string> {
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
if (force) {
|
||||||
|
const { data } = await api.get<{ data: string }>(
|
||||||
|
`users/${mainStore.currentUser.userid}/setting/display_name_mode`
|
||||||
|
);
|
||||||
|
this.userSettings['display_name'] = data.data;
|
||||||
|
}
|
||||||
|
return this.userSettings['display_name'];
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Set Settings for display name mode
|
||||||
|
* @param mode New display name mode
|
||||||
|
* @throws Probably an AxiosError if request failed
|
||||||
|
* @returns Settings for display name mode
|
||||||
|
*/
|
||||||
|
async setDisplayNameModeSetting(mode: string): Promise<string> {
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
await api.put(`users/${mainStore.currentUser.userid}/setting/display_name_mode`, {
|
||||||
|
data: mode,
|
||||||
|
});
|
||||||
|
this.userSettings['display_name'] = mode;
|
||||||
|
return mode;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
import { LocalStorage, Platform } from 'quasar';
|
import { LocalStorage, Platform } from 'quasar';
|
||||||
import { Storage } from '@capacitor/storage';
|
import { Preferences } from '@capacitor/preferences';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type PersitentTypes = Date | RegExp | number | boolean | string | object;
|
type PersitentTypes = Date | RegExp | number | boolean | string | object;
|
||||||
|
|
||||||
export class PersistentStorage {
|
export class PersistentStorage {
|
||||||
static clear() {
|
static clear() {
|
||||||
if (Platform.is.capacitor) return Storage.clear();
|
if (Platform.is.capacitor) return Preferences.clear();
|
||||||
else return Promise.resolve(LocalStorage.clear());
|
else return Promise.resolve(LocalStorage.clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
static remove(key: string) {
|
static remove(key: string) {
|
||||||
if (Platform.is.capacitor) return Storage.remove({ key: key });
|
if (Platform.is.capacitor) return Preferences.remove({ key: key });
|
||||||
else return Promise.resolve(LocalStorage.remove(key));
|
else return Promise.resolve(LocalStorage.remove(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
static set(key: string, value: PersitentTypes) {
|
static set(key: string, value: PersitentTypes) {
|
||||||
if (Platform.is.capacitor) return Storage.set({ key, value: JSON.stringify(value) });
|
if (Platform.is.capacitor) return Preferences.set({ key, value: JSON.stringify(value) });
|
||||||
else return Promise.resolve(LocalStorage.set(key, value));
|
else return Promise.resolve(LocalStorage.set(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
static get<T extends PersitentTypes>(key: string) {
|
static get<T extends PersitentTypes>(key: string) {
|
||||||
if (Platform.is.capacitor)
|
if (Platform.is.capacitor)
|
||||||
return Storage.get({ key }).then((v) =>
|
return Preferences.get({ key }).then((v) =>
|
||||||
v.value === null ? null : (JSON.parse(v.value) as T)
|
v.value === null ? null : (JSON.parse(v.value) as T)
|
||||||
);
|
);
|
||||||
else return Promise.resolve(LocalStorage.getItem<T>(key));
|
else return Promise.resolve(LocalStorage.getItem<T>(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
static keys() {
|
static keys() {
|
||||||
if (Platform.is.capacitor) return Storage.keys().then((v) => v.keys);
|
if (Platform.is.capacitor) return Preferences.keys().then((v) => v.keys);
|
||||||
else return Promise.resolve(LocalStorage.getAllKeys());
|
else return Promise.resolve(LocalStorage.getAllKeys());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,7 @@
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"lib": [
|
"lib": ["es2020", "dom"],
|
||||||
"es2020",
|
"types": ["@flaschengeist/types", "@quasar/app", "node"]
|
||||||
"dom"
|
}
|
||||||
],
|
|
||||||
"types": [
|
|
||||||
"@flaschengeist/types",
|
|
||||||
"@quasar/app",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
42
package.json
42
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "2.0.0-alpha.1",
|
"version": "2.1.0",
|
||||||
"productName": "flaschengeist-frontend",
|
"productName": "flaschengeist-frontend",
|
||||||
"name": "flaschengeist",
|
"name": "flaschengeist",
|
||||||
"author": "Tim Gröger <flaschengeist@wu5.de>",
|
"author": "Tim Gröger <flaschengeist@wu5.de>",
|
||||||
|
@ -15,31 +15,37 @@
|
||||||
"lint": "eslint --ext .js,.ts,.vue ./src ./api"
|
"lint": "eslint --ext .js,.ts,.vue ./src ./api"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@flaschengeist/api": "file:./api",
|
"@flaschengeist/api": "^1.0.0",
|
||||||
"@flaschengeist/users": "^1.0.0-alpha.3",
|
"@flaschengeist/balance": "^1.0.0",
|
||||||
"axios": "^0.24.0",
|
"@flaschengeist/pricelist-old": "^1.0.0",
|
||||||
"pinia": "^2.0.6",
|
"@flaschengeist/schedule": "^1.0.0",
|
||||||
"quasar": "^2.3.3"
|
"@flaschengeist/users": "^1.0.0",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"pinia": "^2.0.8",
|
||||||
|
"quasar": "^2.11.10",
|
||||||
|
"vue": "^3.0.0",
|
||||||
|
"vue-router": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/core": "^3.3.2",
|
"@capacitor/core": "^5.0.0",
|
||||||
"@capacitor/storage": "^1.2.3",
|
"@capacitor/preferences": "^5.0.0",
|
||||||
"@flaschengeist/types": "^1.0.0-alpha.10",
|
"@flaschengeist/types": "^1.0.0",
|
||||||
"@quasar/app": "^3.2.4",
|
"@quasar/app-webpack": "^3.7.2",
|
||||||
"@quasar/extras": "^1.12.2",
|
"@quasar/extras": "^1.16.3",
|
||||||
"@types/node": "^14.18.0",
|
"@types/node": "^14.18.0",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
"@types/webpack-env": "^1.16.3",
|
"@types/webpack-env": "^1.16.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.5.0",
|
"@typescript-eslint/eslint-plugin": "^5.8.0",
|
||||||
"@typescript-eslint/parser": "^5.5.0",
|
"@typescript-eslint/parser": "^5.8.0",
|
||||||
"eslint": "^8.4.0",
|
"@vue/devtools": "^6.5.0",
|
||||||
|
"eslint": "^8.5.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-vue": "^8.1.1",
|
"eslint-plugin-vue": "^9.14.1",
|
||||||
"eslint-webpack-plugin": "^3.1.1",
|
"eslint-webpack-plugin": "^4.0.1",
|
||||||
"modify-source-webpack-plugin": "^3.0.0",
|
"modify-source-webpack-plugin": "^4.1.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"typescript": "^4.5.2",
|
"typescript": "^4.5.4",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
|
|
|
@ -3,4 +3,4 @@ module.exports = [
|
||||||
// '@flaschengeist/balance',
|
// '@flaschengeist/balance',
|
||||||
// '@flaschengeist/schedule',
|
// '@flaschengeist/schedule',
|
||||||
// '@flaschengeist/pricelist',
|
// '@flaschengeist/pricelist',
|
||||||
]
|
'@flaschengeist/schedule',
|
||||||
|
|
|
@ -9,9 +9,23 @@
|
||||||
/* eslint-env node */
|
/* eslint-env node */
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||||
const { ModifySourcePlugin } = require('modify-source-webpack-plugin');
|
const { ModifySourcePlugin, ReplaceOperation } = require('modify-source-webpack-plugin');
|
||||||
const { configure } = require('quasar/wrappers');
|
const { configure } = require('quasar/wrappers');
|
||||||
|
|
||||||
|
const operation = () => {
|
||||||
|
const custom_plgns = require('./plugin.config.js');
|
||||||
|
const required_plgns = require('./src/vendor-plugin.config.js');
|
||||||
|
const plugins = [...custom_plgns, ...required_plgns].map(
|
||||||
|
(v) => `import("${v}").catch(() => "${v}")`
|
||||||
|
);
|
||||||
|
const replace = new ReplaceOperation(
|
||||||
|
'all',
|
||||||
|
`\\/\\* *INSERT_PLUGIN_LIST *\\*\\/`,
|
||||||
|
`${plugins.join(', ')}`
|
||||||
|
);
|
||||||
|
return replace;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = configure(function (/* ctx */) {
|
module.exports = configure(function (/* ctx */) {
|
||||||
return {
|
return {
|
||||||
// https://quasar.dev/quasar-cli/supporting-ts
|
// https://quasar.dev/quasar-cli/supporting-ts
|
||||||
|
@ -42,7 +56,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
// 'ionicons-v5',
|
// 'ionicons-v5',
|
||||||
// 'line-awesome',
|
// 'line-awesome',
|
||||||
// 'material-icons',
|
// 'material-icons',
|
||||||
'mdi-v6',
|
'mdi-v7',
|
||||||
// 'themify',
|
// 'themify',
|
||||||
|
|
||||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
@ -52,7 +66,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
|
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
|
||||||
build: {
|
build: {
|
||||||
vueRouterMode: 'history', // available values: 'hash', 'history'
|
vueRouterMode: 'history', // available values: 'hash', 'history'
|
||||||
|
//publicPath: 'flaschengeist2',
|
||||||
// transpile: false,
|
// transpile: false,
|
||||||
|
|
||||||
// Add dependencies for transpiling with Babel (Array of string/regex)
|
// Add dependencies for transpiling with Babel (Array of string/regex)
|
||||||
|
@ -81,16 +95,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /plugins\.ts$/,
|
test: /plugins\.ts$/,
|
||||||
modify: (src, filename) => {
|
operations: [operation()],
|
||||||
const custom_plgns = require('./plugin.config.js');
|
|
||||||
const required_plgns = require('./src/vendor-plugin.config.js');
|
|
||||||
return src.replace(
|
|
||||||
/\/\* *INSERT_PLUGIN_LIST *\*\//,
|
|
||||||
[...custom_plgns, ...required_plgns]
|
|
||||||
.map((v) => `import("${v}").catch(() => "${v}")`)
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -215,5 +220,8 @@ module.exports = configure(function (/* ctx */) {
|
||||||
// chainWebpack also available besides this extendWebpack
|
// chainWebpack also available besides this extendWebpack
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
bin: {
|
||||||
|
linuxAndroidStudio: '/home/crimsen/.local/share/JetBrains/Toolbox/scripts/studio',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
"bundledWebRuntime": false,
|
"bundledWebRuntime": false,
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"webDir": "www",
|
"webDir": "www",
|
||||||
|
"android": {
|
||||||
|
"minWebViewVersion": 71
|
||||||
|
},
|
||||||
"ios": {
|
"ios": {
|
||||||
"allowsLinkPreview": false
|
"allowsLinkPreview": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Quasar</title>
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="Quasar Capacitor App">
|
||||||
|
<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, viewport-fit=cover">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
<div>
|
||||||
|
This file will be auto-generated. Do not edit.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Run "quasar dev" or "quasar build" with Capacitor mode.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,16 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "flaschengeist",
|
"name": "flaschengeist",
|
||||||
"version": "2.0.0-alpha.1",
|
"version": "2.0.0",
|
||||||
"description": "Modular student club administration system",
|
"description": "Modular student club administration system",
|
||||||
"author": "Tim Gröger <flaschengeist@wu5.de>",
|
"author": "Tim Gröger <flaschengeist@wu5.de>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^3.3.2",
|
"@capacitor/android": "^5.0.0-beta.0",
|
||||||
"@capacitor/app": "^1.0.0",
|
"@capacitor/app": "^5.0.0",
|
||||||
"@capacitor/cli": "^3.0.0",
|
"@capacitor/cli": "^5.0.0",
|
||||||
"@capacitor/core": "^3.0.0",
|
"@capacitor/core": "^5.0.0",
|
||||||
"@capacitor/ios": "^3.0.0-beta.0",
|
"@capacitor/ios": "^5.0.0",
|
||||||
"@capacitor/splash-screen": "^1.0.0",
|
"@capacitor/preferences": "^5.0.0",
|
||||||
"@capacitor/storage": "^1.2.3"
|
"@capacitor/splash-screen": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ async function loadBaseUrl() {
|
||||||
console.warn('Could not load BaseURL', e);
|
console.warn('Could not load BaseURL', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
class BackendError extends Error {}
|
class BackendError extends Error { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loading backend information
|
* Loading backend information
|
||||||
|
@ -68,8 +68,18 @@ export default boot(async ({ app, router }) => {
|
||||||
// Handle errors from loading the backend information
|
// Handle errors from loading the backend information
|
||||||
if (error instanceof BackendError || isAxiosError(error)) {
|
if (error instanceof BackendError || isAxiosError(error)) {
|
||||||
router.isReady().finally(() => {
|
router.isReady().finally(() => {
|
||||||
if (Platform.is.capacitor) void router.push({ name: 'setup_backend' });
|
// if (Platform.is.capacitor) void router.push({ name: 'setup_backend' });
|
||||||
else void router.push({ name: 'offline', params: { refresh: 1 } });
|
if (Platform.is.capacitor) {
|
||||||
|
//void router.push({ name: 'setup_backend' })
|
||||||
|
Notify.create({
|
||||||
|
type: 'negative',
|
||||||
|
message:
|
||||||
|
'Backend nicht erreichbar! Prüfe deine Internetverbindung oder probiere es später nochmal.',
|
||||||
|
timeout: 0,
|
||||||
|
icon: 'mdi-alert-circle-outline',
|
||||||
|
closeBtn: true,
|
||||||
|
});
|
||||||
|
} else void router.push({ name: 'offline', params: { refresh: 1 } });
|
||||||
});
|
});
|
||||||
} else if (typeof error === 'string') {
|
} else if (typeof error === 'string') {
|
||||||
// Handle plugin not found errors
|
// Handle plugin not found errors
|
||||||
|
|
|
@ -234,6 +234,12 @@ function loadPlugin(
|
||||||
Array.prototype.push.apply(loadedPlugins.widgets, plugin.widgets);
|
Array.prototype.push.apply(loadedPlugins.widgets, plugin.widgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!plugin.settingWidgets) plugin.settingWidgets = [];
|
||||||
|
if (plugin.settingWidgets.length > 0) {
|
||||||
|
plugin.settingWidgets.forEach((widget) => (widget.name = plugin.id + '.' + widget.name));
|
||||||
|
Array.prototype.push.apply(loadedPlugins.settingWidgets, plugin.settingWidgets);
|
||||||
|
}
|
||||||
|
|
||||||
loadedPlugins.plugins.push({
|
loadedPlugins.plugins.push({
|
||||||
id: plugin.id,
|
id: plugin.id,
|
||||||
name: plugin.name,
|
name: plugin.name,
|
||||||
|
@ -252,6 +258,7 @@ export async function loadPlugins(backend: FG.Backend, baseRoutes: RouteRecordRa
|
||||||
shortcuts: [],
|
shortcuts: [],
|
||||||
outerShortcuts: [],
|
outerShortcuts: [],
|
||||||
widgets: [],
|
widgets: [],
|
||||||
|
settingWidgets: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wait for all plugins to be loaded
|
// Wait for all plugins to be loaded
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
class="q-ma-xs"
|
class="q-ma-xs"
|
||||||
title="Löschen"
|
title="Löschen"
|
||||||
style="position: absolute; top: 0; right: 0; z-index: 999"
|
style="position: absolute; top: 0; right: 0; z-index: 999"
|
||||||
@click="dismiss"
|
@click.stop.prevent="dismiss"
|
||||||
/>
|
/>
|
||||||
<q-card-section class="q-pa-xs">
|
<q-card-section class="q-pa-xs">
|
||||||
<div class="text-overline">{{ dateString }}</div>
|
<div class="text-overline">{{ dateString }}</div>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
size="sm"
|
size="sm"
|
||||||
@click="accept"
|
@click.stop.prevent="accept"
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="modelValue.reject"
|
v-if="modelValue.reject"
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
size="sm"
|
size="sm"
|
||||||
@click="reject"
|
@click.stop.prevent="reject"
|
||||||
/>
|
/>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<q-layout view="hHh Lpr lff">
|
<q-layout view="hHh Lpr lff">
|
||||||
<q-header elevated class="bg-primary text-white">
|
<q-header elevated class="bg-primary text-white">
|
||||||
<q-toolbar>
|
<q-toolbar>
|
||||||
<q-btn dense flat round icon="mdi-menu" @click="openMenu" />
|
<q-btn dense flat round icon="mdi-menu" @click="openMenu(true)" />
|
||||||
|
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
<router-link :to="{ name: 'dashboard' }" style="text-decoration: none; color: inherit">
|
<router-link :to="{ name: 'dashboard' }" style="text-decoration: none; color: inherit">
|
||||||
|
@ -56,32 +56,44 @@
|
||||||
side="left"
|
side="left"
|
||||||
bordered
|
bordered
|
||||||
:mini="leftDrawerMini"
|
:mini="leftDrawerMini"
|
||||||
@click.capture="openMenu"
|
@click.capture="openMenuMini"
|
||||||
>
|
>
|
||||||
<!-- Plugins -->
|
<!-- Plugins -->
|
||||||
<essential-expansion-link
|
<q-scroll-area class="fit">
|
||||||
v-for="(entry, index) in mainLinks"
|
<essential-expansion-link
|
||||||
:key="'plugin' + index"
|
v-for="(entry, index) in mainLinks"
|
||||||
:entry="entry"
|
:key="'plugin' + index"
|
||||||
@add-short-cut="addShortcut"
|
:entry="entry"
|
||||||
/>
|
@add-short-cut="addShortcut"
|
||||||
<q-separator />
|
/>
|
||||||
<essential-link
|
|
||||||
v-for="(entry, index) in essentials"
|
|
||||||
:key="'essential' + index"
|
|
||||||
:entry="entry"
|
|
||||||
/>
|
|
||||||
<div v-if="platform.is.capacitor">
|
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-item clickable tag="a" target="self" @click="logout">
|
<essential-link
|
||||||
<q-item-section avatar>
|
v-for="(entry, index) in essentials"
|
||||||
<q-icon name="mdi-exit-to-app" />
|
:key="'essential' + index"
|
||||||
</q-item-section>
|
:entry="entry"
|
||||||
|
/>
|
||||||
|
<div v-if="platform.is.capacitor">
|
||||||
|
<q-separator />
|
||||||
|
<q-item clickable tag="a" target="self" @click="logout">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="mdi-exit-to-app" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>Logout</q-item-label>
|
<q-item-label>Logout</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
</div>
|
||||||
|
</q-scroll-area>
|
||||||
|
<div class="q-mini-drawer-hide absolute" style="top: 15px; right: -17px">
|
||||||
|
<q-btn
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
unelevated
|
||||||
|
color="accent"
|
||||||
|
icon="mdi-chevron-left"
|
||||||
|
@click="openMenuMini(true)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
|
@ -125,7 +137,7 @@ export default defineComponent({
|
||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist');
|
const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist');
|
||||||
const leftDrawer = ref(!Platform.is.mobile);
|
const leftDrawer = ref(!Platform.is.mobile);
|
||||||
const leftDrawerMini = ref(false);
|
const leftDrawerMini = ref(true);
|
||||||
const mainLinks = flaschengeist?.menuLinks || [];
|
const mainLinks = flaschengeist?.menuLinks || [];
|
||||||
const notifications = computed(() => mainStore.notifications.slice().reverse());
|
const notifications = computed(() => mainStore.notifications.slice().reverse());
|
||||||
const polling = ref(NaN);
|
const polling = ref(NaN);
|
||||||
|
@ -138,9 +150,11 @@ export default defineComponent({
|
||||||
void mainStore.getShortcuts();
|
void mainStore.getShortcuts();
|
||||||
});
|
});
|
||||||
onBeforeUnmount(() => window.clearInterval(polling.value));
|
onBeforeUnmount(() => window.clearInterval(polling.value));
|
||||||
|
/*
|
||||||
function openMenu(event: { target: HTMLInputElement }) {
|
function openMenu(event: { target: HTMLInputElement }) {
|
||||||
if (event.target.nodeName === 'DIV') leftDrawerMini.value = false;
|
console.log(event.target.nodeName);
|
||||||
|
if (event.target.nodeName === 'DIV' || event.target.nodeName === 'I')
|
||||||
|
leftDrawerMini.value = false;
|
||||||
else {
|
else {
|
||||||
if (!leftDrawer.value || leftDrawerMini.value) {
|
if (!leftDrawer.value || leftDrawerMini.value) {
|
||||||
leftDrawer.value = true;
|
leftDrawer.value = true;
|
||||||
|
@ -151,7 +165,13 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
function openMenu(value = !leftDrawer.value) {
|
||||||
|
leftDrawer.value = value;
|
||||||
|
}
|
||||||
|
function openMenuMini(value = !leftDrawerMini.value) {
|
||||||
|
leftDrawerMini.value = value;
|
||||||
|
}
|
||||||
function logout() {
|
function logout() {
|
||||||
void router.push({ name: 'login', params: { logout: 'logout' } });
|
void router.push({ name: 'login', params: { logout: 'logout' } });
|
||||||
void mainStore.logout();
|
void mainStore.logout();
|
||||||
|
@ -214,6 +234,7 @@ export default defineComponent({
|
||||||
notifications,
|
notifications,
|
||||||
noPermission,
|
noPermission,
|
||||||
openMenu,
|
openMenu,
|
||||||
|
openMenuMini,
|
||||||
remove,
|
remove,
|
||||||
requestPermission,
|
requestPermission,
|
||||||
useNative,
|
useNative,
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<q-page
|
<q-page
|
||||||
padding
|
padding
|
||||||
style="grid-auto-rows: 1fr"
|
style="grid-auto-rows: 1fr"
|
||||||
class="fit row justify-around items-start q-col-gutter-sm"
|
class="row justify-center content-center items-center q-col-gutter-lg"
|
||||||
>
|
>
|
||||||
<div v-for="(item, index) in widgets" :key="index" class="col-4 full-height col-sm-6 col-xs-12">
|
<div v-for="(item, index) in widgets" :key="index" class="full-height col-sm-6 col-xs-12">
|
||||||
<component :is="item.widget" />
|
<component :is="item.widget" />
|
||||||
</div>
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
|
|
|
@ -121,11 +121,24 @@ export default defineComponent({
|
||||||
if (quasar.platform.is.capacitor)
|
if (quasar.platform.is.capacitor)
|
||||||
await sessionStore.updateSession(14 * 24 * 60 * 60, mainStore.currentSession.token);
|
await sessionStore.updateSession(14 * 24 * 60 * 60, mainStore.currentSession.token);
|
||||||
// Redirect user to previous page, if any.
|
// Redirect user to previous page, if any.
|
||||||
const redirect =
|
// there are different redirects possible:
|
||||||
router.currentRoute.value.redirectedFrom || 'redirect' in router.currentRoute.value.query
|
// 1. when explicitely entered
|
||||||
? { path: router.currentRoute.value.query.redirect as string }
|
// a) http://localhost:8080/ -> should be redirected to mainRoute
|
||||||
: mainRoute;
|
// b) http://localhost:8080/in/user/settings -> should be redirected to in/user/settings
|
||||||
|
// 2. when automatically logged out:
|
||||||
|
// http://localhost:8080/login?redirect=/in/user/settings
|
||||||
|
// -> should be redirected to in/user/settings
|
||||||
|
var redirect;
|
||||||
|
if (router.currentRoute.value.redirectedFrom) {
|
||||||
|
redirect = router.currentRoute.value.redirectedFrom.path;
|
||||||
|
if (redirect === '/') {
|
||||||
|
redirect = mainRoute;
|
||||||
|
}
|
||||||
|
} else if ('redirect' in router.currentRoute.value.query) {
|
||||||
|
redirect = { path: router.currentRoute.value.query.redirect as string };
|
||||||
|
} else {
|
||||||
|
redirect = mainRoute;
|
||||||
|
}
|
||||||
void router.push(redirect);
|
void router.push(redirect);
|
||||||
} else {
|
} else {
|
||||||
// Login failed, notify and reset form
|
// Login failed, notify and reset form
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "@quasar/app/tsconfig-preset",
|
"extends": "@quasar/app-webpack/tsconfig-preset",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
|
Loading…
Reference in New Issue