mirror of
https://github.com/janishutz/MusicPlayer.git
synced 2026-09-10 14:25:24 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
494eef6cd2 | ||
|
|
cb0db033ea |
@@ -21,6 +21,12 @@
|
||||
<img alt="App Version" src="https://img.shields.io/github/package-json/v/simplePCBuilding/MusicPlayerV2.svg?label=Development Version">
|
||||
</div>
|
||||
|
||||
# Development Branch
|
||||
This branch is for the upcoming version 4 of Music Player and is in early stages of development
|
||||
|
||||
|
||||
# MusicPlayer
|
||||
|
||||
A music player, specifically created for displaying song information on multiple different displays that are connected to the same network, just from the browser.
|
||||
|
||||
The [hosted version](https://music.janishutz.com) of this MusicPlayer, which is fully set up for you is subscription-based and can be paid for on my [store](https://store.janishutz.com/product/com.janishutz).
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,744 @@
|
||||
import eslint from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import typescript from '@typescript-eslint/eslint-plugin';
|
||||
import vue from 'eslint-plugin-vue';
|
||||
|
||||
const style = {
|
||||
'plugins': {
|
||||
'@stylistic': stylistic,
|
||||
'@stylistic/js': stylistic,
|
||||
'@stylistic/ts': stylistic
|
||||
},
|
||||
'files': [
|
||||
'**/*.ts',
|
||||
'**/*.js',
|
||||
'**/*.mjs',
|
||||
'**/*.cjs',
|
||||
'**/*.tsx',
|
||||
'**/*.jsx'
|
||||
],
|
||||
'rules': {
|
||||
'sort-imports': [
|
||||
'warn',
|
||||
{
|
||||
'ignoreCase': false,
|
||||
'ignoreDeclarationSort': false,
|
||||
'ignoreMemberSort': false,
|
||||
'memberSyntaxSortOrder': [
|
||||
'none',
|
||||
'all',
|
||||
'multiple',
|
||||
'single'
|
||||
],
|
||||
'allowSeparatedGroups': false
|
||||
}
|
||||
],
|
||||
// Formatting
|
||||
'@stylistic/array-bracket-newline': [
|
||||
'error',
|
||||
{
|
||||
'multiline': false,
|
||||
'minItems': 2
|
||||
}
|
||||
],
|
||||
'@stylistic/array-bracket-spacing': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/array-element-newline': [
|
||||
'error',
|
||||
{
|
||||
'consistent': false,
|
||||
'multiline': false,
|
||||
'minItems': 2
|
||||
}
|
||||
],
|
||||
'@stylistic/arrow-parens': [
|
||||
'error',
|
||||
'as-needed'
|
||||
],
|
||||
'@stylistic/arrow-spacing': [
|
||||
'error',
|
||||
{
|
||||
'before': true,
|
||||
'after': true
|
||||
}
|
||||
],
|
||||
'@stylistic/block-spacing': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/brace-style': [
|
||||
'error',
|
||||
'1tbs',
|
||||
{
|
||||
'allowSingleLine': false
|
||||
}
|
||||
],
|
||||
'@stylistic/comma-dangle': [
|
||||
'error',
|
||||
'never'
|
||||
],
|
||||
'@stylistic/comma-spacing': [
|
||||
'error',
|
||||
{
|
||||
'before': false,
|
||||
'after': true
|
||||
}
|
||||
],
|
||||
'@stylistic/comma-style': [
|
||||
'error',
|
||||
'last'
|
||||
],
|
||||
'@stylistic/dot-location': [
|
||||
'error',
|
||||
'property'
|
||||
],
|
||||
'@stylistic/function-call-argument-newline': [
|
||||
'error',
|
||||
'consistent'
|
||||
],
|
||||
'@stylistic/function-call-spacing': [
|
||||
'error',
|
||||
'never'
|
||||
],
|
||||
'@stylistic/function-paren-newline': [
|
||||
'error',
|
||||
'multiline-arguments'
|
||||
],
|
||||
'@stylistic/implicit-arrow-linebreak': [
|
||||
'error',
|
||||
'beside'
|
||||
],
|
||||
'@stylistic/indent': [
|
||||
'error',
|
||||
4
|
||||
],
|
||||
'@stylistic/indent-binary-ops': [
|
||||
'error',
|
||||
4
|
||||
],
|
||||
'@stylistic/key-spacing': [
|
||||
'error',
|
||||
{
|
||||
'beforeColon': false,
|
||||
'afterColon': true
|
||||
}
|
||||
],
|
||||
'@stylistic/keyword-spacing': [
|
||||
'error',
|
||||
{
|
||||
'before': true,
|
||||
'after': true
|
||||
}
|
||||
],
|
||||
'@stylistic/lines-between-class-members': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/max-len': [
|
||||
'warn',
|
||||
{
|
||||
'code': 140,
|
||||
'comments': 160,
|
||||
'ignoreComments': false,
|
||||
'ignoreUrls': true,
|
||||
'ignoreStrings': true,
|
||||
'ignoreTemplateLiterals': true,
|
||||
'ignoreRegExpLiterals': true
|
||||
}
|
||||
],
|
||||
'@stylistic/max-statements-per-line': [
|
||||
'error',
|
||||
{
|
||||
'max': 1
|
||||
}
|
||||
],
|
||||
'@stylistic/multiline-ternary': [
|
||||
'error',
|
||||
'always-multiline'
|
||||
],
|
||||
'@stylistic/new-parens': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/newline-per-chained-call': 'error',
|
||||
'@stylistic/no-confusing-arrow': 'error',
|
||||
'@stylistic/no-extra-parens': [
|
||||
'error',
|
||||
'all',
|
||||
{
|
||||
'nestedBinaryExpressions': false,
|
||||
'ternaryOperandBinaryExpressions': false,
|
||||
'ignoreJSX': 'multi-line',
|
||||
'nestedConditionalExpressions': false
|
||||
}
|
||||
],
|
||||
'@stylistic/no-extra-semi': 'error',
|
||||
'@stylistic/no-floating-decimal': 'error',
|
||||
'@stylistic/no-mixed-operators': 'error',
|
||||
'@stylistic/no-mixed-spaces-and-tabs': 'error',
|
||||
'@stylistic/no-multi-spaces': 'error',
|
||||
'@stylistic/no-multiple-empty-lines': [
|
||||
'error',
|
||||
{
|
||||
'max': 3,
|
||||
'maxEOF': 2
|
||||
}
|
||||
],
|
||||
'@stylistic/no-tabs': 'error',
|
||||
'@stylistic/no-trailing-spaces': 'error',
|
||||
'@stylistic/no-whitespace-before-property': 'error',
|
||||
'@stylistic/object-curly-newline': [
|
||||
'error',
|
||||
{
|
||||
'multiline': true,
|
||||
'minProperties': 1
|
||||
}
|
||||
],
|
||||
'@stylistic/object-curly-spacing': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/object-property-newline': 'error',
|
||||
'@stylistic/one-var-declaration-per-line': 'error',
|
||||
'@stylistic/operator-linebreak': [
|
||||
'error',
|
||||
'before'
|
||||
],
|
||||
'@stylistic/padded-blocks': [
|
||||
'error',
|
||||
{
|
||||
'blocks': 'never',
|
||||
'classes': 'always',
|
||||
'switches': 'never'
|
||||
}
|
||||
],
|
||||
|
||||
// Padding lines. The most in-depth part of this config
|
||||
'@stylistic/padding-line-between-statements': [
|
||||
'error',
|
||||
// Variables, Constants
|
||||
{
|
||||
'blankLine': 'never',
|
||||
'prev': 'var',
|
||||
'next': 'var'
|
||||
},
|
||||
{
|
||||
'blankLine': 'never',
|
||||
'prev': 'let',
|
||||
'next': 'let'
|
||||
},
|
||||
{
|
||||
'blankLine': 'never',
|
||||
'prev': 'const',
|
||||
'next': 'const'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'var',
|
||||
'next': [
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'cjs-export',
|
||||
'cjs-import',
|
||||
'class',
|
||||
'const',
|
||||
'continue',
|
||||
'debugger',
|
||||
'directive',
|
||||
'do',
|
||||
'empty',
|
||||
'export',
|
||||
'expression',
|
||||
'for',
|
||||
'function',
|
||||
'if',
|
||||
'iife',
|
||||
'import',
|
||||
'let',
|
||||
'return',
|
||||
'switch',
|
||||
'throw',
|
||||
'try',
|
||||
'var',
|
||||
'with'
|
||||
]
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'let',
|
||||
'next': [
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'cjs-export',
|
||||
'cjs-import',
|
||||
'class',
|
||||
'const',
|
||||
'continue',
|
||||
'debugger',
|
||||
'directive',
|
||||
'do',
|
||||
'empty',
|
||||
'export',
|
||||
'expression',
|
||||
'for',
|
||||
'function',
|
||||
'if',
|
||||
'iife',
|
||||
'import',
|
||||
'return',
|
||||
'switch',
|
||||
'throw',
|
||||
'try',
|
||||
'var',
|
||||
'while',
|
||||
'with'
|
||||
]
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'const',
|
||||
'next': [
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'cjs-export',
|
||||
'cjs-import',
|
||||
'class',
|
||||
'continue',
|
||||
'debugger',
|
||||
'directive',
|
||||
'do',
|
||||
'empty',
|
||||
'export',
|
||||
'expression',
|
||||
'for',
|
||||
'function',
|
||||
'if',
|
||||
'iife',
|
||||
'import',
|
||||
'let',
|
||||
'return',
|
||||
'switch',
|
||||
'throw',
|
||||
'try',
|
||||
'var',
|
||||
'while',
|
||||
'with'
|
||||
]
|
||||
},
|
||||
// Import
|
||||
{
|
||||
'blankLine': 'never',
|
||||
'prev': 'import',
|
||||
'next': 'import'
|
||||
},
|
||||
{
|
||||
'blankLine': 'never',
|
||||
'prev': 'cjs-import',
|
||||
'next': 'cjs-import'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': [
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'cjs-export',
|
||||
'class',
|
||||
'const',
|
||||
'continue',
|
||||
'debugger',
|
||||
'directive',
|
||||
'do',
|
||||
'empty',
|
||||
'export',
|
||||
'expression',
|
||||
'for',
|
||||
'function',
|
||||
'if',
|
||||
'iife',
|
||||
'let',
|
||||
'return',
|
||||
'switch',
|
||||
'throw',
|
||||
'try',
|
||||
'var',
|
||||
'while',
|
||||
'with'
|
||||
],
|
||||
'next': 'cjs-import'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'cjs-import',
|
||||
'next': [
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'cjs-export',
|
||||
'class',
|
||||
'const',
|
||||
'continue',
|
||||
'debugger',
|
||||
'directive',
|
||||
'do',
|
||||
'empty',
|
||||
'export',
|
||||
'expression',
|
||||
'for',
|
||||
'function',
|
||||
'if',
|
||||
'iife',
|
||||
'let',
|
||||
'return',
|
||||
'switch',
|
||||
'throw',
|
||||
'try',
|
||||
'var',
|
||||
'while',
|
||||
'with'
|
||||
]
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': [
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'cjs-export',
|
||||
'class',
|
||||
'const',
|
||||
'continue',
|
||||
'debugger',
|
||||
'directive',
|
||||
'do',
|
||||
'empty',
|
||||
'export',
|
||||
'expression',
|
||||
'for',
|
||||
'function',
|
||||
'if',
|
||||
'iife',
|
||||
'let',
|
||||
'return',
|
||||
'switch',
|
||||
'throw',
|
||||
'try',
|
||||
'var',
|
||||
'while',
|
||||
'with'
|
||||
],
|
||||
'next': 'import'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'import',
|
||||
'next': [
|
||||
'block',
|
||||
'block-like',
|
||||
'break',
|
||||
'cjs-export',
|
||||
'class',
|
||||
'const',
|
||||
'continue',
|
||||
'debugger',
|
||||
'directive',
|
||||
'do',
|
||||
'empty',
|
||||
'export',
|
||||
'expression',
|
||||
'for',
|
||||
'function',
|
||||
'if',
|
||||
'iife',
|
||||
'let',
|
||||
'return',
|
||||
'switch',
|
||||
'throw',
|
||||
'try',
|
||||
'var',
|
||||
'while',
|
||||
'with'
|
||||
]
|
||||
},
|
||||
// If
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'if'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'if',
|
||||
'next': '*'
|
||||
},
|
||||
// For
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'for'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'for',
|
||||
'next': '*'
|
||||
},
|
||||
// While
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'while'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'while',
|
||||
'next': '*'
|
||||
},
|
||||
// Functions
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'function'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'function',
|
||||
'next': '*'
|
||||
},
|
||||
// Block Statements
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'block-like'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'block-like',
|
||||
'next': '*'
|
||||
},
|
||||
// Switch
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'switch'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'switch',
|
||||
'next': '*'
|
||||
},
|
||||
// Try-Catch
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'try'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'try',
|
||||
'next': '*'
|
||||
},
|
||||
// Throw
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'throw'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'throw',
|
||||
'next': '*'
|
||||
},
|
||||
// Return
|
||||
{
|
||||
'blankLine': 'never',
|
||||
'prev': 'return',
|
||||
'next': '*'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'return'
|
||||
},
|
||||
// Export
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'export'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'export',
|
||||
'next': '*'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'cjs-export'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'cjs-export',
|
||||
'next': '*'
|
||||
},
|
||||
// Classes
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': '*',
|
||||
'next': 'class'
|
||||
},
|
||||
{
|
||||
'blankLine': 'always',
|
||||
'prev': 'class',
|
||||
'next': '*'
|
||||
}
|
||||
],
|
||||
'@stylistic/quote-props': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/quotes': [
|
||||
'error',
|
||||
'single'
|
||||
],
|
||||
'@stylistic/rest-spread-spacing': [
|
||||
'error',
|
||||
'never'
|
||||
],
|
||||
'@stylistic/semi': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/semi-spacing': [
|
||||
'error',
|
||||
{
|
||||
'before': false,
|
||||
'after': true
|
||||
}
|
||||
],
|
||||
'@stylistic/semi-style': [
|
||||
'error',
|
||||
'last'
|
||||
],
|
||||
'@stylistic/space-before-blocks': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/space-before-function-paren': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/space-in-parens': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/space-infix-ops': [
|
||||
'error',
|
||||
{
|
||||
'int32Hint': false
|
||||
}
|
||||
],
|
||||
'@stylistic/space-unary-ops': 'error',
|
||||
'@stylistic/spaced-comment': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/switch-colon-spacing': 'error',
|
||||
'@stylistic/template-curly-spacing': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/template-tag-spacing': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'@stylistic/type-generic-spacing': 'error',
|
||||
'@stylistic/type-named-tuple-spacing': 'error',
|
||||
'@stylistic/wrap-iife': [
|
||||
'error',
|
||||
'inside'
|
||||
],
|
||||
'@stylistic/wrap-regex': 'error',
|
||||
'@stylistic/ts/type-annotation-spacing': 'error'
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
export default tseslint.config(
|
||||
// Base JavaScript rules
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
style,
|
||||
|
||||
// Vue support (including TS and JSX inside SFCs)
|
||||
{
|
||||
'files': [ '**/*.vue' ],
|
||||
'languageOptions': {
|
||||
'sourceType': 'module',
|
||||
'ecmaVersion': 'latest',
|
||||
'globals': globals.browser,
|
||||
'parserOptions': {
|
||||
'parser': tseslint.parser
|
||||
}
|
||||
},
|
||||
'plugins': {
|
||||
'vue': vue,
|
||||
'@stylistic': stylistic,
|
||||
'@stylistic/js': stylistic,
|
||||
'@stylistic/ts': stylistic,
|
||||
'@typescript-eslint': typescript
|
||||
},
|
||||
'extends': [
|
||||
eslint.configs.recommended,
|
||||
...vue.configs['flat/recommended']
|
||||
],
|
||||
'rules': {
|
||||
...typescript.configs.recommended.rules,
|
||||
...style.rules,
|
||||
|
||||
// Vue specific rules
|
||||
'@stylistic/indent': 'off',
|
||||
'vue/html-indent': [
|
||||
'error',
|
||||
4
|
||||
],
|
||||
'vue/html-comment-indent': [
|
||||
'error',
|
||||
4
|
||||
],
|
||||
'vue/script-indent': [
|
||||
'error',
|
||||
4,
|
||||
{
|
||||
'baseIndent': 1,
|
||||
'switchCase': 1
|
||||
}
|
||||
],
|
||||
'vue/html-self-closing': [
|
||||
'error',
|
||||
{
|
||||
'html': {
|
||||
'void': 'never',
|
||||
'normal': 'never',
|
||||
'component': 'always'
|
||||
},
|
||||
'svg': 'always',
|
||||
'math': 'never'
|
||||
}
|
||||
],
|
||||
'vue/max-attributes-per-line': [
|
||||
'error',
|
||||
{
|
||||
'singleline': 3,
|
||||
'multiline': 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "vue-project",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"pinia": "^4.0.2",
|
||||
"vue": "^3.5.40",
|
||||
"vue-router": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@stylistic/eslint-plugin": "^5.10.0",
|
||||
"@tsconfig/node24": "^24.0.4",
|
||||
"@types/node": "^24.13.3",
|
||||
"@vitejs/plugin-vue": "^6.0.8",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"eslint-plugin-vue": "^10.10.0",
|
||||
"globals": "^17.11.0",
|
||||
"npm-run-all2": "^9.0.2",
|
||||
"typescript": "~6.0.0",
|
||||
"typescript-eslint": "^8.67.0",
|
||||
"vite": "^8.1.5",
|
||||
"vite-plugin-vue-devtools": "^8.1.5",
|
||||
"vue-tsc": "^3.3.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.18.0 || >=24.12.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"ages": {
|
||||
"below": "Orange",
|
||||
"16-18": "Yellow",
|
||||
"18+": "Turquoise"
|
||||
},
|
||||
"offering": {
|
||||
"big-bar": {
|
||||
"offering": {
|
||||
"softdrinks": {
|
||||
"name": "Softdrinks",
|
||||
"price": 400,
|
||||
"id": "softdrinks"
|
||||
},
|
||||
"energy": {
|
||||
"name": "Energy",
|
||||
"price": 400,
|
||||
"id": "energy"
|
||||
},
|
||||
"mate": {
|
||||
"name": "Mate",
|
||||
"price": 500,
|
||||
"id": "mate",
|
||||
"depot": 200
|
||||
},
|
||||
"sparkly-water": {
|
||||
"name": "Mineralwasser mit",
|
||||
"price": 300,
|
||||
"id": "sparkly-water",
|
||||
"showLine": true
|
||||
},
|
||||
"rose": {
|
||||
"name": "Rosé",
|
||||
"price": 1500,
|
||||
"id": "rose",
|
||||
"depot": 200
|
||||
},
|
||||
"red": {
|
||||
"name": "Rotwein",
|
||||
"price": 2000,
|
||||
"id": "red",
|
||||
"depot": 200
|
||||
},
|
||||
"wine-glasses": {
|
||||
"name": "Weingläser",
|
||||
"price": 0,
|
||||
"id": "wine-glasses",
|
||||
"depot": 200
|
||||
},
|
||||
"appenzeller": {
|
||||
"name": "Appenzeller Vollmond",
|
||||
"price": 500,
|
||||
"id": "appenzeller",
|
||||
"depot": 200
|
||||
},
|
||||
"feldschloesschen": {
|
||||
"name": "Feldschlösschen",
|
||||
"price": 500,
|
||||
"id": "feldschloesschen",
|
||||
"depot": 200
|
||||
},
|
||||
"sommersby": {
|
||||
"name": "Sommersby",
|
||||
"price": 500,
|
||||
"id": "sommersby",
|
||||
"depot": 200
|
||||
},
|
||||
"jever-fun": {
|
||||
"name": "Jever Fun",
|
||||
"price": 400,
|
||||
"id": "jever-fun",
|
||||
"depot": 200
|
||||
},
|
||||
"trojka-ice": {
|
||||
"name": "Trojka Ice",
|
||||
"price": 600,
|
||||
"id": "trojka-ice",
|
||||
"depot": 200,
|
||||
"showLine": true
|
||||
},
|
||||
"vodka-red-energy": {
|
||||
"name": "Vodka Rot Energy",
|
||||
"price": 800,
|
||||
"id": "vodka-red-energy"
|
||||
},
|
||||
"vodka-green-citro": {
|
||||
"name": "Vodka Grün Citro",
|
||||
"price": 800,
|
||||
"id": "vodka-green-citro"
|
||||
},
|
||||
"vodka-white-energy": {
|
||||
"name": "Vodka White Energy",
|
||||
"price": 900,
|
||||
"id": "vodka-white-energy"
|
||||
},
|
||||
"gin-tonic": {
|
||||
"name": "Gin Tonic",
|
||||
"price": 900,
|
||||
"id": "gin-tonic"
|
||||
},
|
||||
"rum-cola": {
|
||||
"name": "Rum Cola",
|
||||
"price": 900,
|
||||
"id": "rum-cola"
|
||||
},
|
||||
"whiskey-cola": {
|
||||
"name": "Whiskey Cola",
|
||||
"price": 900,
|
||||
"id": "whiskey-cola"
|
||||
},
|
||||
"mate-mit-schuss": {
|
||||
"name": "Mate mit Schuss",
|
||||
"price": 1200,
|
||||
"id": "mate-mit-schuss",
|
||||
"depot": 200,
|
||||
"showLine": true
|
||||
},
|
||||
"poseidon": {
|
||||
"name": "Poseidon",
|
||||
"price": 900,
|
||||
"id": "poseidon"
|
||||
},
|
||||
"arielle": {
|
||||
"name": "Arielle",
|
||||
"price": 900,
|
||||
"id": "arielle"
|
||||
},
|
||||
"pearl-driver": {
|
||||
"name": "Pearl Driver",
|
||||
"price": 400,
|
||||
"id": "pearl-driver"
|
||||
}
|
||||
},
|
||||
"name": "Poseidon's Quelle",
|
||||
"id": "big-bar"
|
||||
},
|
||||
"small-bar": {
|
||||
"offering": {
|
||||
"softdrinks": {
|
||||
"name": "Softdrinks (Alle)",
|
||||
"price": 300,
|
||||
"id": "softdrinks",
|
||||
"showLine": true
|
||||
},
|
||||
"appenzeller": {
|
||||
"name": "Appenzeller Vollmond",
|
||||
"price": 500,
|
||||
"id": "appenzeller",
|
||||
"depot": 200
|
||||
},
|
||||
"feldschloesschen": {
|
||||
"name": "Feldschlösschen",
|
||||
"price": 500,
|
||||
"id": "feldschloesschen",
|
||||
"depot": 200
|
||||
},
|
||||
"sommersby": {
|
||||
"name": "Sommersby",
|
||||
"price": 500,
|
||||
"id": "sommersby",
|
||||
"depot": 200
|
||||
},
|
||||
"jever-fun": {
|
||||
"name": "Jever Fun",
|
||||
"price": 400,
|
||||
"id": "jever-fun",
|
||||
"depot": 200,
|
||||
"showLine": true
|
||||
},
|
||||
"rose": {
|
||||
"name": "Rosé",
|
||||
"price": 1500,
|
||||
"id": "rose",
|
||||
"depot": 200
|
||||
},
|
||||
"red": {
|
||||
"name": "Rotwein",
|
||||
"price": 2000,
|
||||
"id": "red",
|
||||
"depot": 200
|
||||
},
|
||||
"wine-glasses": {
|
||||
"name": "Weingläser",
|
||||
"price": 0,
|
||||
"id": "wine-glasses",
|
||||
"depot": 200
|
||||
}
|
||||
},
|
||||
"name": "Seepferdchenbar",
|
||||
"id": "small-bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
+209
@@ -0,0 +1,209 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
RouterView
|
||||
} from 'vue-router';
|
||||
import {
|
||||
ref
|
||||
} from 'vue';
|
||||
|
||||
const theme = ref( '☼' );
|
||||
|
||||
const changeTheme = () => {
|
||||
if ( theme.value === '☽' ) {
|
||||
document.documentElement.classList.remove( 'dark' );
|
||||
document.documentElement.classList.add( 'light' );
|
||||
localStorage.setItem( 'theme', '☼' );
|
||||
theme.value = '☼';
|
||||
} else if ( theme.value === '☼' ) {
|
||||
document.documentElement.classList.remove( 'light' );
|
||||
document.documentElement.classList.add( 'dark' );
|
||||
localStorage.setItem( 'theme', '☽' );
|
||||
theme.value = '☽';
|
||||
}
|
||||
};
|
||||
|
||||
theme.value = localStorage.getItem( 'theme' ) ?? '';
|
||||
|
||||
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || theme.value === '☽' ) {
|
||||
document.documentElement.classList.add( 'dark' );
|
||||
theme.value = '☽';
|
||||
} else {
|
||||
document.documentElement.classList.add( 'light' );
|
||||
theme.value = '☼';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button id="themeSelector" title="Toggle between light and dark mode" @click="changeTheme();">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span class="material-symbols-outlined" v-html="theme"></span>
|
||||
</button>
|
||||
<router-view id="main-view" v-slot="{ Component, route }">
|
||||
<transition :name="route.meta.transition ? String( route.meta.transition ) : 'fade'" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: var( --background-color );
|
||||
}
|
||||
|
||||
:root, :root.light {
|
||||
--primary-color: #0a1520;
|
||||
--secondary-color: white;
|
||||
--background-color: rgb(221, 221, 221);
|
||||
--nav-background: white;
|
||||
--hover-color: #00457a;
|
||||
--popup-color: rgb(224, 224, 224);
|
||||
--overlay-color: rgba(0, 0, 0, 0.7);
|
||||
--PI: 3.14159265358979;
|
||||
--gray-color: rgb(53, 53, 53);
|
||||
--footer-background: rgb(233, 233, 233);
|
||||
--accent-background: rgb(195, 235, 243);
|
||||
--loading-color: rgb(167, 167, 167);
|
||||
--slider-color: rgb(119, 132, 255);
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
--primary-color: white;
|
||||
--secondary-color: black;
|
||||
--background-color: rgb(32, 32, 32);
|
||||
--nav-background: rgb(54, 54, 54);
|
||||
--popup-color: rgb(58, 58, 58);
|
||||
--hover-color: #007ddd;
|
||||
--overlay-color: rgba(104, 104, 104, 0.575);
|
||||
--gray-color: rgb(207, 207, 207);
|
||||
--footer-background: rgb(53, 53, 53);
|
||||
--accent-background: rgb(24, 12, 58);
|
||||
--loading-color: rgb(65, 65, 65);
|
||||
--slider-color: rgb(119, 132, 255);
|
||||
}
|
||||
|
||||
@media ( prefers-color-scheme: dark ) {
|
||||
:root {
|
||||
--primary-color: white;
|
||||
--secondary-color: black;
|
||||
--background-color: rgb(32, 32, 32);
|
||||
--nav-background: rgb(54, 54, 54);
|
||||
--popup-color: rgb(58, 58, 58);
|
||||
--hover-color: #007ddd;
|
||||
--overlay-color: rgba(104, 104, 104, 0.575);
|
||||
--gray-color: rgb(207, 207, 207);
|
||||
--footer-background: rgb(53, 53, 53);
|
||||
--accent-background: rgb(24, 12, 58);
|
||||
--loading-color: rgb(65, 65, 65);
|
||||
--slider-color: rgb(119, 132, 255);
|
||||
}
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #7c8cec;
|
||||
color: white;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
#app {
|
||||
transition: 0.5s;
|
||||
background-color: var( --background-color );
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
/* font-family: Avenir, Helvetica, Arial, sans-serif; */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: var( --primary-color );
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#main-view {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.scale-enter-active,
|
||||
.scale-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.scale-enter-from,
|
||||
.scale-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 48
|
||||
}
|
||||
|
||||
.clr-open {
|
||||
border: black solid 1px !important;
|
||||
}
|
||||
|
||||
#themeSelector {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var( --primary-color );
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.fancy-button {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
background: linear-gradient( 45deg, rgb(0, 33, 139), rgb(151, 0, 0) );
|
||||
font-size: larger;
|
||||
transition: all 0.5s;
|
||||
background-size: 150%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fancy-button:hover {
|
||||
border-radius: 5px;
|
||||
background-position: 50%;
|
||||
}
|
||||
|
||||
.fancy-button-inactive {
|
||||
background: linear-gradient( 45deg, rgba(0, 33, 139, 0.6), rgba(151, 0, 0, 0.6) );
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.fancy-button-inactive:hover {
|
||||
border-radius: 20px;
|
||||
background-position: 0px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :class="'popup-backdrop' + ( isOpen ? '' : ' hidden' )">
|
||||
<div class="popup-main">
|
||||
<span v-if="props.showClose" class="material-symbols-outlined close-icon" @click="closePopup()">close</span>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
const isOpen = defineModel<boolean>();
|
||||
const props = defineProps<{
|
||||
'showClose'?: boolean
|
||||
}>();
|
||||
|
||||
const closePopup = () => {
|
||||
isOpen.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.popup-backdrop {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
background-color: var( --overlay-color );
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
transition: all 0.5s;
|
||||
transform: scale(1);
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.popup-main {
|
||||
width: 40%;
|
||||
height: 50%;
|
||||
background-color: var( --secondary-color );
|
||||
padding: 2.5%;
|
||||
border-radius: 20px;
|
||||
position: relative;
|
||||
overflow-y: scroll;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
font-size: 2rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,15 @@
|
||||
import App from './App.vue';
|
||||
import {
|
||||
createApp
|
||||
} from 'vue';
|
||||
import {
|
||||
createPinia
|
||||
} from 'pinia';
|
||||
import router from './router';
|
||||
|
||||
const app = createApp( App );
|
||||
|
||||
app.use( createPinia() );
|
||||
app.use( router );
|
||||
|
||||
app.mount( '#app' );
|
||||
@@ -0,0 +1,10 @@
|
||||
import {
|
||||
createRouter, createWebHistory
|
||||
} from 'vue-router';
|
||||
|
||||
const router = createRouter( {
|
||||
'history': createWebHistory( import.meta.env.BASE_URL ),
|
||||
'routes': []
|
||||
} );
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
// NOTE: For re-associating files with details here, can use dropdown in UI
|
||||
|
||||
export type Playlist = Song[];
|
||||
|
||||
export interface UrlToFileMapping {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface Song {
|
||||
/**
|
||||
* Where the song can be found
|
||||
*/
|
||||
'source': 'local' | 'applemusic';
|
||||
|
||||
/**
|
||||
* The identifier for the song so it can be found again (such as Apple Music ID or filename)
|
||||
*/
|
||||
'identifier': string;
|
||||
|
||||
/**
|
||||
* Additional info, such as dance type to be displayed on remote screens
|
||||
*/
|
||||
'additional-info': string;
|
||||
|
||||
/**
|
||||
* The artist of the song
|
||||
*/
|
||||
'artist': string;
|
||||
|
||||
/**
|
||||
* The name of the song
|
||||
*/
|
||||
'name': string;
|
||||
|
||||
/**
|
||||
* The cover image as a URL
|
||||
*/
|
||||
'cover': string;
|
||||
|
||||
/**
|
||||
* Song duration
|
||||
*/
|
||||
'duration': string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export const useMusicKit = () => {
|
||||
const init = () => {
|
||||
isInit = true;
|
||||
};
|
||||
|
||||
let isInit = false;
|
||||
|
||||
if ( !window.MusicKit ) {
|
||||
document.addEventListener( 'musickitloaded', () => {
|
||||
init();
|
||||
} );
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
export const play = () => {};
|
||||
|
||||
export const pause = () => {};
|
||||
|
||||
export const skip10 = () => {};
|
||||
|
||||
export const back10 = () => {};
|
||||
|
||||
export const playSong = ( id: string ) => {};
|
||||
@@ -0,0 +1,3 @@
|
||||
export const login = async () => {
|
||||
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
type Ref,
|
||||
ref
|
||||
} from 'vue';
|
||||
import type {
|
||||
Playlist
|
||||
} from '../dtype/playlist';
|
||||
|
||||
export const queueIdx = ref( 0 );
|
||||
|
||||
export const queue: Ref<Playlist> = ref( [] );
|
||||
|
||||
export const isPlaying = ref( false );
|
||||
|
||||
export const shuffle = ref( false );
|
||||
|
||||
export const repeat: Ref<'off' | 'one' | 'all'> = ref( 'off' );
|
||||
|
||||
export const playbackPercentage = ref( 0 );
|
||||
|
||||
export const duration = ref( 0 );
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="home-view">
|
||||
<img src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png" alt="MusicPlayer Logo" class="logo">
|
||||
<h1>404</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-view {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 50vh;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,229 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
type Ref,
|
||||
computed, ref
|
||||
} from 'vue';
|
||||
|
||||
interface FullConfig {
|
||||
'offering': Bars;
|
||||
'ages': Ages;
|
||||
}
|
||||
|
||||
interface Bars {
|
||||
[name: string]: {
|
||||
'offering': BarConfig;
|
||||
'name': string;
|
||||
'id': string;
|
||||
}
|
||||
}
|
||||
|
||||
interface Ages {
|
||||
'18+': string;
|
||||
'16-18': string;
|
||||
}
|
||||
|
||||
interface BarConfig {
|
||||
[id: string]: Offer
|
||||
}
|
||||
|
||||
interface Offer {
|
||||
'name': string;
|
||||
'price': number; // In cents
|
||||
'depot'?: number; // In cents
|
||||
'showLine'?: boolean;
|
||||
'id': string;
|
||||
}
|
||||
|
||||
interface Selection {
|
||||
[id: string]: number;
|
||||
}
|
||||
|
||||
const ages: Ref<Ages> = ref( {
|
||||
'18+': '',
|
||||
'16-18': '',
|
||||
'below': ''
|
||||
} );
|
||||
const offering: Ref<Bars> = ref( {} );
|
||||
const selection: Ref<Selection> = ref( {} );
|
||||
const selectedBar: Ref<string> = ref( '' );
|
||||
const enableDepotReminder = ref( true );
|
||||
|
||||
let cashinInDepot = false;
|
||||
|
||||
fetch( '/bar-config.json', {
|
||||
'cache': 'no-store'
|
||||
} ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.json().then( json => {
|
||||
const data: FullConfig = json;
|
||||
|
||||
offering.value = data.offering;
|
||||
ages.value = data.ages;
|
||||
} );
|
||||
} else {
|
||||
alert( 'Failed to load' );
|
||||
}
|
||||
} );
|
||||
|
||||
const reset = ( skipCheck = true ) => {
|
||||
if ( !skipCheck && !Object.keys( offering.value ).includes( selectedBar.value ) ) return;
|
||||
|
||||
if ( cashinInDepot && enableDepotReminder.value ) alert( 'Hand out chips for depot' );
|
||||
|
||||
cashinInDepot = false;
|
||||
|
||||
const keys = Object.keys( offering.value[ selectedBar.value ]!.offering );
|
||||
|
||||
selection.value = {};
|
||||
|
||||
keys.forEach( val => {
|
||||
selection.value[ val ] = 0;
|
||||
} );
|
||||
};
|
||||
|
||||
const total = computed( () => {
|
||||
const keys = Object.keys( selection.value );
|
||||
|
||||
let totalPrice = 0;
|
||||
let totalDepot = 0;
|
||||
|
||||
for ( let i = 0; i < keys.length; i++ ) {
|
||||
const o = selection.value[ keys[ i ]! ]!;
|
||||
|
||||
totalPrice += o * offering.value[ selectedBar.value ]!.offering[ keys[ i ]! ]!.price;
|
||||
totalDepot += o * ( offering.value[ selectedBar.value ]!.offering[ keys[ i ]! ]!.depot ?? 0 );
|
||||
}
|
||||
|
||||
if ( totalDepot > 0 ) {
|
||||
cashinInDepot = true;
|
||||
}
|
||||
|
||||
totalPrice += totalDepot;
|
||||
|
||||
return ( totalPrice / 100 ) + ( totalDepot ? ` (Depot = ${ totalDepot })` : '' );
|
||||
} );
|
||||
|
||||
const changeValue = ( id: string, amount: number ) => {
|
||||
selection.value[ id ]! += amount;
|
||||
|
||||
if ( selection.value[ id ]! < 0 ) {
|
||||
selection.value[ id ] = 0;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bar-utility">
|
||||
<div style="margin: 0">
|
||||
<label> Depot chips reminder</label>
|
||||
<input v-model="enableDepotReminder" type="checkbox">
|
||||
</div>
|
||||
<h1 style="margin: 15px;">
|
||||
Bar utility
|
||||
</h1>
|
||||
<div>
|
||||
<label for="bar-select">Select bar </label>
|
||||
<select id="bar-select" v-model="selectedBar" @change="reset()">
|
||||
<option v-for="bar in Object.values( offering )" :key="bar.id" :value="bar.id">
|
||||
{{ bar.name }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click="reset( false )">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
<p>Check ages! (18+: {{ ages[ '18+' ] }}, 16-18: {{ ages[ '16-18' ] }})</p>
|
||||
<p v-if="Object.keys( offering ).includes( selectedBar )">
|
||||
Total: CHF {{ total }}
|
||||
</p>
|
||||
<table v-if="Object.keys( offering ).includes( selectedBar )" class="offering-wrapper">
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="offer in offering[ selectedBar ]!.offering"
|
||||
:key="offer.id"
|
||||
:class="[ 'offering', offer.showLine ? 'show-line' : '' ]"
|
||||
>
|
||||
<td>
|
||||
<p>
|
||||
{{ offer.name }} (CHF {{ offer.price / 100 }}{{
|
||||
offer.depot ? ' + ' + ( offer.depot / 100 ) : '' }})
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<button class="inc-dec" @click="changeValue( offer.id, 1 )">
|
||||
+
|
||||
</button>
|
||||
<p>{{ selection[ offer.id ] }}</p>
|
||||
<button class="inc-dec" @click="changeValue( offer.id, -1 )">
|
||||
-
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p v-if="Object.keys( offering ).includes( selectedBar )">
|
||||
Total: CHF {{ total }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bar-utility {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
>.offering-wrapper {
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 5vh;
|
||||
|
||||
.offering {
|
||||
&.show-line {
|
||||
>td {
|
||||
border-bottom: solid 1px black;
|
||||
}
|
||||
}
|
||||
|
||||
>td {
|
||||
padding: 5px;
|
||||
p {
|
||||
margin: 0;
|
||||
margin-right: 15px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
>div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
p {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
>.inc-dec {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: solid var( --primary-color ) 1px;
|
||||
border-radius: 20px;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.5rem;
|
||||
color: var( --primary-color );
|
||||
touch-action: manipulation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
// Extra safety for array and object lookups, but may have false positives.
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Path mapping for cleaner imports.
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
|
||||
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||
// Specified here to keep it out of the root directory.
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
|
||||
{
|
||||
"extends": "@tsconfig/node24/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
// Most tools use transpilation instead of Node.js's native type-stripping.
|
||||
// Bundler mode provides a smoother developer experience.
|
||||
"module": "preserve",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
// Include Node.js types and avoid accidentally including other `@types/*` packages.
|
||||
"types": ["node"],
|
||||
|
||||
// Disable emitting output during `vue-tsc --build`, which is used for type-checking only.
|
||||
"noEmit": true,
|
||||
|
||||
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||
// Specified here to keep it out of the root directory.
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
URL, fileURLToPath
|
||||
} from 'node:url';
|
||||
import {
|
||||
defineConfig
|
||||
} from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig( {
|
||||
'plugins': [
|
||||
vue(),
|
||||
vueDevTools()
|
||||
],
|
||||
'resolve': {
|
||||
'alias': {
|
||||
'@': fileURLToPath( new URL( './src', import.meta.url ) )
|
||||
}
|
||||
},
|
||||
'server': {
|
||||
'port': 8081
|
||||
}
|
||||
} );
|
||||
Reference in New Issue
Block a user