mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-24 02:39:20 -05:00
allow opening shared recipes and redirect old urls
This commit is contained in:
@@ -101,6 +101,10 @@ urlpatterns = [
|
||||
path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'),
|
||||
path('delete/recipe-source/<int:pk>/', delete.delete_recipe_source, name='delete_recipe_source'),
|
||||
|
||||
# Tandoor v1 redirects
|
||||
path('view/recipe/<int:pk>', views.redirect_recipe_view, name='redirect_recipe_view'),
|
||||
path('view/recipe/<int:pk>/<slug:share>', views.redirect_recipe_share_view, name='redirect_recipe_share_view'),
|
||||
|
||||
# TODO move to generic "new" view
|
||||
path('data/sync', data.sync, name='data_sync'),
|
||||
path('data/batch/edit', data.batch_edit, name='data_batch_edit'),
|
||||
|
||||
@@ -1066,7 +1066,9 @@ class RecipePagination(PageNumberPagination):
|
||||
('previous', self.get_previous_link()), ('results', data), ]))
|
||||
|
||||
|
||||
@extend_schema_view(list=extend_schema(parameters=[
|
||||
@extend_schema_view(retrieve=extend_schema(parameters=[
|
||||
OpenApiParameter(name='share', type=str),
|
||||
]), list=extend_schema(parameters=[
|
||||
OpenApiParameter(name='query', description=_(
|
||||
'Query string matched (fuzzy) against recipe name. In the future also fulltext search.'), type=str),
|
||||
OpenApiParameter(name='keywords', description=_(
|
||||
@@ -1163,7 +1165,7 @@ class RecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
|
||||
pagination_class = RecipePagination
|
||||
|
||||
def get_queryset(self):
|
||||
share = self.request.query_params.get('share', None)
|
||||
share = self.request.GET.get('share', None)
|
||||
|
||||
if self.detail: # if detail request and not list, private condition is verified by permission class
|
||||
if not share: # filter for space only if not shared
|
||||
@@ -1831,14 +1833,14 @@ class ImageToRecipeView(APIView):
|
||||
|
||||
model = generativeai.GenerativeModel('gemini-1.5-flash-latest')
|
||||
img = PIL.Image.open(serializer.validated_data['image'])
|
||||
# response = model.generate_content([
|
||||
# response = model.generate_content([
|
||||
# "The Image given is a photograph or screenshot taken of a cooking recipe. The data contained in the image should be converted to a structured json format adhering to the specification given in the schema.org/recipes schema. It is not allowed to make any data up, the response must only contain data given in the original image. The response must be a plain json object without any formatting characters or other unnecessary information. If unsure please return an empty json object {}. Do not wrap the response in a markdown codeblock (like ```json ```)",
|
||||
# img], stream=True)
|
||||
#response.resolve()
|
||||
# img], stream=True)
|
||||
# response.resolve()
|
||||
|
||||
#response_text = response.text
|
||||
#response_text.replace('```json', '')
|
||||
#response_text.replace('```', '')
|
||||
# response_text = response.text
|
||||
# response_text.replace('```json', '')
|
||||
# response_text.replace('```', '')
|
||||
|
||||
response_text = '{"@context": "https://schema.org", "@type": "Recipe", "name": "Pennettine mit Nüssen und Zitrone", "prepTime": "PT25M", "recipeYield": "4", "ingredients": [{"@type": "Quantity", "name": "BARILLA Pennettine", "amount": 500, "unitCode": "G"}, {"@type": "Quantity", "name": "Walnußkerne", "amount": 100, "unitCode": "G"}, {"@type": "Quantity", "name": "Sahne", "amount": "1/2-1", "unitCode": "CUP"}, {"@type": "Quantity", "name": "Butter", "amount": 20, "unitCode": "G"}, {"@type": "Quantity", "name": "Zucker", "amount": 10, "unitCode": "G"}, {"@type": "Quantity", "name": "gemahlener Zimt", "amount": 1, "unitCode": "UNIT"}, {"@type": "Quantity", "name": "Zitrone, unbehandelt", "amount": "1/2-1", "unitCode": "UNIT"}, {"@type": "Quantity", "name": "Salz", "amount": null, "unitCode": null}, {"@type": "Quantity", "name": "frisch gemahlener Pfeffer", "amount": null, "unitCode": null}], "recipeInstructions": [{"@type": "HowToStep", "text": "Walnußkerne hacken, in eine große Schüssel geben, Zimt und Zucker mit einer Prise Salz und Pfeffer untermischen."}, {"@type": "HowToStep", "text": "Die Zitrone waschen, Schale einer 1/2 Zitrone abreiben, dann zusammen mit den anderen Zutaten in die Schüssel geben und zuletzt in Stückchen geschnittene Butter untermischen."}, {"@type": "HowToStep", "text": "Die Mischung schmelzen, indem Sie die Schüssel auf den Topf mit dem kochenden Pasta-Wasser stellen und hin und wieder umrühren."}, {"@type": "HowToStep", "text": "Sahne dazugeben und die Zutaten mit einem Schneebesen verrühren."}, {"@type": "HowToStep", "text": "BARILLA Pennettine in reichlich Salzwasser al dente kochen, abgießen und mit der Sauce anrichten."}]}'
|
||||
|
||||
@@ -1851,7 +1853,7 @@ class ImageToRecipeView(APIView):
|
||||
# if '@type' not in data_json:
|
||||
# data_json['@type'] = 'Recipe'
|
||||
data = "<script type='application/ld+json'>" + json.dumps(data_json) + "</script>"
|
||||
#data = "<script type='application/ld+json'>" + response_text + "</script>"
|
||||
# data = "<script type='application/ld+json'>" + response_text + "</script>"
|
||||
|
||||
scrape = scrape_html(html=data, org_url='https://urlnotfound.none', supported_only=False)
|
||||
print(str(scrape.ingredients()))
|
||||
@@ -2030,7 +2032,7 @@ class LocalizationViewSet(viewsets.GenericViewSet):
|
||||
|
||||
# TODO implement schema
|
||||
class ServerSettingsViewSet(viewsets.GenericViewSet):
|
||||
permission_classes = [CustomIsGuest & CustomTokenHasReadWriteScope]
|
||||
permission_classes = [] # public view to use by anonymous request (recipe share page)
|
||||
serializer_class = ServerSettingsSerializer
|
||||
pagination_disabled = True
|
||||
|
||||
@@ -2041,6 +2043,7 @@ class ServerSettingsViewSet(viewsets.GenericViewSet):
|
||||
@decorators.action(detail=False, pagination_class=None, methods=['GET'], serializer_class=ServerSettingsSerializer, )
|
||||
def current(self, request, *args, **kwargs):
|
||||
s = dict()
|
||||
# Attention: No login required, do not return sensitive data
|
||||
s['shopping_min_autosync_interval'] = settings.SHOPPING_MIN_AUTOSYNC_INTERVAL
|
||||
s['enable_pdf_export'] = settings.ENABLE_PDF_EXPORT
|
||||
s['disable_external_connectors'] = settings.DISABLE_EXTERNAL_CONNECTORS
|
||||
@@ -2142,7 +2145,7 @@ def share_link(request, pk):
|
||||
recipe = get_object_or_404(Recipe, pk=pk, space=request.space)
|
||||
link = ShareLink.objects.create(recipe=recipe, created_by=request.user, space=request.space)
|
||||
return JsonResponse({'pk': pk, 'share': link.uuid,
|
||||
'link': request.build_absolute_uri(f'recipe/{pk}/{link.uuid}')})
|
||||
'link': request.build_absolute_uri(reverse('index') + f'recipe/{pk}/{link.uuid}')})
|
||||
else:
|
||||
return JsonResponse({'error': 'sharing_disabled'}, status=403)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from io import StringIO
|
||||
from uuid import UUID
|
||||
|
||||
import redis
|
||||
from allauth.utils import build_absolute_uri
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -46,9 +47,28 @@ def index(request, path=None, resource=None):
|
||||
if request.user.is_authenticated:
|
||||
return HttpResponseRedirect(reverse('view_no_group'))
|
||||
else:
|
||||
print('REQUEST PATH ', request.path)
|
||||
if re.search(r'/recipe/\d+/', request.path[:512]) and request.GET.get('share'):
|
||||
print('MATECHED')
|
||||
return render(request, 'frontend/tandoor.html', {})
|
||||
return HttpResponseRedirect(reverse('account_login') + '?next=' + request.path)
|
||||
|
||||
|
||||
def redirect_recipe_view(request, pk):
|
||||
if request.GET.get('share'):
|
||||
print('send to index')
|
||||
return index(request)
|
||||
print('redirexct old recipe link')
|
||||
return HttpResponseRedirect(build_absolute_uri(request, reverse('index')) + f'recipe/{pk}')
|
||||
|
||||
|
||||
def redirect_recipe_share_view(request, pk, share):
|
||||
print('share redirect')
|
||||
if re.match(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}', share):
|
||||
return HttpResponseRedirect(build_absolute_uri(request, reverse('index')) + f'recipe/{pk}/?share={share}')
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
|
||||
def search(request):
|
||||
if settings.V3_BETA:
|
||||
return HttpResponseRedirect(reverse('vue3'))
|
||||
@@ -620,7 +640,6 @@ def test2(request):
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
|
||||
@group_required('guest')
|
||||
def tandoor_frontend(request):
|
||||
return render(request, 'frontend/tandoor.html', {})
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-app-bar color="tandoor" flat density="comfortable">
|
||||
<v-app-bar color="tandoor" flat density="comfortable" v-if="!useUserPreferenceStore().isAuthenticated">
|
||||
|
||||
</v-app-bar>
|
||||
<v-app-bar color="tandoor" flat density="comfortable" v-if="useUserPreferenceStore().isAuthenticated">
|
||||
<router-link :to="{ name: 'view_home', params: {} }">
|
||||
<v-img src="../../assets/brand_logo.svg" width="140px" class="ms-2"></v-img>
|
||||
</router-link>
|
||||
@@ -42,10 +45,10 @@
|
||||
</v-list-item>
|
||||
<!-- <v-list-item><template #prepend><v-icon icon="fa-solid fa-user-shield"></v-icon></template>Admin</v-list-item>-->
|
||||
<!-- <v-list-item><template #prepend><v-icon icon="fa-solid fa-question"></v-icon></template>Help</v-list-item>-->
|
||||
<template v-if="spaces.length > 1">
|
||||
<template v-if="useUserPreferenceStore().spaces.length > 1">
|
||||
<v-divider></v-divider>
|
||||
<v-list-subheader>{{ $t('YourSpaces') }}</v-list-subheader>
|
||||
<v-list-item v-for="s in spaces" :key="s.id" @click="useUserPreferenceStore().switchSpace(s)">
|
||||
<v-list-item v-for="s in useUserPreferenceStore().spaces" :key="s.id" @click="useUserPreferenceStore().switchSpace(s)">
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-circle-dot" v-if="s.id == useUserPreferenceStore().activeSpace.id"></v-icon>
|
||||
<v-icon icon="fa-solid fa-circle" v-else></v-icon>
|
||||
@@ -78,20 +81,20 @@
|
||||
</v-menu>
|
||||
</v-avatar>
|
||||
</v-app-bar>
|
||||
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().activeSpace.maxRecipes == 10 && useUserPreferenceStore().serverSettings.hosted">
|
||||
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().isAuthenticated && useUserPreferenceStore().activeSpace.maxRecipes == 10 && useUserPreferenceStore().serverSettings.hosted">
|
||||
<p class="text-center w-100">
|
||||
{{ $t('HostedFreeVersion') }}
|
||||
<v-btn color="success" variant="flat" href="https://tandoor.dev/manage">{{ $t('UpgradeNow') }}</v-btn>
|
||||
</p>
|
||||
</v-app-bar>
|
||||
<v-app-bar color="warning" density="compact" v-if="isSpaceAboveLimit(useUserPreferenceStore().activeSpace)">
|
||||
<v-app-bar color="warning" density="compact" v-if="useUserPreferenceStore().isAuthenticated && isSpaceAboveLimit(useUserPreferenceStore().activeSpace)">
|
||||
<p class="text-center w-100">
|
||||
{{ $t('SpaceLimitExceeded') }}
|
||||
<v-btn color="success" variant="flat" :to="{name: 'view_settings_space'}">{{ $t('SpaceSettings') }}</v-btn>
|
||||
</p>
|
||||
</v-app-bar>
|
||||
|
||||
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().activeSpace.message != ''">
|
||||
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().isAuthenticated && useUserPreferenceStore().activeSpace.message != ''">
|
||||
<p class="text-center w-100">
|
||||
{{ useUserPreferenceStore().activeSpace.message }}
|
||||
</p>
|
||||
@@ -101,7 +104,7 @@
|
||||
<router-view></router-view>
|
||||
</v-main>
|
||||
|
||||
<v-navigation-drawer v-if="lgAndUp">
|
||||
<v-navigation-drawer v-if="lgAndUp && useUserPreferenceStore().isAuthenticated">
|
||||
<v-list nav>
|
||||
<v-list-item :to="{ name: 'view_settings', params: {} }">
|
||||
<template #prepend>
|
||||
@@ -132,7 +135,7 @@
|
||||
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-bottom-navigation grow v-if="!lgAndUp">
|
||||
<v-bottom-navigation grow v-if="useUserPreferenceStore().isAuthenticated && !lgAndUp">
|
||||
<v-btn value="recent" :to="{ name: 'view_home', params: {} }">
|
||||
<v-icon icon="fa-fw fas fa-book "/>
|
||||
</v-btn>
|
||||
@@ -183,18 +186,8 @@ import {isSpaceAboveLimit, isSpaceAtLimit} from "@/utils/logic_utils";
|
||||
const {lgAndUp} = useDisplay()
|
||||
const {getDjangoUrl} = useDjangoUrls()
|
||||
|
||||
const spaces = ref([] as Space[])
|
||||
|
||||
onMounted(() => {
|
||||
let api = new ApiApi()
|
||||
|
||||
useUserPreferenceStore()
|
||||
|
||||
api.apiSpaceList().then(r => {
|
||||
spaces.value = r.results
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -51,7 +51,10 @@ const routes = [
|
||||
{path: '/mealplan', component: MealPlanPage, name: 'view_mealplan'},
|
||||
{path: '/books', component: ShoppingListPage, name: 'view_books'},
|
||||
{path: '/recipe/import', component: RecipeImportPage, name: 'RecipeImportPage'},
|
||||
|
||||
{path: '/recipe/:id', component: RecipeViewPage, name: 'view_recipe', props: true},
|
||||
{path: '/view/recipe/:id', redirect: {name: 'view_recipe'}}, // old Tandoor v1 url pattern
|
||||
|
||||
{path: '/recipe/edit/:recipe_id', component: RecipeEditPage, name: 'edit_recipe', props: true},
|
||||
|
||||
{path: '/list/:model?', component: ModelListPage, props: true, name: 'ModelListPage'},
|
||||
|
||||
@@ -1225,6 +1225,7 @@ export interface ApiRecipeRelatedListRequest {
|
||||
|
||||
export interface ApiRecipeRetrieveRequest {
|
||||
id: number;
|
||||
share?: string;
|
||||
}
|
||||
|
||||
export interface ApiRecipeShoppingUpdateRequest {
|
||||
@@ -8989,6 +8990,10 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
if (requestParameters['share'] != null) {
|
||||
queryParameters['share'] = requestParameters['share'];
|
||||
}
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-container class="h-100 pt-0 pl-0 pb-0" fluid>
|
||||
<v-container class="h-100 pt-0 pl-0 pb-0" style="max-width: 100%" fluid>
|
||||
<meal-plan-view></meal-plan-view>
|
||||
</v-container>
|
||||
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {defineComponent, onMounted, ref, watch} from 'vue'
|
||||
import {ApiApi, Recipe, ViewLog} from "@/openapi";
|
||||
import {onMounted, ref, watch} from 'vue'
|
||||
import {ApiApi, ApiRecipeRetrieveRequest, Recipe, ViewLog} from "@/openapi";
|
||||
import RecipeView from "@/components/display/RecipeView.vue";
|
||||
import {useDisplay} from "vuetify";
|
||||
import {useUrlSearchParams} from "@vueuse/core";
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
|
||||
const props = defineProps({
|
||||
id: {type: String, required: true}
|
||||
})
|
||||
|
||||
const params = useUrlSearchParams('history')
|
||||
const {mobile} = useDisplay()
|
||||
|
||||
const recipe = ref({} as Recipe)
|
||||
@@ -30,14 +34,24 @@ onMounted(() => {
|
||||
function refreshData(recipeId: string) {
|
||||
const api = new ApiApi()
|
||||
recipe.value = {} as Recipe
|
||||
api.apiRecipeRetrieve({id: Number(recipeId)}).then(r => {
|
||||
recipe.value = r
|
||||
})
|
||||
|
||||
api.apiViewLogCreate({
|
||||
viewLog: {
|
||||
recipe: Number(recipeId)
|
||||
} as ViewLog
|
||||
let requestParameters: ApiRecipeRetrieveRequest = {id: props.id}
|
||||
if (params.share && typeof params.share == "string") {
|
||||
requestParameters.share = params.share
|
||||
}
|
||||
|
||||
api.apiRecipeRetrieve(requestParameters).then(r => {
|
||||
recipe.value = r
|
||||
|
||||
if (useUserPreferenceStore().isAuthenticated) {
|
||||
api.apiViewLogCreate({viewLog: {recipe: Number(recipeId)} as ViewLog})
|
||||
}
|
||||
}).catch(err => {
|
||||
if (err.response.status == 403) {
|
||||
// TODO maybe redirect to login if fails with 403? or conflict with group/sapce system?
|
||||
} else {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {useStorage} from "@vueuse/core";
|
||||
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
||||
import {ApiApi, ServerSettings, Space, Supermarket, UserPreference, UserSpace} from "@/openapi";
|
||||
import {ShoppingGroupingOptions} from "@/types/Shopping";
|
||||
import {computed, ComputedRef} from "vue";
|
||||
import {computed, ComputedRef, ref} from "vue";
|
||||
import {DeviceSettings} from "@/types/settings";
|
||||
|
||||
const DEVICE_SETTINGS_KEY = 'TANDOOR_DEVICE_SETTINGS'
|
||||
@@ -11,6 +11,7 @@ const USER_PREFERENCE_KEY = 'TANDOOR_USER_PREFERENCE'
|
||||
const SERVER_SETTINGS_KEY = 'TANDOOR_SERVER_SETTINGS'
|
||||
const ACTIVE_SPACE_KEY = 'TANDOOR_ACTIVE_SPACE'
|
||||
const USER_SPACES_KEY = 'TANDOOR_USER_SPACES'
|
||||
const SPACES_KEY = 'TANDOOR_SPACES'
|
||||
|
||||
export const useUserPreferenceStore = defineStore('user_preference_store', () => {
|
||||
/**
|
||||
@@ -30,10 +31,17 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
*/
|
||||
let activeSpace = useStorage(ACTIVE_SPACE_KEY, {} as Space)
|
||||
/**
|
||||
* list of spaces the user has access to and the relevant permissions, cache in local storage in case application is started offline
|
||||
* list of user spaces the user has access to and the relevant permissions, cache in local storage in case application is started offline
|
||||
*/
|
||||
let userSpaces = useStorage(USER_SPACES_KEY, [] as UserSpace[])
|
||||
|
||||
/**
|
||||
* list of spaces the user has access and their space settings/Data, cache in local storage in case application is started offline
|
||||
*/
|
||||
let spaces = useStorage(SPACES_KEY, [] as Space[])
|
||||
/**
|
||||
* some views can be viewed without authentication, this variable centrally detects the authentication state by the response (403) of the settings views
|
||||
*/
|
||||
let isAuthenticated = ref(false)
|
||||
|
||||
/**
|
||||
* holds the active user space if there is one or null if not
|
||||
@@ -57,11 +65,14 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
api.apiUserPreferenceList().then(r => {
|
||||
if (r.length == 1) {
|
||||
userSettings.value = r[0]
|
||||
isAuthenticated.value = true
|
||||
} else {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, r)
|
||||
}
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
if (err.response.status != 403) {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,19 +110,39 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
api.apiSpaceCurrentRetrieve().then(r => {
|
||||
activeSpace.value = r
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
if (err.response.status != 403) {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* load user spaces
|
||||
* load user spaces (permission mapping ot space)
|
||||
*/
|
||||
function loadUserSpaces() {
|
||||
let api = new ApiApi()
|
||||
api.apiUserSpaceList().then(r => {
|
||||
userSpaces.value = r.results
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
if (err.response.status != 403) {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* list all spaces (with their data) a user has access to
|
||||
*/
|
||||
// TODO maybe change userspace api to include space as nested property to make this call redundant
|
||||
function loadSpaces() {
|
||||
let api = new ApiApi()
|
||||
|
||||
api.apiSpaceList().then(r => {
|
||||
spaces.value = r.results
|
||||
}).catch(err => {
|
||||
if (err.response.status != 403) {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -166,6 +197,7 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
loadServerSettings()
|
||||
loadActiveSpace()
|
||||
loadUserSpaces()
|
||||
loadSpaces()
|
||||
|
||||
return {
|
||||
deviceSettings,
|
||||
@@ -173,7 +205,9 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
serverSettings,
|
||||
activeSpace,
|
||||
userSpaces,
|
||||
spaces,
|
||||
activeUserSpace,
|
||||
isAuthenticated,
|
||||
loadUserSettings,
|
||||
loadServerSettings,
|
||||
updateUserSettings,
|
||||
|
||||
@@ -15,6 +15,10 @@ export default createVuetify({
|
||||
// without this action buttons are left aligned in normal cards but right aligned in dialogs (I think)
|
||||
VCardActions: {
|
||||
class: 'float-right'
|
||||
},
|
||||
// limiting max width of base container so UIs dont become too wide
|
||||
VContainer: {
|
||||
maxWidth: '1400px'
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
|
||||
Reference in New Issue
Block a user