init project first version
3
.browserslistrc
Normal file
@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
29
.eslintrc.js
Normal file
@ -0,0 +1,29 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended'
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'vue/no-deprecated-slot-attribute': 'off'
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/*.js?(x)',
|
||||
'**/tests/unit/**/*.spec.js?(x)'
|
||||
],
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
438
.gitignore
vendored
Normal file
@ -0,0 +1,438 @@
|
||||
# Specifies intentionally untracked files to ignore when using Git
|
||||
# http://git-scm.com/docs/gitignore
|
||||
|
||||
*~
|
||||
*.sw[mnpcod]
|
||||
.tmp
|
||||
*.tmp
|
||||
*.tmp.*
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
UserInterfaceState.xcuserstate
|
||||
$RECYCLE.BIN/
|
||||
|
||||
*.log
|
||||
log.txt
|
||||
npm-debug.log*
|
||||
|
||||
/.idea
|
||||
/.ionic
|
||||
/.sass-cache
|
||||
/.sourcemaps
|
||||
/.versions
|
||||
/.vscode
|
||||
/coverage
|
||||
/dist
|
||||
/node_modules
|
||||
/platforms
|
||||
/plugins
|
||||
/www
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
## Core latex/pdflatex auxiliary files:
|
||||
*.aux
|
||||
*.lof
|
||||
*.log
|
||||
*.lot
|
||||
*.fls
|
||||
*.out
|
||||
*.toc
|
||||
*.fmt
|
||||
*.fot
|
||||
*.cb
|
||||
*.cb2
|
||||
.*.lb
|
||||
|
||||
## Intermediate documents:
|
||||
*.dvi
|
||||
*.xdv
|
||||
*-converted-to.*
|
||||
# these rules might exclude image files for figures etc.
|
||||
# *.ps
|
||||
# *.eps
|
||||
# *.pdf
|
||||
|
||||
## Generated if empty string is given at "Please type another file name for output:"
|
||||
.pdf
|
||||
|
||||
## Bibliography auxiliary files (bibtex/biblatex/biber):
|
||||
*.bbl
|
||||
*.bcf
|
||||
*.blg
|
||||
*-blx.aux
|
||||
*-blx.bib
|
||||
*.run.xml
|
||||
|
||||
## Build tool auxiliary files:
|
||||
*.fdb_latexmk
|
||||
*.synctex
|
||||
*.synctex(busy)
|
||||
*.synctex.gz
|
||||
*.synctex.gz(busy)
|
||||
*.pdfsync
|
||||
|
||||
## Build tool directories for auxiliary files
|
||||
# latexrun
|
||||
latex.out/
|
||||
|
||||
## Auxiliary and intermediate files from other packages:
|
||||
# algorithms
|
||||
*.alg
|
||||
*.loa
|
||||
|
||||
# achemso
|
||||
acs-*.bib
|
||||
|
||||
# amsthm
|
||||
*.thm
|
||||
|
||||
# beamer
|
||||
*.nav
|
||||
*.pre
|
||||
*.snm
|
||||
*.vrb
|
||||
|
||||
# changes
|
||||
*.soc
|
||||
|
||||
# comment
|
||||
*.cut
|
||||
|
||||
# cprotect
|
||||
*.cpt
|
||||
|
||||
# elsarticle (documentclass of Elsevier journals)
|
||||
*.spl
|
||||
|
||||
# endnotes
|
||||
*.ent
|
||||
|
||||
# fixme
|
||||
*.lox
|
||||
|
||||
# feynmf/feynmp
|
||||
*.mf
|
||||
*.mp
|
||||
*.t[1-9]
|
||||
*.t[1-9][0-9]
|
||||
*.tfm
|
||||
|
||||
#(r)(e)ledmac/(r)(e)ledpar
|
||||
*.end
|
||||
*.?end
|
||||
*.[1-9]
|
||||
*.[1-9][0-9]
|
||||
*.[1-9][0-9][0-9]
|
||||
*.[1-9]R
|
||||
*.[1-9][0-9]R
|
||||
*.[1-9][0-9][0-9]R
|
||||
*.eledsec[1-9]
|
||||
*.eledsec[1-9]R
|
||||
*.eledsec[1-9][0-9]
|
||||
*.eledsec[1-9][0-9]R
|
||||
*.eledsec[1-9][0-9][0-9]
|
||||
*.eledsec[1-9][0-9][0-9]R
|
||||
|
||||
# glossaries
|
||||
*.acn
|
||||
*.acr
|
||||
*.glg
|
||||
*.glo
|
||||
*.gls
|
||||
*.glsdefs
|
||||
*.lzo
|
||||
*.lzs
|
||||
|
||||
# uncomment this for glossaries-extra (will ignore makeindex's style files!)
|
||||
# *.ist
|
||||
|
||||
# gnuplottex
|
||||
*-gnuplottex-*
|
||||
|
||||
# gregoriotex
|
||||
*.gaux
|
||||
*.glog
|
||||
*.gtex
|
||||
|
||||
# htlatex
|
||||
*.4ct
|
||||
*.4tc
|
||||
*.idv
|
||||
*.lg
|
||||
*.trc
|
||||
*.xref
|
||||
|
||||
# hyperref
|
||||
*.brf
|
||||
|
||||
# knitr
|
||||
*-concordance.tex
|
||||
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
|
||||
# *.tikz
|
||||
*-tikzDictionary
|
||||
|
||||
# listings
|
||||
*.lol
|
||||
|
||||
# luatexja-ruby
|
||||
*.ltjruby
|
||||
|
||||
# makeidx
|
||||
*.idx
|
||||
*.ilg
|
||||
*.ind
|
||||
|
||||
# minitoc
|
||||
*.maf
|
||||
*.mlf
|
||||
*.mlt
|
||||
*.mtc[0-9]*
|
||||
*.slf[0-9]*
|
||||
*.slt[0-9]*
|
||||
*.stc[0-9]*
|
||||
|
||||
# minted
|
||||
_minted*
|
||||
*.pyg
|
||||
|
||||
# morewrites
|
||||
*.mw
|
||||
|
||||
# newpax
|
||||
*.newpax
|
||||
|
||||
# nomencl
|
||||
*.nlg
|
||||
*.nlo
|
||||
*.nls
|
||||
|
||||
# pax
|
||||
*.pax
|
||||
|
||||
# pdfpcnotes
|
||||
*.pdfpc
|
||||
|
||||
# sagetex
|
||||
*.sagetex.sage
|
||||
*.sagetex.py
|
||||
*.sagetex.scmd
|
||||
|
||||
# scrwfile
|
||||
*.wrt
|
||||
|
||||
# sympy
|
||||
*.sout
|
||||
*.sympy
|
||||
sympy-plots-for-*.tex/
|
||||
|
||||
# pdfcomment
|
||||
*.upa
|
||||
*.upb
|
||||
|
||||
# pythontex
|
||||
*.pytxcode
|
||||
pythontex-files-*/
|
||||
|
||||
# tcolorbox
|
||||
*.listing
|
||||
|
||||
# thmtools
|
||||
*.loe
|
||||
|
||||
# TikZ & PGF
|
||||
*.dpth
|
||||
*.md5
|
||||
*.auxlock
|
||||
|
||||
# todonotes
|
||||
*.tdo
|
||||
|
||||
# vhistory
|
||||
*.hst
|
||||
*.ver
|
||||
|
||||
# easy-todo
|
||||
*.lod
|
||||
|
||||
# xcolor
|
||||
*.xcp
|
||||
|
||||
# xmpincl
|
||||
*.xmpi
|
||||
|
||||
# xindy
|
||||
*.xdy
|
||||
|
||||
# xypic precompiled matrices and outlines
|
||||
*.xyc
|
||||
*.xyd
|
||||
|
||||
# endfloat
|
||||
*.ttt
|
||||
*.fff
|
||||
|
||||
# Latexian
|
||||
TSWLatexianTemp*
|
||||
|
||||
## Editors:
|
||||
# WinEdt
|
||||
*.bak
|
||||
*.sav
|
||||
|
||||
# Texpad
|
||||
.texpadtmp
|
||||
|
||||
# LyX
|
||||
*.lyx~
|
||||
|
||||
# Kile
|
||||
*.backup
|
||||
|
||||
# gummi
|
||||
.*.swp
|
||||
|
||||
# KBibTeX
|
||||
*~[0-9]*
|
||||
|
||||
# TeXnicCenter
|
||||
*.tps
|
||||
|
||||
# auto folder when using emacs and auctex
|
||||
./auto/*
|
||||
*.el
|
||||
|
||||
# expex forward references with \gathertags
|
||||
*-tags.tex
|
||||
|
||||
# standalone packages
|
||||
*.sta
|
||||
|
||||
# Makeindex log files
|
||||
*.lpz
|
||||
|
||||
# xwatermark package
|
||||
*.xwm
|
||||
|
||||
# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
|
||||
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
|
||||
# Uncomment the next line to have this generated file ignored.
|
||||
#*Notes.bib
|
||||
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2011-present, Modus Create, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
57
Readme-EN.md
Normal file
@ -0,0 +1,57 @@
|
||||
# CocktailShaker App
|
||||
Project in the context of the event [Content Exploitation Models and their Implementation in Mobile Systems](https://www.tu-ilmenau.de/modultafeln/Informatik/Bachelor/2013/fach/13137/) under Dr. Jürgen Nützel
|
||||
|
||||
## Warning
|
||||
The project is not commercial. It is just a report of a private event. All the information, resources or brand names found in the text or appearing in the photos are provided for information and demonstration purposes only. Any types of direct or indirect marketing are not assumed.
|
||||
The authors of the project warn about the damage alcohol does and don't take any responsibility for a result of the use of any content posted.
|
||||
All the cocktail recipes are for information only.
|
||||
No food bloggers, bartenders or programmers were harmed in the making of this project.
|
||||
|
||||
## Contribution
|
||||
- the (CocktailDB)[https://www.thecocktaildb.com/] API; you can add your own API Key
|
||||
- Cocktails: [International Bartenders Association (Teijo)](https://github.com/teijo/iba-cocktails)
|
||||
- [Cocktailpictures](https://github.com/alfg/opendrinks)
|
||||
- [Glasses](https://github.com/mikeyhogarth/cocktails)
|
||||
- Library for Apps [Ionic](https://github.com/ionic-team/ionic)
|
||||
- JS Framework [Vue](https://github.com/vuejs/vue)
|
||||
- cross-platform compability [Capacitor](https://github.com/ionic-team/capacitor)
|
||||
|
||||
## Develop locally
|
||||
1. download the installer](https://nodejs.org/) for Node LTS
|
||||
2. install the ionic CLI globally: `npm install -g ionic`
|
||||
3. clone this repository: `git clone https://github.com/wieerwill/cocktailshaker-app.git`
|
||||
4. move to folder `cd cocktailshaker-app`
|
||||
5. run `npm install` from the project root
|
||||
6. run `ionic serve` in a terminal from the project root
|
||||
7. follow the link in the console to view the app in the browser or smartphone
|
||||
|
||||
### Deploy
|
||||
To deploy everything to a production-ready app, run this command:
|
||||
```sh
|
||||
ionic build
|
||||
```
|
||||
This will build and update all files in the `dist/` folder
|
||||
|
||||
### Build general
|
||||
1. update the Capacitor config after each standard build process: `ionic cap copy`
|
||||
2. sync Capacitor builds after each new Plugin or huge code change: `ionic cap sync`
|
||||
|
||||
- Android build
|
||||
1. you need [Android SDK](https://developer.android.com/studio/).
|
||||
- the easiest way on a Mac is `homebrew`: `brew install android-sdk`
|
||||
- on Linux you can use the package manager: `sudo apt-get install android-sdk` or via
|
||||
- [Flatpak](https://flathub.org/apps/details/com.google.AndroidStudio) or
|
||||
- [Snap](https://uappexplorer.com/snap/ubuntu/android-studio)
|
||||
2. open the app in your AndroidStudio `ionic cap open android`
|
||||
3. to publish the app you need to [sign](https://developer.android.com/studio/publish/app-signing) it. For local testing, a sample file is available at
|
||||
```sh
|
||||
cp android/signing/keystore.properties.example
|
||||
```
|
||||
4. you may need to adjust the value of `storeFile` for your platform
|
||||
```sh
|
||||
storeFile=~/.android/debug.keystore
|
||||
```
|
||||
- iOS build open in XCode `ionic cap open ios`
|
||||
|
||||
# License
|
||||
The project runs under the [MIT](./LICENSE) licence.
|
64
Readme.md
Normal file
@ -0,0 +1,64 @@
|
||||
# CocktailShaker App
|
||||
[English Readme](Readme-EN.md)
|
||||
|
||||
Ein Projekt für die Veranstaltung [Content Verwertungsmodelle und ihre Umsetzung in mobilen Systemen](https://www.tu-ilmenau.de/modultafeln/Informatik/Bachelor/2013/fach/13137/) unter Dr. Jürgen Nützel.
|
||||
|
||||
Für den Rahmen der App war vorgegeben:
|
||||
- eine Native oder Hybride App für mobile Endgeräte
|
||||
- mindestens ein Sensor des Endgeräts muss verwendet werden (Kamera, Geolocation,...)
|
||||
- die App muss sich mit einem Server verbinden (keine offline App)
|
||||
|
||||
## Warnung
|
||||
Das Projekt ist nicht kommerziell. Es ist lediglich ein Projekt während einer universitären Veranstaltung.
|
||||
Alle Informationen, Ressourcen oder Markennamen, die im Text vorkommen oder auf den Fotos zu sehen sind, werden nur zu Informations- und Demonstrationszwecken bereitgestellt. Jegliche Art von direktem oder indirektem Marketing wird nicht angenommen.
|
||||
Die Autoren des Projekts warnen vor den Schäden, die Alkohol anrichtet, und übernehmen keine Verantwortung für ein Ergebnis der Verwendung der eingestellten Inhalte.
|
||||
Alle Cocktailrezepte dienen nur zur Information.
|
||||
Bei der Erstellung dieses Projekts wurden keine Food-Blogger, Barkeeper oder Programmierer geschädigt.
|
||||
|
||||
## Quellen und Bibliotheken
|
||||
- die (CocktailDB)[https://www.thecocktaildb.com/] API; der eigene API Schlüssel kann in der App gespeichert werden
|
||||
- Cocktails der [International Bartenders Association (Teijo)](https://github.com/teijo/iba-cocktails)
|
||||
- [Gläser](https://github.com/mikeyhogarth/cocktails)
|
||||
- Bibliothek für Apps [Ionic](https://github.com/ionic-team/ionic)
|
||||
- JS Framework [Vue](https://github.com/vuejs/vue)
|
||||
- Kompabilität für mobile Systeme [Capacitor](https://github.com/ionic-team/capacitor)
|
||||
|
||||
## Lokal weiterentwickeln
|
||||
1. Installiere [NodeJS](https://nodejs.org/) in der aktuellen LTS Version (>=14.15)
|
||||
2. Installiere die Ionic CLI ionic global: `npm install -g ionic`
|
||||
3. Klone das Repository `git clone https://github.com/wieerwill/cocktailshaker-app.git`
|
||||
4. In den neuen Ordner wechseln `cd cocktailshaker-app`
|
||||
5. Pakete mit `npm install` installieren
|
||||
6. den Entwicklungsserver mit `ionic serve` starten
|
||||
7. Folge dem Link in der Konsole um die App im Browser oder Smartphone zu betrachten
|
||||
|
||||
## Deploy
|
||||
Um alles für eine Produktionsfertige App bereitzustellen führe diesen Befehl aus:
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
Das wird alle Dateien im `dist/` Ordner erstellen und updaten
|
||||
|
||||
### Build general
|
||||
1. bei jedem Build Prozess müssen die Capacitor Ordner aktualisiert werden: `ionic cap copy`
|
||||
2. nach Updates oder großen Änderungen des Codes (neues Plugin) muss Capacitor synchronisiert werden: `ionic cap sync`
|
||||
|
||||
- Android build
|
||||
1. Du benötigst die [Android SDK](https://developer.android.com/studio/).
|
||||
- der einfachste Weg auf einem Mac ist `homebrew`: `brew install android-sdk`
|
||||
- auf Linux kann man den Paketmanager nutzen: `sudo apt-get install android-sdk` or via
|
||||
- [Flatpak](https://flathub.org/apps/details/com.google.AndroidStudio) or
|
||||
- [Snap](https://uappexplorer.com/snap/ubuntu/android-studio)
|
||||
2. die App im Android Studio öffnen `ionic cap open android`
|
||||
3. Um die App zu veröffentlichen benötigst musst du diese [signieren](https://developer.android.com/studio/publish/app-signing). Für lokale Tests ist eine Beispieldatei unter
|
||||
```sh
|
||||
cp android/signing/keystore.properties.example
|
||||
```
|
||||
4. Möglicherweise musst du den Wert von `storeFile` für deine Platform anpassen
|
||||
```sh
|
||||
storeFile=~/.android/debug.keystore
|
||||
```
|
||||
- iOS build in XCode öffnen `ionic cap open ios`
|
||||
|
||||
## Lizenz
|
||||
Das Projekt läuft unter der [MIT](./LICENSE) Lizenz.
|
5
babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
13
capacitor.config.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"appId": "io.ionic.starter",
|
||||
"appName": "cocktailShakerApp",
|
||||
"bundledWebRuntime": false,
|
||||
"npmClient": "yarn",
|
||||
"webDir": "dist",
|
||||
"plugins": {
|
||||
"SplashScreen": {
|
||||
"launchShowDuration": 0
|
||||
}
|
||||
},
|
||||
"cordova": {}
|
||||
}
|
3
cypress.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"pluginsFile": "tests/e2e/plugins/index.js"
|
||||
}
|
7
ionic.config.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "cocktailShakerApp",
|
||||
"integrations": {
|
||||
"capacitor": {}
|
||||
},
|
||||
"type": "vue"
|
||||
}
|
7
jest.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
|
||||
transform: {
|
||||
'^.+\\.vue$': 'vue-jest'
|
||||
},
|
||||
transformIgnorePatterns: ['/node_modules/(?!@ionic/vue|@ionic/vue-router)']
|
||||
}
|
52
package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "cocktail-shaker-app",
|
||||
"author": "robert jeutter",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "shake your way trough the cocktailbar with this multi-platform app",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"test:e2e": "vue-cli-service test:e2e",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "2.4.7",
|
||||
"@capacitor/core": "2.4.7",
|
||||
"@capacitor/ios": "2.4.7",
|
||||
"@ionic-native/core": "^5.33.1",
|
||||
"@ionic-native/social-sharing": "^5.33.1",
|
||||
"@ionic/cli": "^6.15.0",
|
||||
"@ionic/pwa-elements": "^3.0.2",
|
||||
"@ionic/storage": "^3.0.4",
|
||||
"@ionic/vue": "^5.4.0",
|
||||
"@ionic/vue-router": "^5.4.0",
|
||||
"@vueuse/core": "^5.0.2",
|
||||
"axios": "^0.21.1",
|
||||
"cordova-plugin-x-socialsharing": "^6.0.3",
|
||||
"core-js": "^3.6.5",
|
||||
"es6-promise-plugin": "^4.2.2",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"vue": "^3.0.0-0",
|
||||
"vue-router": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "2.4.7",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-e2e-cypress": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-pwa": "^4.5.13",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-unit-jest": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0-0",
|
||||
"@vue/test-utils": "^2.0.0-0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^7.0.0-0",
|
||||
"vue-jest": "^5.0.0-0"
|
||||
},
|
||||
"resolutions": {
|
||||
"cypress": "^4.4.0"
|
||||
}
|
||||
}
|
BIN
public/img/favicon.ico
Normal file
After Width: | Height: | Size: 61 KiB |
8
public/img/glasses/ChampagneFlute.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Champagne Flute</title>
|
||||
<path d="M 9 2 C 8.843549 2 10.143933 12.844562 11.75 13.908203 L 11.75 22.113281 A 4 0.33162412 0 0 0 8 22.443359 A 4 0.33162412 0 0 0 12 22.775391 A 4 0.33162412 0 0 0 16 22.443359 A 4 0.33162412 0 0 0 12.25 22.113281 L 12.25 13.908203 C 13.843978 12.84149 15.027726 2 15 2 L 9 2 z " />
|
||||
</svg>
|
After Width: | Height: | Size: 502 B |
8
public/img/glasses/ChampagneTulip.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Champagne Tulip</title>
|
||||
<path d="M 10 2 C 8.4883687 7.641485 10.10868 13.398989 11.75 13.945312 L 11.75 22.113281 A 4 0.33162412 0 0 0 8 22.443359 A 4 0.33162412 0 0 0 12 22.775391 A 4 0.33162412 0 0 0 16 22.443359 A 4 0.33162412 0 0 0 12.25 22.113281 L 12.25 13.945312 C 13.89132 13.398989 15.511632 7.641485 14 2 L 10 2 z " />
|
||||
</svg>
|
After Width: | Height: | Size: 522 B |
8
public/img/glasses/Collins.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Collins</title>
|
||||
<path d="M 7,2 H 17 V 24 H 7 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 240 B |
8
public/img/glasses/Highball.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Highball</title>
|
||||
<path d="M 2,0 H 22 L 19,24 H 5 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 248 B |
8
public/img/glasses/HotDrink.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Hot Drink</title>
|
||||
<path d="M 20,5.5 H 4 v 10 c 0,2.21 1.79,4 4,4 h 6 c 2.21,0 4,-1.79 4,-4 v -3 h 2 c 1.11,0 2,-0.9 2,-2 v -3 c 0,-1.11 -0.89,-2 -2,-2 z m 0,5 h -2 v -3 h 2 z m -16,11 h 16 v 2 H 4 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 396 B |
8
public/img/glasses/Hurricane.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Hurricane</title>
|
||||
<path d="M 17.959,0.5 H 6 c 0,0 1.953125,4.3172276 1.953125,6.8320312 0,2.2907126 -2.6559146,4.5223298 -1.7792969,6.6386718 C 6.8846738,15.686836 11,19.814453 11,19.814453 v 2.917969 L 6,23.5 H 17.958984 L 13,22.732422 v -2.917969 c 0,0 4.236581,-4.090798 4.958984,-5.84375 C 18.835479,11.843839 16.100682,9.6322208 16.070312,7.3320312 16.036822,4.7954668 17.959,0.5 17.959,0.5 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 595 B |
8
public/img/glasses/Margarita.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Margarita</title>
|
||||
<path d="M 0 0 C 0 4.0686355 2.0299978 6.5292006 5.1269531 7.8261719 C 5.4472547 11.625511 6.6408432 13.70419 11 13.966797 L 11 22 L 12 22 L 13 22 L 13 13.966797 C 17.359157 13.70419 18.552745 11.625511 18.873047 7.8261719 C 21.970002 6.5292006 24 4.0686355 24 0 C 19.113636 0.15886848 10.482798 0.03662556 0 0 z M 12 22 C 7.4967668 22 3.9999997 23.249461 4 24 C 9.0181747 24.03423 14.314143 24.228861 20 24 C 20 23.249461 16.503233 22 12 22 z " />
|
||||
</svg>
|
After Width: | Height: | Size: 660 B |
9
public/img/glasses/Martini.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Martini</title>
|
||||
<path d="m 21,7.5 v -2 H 3 v 2 l 8,9 v 5 H 6 v 2 h 12 v -2 h -5 v -5 z m -13.57,2 -1.77,-2 h 12.69 l -1.78,2 z" />
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 321 B |
8
public/img/glasses/OldFashioned.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Old Fashioned</title>
|
||||
<path d="M 4,6 H 20 V 24 H 4 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 250 B |
8
public/img/glasses/Shot.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>Shot</title>
|
||||
<path d="M 4,10.344 H 20 L 18,24 H 6 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 245 B |
8
public/img/glasses/WhiteWine.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1" baseProfile="full"
|
||||
width="25" height="25"
|
||||
viewBox="25 25">
|
||||
<title>White Wine</title>
|
||||
<path d="m 2,0 c 0.8985058,5.2114236 5.5451555,12.729721 9,13 v 9 h 1 1 V 13 C 17.626082,12.225887 21.941651,3.0666783 22,0 17.113636,0.15886848 12.482798,0.03662556 2,0 Z m 10,22 c -4.5032332,0 -8.0000003,1.249461 -8,2 5.0181747,0.03423 10.314143,0.228861 16,0 0,-0.750539 -3.496767,-2 -8,-2 z" />
|
||||
</svg>
|
After Width: | Height: | Size: 507 B |
BIN
public/img/icon.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
public/img/splash.png
Normal file
After Width: | Height: | Size: 165 KiB |
27
public/index.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Cocktail Shaker App</title>
|
||||
|
||||
<base href="/" />
|
||||
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no , viewport-fit=cover" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="<%= BASE_URL %>img/favicon.ico" />
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="Cocktail Shaker App" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
27
public/manifest.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "cocktail-shaker",
|
||||
"short_name": "CS",
|
||||
"theme_color": "#4DBA87",
|
||||
"icons": [{
|
||||
"src": "./img/favicon.ico",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "./img/icon.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "./img/splash.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#000000"
|
||||
}
|
2
public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
18
src/App.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<ion-app>
|
||||
<ion-router-outlet />
|
||||
</ion-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonApp, IonRouterOutlet } from '@ionic/vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
IonApp,
|
||||
IonRouterOutlet
|
||||
}
|
||||
});
|
||||
</script>
|
31
src/composables/useGeolocation.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { Plugins } from '@capacitor/core';
|
||||
const { Geolocation } = Plugins;
|
||||
let position;
|
||||
|
||||
export function useGeolocation() {
|
||||
|
||||
const updatePosition = async () => {
|
||||
position = await Geolocation.getCurrentPosition()
|
||||
}
|
||||
|
||||
const getCurrentPosition = () => {
|
||||
updatePosition();
|
||||
return position;
|
||||
}
|
||||
|
||||
const getLatitude = () => {
|
||||
updatePosition();
|
||||
return position.coords.latitude;
|
||||
}
|
||||
|
||||
const getLongitude = () => {
|
||||
updatePosition();
|
||||
return position.coords.longitude;
|
||||
}
|
||||
|
||||
return {
|
||||
getCurrentPosition,
|
||||
getLatitude,
|
||||
getLongitude
|
||||
}
|
||||
}
|
81
src/composables/useMotion.js
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
Plugins
|
||||
} from '@capacitor/core';
|
||||
const {
|
||||
Motion
|
||||
} = Plugins;
|
||||
|
||||
export function useMotion() {
|
||||
let acceleration = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
}
|
||||
|
||||
const initMotionSensor = async () => {
|
||||
try {
|
||||
await Motion.requestPermissions
|
||||
} catch (e) {
|
||||
// Handle error
|
||||
console.log("coudn't get motion sensor permission")
|
||||
return false
|
||||
}
|
||||
console.log("success");
|
||||
return true
|
||||
}
|
||||
|
||||
const getMotion = async () => {
|
||||
//Motion.requestPermissions
|
||||
//initMotionSensor()
|
||||
Motion.addListener('accel', (event) => {
|
||||
console.log(
|
||||
"x: ", event.acceleration.x,
|
||||
"\ny: ", event.acceleration.y,
|
||||
"\nz: ", event.acceleration.z
|
||||
)
|
||||
});
|
||||
return 12
|
||||
}
|
||||
|
||||
const detectShake = () => {
|
||||
this.subscription = Motion.watchAcceleration({
|
||||
frequency: 200
|
||||
}).subscribe(acc => {
|
||||
console.log(acc);
|
||||
|
||||
if (!this.lastX) {
|
||||
this.lastX = acc.x;
|
||||
this.lastY = acc.y;
|
||||
this.lastZ = acc.z;
|
||||
return;
|
||||
}
|
||||
|
||||
let deltaX, deltaY, deltaZ;
|
||||
deltaX = Math.abs(acc.x - this.lastX);
|
||||
deltaY = Math.abs(acc.y - this.lastY);
|
||||
deltaZ = Math.abs(acc.z - this.lastZ);
|
||||
|
||||
if (deltaX + deltaY + deltaZ > 3) {
|
||||
this.moveCounter++;
|
||||
} else {
|
||||
this.moveCounter = Math.max(0, --this.moveCounter);
|
||||
}
|
||||
|
||||
if (this.moveCounter > 2) {
|
||||
console.log('SHAKE');
|
||||
this.moveCounter = 0;
|
||||
}
|
||||
|
||||
this.lastX = acc.x;
|
||||
this.lastY = acc.y;
|
||||
this.lastZ = acc.z;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
coord,
|
||||
initMotionSensor,
|
||||
getMotion,
|
||||
detectShake
|
||||
}
|
||||
}
|
287
src/composables/useStorage.js
Normal file
@ -0,0 +1,287 @@
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
} from 'vue';
|
||||
import {
|
||||
Plugins,
|
||||
CameraResultType,
|
||||
CameraSource,
|
||||
FilesystemDirectory,
|
||||
Capacitor
|
||||
} from "@capacitor/core";
|
||||
import {
|
||||
isPlatform
|
||||
} from '@ionic/vue';
|
||||
import axios from 'axios';
|
||||
|
||||
import cocktailJSON from "../data/cocktails.json";
|
||||
|
||||
let cocktails = ref([]);
|
||||
const cocktailStorage = "cocktails";
|
||||
|
||||
let apiKey = "1";
|
||||
const apiKeyStorage = "cocktailDBapiKey";
|
||||
|
||||
export function useStorage() {
|
||||
const {
|
||||
Camera,
|
||||
Filesystem,
|
||||
Storage
|
||||
} = Plugins;
|
||||
|
||||
const cacheCocktails = () => {
|
||||
Storage.set({
|
||||
key: cocktailStorage,
|
||||
value: JSON.stringify(cocktails.value)
|
||||
});
|
||||
}
|
||||
|
||||
const loadSaved = async () => {
|
||||
const cocktailList = await Storage.get({
|
||||
key: cocktailStorage
|
||||
});
|
||||
const cocktailsInStorage = cocktailList.value ? JSON.parse(cocktailList.value) : [];
|
||||
// If running on the web...
|
||||
if (!isPlatform('hybrid')) {
|
||||
for (const cocktail of cocktailsInStorage) {
|
||||
if (cocktail.image) {
|
||||
const file = await Filesystem.readFile({
|
||||
path: cocktail.image.filepath,
|
||||
directory: FilesystemDirectory.Data
|
||||
});
|
||||
// Web platform only: Load the photo as base64 data
|
||||
cocktail.image.webviewPath = `data:image/jpeg;base64,${file.data}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
cocktails.value = cocktailsInStorage;
|
||||
}
|
||||
onMounted(loadSaved);
|
||||
|
||||
const restoreCocktails = () => {
|
||||
//console.log("restoring cocktails")
|
||||
cocktailJSON.sort(function(a, b) {
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
cocktails.value = cocktailJSON;
|
||||
}
|
||||
|
||||
const getRandomCocktail = () => {
|
||||
fetchRandomCocktail();
|
||||
return cocktails.value[Math.random() * cocktails.value.length]
|
||||
}
|
||||
|
||||
const getCocktail = (id) => {
|
||||
//console.log(id)
|
||||
let cocktail = cocktails.value[id]
|
||||
return cocktail
|
||||
}
|
||||
|
||||
const favouriseCocktail = (cocktail) => {
|
||||
let cocktailNr = cocktails.value.indexOf(cocktail)
|
||||
cocktails.value[cocktailNr].favourite = !cocktails.value[cocktailNr].favourite;
|
||||
cacheCocktails()
|
||||
}
|
||||
|
||||
const addCocktail = (cocktail) => {
|
||||
let newCocktail = {
|
||||
"name": cocktail.name,
|
||||
"ingredients": cocktail.ingredients,
|
||||
"directions": cocktail.directions,
|
||||
"glass": cocktail.glass,
|
||||
"author": cocktail.author ? cocktail.author : "You"
|
||||
}
|
||||
let tempCocktailList = [newCocktail, ...cocktails.value]
|
||||
tempCocktailList.sort(function(a, b) {
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
cocktails.value = tempCocktailList
|
||||
cacheCocktails()
|
||||
}
|
||||
|
||||
const editCocktail = (cocktail) => {
|
||||
let cocktailNr = cocktails.value.indexOf(cocktail)
|
||||
cocktails.value[cocktailNr] = cocktail;
|
||||
cacheCocktails()
|
||||
}
|
||||
|
||||
const removeCocktail = async (cocktail) => {
|
||||
if (cocktail.image) {
|
||||
// delete photo file from filesystem
|
||||
const filename = cocktail.image.filepath.substr(cocktail.image.filepath.lastIndexOf('/') + 1);
|
||||
await Filesystem.deleteFile({
|
||||
path: filename,
|
||||
directory: FilesystemDirectory.Data
|
||||
});
|
||||
}
|
||||
cocktails.value.splice(cocktails.value.indexOf(cocktail), 1)
|
||||
cacheCocktails()
|
||||
}
|
||||
|
||||
|
||||
const convertBlobToBase64 = (blob) => new Promise((resolve, reject) => {
|
||||
const reader = new FileReader;
|
||||
reader.onerror = reject;
|
||||
reader.onload = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
|
||||
const savePicture = async (photo, fileName) => {
|
||||
let base64Data;
|
||||
if (isPlatform('hybrid')) {
|
||||
// "hybrid" will detect mobile - iOS or Android
|
||||
const file = await Filesystem.readFile({
|
||||
path: photo.path
|
||||
});
|
||||
base64Data = file.data;
|
||||
} else {
|
||||
// Fetch the photo, read as a blob, then convert to base64 format
|
||||
const response = await fetch(photo.webPath);
|
||||
const blob = await response.blob();
|
||||
base64Data = await convertBlobToBase64(blob);
|
||||
}
|
||||
const savedFile = await Filesystem.writeFile({
|
||||
path: fileName,
|
||||
data: base64Data,
|
||||
directory: FilesystemDirectory.Data
|
||||
});
|
||||
|
||||
if (isPlatform('hybrid')) {
|
||||
// rewritr the 'file://' path to HTTP
|
||||
return {
|
||||
filepath: savedFile.uri,
|
||||
webviewPath: Capacitor.convertFileSrc(savedFile.uri),
|
||||
};
|
||||
} else {
|
||||
// Use webPath to display image
|
||||
return {
|
||||
filepath: fileName,
|
||||
webviewPath: photo.webPath
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const takePhoto = async (cocktail) => {
|
||||
const cameraPhoto = await Camera.getPhoto({
|
||||
resultType: CameraResultType.Uri,
|
||||
source: CameraSource.Camera,
|
||||
quality: 100
|
||||
});
|
||||
|
||||
const fileName = "cocktailShaker-" + new Date().getTime() + '.jpeg';
|
||||
const savedFileImage = await savePicture(cameraPhoto, fileName);
|
||||
//console.log(savedFileImage);
|
||||
|
||||
let cocktailNr = cocktails.value.indexOf(cocktail)
|
||||
cocktails.value[cocktailNr].image = savedFileImage;
|
||||
cacheCocktails()
|
||||
};
|
||||
|
||||
const cacheApiKey = () => {
|
||||
Storage.set({
|
||||
key: apiKeyStorage,
|
||||
value: apiKey
|
||||
})
|
||||
}
|
||||
|
||||
const loadApiKey = async () => {
|
||||
let myApiKey = await Storage.get({
|
||||
key: apiKeyStorage
|
||||
});
|
||||
apiKey = apiKey || myApiKey;
|
||||
}
|
||||
onMounted(loadApiKey);
|
||||
|
||||
const restoreApiKey = () => {
|
||||
apiKey = "1";
|
||||
cacheApiKey();
|
||||
//console.log(apiKey)
|
||||
}
|
||||
|
||||
const updateApiKey = async (key) => {
|
||||
//console.log("received key", key.toString())
|
||||
apiKey = key.toString();
|
||||
try {
|
||||
cacheApiKey();
|
||||
return apiKey
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const testApiKey = async () => {
|
||||
return axios({
|
||||
method: 'post',
|
||||
url: `https://www.thecocktaildb.com/api/json/v1/${apiKey}/random.php`,
|
||||
responseType: 'json'
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.data;
|
||||
})
|
||||
.catch(function() {
|
||||
return "error"
|
||||
});
|
||||
}
|
||||
|
||||
const fetchRandomCocktail = async () => {
|
||||
return axios({
|
||||
method: 'post',
|
||||
url: `https://www.thecocktaildb.com/api/json/v1/${apiKey}/random.php`,
|
||||
responseType: 'json'
|
||||
})
|
||||
.then(response => {
|
||||
let fetchedCocktail = {
|
||||
"name": response.data.drinks[0].strDrink,
|
||||
"ingredients": [],
|
||||
"directions": response.data.drinks[0].strInstructions,
|
||||
"glass": response.data.drinks[0].strGlass.replace("glass", "").replace("Glass", "").trim(),
|
||||
"author": response.data.drinks[0].strIBA ? "International Bartenders Association" : "CocktailDB"
|
||||
}
|
||||
for (var i = 1; i <= 15; i++) {
|
||||
if (response.data.drinks[0]["strIngredient" + i] != null) {
|
||||
let amount = null,
|
||||
unit = null;
|
||||
if (response.data.drinks[0]["strMeasure" + i] != null) {
|
||||
amount = response.data.drinks[0]["strMeasure" + i].substr(0, response.data.drinks[0]["strMeasure" + i].indexOf(" "))
|
||||
unit = response.data.drinks[0]["strMeasure" + i].substr(response.data.drinks[0]["strMeasure" + i].indexOf(" ") + 1);
|
||||
}
|
||||
|
||||
fetchedCocktail.ingredients.push({
|
||||
"unit": unit,
|
||||
"amount": amount,
|
||||
"ingredient": response.data.drinks[0]["strIngredient" + i]
|
||||
})
|
||||
}
|
||||
}
|
||||
try {
|
||||
addCocktail(fetchedCocktail);
|
||||
return true;
|
||||
} catch {
|
||||
return "mapping failed"
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
return "error"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
cocktails,
|
||||
restoreCocktails,
|
||||
getRandomCocktail,
|
||||
getCocktail,
|
||||
favouriseCocktail,
|
||||
addCocktail,
|
||||
editCocktail,
|
||||
removeCocktail,
|
||||
takePhoto,
|
||||
apiKey,
|
||||
restoreApiKey,
|
||||
updateApiKey,
|
||||
testApiKey,
|
||||
fetchRandomCocktail
|
||||
}
|
||||
}
|
2104
src/data/cocktails.json
Normal file
13
src/data/glasses.json
Normal file
@ -0,0 +1,13 @@
|
||||
[
|
||||
"Martini",
|
||||
"Old Fashioned",
|
||||
"Collins",
|
||||
"Highball",
|
||||
"Champagne Flute",
|
||||
"Margarita",
|
||||
"Champagne Tulip",
|
||||
"Hurricane",
|
||||
"Shot",
|
||||
"Hot-drink Mug",
|
||||
"White Wine"
|
||||
]
|
365
src/data/ingredients.json
Normal file
@ -0,0 +1,365 @@
|
||||
[
|
||||
{
|
||||
"name": "Absinthe",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Agave nectar",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Angostura bitters",
|
||||
"abv": 44,
|
||||
"taste": "bitter"
|
||||
},
|
||||
{
|
||||
"name": "Aperol",
|
||||
"abv": 11,
|
||||
"taste": "bitter"
|
||||
},
|
||||
{
|
||||
"name": "Apricot brandy",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Blackberry liqueur",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Blue Curaçao",
|
||||
"abv": 40,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Cachaca",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Calvados",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Campari",
|
||||
"abv": 25,
|
||||
"taste": null,
|
||||
"vegan": false
|
||||
},
|
||||
{
|
||||
"name": "Champagne",
|
||||
"abv": 12,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Cherry liqueur",
|
||||
"abv": 30,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Citron Vodka",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Coconut milk",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Coffee",
|
||||
"abv": 0,
|
||||
"taste": "bitter"
|
||||
},
|
||||
{
|
||||
"name": "Coffee liqueur",
|
||||
"abv": 20,
|
||||
"taste": "bitter"
|
||||
},
|
||||
{
|
||||
"name": "Cognac",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Cola",
|
||||
"abv": 0,
|
||||
"taste": "bitter"
|
||||
},
|
||||
{
|
||||
"name": "Cranberry juice",
|
||||
"abv": 0,
|
||||
"taste": "sour"
|
||||
},
|
||||
{
|
||||
"name": "Cream",
|
||||
"abv": 0,
|
||||
"taste": "sweet",
|
||||
"vegan": false
|
||||
},
|
||||
{
|
||||
"name": "Créme de Cacao",
|
||||
"abv": 20,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Créme de Cassis",
|
||||
"abv": 15,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Créme de Menthe",
|
||||
"abv": 25,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Crème de violette",
|
||||
"abv": 20,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Cream liqueur",
|
||||
"abv": 20,
|
||||
"taste": null,
|
||||
"vegan": false
|
||||
},
|
||||
{
|
||||
"name": "Dark rum",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "DiSaronno",
|
||||
"abv": 28,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "DOM Bénédictine",
|
||||
"abv": 40,
|
||||
"taste": null,
|
||||
"vegan": false
|
||||
},
|
||||
{
|
||||
"name": "Drambuie",
|
||||
"abv": 40,
|
||||
"taste": null,
|
||||
"vegan": false
|
||||
},
|
||||
{
|
||||
"name": "Dry White Wine",
|
||||
"abv": 12,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Egg white",
|
||||
"abv": 0,
|
||||
"taste": null,
|
||||
"vegan": false
|
||||
},
|
||||
{
|
||||
"name": "Egg yolk",
|
||||
"abv": 0,
|
||||
"taste": null,
|
||||
"vegan": false
|
||||
},
|
||||
{
|
||||
"name": "Galliano",
|
||||
"abv": 30,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Gin",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Ginger Ale",
|
||||
"abv": 0,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Ginger beer",
|
||||
"abv": 5,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Grapefruit juice",
|
||||
"abv": 0,
|
||||
"taste": "sour"
|
||||
},
|
||||
{
|
||||
"name": "Honey",
|
||||
"abv": 0,
|
||||
"taste": "sweet",
|
||||
"vegan": false
|
||||
},
|
||||
{
|
||||
"name": "Kirsch",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Lemon juice",
|
||||
"abv": 0,
|
||||
"taste": "sour"
|
||||
},
|
||||
{
|
||||
"name": "Lillet Blonde",
|
||||
"abv": 15,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Lime",
|
||||
"abv": 0,
|
||||
"taste": "sour"
|
||||
},
|
||||
{
|
||||
"name": "Lime juice",
|
||||
"abv": 0,
|
||||
"taste": "sour"
|
||||
},
|
||||
{
|
||||
"name": "Mint",
|
||||
"abv": 0,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Olive juice",
|
||||
"abv": 0,
|
||||
"taste": "sour"
|
||||
},
|
||||
{
|
||||
"name": "Orange bitters",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Orange flower water",
|
||||
"abv": 0,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Orange juice",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Peach bitters",
|
||||
"abv": 0,
|
||||
"taste": "fruity"
|
||||
},
|
||||
{
|
||||
"name": "Peach puree",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Peach schnapps",
|
||||
"abv": 40,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Peychaud’s bitters",
|
||||
"abv": 35,
|
||||
"taste": "woody"
|
||||
},
|
||||
{
|
||||
"name": "Pineapple juice",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Pisco",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Prosecco",
|
||||
"abv": 12,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Raspberry liqueur",
|
||||
"abv": 20,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Raspberry syrup",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Red Port",
|
||||
"abv": 20,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Soda water",
|
||||
"abv": 0,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Sparkling Wine",
|
||||
"abv": 12,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Sugar",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Syrup",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Tequila",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Tomato juice",
|
||||
"abv": 0,
|
||||
"taste": "salty"
|
||||
},
|
||||
{
|
||||
"name": "Triple Sec",
|
||||
"abv": 40,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Vanilla extract",
|
||||
"abv": 0,
|
||||
"taste": "sweet"
|
||||
},
|
||||
{
|
||||
"name": "Vermouth",
|
||||
"abv": 17,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Vodka",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Whiskey",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "White rum",
|
||||
"abv": 40,
|
||||
"taste": null
|
||||
},
|
||||
{
|
||||
"name": "Worcestershire Sauce",
|
||||
"abv": 0,
|
||||
"taste": null
|
||||
}
|
||||
]
|
10
src/data/units.json
Normal file
@ -0,0 +1,10 @@
|
||||
[
|
||||
"Piece",
|
||||
"ml",
|
||||
"cl",
|
||||
"Dash",
|
||||
"Splash",
|
||||
"Teaspoon",
|
||||
"Barspoon",
|
||||
"Slice"
|
||||
]
|
43
src/main.js
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
createApp
|
||||
} from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router';
|
||||
|
||||
import {
|
||||
IonicVue
|
||||
} from '@ionic/vue';
|
||||
import {
|
||||
defineCustomElements
|
||||
} from '@ionic/pwa-elements/loader';
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import '@ionic/vue/css/core.css';
|
||||
|
||||
/* Basic CSS for apps built with Ionic */
|
||||
import '@ionic/vue/css/normalize.css';
|
||||
import '@ionic/vue/css/structure.css';
|
||||
import '@ionic/vue/css/typography.css';
|
||||
|
||||
/* Optional CSS utils that can be commented out */
|
||||
import '@ionic/vue/css/padding.css';
|
||||
import '@ionic/vue/css/float-elements.css';
|
||||
import '@ionic/vue/css/text-alignment.css';
|
||||
import '@ionic/vue/css/text-transformation.css';
|
||||
import '@ionic/vue/css/flex-utils.css';
|
||||
import '@ionic/vue/css/display.css';
|
||||
|
||||
/* Theme variables */
|
||||
import './theme/variables.css';
|
||||
|
||||
import './registerServiceWorker'
|
||||
|
||||
const app = createApp(App)
|
||||
.use(IonicVue)
|
||||
.use(router);
|
||||
|
||||
router.isReady().then(() => {
|
||||
app.mount('#app');
|
||||
});
|
||||
|
||||
defineCustomElements(window);
|
39
src/registerServiceWorker.js
Normal file
@ -0,0 +1,39 @@
|
||||
import {
|
||||
register
|
||||
} from 'register-service-worker'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready() {
|
||||
// eslint-disable-next-line
|
||||
console.log(
|
||||
'App is being served from cache by a service worker.\n' +
|
||||
'For more details, visit https://goo.gl/AFskqB'
|
||||
)
|
||||
},
|
||||
registered() {
|
||||
// eslint-disable-next-line
|
||||
console.log('Service worker has been registered.')
|
||||
},
|
||||
cached() {
|
||||
// eslint-disable-next-line
|
||||
console.log('Content has been cached for offline use.')
|
||||
},
|
||||
updatefound() {
|
||||
// eslint-disable-next-line
|
||||
console.log('New content is downloading.')
|
||||
},
|
||||
updated() {
|
||||
// eslint-disable-next-line
|
||||
console.log('New content is available; please refresh.')
|
||||
},
|
||||
offline() {
|
||||
// eslint-disable-next-line
|
||||
console.log('No internet connection found. App is running in offline mode.')
|
||||
},
|
||||
error(error) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error during service worker registration:', error)
|
||||
}
|
||||
})
|
||||
}
|
60
src/router.js
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
createRouter,
|
||||
createWebHistory
|
||||
} from '@ionic/vue-router';
|
||||
import Tabs from './views/Tabs.vue'
|
||||
|
||||
const routes = [{
|
||||
path: '/',
|
||||
redirect: '/tabs/Shake'
|
||||
},
|
||||
{
|
||||
path: '/tabs/',
|
||||
component: Tabs,
|
||||
children: [{
|
||||
path: '',
|
||||
redirect: '/tabs/Shake'
|
||||
},
|
||||
{
|
||||
path: 'Shake',
|
||||
component: () => import('@/views/Shake.vue')
|
||||
},
|
||||
{
|
||||
path: 'Shake/:id',
|
||||
component: () => import('@/views/ViewCocktail.vue')
|
||||
},
|
||||
{
|
||||
path: "New",
|
||||
component: () => import('@/views/NewCocktail.vue')
|
||||
},
|
||||
{
|
||||
path: "New/:id",
|
||||
component: () => import('@/views/EditCocktail.vue')
|
||||
},
|
||||
{
|
||||
path: 'Cocktails',
|
||||
component: () => import('@/views/Cocktails.vue')
|
||||
},
|
||||
{
|
||||
path: "Apikey",
|
||||
component: () => import('@/views/ApiKey.vue')
|
||||
},
|
||||
{
|
||||
path: "motiondetector",
|
||||
component: () => import('@/views/MotionDetector.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/:catchAll(.*)",
|
||||
name: "pageNotFound",
|
||||
component: () => import('@/views/PageNotFound.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
236
src/theme/variables.css
Normal file
@ -0,0 +1,236 @@
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
:root {
|
||||
/** primary **/
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
/** secondary **/
|
||||
--ion-color-secondary: #3dc2ff;
|
||||
--ion-color-secondary-rgb: 61, 194, 255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #36abe0;
|
||||
--ion-color-secondary-tint: #50c8ff;
|
||||
|
||||
/** tertiary **/
|
||||
--ion-color-tertiary: #5260ff;
|
||||
--ion-color-tertiary-rgb: 82, 96, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #4854e0;
|
||||
--ion-color-tertiary-tint: #6370ff;
|
||||
|
||||
/** success **/
|
||||
--ion-color-success: #2dd36f;
|
||||
--ion-color-success-rgb: 45, 211, 111;
|
||||
--ion-color-success-contrast: #ffffff;
|
||||
--ion-color-success-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-success-shade: #28ba62;
|
||||
--ion-color-success-tint: #42d77d;
|
||||
|
||||
/** warning **/
|
||||
--ion-color-warning: #ffc409;
|
||||
--ion-color-warning-rgb: 255, 196, 9;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-warning-shade: #e0ac08;
|
||||
--ion-color-warning-tint: #ffca22;
|
||||
|
||||
/** danger **/
|
||||
--ion-color-danger: #eb445a;
|
||||
--ion-color-danger-rgb: 235, 68, 90;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #cf3c4f;
|
||||
--ion-color-danger-tint: #ed576b;
|
||||
|
||||
/** dark **/
|
||||
--ion-color-dark: #222428;
|
||||
--ion-color-dark-rgb: 34, 36, 40;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-dark-shade: #1e2023;
|
||||
--ion-color-dark-tint: #383a3e;
|
||||
|
||||
/** medium **/
|
||||
--ion-color-medium: #92949c;
|
||||
--ion-color-medium-rgb: 146, 148, 156;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-medium-shade: #808289;
|
||||
--ion-color-medium-tint: #9d9fa6;
|
||||
|
||||
/** light **/
|
||||
--ion-color-light: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 245, 248;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/*
|
||||
* Dark Colors
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
body {
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-color-primary-rgb: 66,140,255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255,255,255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
|
||||
--ion-color-secondary: #50c8ff;
|
||||
--ion-color-secondary-rgb: 80,200,255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255,255,255;
|
||||
--ion-color-secondary-shade: #46b0e0;
|
||||
--ion-color-secondary-tint: #62ceff;
|
||||
|
||||
--ion-color-tertiary: #6a64ff;
|
||||
--ion-color-tertiary-rgb: 106,100,255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255,255,255;
|
||||
--ion-color-tertiary-shade: #5d58e0;
|
||||
--ion-color-tertiary-tint: #7974ff;
|
||||
|
||||
--ion-color-success: #2fdf75;
|
||||
--ion-color-success-rgb: 47,223,117;
|
||||
--ion-color-success-contrast: #000000;
|
||||
--ion-color-success-contrast-rgb: 0,0,0;
|
||||
--ion-color-success-shade: #29c467;
|
||||
--ion-color-success-tint: #44e283;
|
||||
|
||||
--ion-color-warning: #ffd534;
|
||||
--ion-color-warning-rgb: 255,213,52;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0,0,0;
|
||||
--ion-color-warning-shade: #e0bb2e;
|
||||
--ion-color-warning-tint: #ffd948;
|
||||
|
||||
--ion-color-danger: #ff4961;
|
||||
--ion-color-danger-rgb: 255,73,97;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255,255,255;
|
||||
--ion-color-danger-shade: #e04055;
|
||||
--ion-color-danger-tint: #ff5b71;
|
||||
|
||||
--ion-color-dark: #f4f5f8;
|
||||
--ion-color-dark-rgb: 244,245,248;
|
||||
--ion-color-dark-contrast: #000000;
|
||||
--ion-color-dark-contrast-rgb: 0,0,0;
|
||||
--ion-color-dark-shade: #d7d8da;
|
||||
--ion-color-dark-tint: #f5f6f9;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152,154,162;
|
||||
--ion-color-medium-contrast: #000000;
|
||||
--ion-color-medium-contrast-rgb: 0,0,0;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #222428;
|
||||
--ion-color-light-rgb: 34,36,40;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 255,255,255;
|
||||
--ion-color-light-shade: #1e2023;
|
||||
--ion-color-light-tint: #383a3e;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Dark Theme
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
.ios body {
|
||||
--ion-background-color: #000000;
|
||||
--ion-background-color-rgb: 0,0,0;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255,255,255;
|
||||
|
||||
--ion-color-step-50: #0d0d0d;
|
||||
--ion-color-step-100: #1a1a1a;
|
||||
--ion-color-step-150: #262626;
|
||||
--ion-color-step-200: #333333;
|
||||
--ion-color-step-250: #404040;
|
||||
--ion-color-step-300: #4d4d4d;
|
||||
--ion-color-step-350: #595959;
|
||||
--ion-color-step-400: #666666;
|
||||
--ion-color-step-450: #737373;
|
||||
--ion-color-step-500: #808080;
|
||||
--ion-color-step-550: #8c8c8c;
|
||||
--ion-color-step-600: #999999;
|
||||
--ion-color-step-650: #a6a6a6;
|
||||
--ion-color-step-700: #b3b3b3;
|
||||
--ion-color-step-750: #bfbfbf;
|
||||
--ion-color-step-800: #cccccc;
|
||||
--ion-color-step-850: #d9d9d9;
|
||||
--ion-color-step-900: #e6e6e6;
|
||||
--ion-color-step-950: #f2f2f2;
|
||||
|
||||
--ion-item-background: #000000;
|
||||
|
||||
--ion-card-background: #1c1c1d;
|
||||
}
|
||||
|
||||
.ios ion-modal {
|
||||
--ion-background-color: var(--ion-color-step-100);
|
||||
--ion-toolbar-background: var(--ion-color-step-150);
|
||||
--ion-toolbar-border-color: var(--ion-color-step-250);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Material Design Dark Theme
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
.md body {
|
||||
--ion-background-color: #121212;
|
||||
--ion-background-color-rgb: 18,18,18;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255,255,255;
|
||||
|
||||
--ion-border-color: #222222;
|
||||
|
||||
--ion-color-step-50: #1e1e1e;
|
||||
--ion-color-step-100: #2a2a2a;
|
||||
--ion-color-step-150: #363636;
|
||||
--ion-color-step-200: #414141;
|
||||
--ion-color-step-250: #4d4d4d;
|
||||
--ion-color-step-300: #595959;
|
||||
--ion-color-step-350: #656565;
|
||||
--ion-color-step-400: #717171;
|
||||
--ion-color-step-450: #7d7d7d;
|
||||
--ion-color-step-500: #898989;
|
||||
--ion-color-step-550: #949494;
|
||||
--ion-color-step-600: #a0a0a0;
|
||||
--ion-color-step-650: #acacac;
|
||||
--ion-color-step-700: #b8b8b8;
|
||||
--ion-color-step-750: #c4c4c4;
|
||||
--ion-color-step-800: #d0d0d0;
|
||||
--ion-color-step-850: #dbdbdb;
|
||||
--ion-color-step-900: #e7e7e7;
|
||||
--ion-color-step-950: #f3f3f3;
|
||||
|
||||
--ion-item-background: #1e1e1e;
|
||||
|
||||
--ion-toolbar-background: #1f1f1f;
|
||||
|
||||
--ion-tab-bar-background: #1f1f1f;
|
||||
|
||||
--ion-card-background: #1e1e1e;
|
||||
}
|
||||
}
|
208
src/views/ApiKey.vue
Normal file
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>API Key</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">API Key</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<form v-on:submit.prevent="doSaveKey(apiKey)">
|
||||
<ion-item>
|
||||
<ion-label position="fixed">ApiKey: </ion-label>
|
||||
<ion-input
|
||||
type="text"
|
||||
inputmode="text"
|
||||
minlength="1"
|
||||
maxlength="255"
|
||||
name="key"
|
||||
v-model="apiKey"
|
||||
required
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-button
|
||||
slot="start"
|
||||
color="primary"
|
||||
@click="() => router.push('/tabs/Cocktails')"
|
||||
>
|
||||
<ion-icon :icon="arrowBackCircleOutline"></ion-icon>
|
||||
Back
|
||||
</ion-button>
|
||||
<ion-button slot="start" color="danger" @click="doRestoreKey()">
|
||||
<ion-icon :icon="reloadCircleOutline"></ion-icon>
|
||||
Restore
|
||||
</ion-button>
|
||||
<ion-button slot="end" color="success" type="submit">
|
||||
<ion-icon :icon="saveOutline"></ion-icon>
|
||||
Save
|
||||
</ion-button>
|
||||
</ion-toolbar>
|
||||
</form>
|
||||
|
||||
<div class="checker">
|
||||
<ion-button expand="block" color="secondary" @click="doCheckKey()">
|
||||
<ion-icon :icon="helpCircleOutline"></ion-icon>
|
||||
Check Key
|
||||
</ion-button>
|
||||
<ion-item v-if="apiKey == '1'">
|
||||
<p>
|
||||
ApiKey is only test key! Get your own on Patreon
|
||||
<ion-button href="https://www.patreon.com/thedatadb"
|
||||
>CocktailDB</ion-button
|
||||
>
|
||||
</p>
|
||||
</ion-item>
|
||||
</div>
|
||||
<!--<ion-button expand="block" color="secondary" @click="fetchRandomCocktail()">
|
||||
<ion-icon :icon="helpCircleOutline"></ion-icon>
|
||||
fetch
|
||||
</ion-button>-->
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useStorage } from "@/composables/useStorage";
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonLabel,
|
||||
IonItem,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
actionSheetController,
|
||||
alertController,
|
||||
} from "@ionic/vue";
|
||||
import {
|
||||
trash,
|
||||
saveOutline,
|
||||
refreshOutline,
|
||||
arrowBackCircleOutline,
|
||||
helpCircleOutline,
|
||||
reloadCircleOutline,
|
||||
} from "ionicons/icons";
|
||||
export default {
|
||||
name: "ApiKey",
|
||||
components: {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonLabel,
|
||||
IonItem,
|
||||
IonInput,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const { apiKey, updateApiKey, restoreApiKey, testApiKey, fetchRandomCocktail } = useStorage();
|
||||
|
||||
const doCheckKey = async () => {
|
||||
let data = await testApiKey();
|
||||
if (data && data != "error") {
|
||||
const alert = await alertController.create({
|
||||
header: "Success",
|
||||
subHeader: "DB connected successfull",
|
||||
message: "Your API Key is valid and can be used",
|
||||
buttons: ["OK"],
|
||||
});
|
||||
await alert.present();
|
||||
} else {
|
||||
const alert = await alertController.create({
|
||||
header: "Fail",
|
||||
subHeader: "Db connection failed",
|
||||
message:
|
||||
"Your API Key may be invalid or your internet connection is broken",
|
||||
buttons: ["OK"],
|
||||
});
|
||||
await alert.present();
|
||||
}
|
||||
};
|
||||
|
||||
const doRestoreKey = async () => {
|
||||
const actionSheet = await actionSheetController.create({
|
||||
header: "Restore API Key",
|
||||
buttons: [
|
||||
{
|
||||
text: "Restore API Key will remove your current key and set it to the default test key!",
|
||||
role: "destructive",
|
||||
icon: trash,
|
||||
handler: () => {
|
||||
restoreApiKey();
|
||||
window.scrollTo({ top: 0, left: 0 });
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
icon: close,
|
||||
role: "cancel",
|
||||
handler: () => {
|
||||
// Nothing to do, action sheet is automatically closed
|
||||
window.scrollTo({ top: 0, left: 0 });
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await actionSheet.present();
|
||||
};
|
||||
|
||||
const doSaveKey = async (key) => {
|
||||
let data = await updateApiKey(key);
|
||||
if (data && data != false) {
|
||||
const alert = await alertController.create({
|
||||
header: "Success",
|
||||
message: "Your API Key has been saved",
|
||||
buttons: ["OK"],
|
||||
});
|
||||
await alert.present();
|
||||
} else {
|
||||
const alert = await alertController.create({
|
||||
header: "Fail",
|
||||
message: "Your API Key coudn't be saved :(",
|
||||
buttons: ["OK"],
|
||||
});
|
||||
await alert.present();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
apiKey,
|
||||
doSaveKey,
|
||||
doCheckKey,
|
||||
doRestoreKey,
|
||||
fetchRandomCocktail,
|
||||
trash,
|
||||
saveOutline,
|
||||
refreshOutline,
|
||||
router,
|
||||
arrowBackCircleOutline,
|
||||
helpCircleOutline,
|
||||
reloadCircleOutline,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
ion-item {
|
||||
width: 95%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.checker {
|
||||
margin-top: 50px;
|
||||
}
|
||||
</style>
|
344
src/views/Cocktails.vue
Normal file
@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Cocktails</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large"
|
||||
>Cocktaillist
|
||||
{{ cocktails ? "#" + cocktails.length : "#0" }}</ion-title
|
||||
>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-button
|
||||
color="success"
|
||||
expand="full"
|
||||
@click="() => router.push('/tabs/New')"
|
||||
>
|
||||
<ion-icon :icon="addCircleOutline"></ion-icon>
|
||||
Add new cocktail
|
||||
</ion-button>
|
||||
|
||||
<ion-list v-if="cocktails">
|
||||
<ion-item-sliding v-for="cocktail in cocktails" :key="cocktail.name">
|
||||
<ion-item-options side="start">
|
||||
<ion-item-option @click="doDeleteCocktail(cocktail)" color="danger">
|
||||
<ion-icon slot="icon-only" :icon="trash"></ion-icon>
|
||||
</ion-item-option>
|
||||
<ion-item-option
|
||||
@click="
|
||||
() => router.push(`/tabs/New/${cocktails.indexOf(cocktail)}`)
|
||||
"
|
||||
color="warning"
|
||||
>
|
||||
<ion-icon slot="icon-only" :icon="createOutline"></ion-icon>
|
||||
</ion-item-option>
|
||||
</ion-item-options>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<span
|
||||
@click="
|
||||
() =>
|
||||
router.push(`/tabs/Shake/${cocktails.indexOf(cocktail)}`)
|
||||
"
|
||||
>
|
||||
<ion-icon
|
||||
class="golden"
|
||||
v-if="cocktail.favourite"
|
||||
:icon="star"
|
||||
></ion-icon>
|
||||
{{ cocktail.name }}
|
||||
<ion-icon
|
||||
class="green"
|
||||
v-if="cocktail.image"
|
||||
:icon="camera"
|
||||
></ion-icon>
|
||||
</span>
|
||||
</h2>
|
||||
<p>by {{ cocktail.author }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-options side="end">
|
||||
<ion-item-option
|
||||
@click="showShareOptions(cocktail)"
|
||||
color="secondary"
|
||||
>
|
||||
<ion-icon slot="icon-only" :icon="shareSocialOutline"></ion-icon>
|
||||
</ion-item-option>
|
||||
<ion-item-option
|
||||
@click="doFavouriseCocktail(cocktail)"
|
||||
color="success"
|
||||
>
|
||||
<ion-icon slot="icon-only" :icon="star"></ion-icon>
|
||||
</ion-item-option>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
</ion-list>
|
||||
|
||||
<ion-button
|
||||
color="primary"
|
||||
expand="full"
|
||||
@click="() => router.push('/tabs/Apikey')"
|
||||
>
|
||||
<ion-icon :icon="keyOutline"></ion-icon>
|
||||
Your API Key
|
||||
</ion-button>
|
||||
|
||||
<ion-button color="danger" expand="full" @click="doRestoreCocktails()">
|
||||
<ion-icon :icon="removeCircleOutline"></ion-icon>
|
||||
Restore Cocktails
|
||||
</ion-button>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useStorage } from "@/composables/useStorage";
|
||||
import { useRouter } from "vue-router";
|
||||
import { SocialSharing } from "@ionic-native/social-sharing";
|
||||
import {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonItem,
|
||||
IonItemOption,
|
||||
IonItemOptions,
|
||||
IonItemSliding,
|
||||
IonIcon,
|
||||
IonButton,
|
||||
actionSheetController,
|
||||
} from "@ionic/vue";
|
||||
import {
|
||||
addCircleOutline,
|
||||
removeCircleOutline,
|
||||
star,
|
||||
trash,
|
||||
camera,
|
||||
chevronDownCircleOutline,
|
||||
shareSocialOutline,
|
||||
createOutline,
|
||||
logoFacebook,
|
||||
logoTwitter,
|
||||
logoWhatsapp,
|
||||
logoInstagram,
|
||||
mailOutline,
|
||||
callOutline,
|
||||
eyeOutline,
|
||||
keyOutline,
|
||||
} from "ionicons/icons";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Cocktails",
|
||||
components: {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonItem,
|
||||
IonItemOption,
|
||||
IonItemOptions,
|
||||
IonItemSliding,
|
||||
IonIcon,
|
||||
IonButton,
|
||||
},
|
||||
setup() {
|
||||
const { cocktails, favouriseCocktail, removeCocktail, restoreCocktails } =
|
||||
useStorage();
|
||||
const router = useRouter();
|
||||
const socialSharing = SocialSharing;
|
||||
const publicPath = process.env.BASE_URL;
|
||||
|
||||
const doDeleteCocktail = async (cocktail) => {
|
||||
const actionSheet = await actionSheetController.create({
|
||||
header: cocktail.name,
|
||||
buttons: [
|
||||
{
|
||||
text: "Delete",
|
||||
role: "destructive",
|
||||
icon: trash,
|
||||
handler: () => {
|
||||
removeCocktail(cocktail);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
icon: close,
|
||||
role: "cancel",
|
||||
handler: () => {
|
||||
// Nothing to do, action sheet is automatically closed
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await actionSheet.present();
|
||||
};
|
||||
|
||||
const doFavouriseCocktail = async (cocktail) => {
|
||||
favouriseCocktail(cocktail);
|
||||
};
|
||||
|
||||
const showShareOptions = async (cocktail) => {
|
||||
//console.log("share ", cocktail.name);
|
||||
var options = {
|
||||
message: `Check out this fresh cocktail ${cocktail.name} at CocktailShaker APP`, // not supported on some apps (Facebook, Instagram)
|
||||
subject: `i found a new cocktail you could be interested in: ${cocktail.name}`, // fi. for email
|
||||
files: [`${publicPath}img/glasses/${cocktail.glass}.svg`], // an array of filenames either locally or remotely
|
||||
url: "www.northscorp.de/cocktailshaker",
|
||||
chooserTitle: "Share " + cocktail.name, // Android only, you can override the default share sheet title
|
||||
//appPackageName: 'com.apple.social.facebook', // Android only, you can provide id of the App you want to share with
|
||||
//iPadCoordinates: '0,0,0,0' //IOS only iPadCoordinates for where the popover should be point. Format with x,y,width,height
|
||||
};
|
||||
|
||||
const actionSheet = await actionSheetController.create({
|
||||
header: "Share Cocktail: " + cocktail.name,
|
||||
buttons: [
|
||||
{
|
||||
text: "Email",
|
||||
icon: mailOutline,
|
||||
handler: () => {
|
||||
socialSharing.shareViaEmail(
|
||||
options.message + "\n" + options.url,
|
||||
options.subject
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Facebook",
|
||||
icon: logoFacebook,
|
||||
handler: () => {
|
||||
socialSharing.shareViaFacebook(
|
||||
options.message,
|
||||
options.files[0],
|
||||
options.url
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Instagram",
|
||||
icon: logoInstagram,
|
||||
handler: () => {
|
||||
socialSharing.shareViaInstagram(
|
||||
options.message,
|
||||
options.files[0]
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "SMS",
|
||||
icon: callOutline,
|
||||
handler: () => {
|
||||
socialSharing.shareViaSMS(options.message);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Twitter",
|
||||
icon: logoTwitter,
|
||||
handler: () => {
|
||||
socialSharing.shareViaTwitter(
|
||||
options.message,
|
||||
options.files[0],
|
||||
options.url
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Whatsapp",
|
||||
icon: logoWhatsapp,
|
||||
handler: () => {
|
||||
socialSharing.shareViaWhatsApp(
|
||||
options.message,
|
||||
options.files[0],
|
||||
options.url
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
icon: close,
|
||||
role: "cancel",
|
||||
handler: () => {
|
||||
// Nothing to do, action sheet is automatically closed
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await actionSheet.present();
|
||||
};
|
||||
|
||||
const doRestoreCocktails = async () => {
|
||||
const actionSheet = await actionSheetController.create({
|
||||
header: "Cocktail",
|
||||
buttons: [
|
||||
{
|
||||
text: "Restore Cocktails will remove all changes made to the cocktaillist!",
|
||||
role: "destructive",
|
||||
icon: trash,
|
||||
handler: () => {
|
||||
restoreCocktails();
|
||||
window.scrollTo({ top: 0, left: 0 });
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
icon: close,
|
||||
role: "cancel",
|
||||
handler: () => {
|
||||
// Nothing to do, action sheet is automatically closed
|
||||
window.scrollTo({ top: 0, left: 0 });
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await actionSheet.present();
|
||||
};
|
||||
|
||||
return {
|
||||
cocktails,
|
||||
doDeleteCocktail,
|
||||
doFavouriseCocktail,
|
||||
showShareOptions,
|
||||
doRestoreCocktails,
|
||||
router,
|
||||
addCircleOutline,
|
||||
removeCircleOutline,
|
||||
star,
|
||||
trash,
|
||||
camera,
|
||||
chevronDownCircleOutline,
|
||||
shareSocialOutline,
|
||||
createOutline,
|
||||
eyeOutline,
|
||||
keyOutline,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
ion-list {
|
||||
padding-bottom: 35px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
.golden {
|
||||
color: gold;
|
||||
}
|
||||
.green {
|
||||
color: limegreen;
|
||||
}
|
||||
</style>
|
252
src/views/EditCocktail.vue
Normal file
@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{ mode }} Cocktail</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">{{ mode }} Cocktail</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<form v-on:submit.prevent="doEditCocktail(cocktail)">
|
||||
<ion-item>
|
||||
<ion-label position="floating">Name</ion-label>
|
||||
<ion-input
|
||||
type="text"
|
||||
inputmode="text"
|
||||
minlength="3"
|
||||
maxlength="100"
|
||||
name="name"
|
||||
v-model.trim="cocktail.name"
|
||||
required
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-group>
|
||||
<ion-item-divider>
|
||||
<ion-label slot="start" position="fixed">Ingredients</ion-label>
|
||||
<ion-button slot="end" @click="addIngredientField()">
|
||||
+ Add
|
||||
</ion-button>
|
||||
</ion-item-divider>
|
||||
<ion-item
|
||||
v-for="(ingredient, index) in cocktail.ingredients"
|
||||
:key="index"
|
||||
v-bind:item="ingredient"
|
||||
v-bind:index="index"
|
||||
>
|
||||
<!--amount-->
|
||||
<ion-input
|
||||
placeholder="amount"
|
||||
type="number"
|
||||
name="{{ingredient}}-amount"
|
||||
:value="ingredient.amount"
|
||||
v-model.number="cocktail.ingredients[index].amount"
|
||||
max="100"
|
||||
min="1"
|
||||
required
|
||||
></ion-input>
|
||||
<!--Unit-->
|
||||
<ion-select
|
||||
ok-text="Okay"
|
||||
cancel-text="Dismiss"
|
||||
name="{{ingredient}}-unit"
|
||||
:value="ingredient.unit"
|
||||
:v-model="cocktail.ingredients[index].unit"
|
||||
required
|
||||
>
|
||||
<ion-select-option
|
||||
:value="unit"
|
||||
v-for="unit in units"
|
||||
:key="unit"
|
||||
>{{ unit }}</ion-select-option
|
||||
>
|
||||
</ion-select>
|
||||
<!--Ingredient-->
|
||||
<ion-select
|
||||
ok-text="Okay"
|
||||
cancel-text="Dismiss"
|
||||
placeholder="ingredient"
|
||||
name="{{ingredient}}-ingredient"
|
||||
:value="ingredient.ingredient"
|
||||
:v-model="cocktail.ingredients[index].ingredient"
|
||||
required
|
||||
>
|
||||
<ion-select-option
|
||||
:value="ingred"
|
||||
v-for="ingred in ingredientList"
|
||||
:key="ingred"
|
||||
>{{ ingred.name }}</ion-select-option
|
||||
>
|
||||
</ion-select>
|
||||
<p>{{ingredient.ingredient === cocktail.ingredients[index].ingredient}}</p>
|
||||
|
||||
<ion-icon
|
||||
:icon="trash"
|
||||
@click="removeIngredient(ingredient)"
|
||||
></ion-icon>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">Directions</ion-label>
|
||||
<ion-textarea
|
||||
rows="6"
|
||||
type="text"
|
||||
inputmode="text"
|
||||
minlength="3"
|
||||
maxlength="250"
|
||||
name="directions"
|
||||
v-model="cocktail.directions"
|
||||
spellcheck="true"
|
||||
required
|
||||
></ion-textarea>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">Glass</ion-label>
|
||||
<ion-select
|
||||
placeholder="Select glass"
|
||||
ok-text="Okay"
|
||||
cancel-text="Dismiss"
|
||||
name="glass"
|
||||
v-model="cocktail.glass"
|
||||
required
|
||||
>
|
||||
<ion-select-option
|
||||
:value="glass"
|
||||
v-for="glass in glasses"
|
||||
:key="glass"
|
||||
>{{ glass }}</ion-select-option
|
||||
>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-button
|
||||
slot="start"
|
||||
color="danger"
|
||||
@click="() => router.push('/tabs/Cocktails')"
|
||||
>
|
||||
<ion-icon :icon="trash"></ion-icon>
|
||||
Cancel
|
||||
</ion-button>
|
||||
<ion-button slot="end" color="success" type="submit">
|
||||
<!--v-bind:disabled="errors.any()"-->
|
||||
<ion-icon :icon="saveOutline"></ion-icon>
|
||||
Save
|
||||
</ion-button>
|
||||
</ion-toolbar>
|
||||
</form>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useStorage } from "@/composables/useStorage";
|
||||
//import router from "@/router";
|
||||
import glassJson from "../data/glasses.json";
|
||||
import ingredientJson from "../data/ingredients.json";
|
||||
import unitJson from "../data/units.json";
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonLabel,
|
||||
IonItem,
|
||||
IonInput,
|
||||
IonTextarea,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonItemGroup,
|
||||
IonItemDivider,
|
||||
} from "@ionic/vue";
|
||||
import { trash, saveOutline,refreshOutline } from "ionicons/icons";
|
||||
export default {
|
||||
name: "Cocktails",
|
||||
components: {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonLabel,
|
||||
IonItem,
|
||||
IonInput,
|
||||
IonTextarea,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonItemGroup,
|
||||
IonItemDivider,
|
||||
},
|
||||
methods: {
|
||||
addIngredientField() {
|
||||
this.cocktail.ingredients.push({
|
||||
quantity: 0,
|
||||
unit: "ml",
|
||||
ingredient: null,
|
||||
});
|
||||
},
|
||||
removeIngredient(item) {
|
||||
const index = this.cocktail.ingredients.indexOf(item);
|
||||
if (index > -1) {
|
||||
this.cocktail.ingredients.splice(index, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const { cocktails } = useStorage();
|
||||
return {
|
||||
mode: "Edit",
|
||||
cocktails: cocktails,
|
||||
ingredientList: ingredientJson,
|
||||
glasses: glassJson,
|
||||
units: unitJson,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
cocktail(){
|
||||
let editCocktail = this.cocktails[parseInt(this.$route.params.id, 10)];
|
||||
//console.log(parseInt(this.$route.params.id, 10), editCocktail)
|
||||
return editCocktail
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
//const { editCocktail } = useStorage();
|
||||
|
||||
const doEditCocktail = async (cocktail) => {
|
||||
//console.log("editing: ", cocktail);
|
||||
cocktail?cocktail:false;
|
||||
//editCocktail(cocktail);
|
||||
//router.push("/tabs/Cocktails")
|
||||
};
|
||||
|
||||
return {
|
||||
doEditCocktail,
|
||||
trash,
|
||||
saveOutline,
|
||||
refreshOutline,
|
||||
router,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
ion-item {
|
||||
width: 95%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
155
src/views/MotionDetector.vue
Normal file
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title> Motion Detector </ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title id="top" size="large"> Motion Detector </ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<!-- View Shake button -->
|
||||
<div class="container">
|
||||
<ion-button
|
||||
color="success"
|
||||
expand="block"
|
||||
v-if="platform.indexOf('mobile') > -1"
|
||||
@click="activateMotionSensor()"
|
||||
>Activate motion sensor</ion-button
|
||||
>
|
||||
|
||||
<ion-list-header>
|
||||
<ion-label>Data</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>Platform:</ion-label>
|
||||
{{ platform }}
|
||||
</ion-item>
|
||||
<ion-item> <ion-label>x:</ion-label></ion-item>
|
||||
<ion-item> <ion-label>y:</ion-label></ion-item>
|
||||
<ion-item> <ion-label>z:</ion-label></ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-item>
|
||||
{{state.acc}}
|
||||
</ion-item>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from "vue";
|
||||
import {
|
||||
getPlatforms,
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonItem,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
} from "@ionic/vue";
|
||||
|
||||
//import { Motion } from "@capacitor/motion";
|
||||
export default {
|
||||
name: "Shake",
|
||||
components: {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonItem,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
platform: getPlatforms(),
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
acc: null,
|
||||
});
|
||||
|
||||
const requestDeviceMotion = () => {
|
||||
if (window.DeviceMotionEvent == null) {
|
||||
showError("DeviceMotion is not supported.");
|
||||
} else if (DeviceMotionEvent.requestPermission) {
|
||||
DeviceMotionEvent.requestPermission().then(
|
||||
function (state) {
|
||||
if (state == "granted") {
|
||||
createMotionSubscription();
|
||||
} else {
|
||||
//console.log("Permission denied by user");
|
||||
}
|
||||
},
|
||||
function (err) {
|
||||
showError(err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// no need for permission
|
||||
//console.log("no need for permission");
|
||||
createMotionSubscription();
|
||||
}
|
||||
};
|
||||
|
||||
const createMotionSubscription = async () => {
|
||||
if (window.DeviceOrientationEvent) {
|
||||
//console.log("start listening");
|
||||
window.addEventListener(
|
||||
"deviceorientation",
|
||||
function (e) {
|
||||
state.acc = e;
|
||||
//console.log("Device motion event:", e.acceleration.x);
|
||||
},
|
||||
false
|
||||
);
|
||||
setTimeout(function () {
|
||||
//console.log("pause listening");
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
const showError = () => {
|
||||
//console.log("Motion error", error);
|
||||
};
|
||||
|
||||
//createMotionSubscription();
|
||||
requestDeviceMotion();
|
||||
return { state, requestDeviceMotion, createMotionSubscription };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
padding-bottom: 35px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
ion-badge {
|
||||
margin: 0 3px 0 3px;
|
||||
}
|
||||
ion-list,
|
||||
#shorter {
|
||||
width: 90%;
|
||||
}
|
||||
ion-button {
|
||||
margin: 50px;
|
||||
}
|
||||
</style>
|
258
src/views/NewCocktail.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{ mode }} Cocktail</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">{{ mode }} Cocktail</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<form v-on:submit.prevent="doAddCocktail(cocktail)">
|
||||
<ion-item>
|
||||
<ion-label position="floating">Name</ion-label>
|
||||
<ion-input
|
||||
type="text"
|
||||
inputmode="text"
|
||||
minlength="3"
|
||||
maxlength="100"
|
||||
name="name"
|
||||
v-model.trim="cocktail.name"
|
||||
required
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-group>
|
||||
<ion-item-divider>
|
||||
<ion-label slot="start" position="fixed">Ingredients</ion-label>
|
||||
<ion-button slot="end" @click="addIngredientField()">
|
||||
+ Add
|
||||
</ion-button>
|
||||
</ion-item-divider>
|
||||
<ion-item
|
||||
v-for="(ingredient, index) in cocktail.ingredients"
|
||||
:key="index"
|
||||
v-bind:item="ingredient"
|
||||
v-bind:index="index"
|
||||
>
|
||||
<ion-input
|
||||
:placeholder="ingredient.amount"
|
||||
type="number"
|
||||
name="{{ingredient}}-amount"
|
||||
v-model.number="cocktail.ingredients[index].amount"
|
||||
max="100"
|
||||
min="1"
|
||||
required
|
||||
></ion-input>
|
||||
<ion-select
|
||||
:value="ingredient.unit"
|
||||
ok-text="Okay"
|
||||
cancel-text="Dismiss"
|
||||
name="{{ingredient}}-unit"
|
||||
:v-model="cocktail.ingredients[index].unit"
|
||||
required
|
||||
>
|
||||
<ion-select-option
|
||||
:value="unit"
|
||||
v-for="unit in units"
|
||||
:key="unit"
|
||||
>{{ unit }}</ion-select-option
|
||||
>
|
||||
</ion-select>
|
||||
<ion-select
|
||||
placeholder="add Ingredient"
|
||||
ok-text="Okay"
|
||||
cancel-text="Dismiss"
|
||||
name="{{ingredient}}-name"
|
||||
:v-model="cocktail.ingredients[index].ingredient"
|
||||
required
|
||||
>
|
||||
<ion-select-option
|
||||
:value="ingred"
|
||||
v-for="ingred in ingredients"
|
||||
:key="ingred"
|
||||
>{{ ingred.name }}</ion-select-option
|
||||
>
|
||||
</ion-select>
|
||||
|
||||
<ion-icon
|
||||
:icon="trash"
|
||||
@click="removeIngredient(ingredient)"
|
||||
></ion-icon>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">Directions</ion-label>
|
||||
<ion-textarea
|
||||
rows="6"
|
||||
type="text"
|
||||
inputmode="text"
|
||||
minlength="3"
|
||||
maxlength="250"
|
||||
name="directions"
|
||||
v-model="cocktail.directions"
|
||||
spellcheck="true"
|
||||
required
|
||||
></ion-textarea>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="fixed">Glass</ion-label>
|
||||
<ion-select
|
||||
placeholder="Select glass"
|
||||
ok-text="Okay"
|
||||
cancel-text="Dismiss"
|
||||
name="glass"
|
||||
v-model="cocktail.glass"
|
||||
required
|
||||
>
|
||||
<ion-select-option
|
||||
:value="glass"
|
||||
v-for="glass in glasses"
|
||||
:key="glass"
|
||||
>{{ glass }}</ion-select-option
|
||||
>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-button
|
||||
slot="start"
|
||||
color="danger"
|
||||
@click="() => router.push('/tabs/Cocktails')"
|
||||
>
|
||||
<ion-icon :icon="trash"></ion-icon>
|
||||
Cancel
|
||||
</ion-button>
|
||||
<ion-button
|
||||
slot="secondary"
|
||||
color="warning"
|
||||
@click="resetForm()"
|
||||
>
|
||||
<ion-icon :icon="refreshOutline"></ion-icon>
|
||||
Reset
|
||||
</ion-button>
|
||||
<ion-button slot="end" color="success" type="submit">
|
||||
<!--v-bind:disabled="errors.any()"-->
|
||||
<ion-icon :icon="createOutline"></ion-icon>
|
||||
Add
|
||||
</ion-button>
|
||||
</ion-toolbar>
|
||||
</form>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useStorage } from "@/composables/useStorage";
|
||||
//import router from "@/router";
|
||||
import glassJson from "../data/glasses.json";
|
||||
import ingredientJson from "../data/ingredients.json";
|
||||
import unitJson from "../data/units.json";
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonLabel,
|
||||
IonItem,
|
||||
IonInput,
|
||||
IonTextarea,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonItemGroup,
|
||||
IonItemDivider,
|
||||
} from "@ionic/vue";
|
||||
import { trash, createOutline,refreshOutline } from "ionicons/icons";
|
||||
export default {
|
||||
name: "Cocktails",
|
||||
components: {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonLabel,
|
||||
IonItem,
|
||||
IonInput,
|
||||
IonTextarea,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonItemGroup,
|
||||
IonItemDivider,
|
||||
},
|
||||
methods: {
|
||||
addIngredientField() {
|
||||
this.cocktail.ingredients.push({
|
||||
amount: 0,
|
||||
unit: "ml",
|
||||
ingredient: null,
|
||||
});
|
||||
},
|
||||
removeIngredient(item) {
|
||||
const index = this.cocktail.ingredients.indexOf(item);
|
||||
if (index > -1) {
|
||||
this.cocktail.ingredients.splice(index, 1);
|
||||
}
|
||||
},
|
||||
resetForm(){
|
||||
this.cocktail = {
|
||||
name: null,
|
||||
ingredients: [{ amount: 0, unit: "ml", ingredient: null }],
|
||||
directions: null,
|
||||
glass: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mode: "New", //"Edit"
|
||||
cocktail: {
|
||||
name: null,
|
||||
ingredients: [{ amount: 0, unit: "ml", ingredient: null }],
|
||||
directions: null,
|
||||
glass: null,
|
||||
},
|
||||
ingredients: ingredientJson,
|
||||
glasses: glassJson,
|
||||
units: unitJson,
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const { addCocktail } = useStorage();
|
||||
|
||||
const doAddCocktail = async (cocktail) => {
|
||||
//console.log("adding: ", cocktail.name);
|
||||
addCocktail(cocktail);
|
||||
router.push("/tabs/Cocktails")
|
||||
};
|
||||
|
||||
return {
|
||||
doAddCocktail,
|
||||
trash,
|
||||
createOutline,
|
||||
refreshOutline,
|
||||
router,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
ion-item {
|
||||
width: 95%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
69
src/views/PageNotFound.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Error 404</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<container>
|
||||
<h1>Error 404</h1>
|
||||
<p>We are sorry, this page does not exist</p>
|
||||
<br />
|
||||
<ion-button color="success" expand="block" href="/"
|
||||
>Back to home</ion-button
|
||||
>
|
||||
</container>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonButton,
|
||||
} from "@ionic/vue";
|
||||
|
||||
export default {
|
||||
name: "404",
|
||||
components: {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonButton,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
container a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
container ion-button {
|
||||
margin: auto;
|
||||
width: 70%;
|
||||
}
|
||||
</style>
|
206
src/views/Shake.vue
Normal file
@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{
|
||||
chosenCocktail ? chosenCocktail.name : "Shake"
|
||||
}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title id="top" size="large">
|
||||
{{ chosenCocktail ? chosenCocktail.name : "Shake" }}
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<!-- View Shake button -->
|
||||
<div class="container" v-if="!chosenCocktail">
|
||||
<h1 class="ion-text-center">
|
||||
Shake your phone <br />
|
||||
to get a Cocktail
|
||||
</h1>
|
||||
<ion-button color="success" expand="block" @click="doShake()"
|
||||
>Start shaking</ion-button
|
||||
>
|
||||
<ion-img
|
||||
class="image-icon"
|
||||
:src="`${publicPath}img/glasses/Martini.svg`"
|
||||
alt="CocktailShaker"
|
||||
></ion-img>
|
||||
</div>
|
||||
|
||||
<!-- Show cocktail -->
|
||||
<div class="container" v-else>
|
||||
<div class="images">
|
||||
<ion-img
|
||||
class="image-icon"
|
||||
v-if="!chosenCocktail.image"
|
||||
:src="`${publicPath}img/glasses/${chosenCocktail.glass}.svg`"
|
||||
:alt="'Glass: ' + chosenCocktail.glass"
|
||||
></ion-img>
|
||||
<ion-img
|
||||
class="bigimage"
|
||||
v-else
|
||||
:src="chosenCocktail.image.webviewPath"
|
||||
:alt="chosenCocktail.glass"
|
||||
></ion-img>
|
||||
</div>
|
||||
<ion-list-header>
|
||||
<ion-label>Ingredients</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-list>
|
||||
<ion-item
|
||||
v-for="ingredient in chosenCocktail.ingredients"
|
||||
:key="ingredient"
|
||||
>
|
||||
<div v-if="ingredient.amount">
|
||||
{{ ingredient.amount }} {{ ingredient.unit }}
|
||||
{{ ingredient.ingredient }}
|
||||
<span class="small"> {{ ingredient.label }}</span>
|
||||
</div>
|
||||
<div v-else>Spezial: {{ ingredient.special }}</div>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<hr />
|
||||
<ion-list-header>
|
||||
<ion-label>Directions</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-item id="shorter">{{ chosenCocktail.directions }}</ion-item>
|
||||
<hr />
|
||||
<div v-if="chosenCocktail.garnish">
|
||||
<ion-list-header>
|
||||
<ion-label>Garnish</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-item id="shorter">{{ chosenCocktail.garnish }}</ion-item>
|
||||
</div>
|
||||
|
||||
<div class="buttonContainer">
|
||||
<!--Take Photo -->
|
||||
<ion-button color="secondary" @click="takePhoto(chosenCocktail)">
|
||||
<ion-icon :icon="camera"></ion-icon>
|
||||
</ion-button>
|
||||
<!--Next Cocktail-->
|
||||
<ion-button color="success" @click="doShake()"
|
||||
>Shake again</ion-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useStorage } from "@/composables/useStorage";
|
||||
import {
|
||||
getPlatforms,
|
||||
IonPage,
|
||||
IonIcon,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonItem,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
IonImg,
|
||||
} from "@ionic/vue";
|
||||
import { camera } from "ionicons/icons";
|
||||
export default {
|
||||
name: "Shake",
|
||||
components: {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonIcon,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonItem,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
IonImg,
|
||||
},
|
||||
data: function () {
|
||||
const { cocktails } = useStorage();
|
||||
return {
|
||||
cocktails: cocktails,
|
||||
chosenCocktail: null,
|
||||
publicPath: process.env.BASE_URL,
|
||||
platform: getPlatforms(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
doShake: function () {
|
||||
const {fetchRandomCocktail} = useStorage();
|
||||
window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
|
||||
if(Math.random() > 0.8){
|
||||
fetchRandomCocktail();
|
||||
}
|
||||
this.chosenCocktail =
|
||||
this.cocktails[Math.floor(Math.random() * this.cocktails.length)];
|
||||
//console.log(this.chosenCocktail.name);
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { takePhoto } = useStorage();
|
||||
|
||||
return {
|
||||
camera,
|
||||
takePhoto,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
padding-bottom: 35px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
ion-badge {
|
||||
margin: 0 3px 0 3px;
|
||||
}
|
||||
ion-list,
|
||||
#shorter {
|
||||
width: 90%;
|
||||
}
|
||||
.images {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
ion-img.image-icon {
|
||||
filter: invert(68%) sepia(39%) saturate(476%) hue-rotate(86deg)
|
||||
brightness(118%) contrast(119%);
|
||||
height: 60px;
|
||||
margin: auto;
|
||||
border: none;
|
||||
}
|
||||
ion-img.bigimage {
|
||||
max-width: 70%;
|
||||
height: auto;
|
||||
border: 1px solid white;
|
||||
padding: 3px;
|
||||
margin: auto;
|
||||
}
|
||||
.small {
|
||||
font-size: smaller;
|
||||
color: rgb(173, 173, 173);
|
||||
}
|
||||
.buttonContainer {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
ion-button {
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
34
src/views/Tabs.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-tabs>
|
||||
<ion-tab-bar slot="bottom">
|
||||
|
||||
<ion-tab-button tab="Shake" href="/tabs/shake">
|
||||
<ion-icon :icon="dice" />
|
||||
<ion-label>Shake</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="Cocktails" href="/tabs/cocktails">
|
||||
<ion-icon :icon="listOutline" />
|
||||
<ion-label>Cocktails</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonTabBar, IonTabButton, IonTabs, IonLabel, IonIcon, IonPage } from '@ionic/vue';
|
||||
import { dice, listOutline } from 'ionicons/icons';
|
||||
|
||||
export default {
|
||||
name: 'Tabs',
|
||||
components: { IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
|
||||
setup() {
|
||||
return {
|
||||
dice,
|
||||
listOutline
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
172
src/views/ViewCocktail.vue
Normal file
@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{
|
||||
chosenCocktail ? chosenCocktail.name : "View"
|
||||
}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title id="top" size="large">
|
||||
{{ chosenCocktail ? chosenCocktail.name : "View" }}
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<!-- View wait -->
|
||||
<div class="container" v-if="!chosenCocktail">
|
||||
<h1 class="ion-text-center">Please wait</h1>
|
||||
</div>
|
||||
<!-- Show cocktail -->
|
||||
<div class="container" v-else>
|
||||
<div class="images">
|
||||
<ion-img
|
||||
class="image-icon"
|
||||
v-if="!chosenCocktail.image"
|
||||
:src="`${publicPath}img/glasses/${chosenCocktail.glass}.svg`"
|
||||
:alt="'Glass: ' + chosenCocktail.glass"
|
||||
></ion-img>
|
||||
<ion-img
|
||||
class="bigimage"
|
||||
v-else
|
||||
:src="chosenCocktail.image.webviewPath"
|
||||
:alt="chosenCocktail.glass"
|
||||
></ion-img>
|
||||
</div>
|
||||
|
||||
<ion-list-header>
|
||||
<ion-label>Ingredients</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-list>
|
||||
<ion-item
|
||||
v-for="ingredient in chosenCocktail.ingredients"
|
||||
:key="ingredient"
|
||||
>
|
||||
<div v-if="ingredient.amount">
|
||||
{{ ingredient.amount }} {{ ingredient.unit }}
|
||||
{{ ingredient.ingredient }}
|
||||
<span class="small"> {{ ingredient.label }}</span>
|
||||
</div>
|
||||
<div v-else>Spezial: {{ ingredient.special }}</div>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<hr />
|
||||
<ion-list-header>
|
||||
<ion-label>Directions</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-item id="shorter">{{ chosenCocktail.directions }}</ion-item>
|
||||
<hr />
|
||||
<div v-if="chosenCocktail.garnish">
|
||||
<ion-list-header>
|
||||
<ion-label>Garnish</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-item id="shorter">{{ chosenCocktail.garnish }}</ion-item>
|
||||
</div>
|
||||
|
||||
<ion-button
|
||||
color="success"
|
||||
expand="block"
|
||||
@click="() => router.push(`/tabs/Shake`)"
|
||||
>get a new Cocktail</ion-button
|
||||
>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useStorage } from "@/composables/useStorage";
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
IonPage,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonItem,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
IonImg,
|
||||
} from "@ionic/vue";
|
||||
export default {
|
||||
name: "Shake",
|
||||
components: {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonItem,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
IonImg,
|
||||
},
|
||||
data() {
|
||||
const { cocktails } = useStorage();
|
||||
return {
|
||||
cocktails: cocktails,
|
||||
publicPath: process.env.BASE_URL
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
chosenCocktail() {
|
||||
return this.cocktails[parseInt(this.$route.params.id, 10)];
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
return {
|
||||
router
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
padding-bottom: 35px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
ion-badge {
|
||||
margin: 0 3px 0 3px;
|
||||
}
|
||||
ion-list,
|
||||
#shorter {
|
||||
width: 90%;
|
||||
}
|
||||
ion-button {
|
||||
margin: 50px;
|
||||
}
|
||||
.images {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
ion-img.image-icon {
|
||||
filter: invert(68%) sepia(39%) saturate(476%) hue-rotate(86deg)
|
||||
brightness(118%) contrast(119%);
|
||||
height: 60px;
|
||||
margin: auto;
|
||||
border: none;
|
||||
}
|
||||
ion-img.bigimage {
|
||||
max-width: 70%;
|
||||
height: auto;
|
||||
border: 1px solid white;
|
||||
padding: 3px;
|
||||
margin: auto;
|
||||
}
|
||||
.small {
|
||||
font-size: smaller;
|
||||
color: rgb(173, 173, 173);
|
||||
}
|
||||
</style>
|
39
tsconfig.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env",
|
||||
"jest"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.js",
|
||||
"tests/**/*.tsx"
|
||||
, "src/main.js" ],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|