init project first version
This commit is contained in:
18
src/App.vue
Normal file
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
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
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
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
2104
src/data/cocktails.json
Normal file
File diff suppressed because it is too large
Load Diff
13
src/data/glasses.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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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>
|
||||
Reference in New Issue
Block a user