Compare commits

..

3 Commits

Author SHA1 Message Date
Ferdinand Thiessen e8c0001d17 fix(ci): Fix build step
continuous-integration/woodpecker the build was successful Details
2021-12-08 15:32:31 +01:00
Ferdinand Thiessen 5c8637965a feat(ci): More testing
continuous-integration/woodpecker the build failed Details
2021-12-08 15:29:41 +01:00
Ferdinand Thiessen 308d348755 feat(ci): Added woodpecker CI
continuous-integration/woodpecker the build was successful Details
2021-12-08 15:17:25 +01:00
20 changed files with 117 additions and 210 deletions

View File

@ -1,3 +0,0 @@
yarn-error.log
.woodpecker/

View File

@ -1,15 +0,0 @@
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

View File

@ -1,9 +1,16 @@
pipeline: pipeline:
lint: install:
when:
branch: [main, develop]
image: node:lts-alpine image: node:lts-alpine
commands: commands:
- yarn install - yarn install
lint:
image: node:lts-alpine
commands:
- yarn lint - yarn lint
build:
image: node:lts-alpine
commands:
- yarn quasar build
branches: [main, develop]

View File

@ -1,6 +1,6 @@
# Flaschengeist (frontend) # Flaschengeist (frontend)
![status-badge](https://ci.os-sc.org/api/badges/Flaschengeist/flaschengeist-frontend/status.svg)
![status-badge](http://os-sc.org:8000/api/badges/ferfissimo/flaschengeist-frontend/status.svg)
Modular student club administration system, licensed under the MIT license. Modular student club administration system, licensed under the MIT license.

View File

@ -1,6 +1,6 @@
<template> <template>
<q-avatar> <q-avatar>
<slot :avatar-u-r-l="avatarURL(modelValue)"> <slot :avatarURL="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", "version": "1.0.0-alpha.7",
"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-webpack": "^3.7.2", "@quasar/app": "^3.2.4",
"flaschengeist": "^2.0.0", "flaschengeist": "^2.0.0-alpha.1",
"pinia": "^2.0.8" "pinia": "^2.0.6"
}, },
"devDependencies": { "devDependencies": {
"@flaschengeist/types": "^1.0.0", "@flaschengeist/types": "^1.0.0-alpha.10",
"@types/node": "^14.18.0", "@types/node": "^14.18.00",
"typescript": "^4.5.4" "typescript": "^4.5.2"
}, },
"prettier": { "prettier": {
"singleQuote": true, "singleQuote": true,

View File

@ -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,8 +154,6 @@ export const useMainStore = defineStore({
handleLoggedOut() { handleLoggedOut() {
this.$reset(); this.$reset();
void clearPersistant(); void clearPersistant();
LocalStorage.clear();
SessionStorage.clear();
}, },
}, },
}); });

View File

@ -1,35 +1,35 @@
import { LocalStorage, Platform } from 'quasar'; import { LocalStorage, Platform } from 'quasar';
import { Preferences } from '@capacitor/preferences'; import { Storage } from '@capacitor/storage';
// 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 Preferences.clear(); if (Platform.is.capacitor) return Storage.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 Preferences.remove({ key: key }); if (Platform.is.capacitor) return Storage.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 Preferences.set({ key, value: JSON.stringify(value) }); if (Platform.is.capacitor) return Storage.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 Preferences.get({ key }).then((v) => return Storage.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 Preferences.keys().then((v) => v.keys); if (Platform.is.capacitor) return Storage.keys().then((v) => v.keys);
else return Promise.resolve(LocalStorage.getAllKeys()); else return Promise.resolve(LocalStorage.getAllKeys());
} }
} }

View File

