consolidate theme into profile

This commit is contained in:
hay-kot 2021-05-04 20:17:38 -08:00
commit 49a016dc85
17 changed files with 371 additions and 521 deletions

View file

@ -6,10 +6,10 @@ const prefix = baseURL + "themes";
const settingsURLs = {
allThemes: `${baseURL}themes`,
specificTheme: themeName => `${prefix}/${themeName}`,
specificTheme: id => `${prefix}/${id}`,
createTheme: `${prefix}/create`,
updateTheme: themeName => `${prefix}/${themeName}`,
deleteTheme: themeName => `${prefix}/${themeName}`,
updateTheme: id => `${prefix}/${id}`,
deleteTheme: id => `${prefix}/${id}`,
};
export const themeAPI = {
@ -32,22 +32,18 @@ export const themeAPI = {
);
},
update(themeName, colors) {
const body = {
name: themeName,
colors: colors,
};
update(data) {
return apiReq.put(
settingsURLs.updateTheme(themeName),
body,
settingsURLs.updateTheme(data.id),
data,
() => i18n.t("settings.theme.error-updating-theme"),
() => i18n.t("settings.theme.theme-updated")
);
},
delete(themeName) {
delete(id) {
return apiReq.delete(
settingsURLs.deleteTheme(themeName),
settingsURLs.deleteTheme(id),
null,
() => i18n.t("settings.theme.error-deleting-theme"),
() => i18n.t("settings.theme.theme-deleted")

View file

@ -3,7 +3,7 @@
<div class="text-center">
<h3>{{ buttonText }}</h3>
</div>
<v-text-field v-model="color" hide-details class="ma-0 pa-0" solo v-show="$vuetify.breakpoint.mdAndUp">
<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 }">
@ -17,15 +17,7 @@
</v-menu>
</template>
</v-text-field>
<div class="text-center" v-show="$vuetify.breakpoint.smAndDown">
<v-menu v-model="menu" top nudge-bottom="105" nudge-left="16" :close-on-content-click="false">
<template v-slot:activator="{ on, attrs }">
<v-chip label :color="`${color}`" dark v-bind="attrs" v-on="on">
{{ color }}
</v-chip>
</template>
</v-menu>
</div>
</div>
</template>

View file

@ -2,7 +2,7 @@
<v-form ref="file">
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
<slot v-bind="{ isSelecting, onButtonClick }">
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" :text="textBtn">
<v-btn :loading="isSelecting" @click="onButtonClick" :small="small" color="accent" :text="textBtn">
<v-icon left> {{ icon }}</v-icon>
{{ text ? text : defaultText }}
</v-btn>
@ -15,6 +15,9 @@ const UPLOAD_EVENT = "uploaded";
import { api } from "@/api";
export default {
props: {
small: {
default: false,
},
post: {
type: Boolean,
default: true,
@ -27,7 +30,7 @@ export default {
default: true,
},
},
data: () => ({
data: () => ({
file: null,
isSelecting: false,
}),

View file

@ -18,6 +18,10 @@
{{ $t("general.cancel") }}
</v-btn>
<v-spacer></v-spacer>
<v-btn color="error" text @click="deleteEvent" v-if="$listeners.delete">
{{ $t("general.delete") }}
</v-btn>
<v-btn color="success" @click="submitEvent">
{{ submitText }}
</v-btn>
@ -87,6 +91,10 @@ export default {
close() {
this.dialog = false;
},
deleteEvent() {
this.$emit("delete");
this.submitted = true;
},
},
};
</script>

View file

@ -23,7 +23,7 @@ w<template>
<slot />
<template v-if="$slots.actions">
<v-divider class="mt-2" v-if="!$slots.bottom" />
<v-divider class="mt-2" />
<v-card-actions class="pb-0">
<slot name="actions" />

View file

@ -133,11 +133,6 @@ export default {
to: "/admin/profile",
title: this.$t("settings.profile"),
},
{
icon: "mdi-format-color-fill",
to: "/admin/themes",
title: this.$t("general.themes"),
},
{
icon: "mdi-food",
to: "/admin/meal-planner",

View file

@ -0,0 +1,215 @@
<template>
<div>
<StatCard icon="mdi-format-color-fill" :color="color">
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div class="body-3 grey--text font-weight-light" v-text="$t('general.themes')" />
<h3 class="display-2 font-weight-light text--primary">
<small> {{ selectedTheme.name }} </small>
</h3>
</div>
</template>
<template v-slot:actions>
<v-btn-toggle v-model="darkMode" color="primary " mandatory>
<v-btn small value="system">
<v-icon>mdi-desktop-tower-monitor</v-icon>
<span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
{{ $t("settings.theme.default-to-system") }}
</span>
</v-btn>
<v-btn small value="light">
<v-icon>mdi-white-balance-sunny</v-icon>
<span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
{{ $t("settings.theme.light") }}
</span>
</v-btn>
<v-btn small value="dark">
<v-icon>mdi-weather-night</v-icon>
<span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
{{ $t("settings.theme.dark") }}
</span>
</v-btn>
</v-btn-toggle>
</template>
<template v-slot:bottom>
<v-virtual-scroll height="290" item-height="70" :items="availableThemes" class="mt-2">
<template v-slot:default="{ item }">
<v-list-item @click="selectedTheme = item">
<v-list-item-avatar>
<v-icon large dark :color="item.colors.primary">
mdi-format-color-fill
</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title>
<v-row flex align-center class="mt-2 justify-space-around px-4 pb-2">
<v-sheet
class="rounded flex mx-1"
v-for="(item, index) in item.colors"
:key="index"
:color="item"
height="20"
>
</v-sheet>
</v-row>
</v-list-item-content>
<v-list-item-action class="ml-auto">
<v-btn large icon @click.stop="editTheme(item)">
<v-icon color="accent">mdi-square-edit-outline</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</template>
</v-virtual-scroll>
<v-divider></v-divider>
<v-card-actions>
<v-spacer class="mx-2"></v-spacer>
<v-btn class="my-1 mb-n1" :color="color" @click="createTheme">
<v-icon left> mdi-plus </v-icon> {{ $t("general.create") }}
</v-btn>
</v-card-actions>
</template>
</StatCard>
<BaseDialog
:loading="loading"
:title="modalLabel.title"
title-icon="mdi-format-color-fill"
modal-width="700"
ref="themeDialog"
:submit-text="modalLabel.button"
@submit="processSubmit"
@delete="deleteTheme"
>
<v-card-text class="mt-3">
<v-text-field
:label="$t('settings.theme.theme-name')"
v-model="defaultData.name"
:rules="[rules.required]"
></v-text-field>
<v-row dense dflex wrap justify-content-center v-if="defaultData.colors">
<v-col cols="12" sm="6" v-for="(_, key) in defaultData.colors" :key="key">
<ColorPickerDialog :button-text="labels[key]" v-model="defaultData.colors[key]" />
</v-col>
</v-row>
</v-card-text>
</BaseDialog>
</div>
</template>
<script>
import { api } from "@/api";
import ColorPickerDialog from "@/components/FormHelpers/ColorPickerDialog";
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
import StatCard from "@/components/UI/StatCard";
export default {
components: { StatCard, BaseDialog, ColorPickerDialog },
data() {
return {
availableThemes: [],
color: "accent",
newTheme: false,
loading: false,
defaultData: {
name: "",
colors: {
primary: "#E58325",
accent: "#00457A",
secondary: "#973542",
success: "#43A047",
info: "#4990BA",
warning: "#FF4081",
error: "#EF5350",
},
},
rules: {
required: val => !!val || this.$t("settings.theme.theme-name-is-required"),
},
};
},
computed: {
labels() {
return {
primary: this.$t("settings.theme.primary"),
secondary: this.$t("settings.theme.secondary"),
accent: this.$t("settings.theme.accent"),
success: this.$t("settings.theme.success"),
info: this.$t("settings.theme.info"),
warning: this.$t("settings.theme.warning"),
error: this.$t("settings.theme.error"),
};
},
modalLabel() {
if (this.newTheme) {
return {
title: this.$t("settings.add-a-new-theme"),
button: this.$t("general.create"),
};
} else {
return {
title: "Update Theme",
button: this.$t("general.update"),
};
}
},
selectedTheme: {
set(val) {
this.$store.commit("setTheme", val);
},
get() {
return this.$store.getters.getActiveTheme;
},
},
darkMode: {
set(val) {
this.$store.commit("setDarkMode", val);
},
get() {
return this.$store.getters.getDarkMode;
},
},
},
async mounted() {
await this.getAllThemes();
},
methods: {
async getAllThemes() {
this.availableThemes = await api.themes.requestAll();
},
editTheme(theme) {
console.log(theme);
this.defaultData = theme;
this.newTheme = false;
this.$refs.themeDialog.open();
},
createTheme() {
this.newTheme = true;
this.$refs.themeDialog.open();
console.log("Create Theme");
},
async processSubmit() {
if (this.newTheme) {
console.log("New Theme");
await api.themes.create(this.defaultData);
} else {
await api.themes.update(this.defaultData);
}
this.getAllThemes();
},
async deleteTheme() {
console.log(this.defaultData);
await api.themes.delete(this.defaultData.id);
this.getAllThemes();
},
},
};
</script>
<style lang="scss" scoped>
</style>

View file

@ -1,120 +1,105 @@
<template>
<div class="mt-10">
<v-row>
<v-col cols="12" sm="12" md="6">
<StatCard icon="mdi-account">
<template v-slot:avatar>
<v-avatar color="accent" size="120" class="white--text headline mt-n12">
<img :src="userProfileImage" v-if="!hideImage" @error="hideImage = true" />
<div v-else>
{{ initials }}
</div>
</v-avatar>
</template>
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div
class="body-3 grey--text font-weight-light"
v-text="$t('user.user-id-with-value', { id: user.id })"
/>
<StatCard icon="mdi-account">
<template v-slot:avatar>
<v-avatar color="accent" size="120" class="white--text headline mt-n16">
<img :src="userProfileImage" v-if="!hideImage" @error="hideImage = true" />
<div v-else>
{{ initials }}
</div>
</v-avatar>
</template>
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div class="body-3 grey--text font-weight-light" v-text="$t('user.user-id-with-value', { id: user.id })" />
<h3 class="display-2 font-weight-light text--primary">
<small> {{ $t("group.group") }}: {{ user.group }} </small>
</h3>
</div>
</template>
<template v-slot:actions>
<TheUploadBtn
icon="mdi-image-area"
:text="$t('user.upload-photo')"
:url="userProfileImage"
file-name="profile_image"
/>
<h3 class="display-2 font-weight-light text--primary">
<small> {{ $t("group.group") }}: {{ user.group }} </small>
</h3>
</div>
</template>
<template v-slot:actions>
<BaseDialog
:title="$t('user.reset-password')"
title-icon="mdi-lock"
:submit-text="$t('settings.change-password')"
@submit="changePassword"
:loading="loading"
:top="true"
>
<template v-slot:open="{ open }">
<v-btn color="primary" class="mr-1" small @click="open">
<v-icon left>mdi-lock</v-icon>
Change Password
</v-btn>
</template>
<v-spacer></v-spacer>
<BaseDialog
:title="$t('user.reset-password')"
title-icon="mdi-lock"
:submit-text="$t('settings.change-password')"
@submit="changePassword"
:loading="loading"
:top="true"
>
<template v-slot:open="{ open }">
<v-btn color="primary" class="mr-1" small @click="open">
<v-icon left>mdi-lock</v-icon>
Change Password
</v-btn>
</template>
<v-card-text>
<v-form ref="passChange">
<v-text-field
v-model="password.current"
prepend-icon="mdi-lock"
:label="$t('user.current-password')"
:rules="[existsRule]"
validate-on-blur
:type="showPassword ? 'text' : 'password'"
@click:append="showPassword.current = !showPassword.current"
></v-text-field>
<v-text-field
v-model="password.newOne"
prepend-icon="mdi-lock"
:label="$t('user.new-password')"
:rules="[minRule]"
:type="showPassword ? 'text' : 'password'"
@click:append="showPassword.newOne = !showPassword.newOne"
></v-text-field>
<v-text-field
v-model="password.newTwo"
prepend-icon="mdi-lock"
:label="$t('user.confirm-password')"
:rules="[password.newOne === password.newTwo || $t('user.password-must-match')]"
validate-on-blur
:type="showPassword ? 'text' : 'password'"
@click:append="showPassword.newTwo = !showPassword.newTwo"
></v-text-field>
</v-form>
</v-card-text>
</BaseDialog>
<v-btn color="success" small class="mr-2" @click="updateUser">
<v-icon left> mdi-content-save </v-icon>
{{ $t("general.update") }}
</v-btn>
</template>
<template v-slot:bottom>
<v-card-text>
<v-form>
<v-text-field
:label="$t('user.full-name')"
required
v-model="user.fullName"
:rules="[existsRule]"
validate-on-blur
>
</v-text-field>
<v-text-field
:label="$t('user.email')"
:rules="[emailRule]"
validate-on-blur
required
v-model="user.email"
>
</v-text-field>
</v-form>
</v-card-text>
</template>
</StatCard>
</v-col>
<v-col cols="12" sm="12" md="6"> </v-col>
</v-row>
</div>
<v-card-text>
<v-form ref="passChange">
<v-text-field
v-model="password.current"
prepend-icon="mdi-lock"
:label="$t('user.current-password')"
:rules="[existsRule]"
validate-on-blur
:type="showPassword ? 'text' : 'password'"
@click:append="showPassword.current = !showPassword.current"
></v-text-field>
<v-text-field
v-model="password.newOne"
prepend-icon="mdi-lock"
:label="$t('user.new-password')"
:rules="[minRule]"
:type="showPassword ? 'text' : 'password'"
@click:append="showPassword.newOne = !showPassword.newOne"
></v-text-field>
<v-text-field
v-model="password.newTwo"
prepend-icon="mdi-lock"
:label="$t('user.confirm-password')"
:rules="[password.newOne === password.newTwo || $t('user.password-must-match')]"
validate-on-blur
:type="showPassword ? 'text' : 'password'"
@click:append="showPassword.newTwo = !showPassword.newTwo"
></v-text-field>
</v-form>
</v-card-text>
</BaseDialog>
</template>
<template v-slot:bottom>
<v-card-text>
<v-form>
<v-text-field
:label="$t('user.full-name')"
required
v-model="user.fullName"
:rules="[existsRule]"
validate-on-blur
>
</v-text-field>
<v-text-field :label="$t('user.email')" :rules="[emailRule]" validate-on-blur required v-model="user.email">
</v-text-field>
</v-form>
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pb-1 pt-3">
<TheUploadBtn
icon="mdi-image-area"
:text="$t('user.upload-photo')"
:url="userProfileImage"
file-name="profile_image"
/>
<v-spacer></v-spacer>
<v-btn color="success" @click="updateUser">
<v-icon left> mdi-content-save </v-icon>
{{ $t("general.update") }}
</v-btn>
</v-card-actions>
</template>
</StatCard>
</template>
<script>
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
import StatCard from "@/components/UI/StatCard";
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";

View file

@ -1,14 +1,27 @@
<template>
<div class="mt-10">
<UserCard />
<v-row>
<v-col cols="12" sm="12" lg="6">
<UserCard />
</v-col>
<v-col cols="12" sm="12" lg="6"> </v-col>
</v-row>
<v-row class="mt-7">
<v-col cols="12" sm="12" lg="6">
<ThemeCard />
</v-col>
<v-col cols="12" sm="12" lg="6"> </v-col>
</v-row>
</div>
</template>
<script>
import ThemeCard from "./ThemeCard";
import UserCard from "./UserCard";
export default {
components: {
UserCard,
ThemeCard,
},
};
</script>

View file

@ -1,89 +0,0 @@
<template>
<div>
<v-btn text color="info" @click="dialog = true">
{{ $t("settings.add-a-new-theme") }}
</v-btn>
<v-dialog v-model="dialog" width="500">
<v-card>
<v-app-bar dense dark color="primary mb-2">
<v-icon large left class="mt-1">
mdi-format-color-fill
</v-icon>
<v-toolbar-title class="headline">
{{ $t("settings.add-a-new-theme") }}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-card-title> </v-card-title>
<v-form @submit.prevent="select">
<v-card-text>
<v-text-field
:label="$t('settings.theme.theme-name')"
v-model="themeName"
:rules="[rules.required]"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="dialog = false">
{{ $t("general.cancel") }}
</v-btn>
<v-btn color="success" text type="submit" :disabled="!themeName">
{{ $t("general.create") }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
props: {
buttonText: String,
value: String,
},
data() {
return {
dialog: false,
themeName: "",
rules: {
required: val => !!val || this.$t("settings.theme.theme-name-is-required"),
},
};
},
watch: {
color() {
this.updateColor();
},
},
methods: {
randomColor() {
return "#" + Math.floor(Math.random() * 16777215).toString(16);
},
select() {
const newTheme = {
name: this.themeName,
colors: {
primary: "#E58325",
accent: "#00457A",
secondary: "#973542",
success: "#5AB1BB",
info: "#4990BA",
warning: "#FF4081",
error: "#EF5350",
},
};
this.$emit("new-theme", newTheme);
this.dialog = false;
},
},
};
</script>
<style></style>

View file

@ -1,88 +0,0 @@
<template>
<div>
<ConfirmationDialog
:title="$t('settings.theme.delete-theme')"
:message="$t('settings.theme.are-you-sure-you-want-to-delete-this-theme')"
color="error"
icon="mdi-alert-circle"
ref="deleteThemeConfirm"
v-on:confirm="deleteSelectedTheme()"
/>
<v-card flat outlined class="ma-2">
<v-card-text class="mb-n5 mt-n2">
<h3>
{{ theme.name }}
{{ current ? $t("general.current-parenthesis") : "" }}
</h3>
</v-card-text>
<v-card-text>
<v-row flex align-center>
<v-card
v-for="(color, index) in theme.colors"
:key="index"
class="ma-1 mx-auto"
height="34"
width="36"
:color="color"
>
</v-card>
</v-row>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn text color="error" @click="confirmDelete">
{{ $t("general.delete") }}
</v-btn>
<v-spacer></v-spacer>
<!-- <v-btn text color="accent" @click="editTheme">Edit</v-btn> -->
<v-btn text color="success" @click="saveThemes">{{ $t("general.apply") }}</v-btn>
</v-card-actions>
</v-card>
</div>
</template>
<script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api";
const DELETE_EVENT = "delete";
const APPLY_EVENT = "apply";
const EDIT_EVENT = "edit";
export default {
components: {
ConfirmationDialog,
},
props: {
theme: Object,
current: {
default: false,
},
},
methods: {
confirmDelete() {
if (this.theme.name === "default") {
// Notify User Can't Delete Default
} else if (this.theme !== {}) {
this.$refs.deleteThemeConfirm.open();
}
},
async deleteSelectedTheme() {
//Delete Theme from DB
if (await api.themes.delete(this.theme.name)) {
//Get the new list of available from DB
this.availableThemes = await api.themes.requestAll();
this.$emit(DELETE_EVENT);
}
},
async saveThemes() {
this.$store.commit("setTheme", this.theme);
this.$emit(APPLY_EVENT, this.theme);
},
editTheme() {
this.$emit(EDIT_EVENT);
},
},
};
</script>
<style></style>

View file

@ -1,155 +0,0 @@
<template>
<v-card>
<v-card-title class="headline">
{{ $t("settings.theme.theme-settings") }}
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<h2 class="mt-4 mb-1">{{ $t("settings.theme.dark-mode") }}</h2>
<p>
{{
$t(
"settings.theme.choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme"
)
}}
</p>
<v-row dense align="center">
<v-col cols="6">
<v-btn-toggle v-model="selectedDarkMode" color="primary " mandatory @change="setStoresDarkMode">
<v-btn value="system">
<v-icon>mdi-desktop-tower-monitor</v-icon>
<span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
{{ $t("settings.theme.default-to-system") }}
</span>
</v-btn>
<v-btn value="light">
<v-icon>mdi-white-balance-sunny</v-icon>
<span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
{{ $t("settings.theme.light") }}
</span>
</v-btn>
<v-btn value="dark">
<v-icon>mdi-weather-night</v-icon>
<span class="ml-1" v-show="$vuetify.breakpoint.smAndUp">
{{ $t("settings.theme.dark") }}
</span>
</v-btn>
</v-btn-toggle>
</v-col>
</v-row></v-card-text
>
<v-divider></v-divider>
<v-card-text>
<h2 class="mt-1 mb-1">{{ $t("settings.theme.theme") }}</h2>
<p>
{{
$t(
"settings.theme.select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference"
)
}}
</p>
<v-row dense align-content="center" v-if="selectedTheme.colors">
<v-col>
<ColorPickerDialog :button-text="$t('settings.theme.primary')" v-model="selectedTheme.colors.primary" />
</v-col>
<v-col>
<ColorPickerDialog :button-text="$t('settings.theme.secondary')" v-model="selectedTheme.colors.secondary" />
</v-col>
<v-col>
<ColorPickerDialog :button-text="$t('settings.theme.accent')" v-model="selectedTheme.colors.accent" />
</v-col>
<v-col>
<ColorPickerDialog :button-text="$t('settings.theme.success')" v-model="selectedTheme.colors.success" />
</v-col>
<v-col>
<ColorPickerDialog :button-text="$t('settings.theme.info')" v-model="selectedTheme.colors.info" />
</v-col>
<v-col>
<ColorPickerDialog :button-text="$t('settings.theme.warning')" v-model="selectedTheme.colors.warning" />
</v-col>
<v-col>
<ColorPickerDialog :button-text="$t('settings.theme.error')" v-model="selectedTheme.colors.error" />
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="12" sm="12" md="6" lg="4" xl="3" v-for="theme in availableThemes" :key="theme.name">
<ThemeCard :theme="theme" :current="selectedTheme.name == theme.name ? true : false" @delete="getAllThemes" />
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<NewThemeDialog @new-theme="appendTheme" class="mt-1" />
<v-spacer></v-spacer>
<v-btn color="success" @click="saveThemes" class="mr-2">
<v-icon left> mdi-content-save </v-icon>
{{ $t("general.save") }}
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
import { api } from "@/api";
import ColorPickerDialog from "@/components/FormHelpers/ColorPickerDialog";
import NewThemeDialog from "./NewThemeDialog";
import ThemeCard from "./ThemeCard";
export default {
components: {
ColorPickerDialog,
NewThemeDialog,
ThemeCard,
},
data() {
return {
selectedDarkMode: "system",
availableThemes: [],
};
},
async mounted() {
await this.getAllThemes();
this.selectedDarkMode = this.$store.getters.getDarkMode;
},
computed: {
selectedTheme() {
return this.$store.getters.getActiveTheme;
},
},
methods: {
async getAllThemes() {
this.availableThemes = await api.themes.requestAll();
},
/**
* Create the new Theme and select it.
*/
async appendTheme(NewThemeDialog) {
const response = await api.themes.create(NewThemeDialog);
if (response) {
this.availableThemes.push(NewThemeDialog);
this.$store.commit("setTheme", NewThemeDialog);
}
},
setStoresDarkMode() {
this.$store.commit("setDarkMode", this.selectedDarkMode);
},
/**
* This will save the current colors and make the selected theme live.
*/
saveThemes() {
api.themes.update(this.selectedTheme.name, this.selectedTheme.colors);
},
},
};
</script>
<style></style>

View file

@ -1,5 +1,4 @@
import Admin from "@/pages/Admin";
import Theme from "@/pages/Admin/Theme";
import MealPlanner from "@/pages/Admin/MealPlanner";
import Migration from "@/pages/Admin/Migration";
import Profile from "@/pages/Admin/Profile";
@ -30,13 +29,6 @@ export const adminRoutes = {
title: "settings.profile",
},
},
{
path: "themes",
component: Theme,
meta: {
title: "general.themes",
},
},
{
path: "meal-planner",
component: MealPlanner,

View file

@ -93,7 +93,7 @@ class _Settings(BaseDocument):
class _Themes(BaseDocument):
def __init__(self) -> None:
self.primary_key = "name"
self.primary_key = "id"
self.sql_model = SiteThemeModel
self.schema = SiteTheme

View file

@ -1,23 +1,21 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from mealie.db.models.model_base import SqlAlchemyBase
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from sqlalchemy.sql.sqltypes import Integer
class SiteThemeModel(SqlAlchemyBase):
class SiteThemeModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "site_theme"
name = sa.Column(sa.String, primary_key=True)
id = sa.Column(Integer, primary_key=True)
name = sa.Column(sa.String, nullable=False)
colors = orm.relationship("ThemeColorsModel", uselist=False, cascade="all, delete")
def __init__(self, name: str, colors: dict, session=None) -> None:
def __init__(self, name: str, colors: dict, *arg, **kwargs) -> None:
self.name = name
self.colors = ThemeColorsModel(**colors)
def update(self, session=None, name: str = None, colors: dict = None) -> dict:
self.colors.update(**colors)
return self
class ThemeColorsModel(SqlAlchemyBase):
class ThemeColorsModel(SqlAlchemyBase, BaseMixins):
__tablename__ = "theme_colors"
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("site_theme.name"))
@ -28,21 +26,3 @@ class ThemeColorsModel(SqlAlchemyBase):
info = sa.Column(sa.String)
warning = sa.Column(sa.String)
error = sa.Column(sa.String)
def update(
self,
primary: str = None,
accent: str = None,
secondary: str = None,
success: str = None,
info: str = None,
warning: str = None,
error: str = None,
) -> None:
self.primary = primary
self.accent = accent
self.secondary = secondary
self.success = success
self.info = info
self.warning = warning
self.error = error

View file

@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, status, HTTPException
from fastapi import APIRouter, Depends, HTTPException, status
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
@ -21,27 +21,27 @@ def create_theme(data: SiteTheme, session: Session = Depends(generate_session),
db.themes.create(session, data.dict())
@router.get("/themes/{theme_name}")
def get_single_theme(theme_name: str, session: Session = Depends(generate_session)):
@router.get("/themes/{id}")
def get_single_theme(id: int, session: Session = Depends(generate_session)):
""" Returns a named theme """
return db.themes.get(session, theme_name)
return db.themes.get(session, id)
@router.put("/themes/{theme_name}", status_code=status.HTTP_200_OK)
@router.put("/themes/{id}", status_code=status.HTTP_200_OK)
def update_theme(
theme_name: str,
id: int,
data: SiteTheme,
session: Session = Depends(generate_session),
current_user=Depends(get_current_user),
):
""" Update a theme database entry """
db.themes.update(session, theme_name, data.dict())
db.themes.update(session, id, data.dict())
@router.delete("/themes/{theme_name}", status_code=status.HTTP_200_OK)
def delete_theme(theme_name: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
@router.delete("/themes/{id}", status_code=status.HTTP_200_OK)
def delete_theme(id: int, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
""" Deletes theme from the database """
try:
db.themes.delete(session, theme_name)
db.themes.delete(session, id)
except Exception:
raise HTTPException(status.HTTP_400_BAD_REQUEST)

View file

@ -1,3 +1,5 @@
from typing import Optional
from pydantic import BaseModel
@ -5,7 +7,7 @@ class Colors(BaseModel):
primary: str = "#E58325"
accent: str = "#00457A"
secondary: str = "#973542"
success: str = "#4CAF50"
success: str = "#43A047"
info: str = "#4990BA"
warning: str = "#FF4081"
error: str = "#EF5350"
@ -15,6 +17,7 @@ class Colors(BaseModel):
class SiteTheme(BaseModel):
id: Optional[int]
name: str = "default"
colors: Colors = Colors()