init project first version

This commit is contained in:
2021-06-17 20:31:10 +02:00
commit 60bf85d0d3
52 changed files with 17943 additions and 0 deletions

18
src/App.vue Normal file
View 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>

View 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
}
}

View 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
}
}

View 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

File diff suppressed because it is too large Load Diff

13
src/data/glasses.json Normal file
View 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
View 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": "Peychauds 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
View File

@@ -0,0 +1,10 @@
[
"Piece",
"ml",
"cl",
"Dash",
"Splash",
"Teaspoon",
"Barspoon",
"Slice"
]

43
src/main.js Normal file
View 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);

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

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

View 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
View 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">&nbsp;&nbsp; {{ 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
View 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
View 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">&nbsp;&nbsp; {{ 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>