@ -3,7 +3,14 @@
"target": "esnext", "target": "esnext",
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "./",
"lib": ["es2020", "dom"], "lib": [
"types": ["@flaschengeist/types", "@quasar/app", "node"] "es2020",
"dom"
],
"types": [
"@flaschengeist/types",
"@quasar/app",
"node"
]
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"version": "2.0.0", "version": "2.0.0-alpha.1",
"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,37 +15,31 @@
"lint": "eslint --ext .js,.ts,.vue ./src ./api" "lint": "eslint --ext .js,.ts,.vue ./src ./api"
}, },
"dependencies": { "dependencies": {
"@flaschengeist/api": "^1.0.0", "@flaschengeist/api": "file:./api",
"@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", "pinia": "^2.0.6",
"@flaschengeist/users": "^1.0.0", "quasar": "^2.3.3"
"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": "^5.0.0", "@capacitor/core": "^3.3.2",
"@capacitor/preferences": "^5.0.0", "@capacitor/storage": "^1.2.3",
"@flaschengeist/types": "^1.0.0", "@flaschengeist/types": "^1.0.0-alpha.10",
"@quasar/app-webpack": "^3.7.2", "@quasar/app": "^3.2.4",
"@quasar/extras": "^1.16.3", "@quasar/extras": "^1.12.2",
"@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.8.0", "@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.8.0", "@typescript-eslint/parser": "^5.5.0",
"@vue/devtools": "^6.5.0", "eslint": "^8.4.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": "^9.14.1", "eslint-plugin-vue": "^8.1.1",
"eslint-webpack-plugin": "^4.0.1", "eslint-webpack-plugin": "^3.1.1",
"modify-source-webpack-plugin": "^4.1.0", "modify-source-webpack-plugin": "^3.0.0",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"typescript": "^4.5.4", "typescript": "^4.5.2",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
"prettier": { "prettier": {

View File

@ -9,17 +9,9 @@
/* 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, ReplaceOperation } = require('modify-source-webpack-plugin'); const { ModifySourcePlugin } = 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
@ -89,7 +81,16 @@ module.exports = configure(function(/* ctx */) {
rules: [ rules: [
{ {
test: /plugins\.ts$/, test: /plugins\.ts$/,
operations: [operation()], modify: (src, filename) => {
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(',')
);
},
}, },
], ],
}, },

View File

@ -4,9 +4,6 @@
"bundledWebRuntime": false, "bundledWebRuntime": false,
"npmClient": "yarn", "npmClient": "yarn",
"webDir": "www", "webDir": "www",
"android": {
"minWebViewVersion": 71
},
"ios": { "ios": {
"allowsLinkPreview": false "allowsLinkPreview": false
} }

View File

@ -1,35 +0,0 @@
<!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,16 +1,16 @@
{ {
"name": "flaschengeist", "name": "flaschengeist",
"version": "2.0.0", "version": "2.0.0-alpha.1",
"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": "^5.0.0-beta.0", "@capacitor/android": "^3.3.2",
"@capacitor/app": "^5.0.0", "@capacitor/app": "^1.0.0",
"@capacitor/cli": "^5.0.0", "@capacitor/cli": "^3.0.0",
"@capacitor/core": "^5.0.0", "@capacitor/core": "^3.0.0",
"@capacitor/ios": "^5.0.0", "@capacitor/ios": "^3.0.0-beta.0",
"@capacitor/preferences": "^5.0.0", "@capacitor/splash-screen": "^1.0.0",
"@capacitor/splash-screen": "^5.0.0" "@capacitor/storage": "^1.2.3"
} }
} }

View File

@ -21,7 +21,7 @@ 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 {}
/** /**
@ -68,18 +68,8 @@ 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' });
if (Platform.is.capacitor) { else void router.push({ name: 'offline', params: { refresh: 1 } });
//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

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.stop.prevent="dismiss" @click="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.stop.prevent="accept" @click="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.stop.prevent="reject" @click="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(true)" /> <q-btn dense flat round icon="mdi-menu" @click="openMenu" />
<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,10 +56,9 @@
side="left" side="left"
bordered bordered
:mini="leftDrawerMini" :mini="leftDrawerMini"
@click.capture="openMenuMini" @click.capture="openMenu"
> >
<!-- Plugins --> <!-- Plugins -->
<q-scroll-area class="fit">
<essential-expansion-link <essential-expansion-link
v-for="(entry, index) in mainLinks" v-for="(entry, index) in mainLinks"
:key="'plugin' + index" :key="'plugin' + index"
@ -84,17 +83,6 @@
</q-item-section> </q-item-section>
</q-item> </q-item>
</div> </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>
</q-drawer> </q-drawer>
<q-page-container> <q-page-container>
<router-view /> <router-view />
@ -137,7 +125,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(true); const leftDrawerMini = ref(false);
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);
@ -150,11 +138,9 @@ 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 }) {
console.log(event.target.nodeName); if (event.target.nodeName === 'DIV') leftDrawerMini.value = false;
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;
@ -165,13 +151,7 @@ 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();
@ -234,7 +214,6 @@ 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="row justify-center content-center items-center q-col-gutter-lg" class="fit row justify-around items-start q-col-gutter-sm"
> >
<div v-for="(item, index) in widgets" :key="index" class="full-height col-sm-6 col-xs-12"> <div v-for="(item, index) in widgets" :key="index" class="col-4 full-height col-sm-6 col-xs-12">
<component :is="item.widget" /> <component :is="item.widget" />
</div> </div>
</q-page> </q-page>

View File

@ -121,24 +121,11 @@ 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.
// there are different redirects possible: const redirect =
// 1. when explicitely entered router.currentRoute.value.redirectedFrom || 'redirect' in router.currentRoute.value.query
// a) http://localhost:8080/ -> should be redirected to mainRoute ? { path: router.currentRoute.value.query.redirect as string }
// b) http://localhost:8080/in/user/settings -> should be redirected to in/user/settings : mainRoute;
// 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

View File

@ -1,5 +1,5 @@
{ {
"extends": "@quasar/app-webpack/tsconfig-preset", "extends": "@quasar/app/tsconfig-preset",
"target": "esnext", "target": "esnext",
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",