mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-03 09:04:06 -05:00
Migration redesign (#119)
* migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me>
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export default {
|
||||
mounted() {
|
||||
this.$store.dispatch("initTheme")
|
||||
this.$store.dispatch("requestRecentRecipes")
|
||||
this.$store.dispatch("initLang")
|
||||
this.darkModeSystemCheck()
|
||||
this.darkModeAddEventListener()
|
||||
},
|
||||
|
||||
@@ -2,42 +2,27 @@ import { baseURL } from "./api-utils";
|
||||
import { apiReq } from "./api-utils";
|
||||
import { store } from "../store/store";
|
||||
|
||||
const migrationBase = baseURL + "migration/";
|
||||
const migrationBase = baseURL + "migrations/";
|
||||
|
||||
const migrationURLs = {
|
||||
upload: migrationBase + "upload/",
|
||||
delete: (file) => `${migrationBase}${file}/delete/`,
|
||||
chowdownURL: migrationBase + "chowdown/repo/",
|
||||
nextcloudAvaiable: migrationBase + "nextcloud/available/",
|
||||
nextcloudImport: (selection) =>
|
||||
`${migrationBase}nextcloud/${selection}/import/`,
|
||||
// New
|
||||
all: migrationBase,
|
||||
delete: (folder, file) => `${migrationBase}/${folder}/${file}/delete/`,
|
||||
import: (folder, file) => `${migrationBase}/${folder}/${file}/import/`,
|
||||
};
|
||||
|
||||
export default {
|
||||
async migrateChowdown(repoURL) {
|
||||
let postBody = { url: repoURL };
|
||||
let response = await apiReq.post(migrationURLs.chowdownURL, postBody);
|
||||
async getMigrations() {
|
||||
let response = await apiReq.get(migrationURLs.all);
|
||||
return response.data;
|
||||
},
|
||||
async delete(folder, file) {
|
||||
let response = await apiReq.delete(migrationURLs.delete(folder, file));
|
||||
return response.data;
|
||||
},
|
||||
async import(folder, file) {
|
||||
let response = await apiReq.post(migrationURLs.import(folder, file));
|
||||
store.dispatch("requestRecentRecipes");
|
||||
return response.data;
|
||||
},
|
||||
async getNextcloudImports() {
|
||||
let response = await apiReq.get(migrationURLs.nextcloudAvaiable);
|
||||
return response.data;
|
||||
},
|
||||
async importNextcloud(selected) {
|
||||
let response = await apiReq.post(migrationURLs.nextcloudImport(selected));
|
||||
return response.data;
|
||||
},
|
||||
async uploadFile(form_data) {
|
||||
let response = await apiReq.post(migrationURLs.upload, form_data, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
async delete(file_folder_name) {
|
||||
let response = await apiReq.delete(migrationURLs.delete(file_folder_name));
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,26 +12,26 @@
|
||||
></v-file-input>
|
||||
</v-col>
|
||||
<v-col cols="3"></v-col>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
label="Total Time"
|
||||
v-model="value.totalTime"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col
|
||||
><v-text-field
|
||||
label="Prep Time"
|
||||
v-model="value.prepTime"
|
||||
></v-text-field
|
||||
></v-col>
|
||||
<v-col
|
||||
><v-text-field
|
||||
label="Cook Time / Perform Time"
|
||||
v-model="value.performTime"
|
||||
></v-text-field
|
||||
></v-col>
|
||||
</v-row>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
label="Total Time"
|
||||
v-model="value.totalTime"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col
|
||||
><v-text-field
|
||||
label="Prep Time"
|
||||
v-model="value.prepTime"
|
||||
></v-text-field
|
||||
></v-col>
|
||||
<v-col
|
||||
><v-text-field
|
||||
label="Cook Time / Perform Time"
|
||||
v-model="value.performTime"
|
||||
></v-text-field
|
||||
></v-col>
|
||||
</v-row>
|
||||
<v-text-field
|
||||
class="my-3"
|
||||
@@ -206,6 +206,11 @@
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<BulkAdd @bulk-data="appendSteps" />
|
||||
<v-text-field
|
||||
v-model="value.orgURL"
|
||||
class="mt-10"
|
||||
label="Original URL"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
v-for="backup in backups"
|
||||
:key="backup.name"
|
||||
>
|
||||
<v-card @click="openDialog(backup)">
|
||||
<v-card hover outlined @click="openDialog(backup)">
|
||||
<v-card-text>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" sm="2">
|
||||
<v-icon color="primary"> mdi-backup-restore </v-icon>
|
||||
<v-icon large color="primary"> mdi-backup-restore </v-icon>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="10">
|
||||
<div>
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
:backups="availableBackups"
|
||||
/>
|
||||
<SuccessFailureAlert
|
||||
ref="report"
|
||||
title="Back Restore Report"
|
||||
success-header="Successfully Imported"
|
||||
:success="successfulImports"
|
||||
failed-header="Failed Imports"
|
||||
@@ -91,6 +93,7 @@ export default {
|
||||
this.backupLoading = false;
|
||||
this.successfulImports = successful;
|
||||
this.failedImports = failed;
|
||||
this.$refs.report.open();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
50
frontend/src/components/Settings/General/index.vue
Normal file
50
frontend/src/components/Settings/General/index.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title> General Settings </v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-select
|
||||
v-model="selectedLang"
|
||||
:items="langOptions"
|
||||
item-text="name"
|
||||
item-value="value"
|
||||
label="Language"
|
||||
>
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-spacer></v-spacer>
|
||||
<v-spacer></v-spacer>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
langOptions: [],
|
||||
selectedLang: "en",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getOptions();
|
||||
},
|
||||
watch: {
|
||||
selectedLang() {
|
||||
this.$store.commit("setLang", this.selectedLang);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getOptions() {
|
||||
this.langOptions = this.$store.getters.getAllLangs;
|
||||
this.selectedLang = this.$store.getters.getActiveLang;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
97
frontend/src/components/Settings/Migration/MigrationCard.vue
Normal file
97
frontend/src/components/Settings/Migration/MigrationCard.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<v-card class="my-2" :loading="loading">
|
||||
<v-card-title>
|
||||
{{ title }}
|
||||
<v-spacer></v-spacer>
|
||||
<span>
|
||||
<UploadBtn
|
||||
class="mt-1"
|
||||
:url="`/api/migrations/${folder}/upload/`"
|
||||
@uploaded="$emit('refresh')"
|
||||
/>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text> {{ description }}</v-card-text>
|
||||
<div v-if="available[0]">
|
||||
<v-card
|
||||
outlined
|
||||
v-for="migration in available"
|
||||
:key="migration.name"
|
||||
class="ma-2"
|
||||
>
|
||||
<v-card-text>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" sm="2">
|
||||
<v-icon large color="primary"> mdi-import </v-icon>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="10">
|
||||
<div>
|
||||
<strong>{{ migration.name }}</strong>
|
||||
</div>
|
||||
<div>{{ readableTime(migration.date) }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="mt-n6">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="error" text @click="deleteMigration(migration.name)">
|
||||
Delete
|
||||
</v-btn>
|
||||
<v-btn color="accent" text @click="importMigration(migration.name)">
|
||||
Import
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-card class="text-center ma-2">
|
||||
<v-card-text>
|
||||
No Migration Data Avaiable
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
<br />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UploadBtn from "../../UI/UploadBtn";
|
||||
import utils from "../../../utils";
|
||||
import api from "../../../api";
|
||||
export default {
|
||||
props: {
|
||||
folder: String,
|
||||
title: String,
|
||||
description: String,
|
||||
available: Array,
|
||||
},
|
||||
components: {
|
||||
UploadBtn,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
deleteMigration(file_name) {
|
||||
api.migrations.delete(this.folder, file_name);
|
||||
this.$emit("refresh");
|
||||
},
|
||||
async importMigration(file_name) {
|
||||
this.loading == true;
|
||||
let response = await api.migrations.import(this.folder, file_name);
|
||||
console.log(response);
|
||||
this.$emit("imported", response.successful, response.failed);
|
||||
this.loading == false;
|
||||
},
|
||||
readableTime(timestamp) {
|
||||
let date = new Date(timestamp);
|
||||
return utils.getDateAsText(date);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,44 +1,96 @@
|
||||
<template>
|
||||
<v-card :loading="loading">
|
||||
<v-card-title class="headline"> {{$t('migration.recipe-migration')}} </v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<div>
|
||||
<SuccessFailureAlert
|
||||
title="Migration Report"
|
||||
ref="report"
|
||||
failedHeader="Failed Imports"
|
||||
:failed="failed"
|
||||
successHeader="Successful Imports"
|
||||
:success="success"
|
||||
/>
|
||||
<v-card :loading="loading">
|
||||
<v-card-title class="headline">
|
||||
{{ $t("migration.recipe-migration") }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
</v-card>
|
||||
|
||||
<v-tabs v-model="tab">
|
||||
<v-tab>Chowdown</v-tab>
|
||||
<v-tab>Nextcloud Recipes</v-tab>
|
||||
|
||||
<v-tab-item>
|
||||
<ChowdownCard @loading="loading = true" @finished="finished" />
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<NextcloudCard @loading="loading = true" @finished="finished" />
|
||||
</v-tab-item>
|
||||
</v-tabs>
|
||||
</v-card>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
:sm="6"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
:xl="3"
|
||||
v-for="migration in migrations"
|
||||
:key="migration.title"
|
||||
>
|
||||
<MigrationCard
|
||||
:title="migration.title"
|
||||
:folder="migration.urlVariable"
|
||||
:description="migration.description"
|
||||
:available="migration.availableImports"
|
||||
@refresh="getAvailableMigrations"
|
||||
@imported="showReport"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import ChowdownCard from "./ChowdownCard";
|
||||
import NextcloudCard from "./NextcloudCard";
|
||||
// import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
// import TimePicker from "./Webhooks/TimePicker";
|
||||
import MigrationCard from "./MigrationCard";
|
||||
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
import api from "../../../api";
|
||||
export default {
|
||||
components: {
|
||||
ChowdownCard,
|
||||
NextcloudCard,
|
||||
MigrationCard,
|
||||
SuccessFailureAlert,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tab: null,
|
||||
loading: false,
|
||||
success: [],
|
||||
failed: [],
|
||||
migrations: {
|
||||
nextcloud: {
|
||||
title: "Nextcloud Cookbook",
|
||||
description: "migrate data from a nextcloud cookbook intance",
|
||||
urlVariable: "nextcloud",
|
||||
availableImports: [],
|
||||
},
|
||||
chowdown: {
|
||||
title: "Chowdown",
|
||||
description: "Migrate From Chowdown",
|
||||
urlVariable: "chowdown",
|
||||
availableImports: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getAvailableMigrations();
|
||||
},
|
||||
methods: {
|
||||
finished() {
|
||||
this.loading = false;
|
||||
this.$store.dispatch("requestRecentRecipes");
|
||||
},
|
||||
async getAvailableMigrations() {
|
||||
let response = await api.migrations.getMigrations();
|
||||
response.forEach(element => {
|
||||
if (element.type === "nextcloud") {
|
||||
this.migrations.nextcloud.availableImports = element.files;
|
||||
} else if (element.type === "chowdown") {
|
||||
this.migrations.chowdown.availableImports = element.files;
|
||||
}
|
||||
});
|
||||
},
|
||||
showReport(successful, failed) {
|
||||
this.success = successful;
|
||||
this.failed = failed;
|
||||
this.$refs.report.open();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,36 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-btn block :color="value" @click="dialog = true">
|
||||
{{ buttonText }}
|
||||
</v-btn>
|
||||
<v-dialog v-model="dialog" width="400">
|
||||
<v-card>
|
||||
<v-card-title> {{ buttonText }} {{$t('settings.color')}} </v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field v-model="color"> </v-text-field>
|
||||
<v-row>
|
||||
<v-col></v-col>
|
||||
<v-col>
|
||||
<v-color-picker
|
||||
dot-size="28"
|
||||
hide-inputs
|
||||
hide-mode-switch
|
||||
mode="hexa"
|
||||
:show-swatches="swatches"
|
||||
swatches-max-height="300"
|
||||
v-model="color"
|
||||
@change="updateColor"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
<v-col></v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn text @click="toggleSwatches"> {{$t('settings.swatches')}} </v-btn>
|
||||
<v-btn text @click="dialog = false"> {{$t('general.select')}} </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<div class="text-center">
|
||||
<h3>{{ buttonText }}</h3>
|
||||
</div>
|
||||
<v-text-field v-model="color" hide-details class="ma-0 pa-0" solo>
|
||||
<template v-slot:append>
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
top
|
||||
nudge-bottom="105"
|
||||
nudge-left="16"
|
||||
:close-on-content-click="false"
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<div :style="swatchStyle" v-on="on" swatches-max-height="300" />
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-text class="pa-0">
|
||||
<v-color-picker v-model="color" flat show-swatches />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -44,21 +36,30 @@ export default {
|
||||
return {
|
||||
dialog: false,
|
||||
swatches: false,
|
||||
color: "#FF00FF",
|
||||
color: "#1976D2",
|
||||
mask: "!#XXXXXXXX",
|
||||
menu: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
swatchStyle() {
|
||||
const { value, menu } = this;
|
||||
return {
|
||||
backgroundColor: value,
|
||||
cursor: "pointer",
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
borderRadius: menu ? "50%" : "4px",
|
||||
transition: "border-radius 200ms ease-in-out",
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
color() {
|
||||
this.updateColor();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleSwatches() {
|
||||
if (this.swatches) {
|
||||
this.swatches = false;
|
||||
} else this.swatches = true;
|
||||
},
|
||||
updateColor() {
|
||||
this.$emit("input", this.color);
|
||||
},
|
||||
|
||||
@@ -1,32 +1,50 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-alert v-if="success[0]" outlined dense type="success">
|
||||
<h4>{{ successHeader }}</h4>
|
||||
<v-list dense>
|
||||
<v-list-item v-for="success in this.success" :key="success">
|
||||
{{ success }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-alert>
|
||||
<v-alert v-if="failed[0]" outlined dense type="error">
|
||||
<h4>{{ failedHeader }}</h4>
|
||||
<v-list dense>
|
||||
<v-list-item v-for="fail in this.failed" :key="fail">
|
||||
{{ fail }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-alert>
|
||||
</div>
|
||||
<v-dialog v-model="dialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title> {{ title }} </v-card-title>
|
||||
<v-card-text class="mt-3">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-alert outlined dense type="success">
|
||||
<h4>{{ successHeader }}</h4>
|
||||
<p v-for="success in this.success" :key="success" class="my-1">
|
||||
- {{ success }}
|
||||
</p>
|
||||
</v-alert>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-alert v-if="failed[0]" outlined dense type="error">
|
||||
<h4>{{ failedHeader }}</h4>
|
||||
<p v-for="fail in this.failed" :key="fail" class="my-1">
|
||||
- {{ fail }}
|
||||
</p>
|
||||
</v-alert>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: String,
|
||||
successHeader: String,
|
||||
success: Array,
|
||||
failedHeader: String,
|
||||
failed: Array,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import Vue from "vue";
|
||||
import VueI18n from "vue-i18n";
|
||||
|
||||
Vue.use(VueI18n)
|
||||
Vue.use(VueI18n);
|
||||
|
||||
function loadLocaleMessages () {
|
||||
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
|
||||
const messages = {}
|
||||
function loadLocaleMessages() {
|
||||
const locales = require.context(
|
||||
"./locales",
|
||||
true,
|
||||
/[A-Za-z0-9-_,\s]+\.json$/i
|
||||
);
|
||||
const messages = {};
|
||||
locales.keys().forEach(key => {
|
||||
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
|
||||
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
|
||||
if (matched && matched.length > 1) {
|
||||
const locale = matched[1]
|
||||
messages[locale] = locales(key)
|
||||
const locale = matched[1];
|
||||
messages[locale] = locales(key);
|
||||
}
|
||||
})
|
||||
return messages
|
||||
});
|
||||
return messages;
|
||||
}
|
||||
|
||||
export default new VueI18n({
|
||||
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
|
||||
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
|
||||
messages: loadLocaleMessages()
|
||||
})
|
||||
locale: "en",
|
||||
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en",
|
||||
messages: loadLocaleMessages(),
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import vuetify from "./plugins/vuetify";
|
||||
import store from "./store/store";
|
||||
import VueRouter from "vue-router";
|
||||
import { routes } from "./routes";
|
||||
import i18n from './i18n'
|
||||
import i18n from "./i18n";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.use(VueRouter);
|
||||
@@ -14,12 +14,13 @@ const router = new VueRouter({
|
||||
mode: process.env.NODE_ENV === "production" ? "history" : "hash",
|
||||
});
|
||||
|
||||
|
||||
new Vue({
|
||||
vuetify,
|
||||
store,
|
||||
router,
|
||||
i18n,
|
||||
render: (h) => h(App)
|
||||
render: h => h(App),
|
||||
}).$mount("#app");
|
||||
|
||||
// Truncate
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
"
|
||||
>
|
||||
</v-alert>
|
||||
<Theme />
|
||||
<General />
|
||||
<Theme class="mt-2" />
|
||||
<Backup class="mt-2" />
|
||||
<Webhooks class="mt-2" />
|
||||
<Migration class="mt-2" />
|
||||
@@ -39,6 +40,7 @@
|
||||
|
||||
<script>
|
||||
import Backup from "../components/Settings/Backup";
|
||||
import General from "../components/Settings/General";
|
||||
import Webhooks from "../components/Settings/Webhook";
|
||||
import Theme from "../components/Settings/Theme";
|
||||
import Migration from "../components/Settings/Migration";
|
||||
@@ -50,6 +52,7 @@ export default {
|
||||
Webhooks,
|
||||
Theme,
|
||||
Migration,
|
||||
General,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
44
frontend/src/store/modules/language.js
Normal file
44
frontend/src/store/modules/language.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import VueI18n from "../../i18n";
|
||||
|
||||
const state = {
|
||||
lang: "en",
|
||||
allLangs: [
|
||||
{
|
||||
name: "English",
|
||||
value: "en",
|
||||
},
|
||||
{
|
||||
name: "Dutch",
|
||||
value: "da",
|
||||
},
|
||||
{
|
||||
name: "French",
|
||||
value: "fr",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
setLang(state, payload) {
|
||||
VueI18n.locale = payload;
|
||||
state.lang = payload;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
initLang({ getters }) {
|
||||
VueI18n.locale = getters.getActiveLang;
|
||||
},
|
||||
};
|
||||
|
||||
const getters = {
|
||||
getActiveLang: (state) => state.lang,
|
||||
getAllLangs: (state) => state.allLangs,
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
};
|
||||
@@ -3,17 +3,19 @@ import Vuex from "vuex";
|
||||
import api from "../api";
|
||||
import createPersistedState from "vuex-persistedstate";
|
||||
import userSettings from "./modules/userSettings";
|
||||
import language from "./modules/language";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
paths: ["userSettings"],
|
||||
paths: ["userSettings", "language"],
|
||||
}),
|
||||
],
|
||||
modules: {
|
||||
userSettings,
|
||||
language,
|
||||
},
|
||||
state: {
|
||||
// Snackbar
|
||||
@@ -59,11 +61,11 @@ const store = new Vuex.Store({
|
||||
|
||||
getters: {
|
||||
//
|
||||
getSnackText: (state) => state.snackText,
|
||||
getSnackActive: (state) => state.snackActive,
|
||||
getSnackType: (state) => state.snackType,
|
||||
getSnackText: state => state.snackText,
|
||||
getSnackActive: state => state.snackActive,
|
||||
getSnackType: state => state.snackType,
|
||||
|
||||
getRecentRecipes: (state) => state.recentRecipes,
|
||||
getRecentRecipes: state => state.recentRecipes,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user