Compare commits

...

17 Commits

Author SHA1 Message Date
Tim Gröger b7735e2924 update to version 2.1.0 2024-10-08 13:23:43 +00:00
Tim Gröger d35cc8e8d1 [feat] sort names by display name mode 2024-10-08 13:23:43 +00:00
Tim Gröger d34898e1e9 [feat] save display name mode 2024-10-08 13:23:43 +00:00
Tim Gröger 93669d66dc [feat] add settings 2024-10-08 13:23:43 +00:00
Tim Gröger ec5458bf7e sort users (by lastname) 2024-10-08 13:23:43 +00:00
Tim Gröger efc7c49a0b update mdi version 2024-10-08 13:23:43 +00:00
Tim Gröger b1e4879881 Merge pull request 'release v2.0.0' (!4) from develop into master
Reviewed-on: #4
2024-01-18 15:15:06 +00:00
Tim Gröger ee7e03ce28 update to version to 2.0.0 2024-01-16 19:52:45 +01:00
Tim Gröger 2928c241ad fix prevention that click card when click on notification buttons 2024-01-16 19:36:49 +01:00
Tim Gröger fe9ec96ce1 update behavior of left drawer 2024-01-16 14:37:38 +01:00
Tim Gröger 417689b725 update dependencies
for modify-source-webpack-plugin new operation function because of api-change
2023-06-14 12:10:33 +02:00
Tim Gröger 847e923265 update to v1.0.0-alpha.2 2023-05-15 23:58:54 +02:00
Tim Gröger 4cb0362bb7 fix capacitor 2023-05-15 23:57:56 +02:00
Tim Gröger a46c41cb5b better widget dashboard 2023-05-14 00:02:08 +02:00
Tim Gröger 3d55f2d2ae fix hyphanation 2023-05-13 09:44:52 +02:00
Tim Gröger 3689da810c add all plugins 2023-05-06 12:35:27 +02:00
Tim Gröger 48972f84e1 Merge branch 'hotfix/bug395' 2020-08-21 15:08:14 +02:00
12 changed files with 202 additions and 62 deletions

View File

@ -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%" />

View File

@ -1,6 +1,6 @@
{ {
"license": "MIT", "license": "MIT",
"version": "1.0.0-alpha.8", "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",
@ -11,11 +11,11 @@
"main": "./src/index.ts", "main": "./src/index.ts",
"peerDependencies": { "peerDependencies": {
"@quasar/app-webpack": "^3.7.2", "@quasar/app-webpack": "^3.7.2",
"flaschengeist": "^2.0.0-alpha.1", "flaschengeist": "^2.0.0",
"pinia": "^2.0.8" "pinia": "^2.0.8"
}, },
"devDependencies": { "devDependencies": {
"@flaschengeist/types": "^1.0.0-alpha.10", "@flaschengeist/types": "^1.0.0",
"@types/node": "^14.18.0", "@types/node": "^14.18.0",
"typescript": "^4.5.4" "typescript": "^4.5.4"
}, },

View File

@ -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;
},
}, },
}); });

View File

@ -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,10 +15,12 @@
"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/schedule": "^1.0.0-alpha.6", "@flaschengeist/balance": "^1.0.0",
"@flaschengeist/users": "^1.0.0-alpha.3", "@flaschengeist/pricelist-old": "^1.0.0",
"axios": "^0.24.0", "@flaschengeist/schedule": "^1.0.0",
"@flaschengeist/users": "^1.0.0",
"axios": "^1.4.0",
"pinia": "^2.0.8", "pinia": "^2.0.8",
"quasar": "^2.11.10", "quasar": "^2.11.10",
"vue": "^3.0.0", "vue": "^3.0.0",
@ -27,7 +29,7 @@
"devDependencies": { "devDependencies": {
"@capacitor/core": "^5.0.0", "@capacitor/core": "^5.0.0",
"@capacitor/preferences": "^5.0.0", "@capacitor/preferences": "^5.0.0",
"@flaschengeist/types": "^1.0.0-alpha.10", "@flaschengeist/types": "^1.0.0",
"@quasar/app-webpack": "^3.7.2", "@quasar/app-webpack": "^3.7.2",
"@quasar/extras": "^1.16.3", "@quasar/extras": "^1.16.3",
"@types/node": "^14.18.0", "@types/node": "^14.18.0",
@ -39,9 +41,9 @@
"eslint": "^8.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.2.0", "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.4", "typescript": "^4.5.4",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"

View File

@ -3,4 +3,4 @@ module.exports = [
// '@flaschengeist/balance', // '@flaschengeist/balance',
// '@flaschengeist/schedule', // '@flaschengeist/schedule',
// '@flaschengeist/pricelist', // '@flaschengeist/pricelist',
] '@flaschengeist/schedule',

View File

@ -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',
},
}; };
}); });

35
src-capacitor/index.html Normal file
View File

@ -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>

View File

@ -1,6 +1,6 @@
{ {
"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,
@ -13,4 +13,4 @@
"@capacitor/preferences": "^5.0.0", "@capacitor/preferences": "^5.0.0",
"@capacitor/splash-screen": "^5.0.0" "@capacitor/splash-screen": "^5.0.0"
} }
} }

View File

@ -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

View File

@ -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>

View File

@ -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,

View File

@ -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>