mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-04 13:48:32 -05:00
overhauld space management and settings system
This commit is contained in:
@@ -368,10 +368,10 @@ class AiLogSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class SpaceSerializer(WritableNestedModelSerializer):
|
class SpaceSerializer(WritableNestedModelSerializer):
|
||||||
created_by = UserSerializer(read_only=True)
|
created_by = UserSerializer(read_only=True)
|
||||||
user_count = serializers.SerializerMethodField('get_user_count')
|
user_count = serializers.SerializerMethodField('get_user_count', read_only=True)
|
||||||
recipe_count = serializers.SerializerMethodField('get_recipe_count')
|
recipe_count = serializers.SerializerMethodField('get_recipe_count', read_only=True)
|
||||||
file_size_mb = serializers.SerializerMethodField('get_file_size_mb')
|
file_size_mb = serializers.SerializerMethodField('get_file_size_mb', read_only=True)
|
||||||
ai_monthly_credits_used = serializers.SerializerMethodField('get_ai_monthly_credits_used')
|
ai_monthly_credits_used = serializers.SerializerMethodField('get_ai_monthly_credits_used', read_only=True)
|
||||||
ai_default_provider = AiProviderSerializer(required=False, allow_null=True)
|
ai_default_provider = AiProviderSerializer(required=False, allow_null=True)
|
||||||
food_inherit = FoodInheritFieldSerializer(many=True, required=False)
|
food_inherit = FoodInheritFieldSerializer(many=True, required=False)
|
||||||
image = UserFileViewSerializer(required=False, many=False, allow_null=True)
|
image = UserFileViewSerializer(required=False, many=False, allow_null=True)
|
||||||
@@ -413,8 +413,8 @@ class SpaceSerializer(WritableNestedModelSerializer):
|
|||||||
name = None
|
name = None
|
||||||
if 'name' in validated_data:
|
if 'name' in validated_data:
|
||||||
name = validated_data['name']
|
name = validated_data['name']
|
||||||
space = create_space_for_user(self.context['request'].user, name)
|
user_space = create_space_for_user(self.context['request'].user, name)
|
||||||
return space
|
return user_space.space
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
if 'ai_enabled' in validated_data and not self.context['request'].user.is_superuser:
|
if 'ai_enabled' in validated_data and not self.context['request'].user.is_superuser:
|
||||||
|
|||||||
@@ -548,7 +548,7 @@ class SpaceViewSet(LoggingMixin, viewsets.ModelViewSet):
|
|||||||
serializer_class = SpaceSerializer
|
serializer_class = SpaceSerializer
|
||||||
permission_classes = [IsReadOnlyDRF & CustomIsGuest | CustomIsOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
|
permission_classes = [IsReadOnlyDRF & CustomIsGuest | CustomIsOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
|
||||||
pagination_class = DefaultPagination
|
pagination_class = DefaultPagination
|
||||||
http_method_names = ['get', 'post', 'patch']
|
http_method_names = ['get', 'post', 'put', 'patch']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.queryset.filter(
|
return self.queryset.filter(
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ let routes = [
|
|||||||
{path: 'meal-plan', component: () => import("@/components/settings/MealPlanSettings.vue"), name: 'MealPlanSettings', meta: {title: 'Settings'}},
|
{path: 'meal-plan', component: () => import("@/components/settings/MealPlanSettings.vue"), name: 'MealPlanSettings', meta: {title: 'Settings'}},
|
||||||
{path: 'search', component: () => import("@/components/settings/SearchSettings.vue"), name: 'SearchSettings', meta: {title: 'Settings'}},
|
{path: 'search', component: () => import("@/components/settings/SearchSettings.vue"), name: 'SearchSettings', meta: {title: 'Settings'}},
|
||||||
{path: 'space', component: () => import("@/components/settings/SpaceSettings.vue"), name: 'SpaceSettings', meta: {title: 'Settings'}},
|
{path: 'space', component: () => import("@/components/settings/SpaceSettings.vue"), name: 'SpaceSettings', meta: {title: 'Settings'}},
|
||||||
{path: 'space-members', component: () => import("@/components/settings/SpaceMemberSettings.vue"), name: 'SpaceMemberSettings', meta: {title: 'Settings'}},
|
|
||||||
{path: 'user-space', component: () => import("@/components/settings/UserSpaceSettings.vue"), name: 'UserSpaceSettings', meta: {title: 'Settings'}},
|
|
||||||
{path: 'open-data-import', component: () => import("@/components/settings/OpenDataImportSettings.vue"), name: 'OpenDataImportSettings', meta: {title: 'Settings'}},
|
{path: 'open-data-import', component: () => import("@/components/settings/OpenDataImportSettings.vue"), name: 'OpenDataImportSettings', meta: {title: 'Settings'}},
|
||||||
{path: 'export', component: () => import("@/components/settings/ExportDataSettings.vue"), name: 'ExportDataSettings', meta: {title: 'Settings'}},
|
{path: 'export', component: () => import("@/components/settings/ExportDataSettings.vue"), name: 'ExportDataSettings', meta: {title: 'Settings'}},
|
||||||
{path: 'api', component: () => import("@/components/settings/ApiSettings.vue"), name: 'ApiSettings', meta: {title: 'Settings'}},
|
{path: 'api', component: () => import("@/components/settings/ApiSettings.vue"), name: 'ApiSettings', meta: {title: 'Settings'}},
|
||||||
|
|||||||
72
vue3/src/components/display/SpaceLimitsInfo.vue
Normal file
72
vue3/src/components/display/SpaceLimitsInfo.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<v-row v-if="props.space.name != undefined">
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card :to="{name: 'SearchPage'}">
|
||||||
|
<v-card-title><i class="fa-solid fa-book"></i> {{ $t('Recipes') }}</v-card-title>
|
||||||
|
<v-card-text>{{ $n(props.space.recipeCount) }} / {{ props.space.maxRecipes == 0 ? '∞' : $n(props.space.maxRecipes) }}</v-card-text>
|
||||||
|
<v-progress-linear :color="isSpaceAboveRecipeLimit(props.space) ? 'error' : 'success'" height="10"
|
||||||
|
:model-value="(props.space.recipeCount / props.space.maxRecipes) * 100"></v-progress-linear>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card :to="{name: 'ModelListPage', params: {model: 'UserSpace'}}">
|
||||||
|
|
||||||
|
<v-card-title><i class="fa-solid fa-users"></i> {{ $t('Users') }}</v-card-title>
|
||||||
|
<v-card-text>{{ $n(props.space.userCount) }} / {{ props.space.maxUsers == 0 ? '∞' : $n(props.space.maxUsers) }}</v-card-text>
|
||||||
|
<v-progress-linear :color="isSpaceAboveUserLimit(props.space) ? 'error' : 'success'" height="10"
|
||||||
|
:model-value="(props.space.userCount / props.space.maxUsers) * 100"></v-progress-linear>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card :to="{name: 'ModelListPage', params: {model: 'UserFile'}}">
|
||||||
|
<v-card-title><i class="fa-solid fa-file"></i> {{ $t('Files') }}</v-card-title>
|
||||||
|
<v-card-text v-if="props.space.maxFileStorageMb > -1">{{ $n(Math.round(props.space.fileSizeMb)) }} /
|
||||||
|
{{ props.space.maxFileStorageMb == 0 ? '∞' : $n(props.space.maxFileStorageMb) }}
|
||||||
|
MB
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text v-if="props.space.maxFileStorageMb == -1">{{ $t('file_upload_disabled') }}</v-card-text>
|
||||||
|
<v-progress-linear v-if="props.space.maxFileStorageMb > -1" :color="isSpaceAboveStorageLimit(props.space) ? 'error' : 'success'" height="10"
|
||||||
|
:model-value="(props.space.fileSizeMb / props.space.maxFileStorageMb) * 100"></v-progress-linear>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-card :to="{name: 'ModelListPage', params: {model: 'AiLog'}}">
|
||||||
|
<v-card-title><i class="fa-solid hand-holding-dollar"></i> {{ $t('MonthlyCredits') }}</v-card-title>
|
||||||
|
<v-card-text>{{ $n(props.space.aiMonthlyCreditsUsed) }} / {{ $n(props.space.aiCreditsMonthly) }} {{ $t('Credits') }}
|
||||||
|
</v-card-text>
|
||||||
|
<v-progress-linear :model-value="props.space.aiMonthlyCreditsUsed" :max="props.space.aiCreditsMonthly" height="10"
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-card :to="{name: 'ModelListPage', params: {model: 'AiLog'}}">
|
||||||
|
<v-card-title><i class="fa-solid hand-holding-dollar"></i> {{ $t('AiCreditsBalance') }}</v-card-title>
|
||||||
|
<v-card-text>{{ $n(props.space.aiCreditsBalance) }} {{ $t('Credits') }}
|
||||||
|
</v-card-text>
|
||||||
|
<v-progress-linear height="10"
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {PropType} from "vue";
|
||||||
|
import {Space} from "@/openapi";
|
||||||
|
import {isSpaceAboveRecipeLimit, isSpaceAboveStorageLimit, isSpaceAboveUserLimit} from "@/utils/logic_utils.ts";
|
||||||
|
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
space: {type: {} as PropType<Space>, required: true},
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
47
vue3/src/components/display/ThankYouNote.vue
Normal file
47
vue3/src/components/display/ThankYouNote.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<v-alert color="primary" variant="tonal" v-if="useUserPreferenceStore().serverSettings.hosted">
|
||||||
|
<v-alert-title>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-avatar image="../../assets/logo_color.svg" class="me-2"></v-avatar>
|
||||||
|
{{ $t('ThankYou') }}!
|
||||||
|
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-btn color="primary" class="float-right" href="https://tandoor.dev/manage" target="_blank">{{ $t('ManageSubscription') }}</v-btn>
|
||||||
|
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-alert-title>
|
||||||
|
<p class="mt-2">{{ $t('ThanksTextHosted') }}</p>
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
<v-alert color="primary" variant="tonal" v-if="!useUserPreferenceStore().serverSettings.hosted">
|
||||||
|
<v-alert-title>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-avatar image="../../assets/logo_color.svg" class="me-2"></v-avatar>
|
||||||
|
{{ $t('ThankYou') }}!
|
||||||
|
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-btn color="primary" class="float-right" href="https://github.com/sponsors/vabene1111" target="_blank"><i class="fa-brands fa-github"></i> GitHub Sponsors
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-alert-title>
|
||||||
|
<p class="mt-2">{{ $t('ThanksTextSelfhosted') }}</p>
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -27,7 +27,9 @@
|
|||||||
|
|
||||||
<user-file-field v-model="editingObj.image" :label="$t('Image')" :hint="$t('CustomImageHelp')" persistent-hint></user-file-field>
|
<user-file-field v-model="editingObj.image" :label="$t('Image')" :hint="$t('CustomImageHelp')" persistent-hint></user-file-field>
|
||||||
|
|
||||||
<v-textarea v-model="editingObj.message" :label="$t('Message')"></v-textarea>
|
<v-textarea v-model="editingObj.message" :label="$t('Message')" clearable></v-textarea>
|
||||||
|
|
||||||
|
<space-limits-info :space="editingObj" :show-thank-you="false"></space-limits-info>
|
||||||
|
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-tabs-window-item>
|
</v-tabs-window-item>
|
||||||
@@ -81,13 +83,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {onMounted, PropType, ref, watch} from "vue";
|
import {onMounted, PropType, ref, watch} from "vue";
|
||||||
import {ConnectorConfig, Space} from "@/openapi";
|
import {ApiApi, ConnectorConfig, Space} from "@/openapi";
|
||||||
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
|
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
|
||||||
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
|
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
|
||||||
import UserFileField from "@/components/inputs/UserFileField.vue";
|
import UserFileField from "@/components/inputs/UserFileField.vue";
|
||||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||||
import editor from "mavon-editor";
|
import editor from "mavon-editor";
|
||||||
|
import SpaceLimitsInfo from "@/components/display/SpaceLimitsInfo.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {type: {} as PropType<Space>, required: false, default: null},
|
item: {type: {} as PropType<Space>, required: false, default: null},
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
<v-form>
|
<v-form>
|
||||||
<p class="text-h6">{{ $t('Profile') }}</p>
|
<p class="text-h6">{{ $t('Profile') }}</p>
|
||||||
<v-divider class="mb-3"></v-divider>
|
<v-divider class="mb-3"></v-divider>
|
||||||
<v-text-field :label="$t('Username')" v-model="user.username" disabled :hint="$t('theUsernameCannotBeChanged')" persistent-hint></v-text-field>
|
|
||||||
|
<thank-you-note></thank-you-note>
|
||||||
|
|
||||||
|
<v-text-field class="mt-3" :label="$t('Username')" v-model="user.username" disabled :hint="$t('theUsernameCannotBeChanged')" persistent-hint></v-text-field>
|
||||||
|
|
||||||
<!-- <v-label>Avatar</v-label><br/>-->
|
<!-- <v-label>Avatar</v-label><br/>-->
|
||||||
<!-- <v-avatar class="mt-3 mb-3" style="height: 10vh; width: 10vh" color="info">V</v-avatar> Feature coming in a future Version of Tandoor.-->
|
<!-- <v-avatar class="mt-3 mb-3" style="height: 10vh; width: 10vh" color="info">V</v-avatar> Feature coming in a future Version of Tandoor.-->
|
||||||
@@ -39,6 +42,7 @@ import {ApiApi, User} from "@/openapi";
|
|||||||
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
||||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||||
|
import ThankYouNote from "@/components/display/ThankYouNote.vue";
|
||||||
|
|
||||||
const {getDjangoUrl} = useDjangoUrls()
|
const {getDjangoUrl} = useDjangoUrls()
|
||||||
|
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-form>
|
|
||||||
<p class="text-h6">{{ $t('SpaceMembers') }}</p>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<p class="text-subtitle-2">{{ $t('SpaceMemberHelp') }}</p>
|
|
||||||
|
|
||||||
<v-data-table :items="spaceUserSpaces" :headers="userTableHeaders" density="compact" :hide-default-footer="spaceUserSpaces.length < 10" class="mt-3">
|
|
||||||
<template #item.groups="{item}">
|
|
||||||
<span v-for="g in item.groups">{{ g.name }} </span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #item.edit="{item}">
|
|
||||||
<v-btn color="edit" size="small" v-if="item.user.id != useUserPreferenceStore().activeSpace.createdBy.id">
|
|
||||||
<v-icon icon="$edit"></v-icon>
|
|
||||||
<model-edit-dialog model="UserSpace" :item="item" @delete="deleteUserSpace(item)" class="mt-2"></model-edit-dialog>
|
|
||||||
</v-btn>
|
|
||||||
<v-chip color="edit" v-else>{{ $t('Owner') }}</v-chip>
|
|
||||||
</template>
|
|
||||||
</v-data-table>
|
|
||||||
|
|
||||||
<p class="text-h6 mt-3">{{ $t('Invites') }}
|
|
||||||
<v-btn size="small" class="float-right" prepend-icon="$create" color="create">
|
|
||||||
{{ $t('New') }}
|
|
||||||
<model-edit-dialog model="InviteLink" @delete="deleteInviteLink" @create="item => spaceInviteLinks.push(item)" class="mt-2"></model-edit-dialog>
|
|
||||||
</v-btn>
|
|
||||||
</p>
|
|
||||||
<v-divider class="mb-3"></v-divider>
|
|
||||||
|
|
||||||
<v-data-table :items="spaceInviteLinks" :headers="inviteTableHeaders" density="compact" :hide-default-footer="spaceInviteLinks.length < 10">
|
|
||||||
<template #item.reusable="{item}">
|
|
||||||
<v-icon icon="fa-solid fa-check" color="success" v-if="item.reusable"></v-icon>
|
|
||||||
<v-icon icon="fa-solid fa-times" color="error" v-if="!item.reusable"></v-icon>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #item.edit="{item}">
|
|
||||||
<btn-copy size="small" :copy-value="inviteLinkUrl(item)" class="me-1"></btn-copy>
|
|
||||||
<v-btn color="edit" size="small">
|
|
||||||
<v-icon icon="$edit"></v-icon>
|
|
||||||
<model-edit-dialog model="InviteLink" :item="item" @delete="deleteInviteLink(item)" class="mt-2"></model-edit-dialog>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
</v-data-table>
|
|
||||||
</v-form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
|
|
||||||
import {onMounted, ref} from "vue";
|
|
||||||
import {ApiApi, InviteLink, UserSpace} from "@/openapi";
|
|
||||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import BtnCopy from "@/components/buttons/BtnCopy.vue";
|
|
||||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
|
||||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
|
||||||
import {useDjangoUrls} from "@/composables/useDjangoUrls.ts";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
|
|
||||||
const spaceUserSpaces = ref([] as UserSpace[])
|
|
||||||
const spaceInviteLinks = ref([] as InviteLink[])
|
|
||||||
|
|
||||||
const userTableHeaders = [
|
|
||||||
{title: t('Username'), key: 'user.username'},
|
|
||||||
{title: t('Role'), key: 'groups'},
|
|
||||||
{title: t('Edit'), key: 'edit', align: 'end'},
|
|
||||||
]
|
|
||||||
|
|
||||||
const inviteTableHeaders = [
|
|
||||||
{title: 'ID', key: 'id'},
|
|
||||||
{title: t('Email'), key: 'email'},
|
|
||||||
{title: t('Role'), key: 'group.name'},
|
|
||||||
{title: t('Reusable'), key: 'reusable'},
|
|
||||||
{title: t('Edit'), key: 'edit', align: 'end'},
|
|
||||||
]
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const api = new ApiApi()
|
|
||||||
|
|
||||||
api.apiUserSpaceList().then(r => {
|
|
||||||
spaceUserSpaces.value = r.results
|
|
||||||
}).catch(err => {
|
|
||||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
api.apiInviteLinkList({unused: true}).then(r => {
|
|
||||||
spaceInviteLinks.value = r.results
|
|
||||||
}).catch(err => {
|
|
||||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* delete userspace from client list (database handled by editor)
|
|
||||||
* @param userSpace UserSpace object that was deleted
|
|
||||||
*/
|
|
||||||
function deleteUserSpace(userSpace: UserSpace) {
|
|
||||||
spaceUserSpaces.value.splice(spaceUserSpaces.value.indexOf(userSpace) - 1, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* delete invite link from client list (database handled by editor)
|
|
||||||
* @param inviteLink InviteLink object that was deleted
|
|
||||||
*/
|
|
||||||
function deleteInviteLink(inviteLink: InviteLink) {
|
|
||||||
spaceInviteLinks.value.splice(spaceInviteLinks.value.indexOf(inviteLink) - 1, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns url for invite link
|
|
||||||
* @param inviteLink InviteLink object to create url for
|
|
||||||
*/
|
|
||||||
function inviteLinkUrl(inviteLink: InviteLink) {
|
|
||||||
return useDjangoUrls().getDjangoUrl(`/invite/${inviteLink.uuid}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -3,172 +3,17 @@
|
|||||||
<p class="text-h6">{{ useUserPreferenceStore().activeSpace.name }}</p>
|
<p class="text-h6">{{ useUserPreferenceStore().activeSpace.name }}</p>
|
||||||
<v-divider class="mb-3"></v-divider>
|
<v-divider class="mb-3"></v-divider>
|
||||||
|
|
||||||
<v-row v-if="space.name != undefined">
|
<space-editor :item-id="useUserPreferenceStore().activeSpace.id!"></space-editor>
|
||||||
<v-col cols="12" md="4">
|
|
||||||
<v-card>
|
|
||||||
<v-card-title><i class="fa-solid fa-book"></i> {{ $t('Recipes') }}</v-card-title>
|
|
||||||
<v-card-text>{{ $n(space.recipeCount) }} / {{ space.maxRecipes == 0 ? '∞' : $n(space.maxRecipes) }}</v-card-text>
|
|
||||||
<v-progress-linear :color="isSpaceAboveRecipeLimit(space) ? 'error' : 'success'" height="10"
|
|
||||||
:model-value="(space.recipeCount / space.maxRecipes) * 100"></v-progress-linear>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="4">
|
|
||||||
<v-card>
|
|
||||||
|
|
||||||
<v-card-title><i class="fa-solid fa-users"></i> {{ $t('Users') }}</v-card-title>
|
|
||||||
<v-card-text>{{ $n(space.userCount) }} / {{ space.maxUsers == 0 ? '∞' : $n(space.maxUsers) }}</v-card-text>
|
|
||||||
<v-progress-linear :color="isSpaceAboveUserLimit(space) ? 'error' : 'success'" height="10"
|
|
||||||
:model-value="(space.userCount / space.maxUsers) * 100"></v-progress-linear>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="4">
|
|
||||||
<v-card>
|
|
||||||
<v-card-title><i class="fa-solid fa-file"></i> {{ $t('Files') }}</v-card-title>
|
|
||||||
<v-card-text v-if="space.maxFileStorageMb > -1">{{ $n(Math.round(space.fileSizeMb)) }} / {{ space.maxFileStorageMb == 0 ? '∞' : $n(space.maxFileStorageMb) }}
|
|
||||||
MB
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-text v-if="space.maxFileStorageMb == -1">{{ $t('file_upload_disabled') }}</v-card-text>
|
|
||||||
<v-progress-linear v-if="space.maxFileStorageMb > -1" :color="isSpaceAboveStorageLimit(space) ? 'error' : 'success'" height="10"
|
|
||||||
:model-value="(space.fileSizeMb / space.maxFileStorageMb) * 100"></v-progress-linear>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-divider class="mt-3 mb-3"></v-divider>
|
|
||||||
|
|
||||||
<v-alert color="primary" variant="tonal" v-if="useUserPreferenceStore().serverSettings.hosted">
|
|
||||||
<v-alert-title>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-avatar image="../../assets/logo_color.svg" class="me-2"></v-avatar>
|
|
||||||
{{ $t('ThankYou') }}!
|
|
||||||
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-btn color="primary" class="float-right" href="https://tandoor.dev/manage" target="_blank">{{ $t('ManageSubscription') }}</v-btn>
|
|
||||||
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-alert-title>
|
|
||||||
<p class="mt-2">{{ $t('ThanksTextHosted') }}</p>
|
|
||||||
</v-alert>
|
|
||||||
|
|
||||||
<v-alert color="primary" variant="tonal" v-if="!useUserPreferenceStore().serverSettings.hosted">
|
|
||||||
<v-alert-title>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-avatar image="../../assets/logo_color.svg" class="me-2"></v-avatar>
|
|
||||||
{{ $t('ThankYou') }}!
|
|
||||||
|
|
||||||
</v-col>
|
|
||||||
<v-col>
|
|
||||||
<v-btn color="primary" class="float-right" href="https://github.com/sponsors/vabene1111" target="_blank"><i class="fa-brands fa-github"></i> GitHub Sponsors
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-alert-title>
|
|
||||||
<p class="mt-2">{{ $t('ThanksTextSelfhosted') }}</p>
|
|
||||||
</v-alert>
|
|
||||||
|
|
||||||
|
|
||||||
<p class="text-h6 mt-2">{{ $t('Settings') }}</p>
|
|
||||||
<v-divider class="mb-2"></v-divider>
|
|
||||||
|
|
||||||
<v-text-field v-model="space.name" :label="$t('Name')"></v-text-field>
|
|
||||||
|
|
||||||
<user-file-field v-model="space.image" :label="$t('Image')" :hint="$t('CustomImageHelp')" persistent-hint></user-file-field>
|
|
||||||
|
|
||||||
|
|
||||||
<v-textarea v-model="space.message" :label="$t('Message')"></v-textarea>
|
|
||||||
<v-btn color="success" @click="updateSpace()" prepend-icon="$save">{{ $t('Save') }}</v-btn>
|
|
||||||
|
|
||||||
<p class="text-h6 mt-2">{{ $t('AI') }}</p>
|
|
||||||
<v-divider class="mb-2"></v-divider>
|
|
||||||
<p class="text-disabled font-italic text-body-2">
|
|
||||||
<span v-if="useUserPreferenceStore().serverSettings.hosted">
|
|
||||||
{{ $t('AISettingsHostedHelp') }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{ $t('SettingsOnlySuperuser') }}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<v-checkbox v-model="space.aiEnabled" :label="$t('Enabled')" :disabled="!useUserPreferenceStore().userSettings.user.isSuperuser" hide-details></v-checkbox>
|
|
||||||
|
|
||||||
<template v-if="space.aiEnabled">
|
|
||||||
<model-select model="AiProvider" :label="$t('Default')" v-model="space.aiDefaultProvider"></model-select>
|
|
||||||
|
|
||||||
<v-number-input v-model="space.aiCreditsMonthly" :precision="2" :label="$t('MonthlyCredits')"
|
|
||||||
:disabled="!useUserPreferenceStore().userSettings.user.isSuperuser"></v-number-input>
|
|
||||||
<v-number-input v-model="space.aiCreditsBalance" :precision="4" :label="$t('AiCreditsBalance')"
|
|
||||||
:disabled="!useUserPreferenceStore().userSettings.user.isSuperuser"></v-number-input>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<v-btn color="success" @click="updateSpace()" prepend-icon="$save">{{ $t('Save') }}</v-btn>
|
|
||||||
|
|
||||||
<v-divider class="mt-4 mb-2"></v-divider>
|
|
||||||
<h2>{{ $t('Cosmetic') }}</h2>
|
|
||||||
<span>{{ $t('Space_Cosmetic_Settings') }}</span>
|
|
||||||
|
|
||||||
<v-label class="mt-4">{{ $t('Nav_Color') }}</v-label>
|
|
||||||
<v-color-picker v-model="space.navBgColor" class="mb-4" mode="hex" :modes="['hex']" show-swatches
|
|
||||||
:swatches="[['#ddbf86'],['#b98766'],['#b55e4f'],['#82aa8b'],['#385f84']]"></v-color-picker>
|
|
||||||
<v-btn class="mb-4" @click="space.navBgColor = ''">{{ $t('Reset') }}</v-btn>
|
|
||||||
|
|
||||||
<user-file-field v-model="space.navLogo" :label="$t('Logo')" :hint="$t('CustomNavLogoHelp')" persistent-hint></user-file-field>
|
|
||||||
|
|
||||||
<user-file-field v-model="space.logoColor32" :label="$t('Logo') + ' 32x32px'"></user-file-field>
|
|
||||||
<user-file-field v-model="space.logoColor128" :label="$t('Logo') + ' 128x128px'"></user-file-field>
|
|
||||||
<user-file-field v-model="space.logoColor144" :label="$t('Logo') + ' 144x144px'"></user-file-field>
|
|
||||||
<user-file-field v-model="space.logoColor180" :label="$t('Logo') + ' 180x180px'"></user-file-field>
|
|
||||||
<user-file-field v-model="space.logoColor192" :label="$t('Logo') + ' 192x192px'"></user-file-field>
|
|
||||||
<user-file-field v-model="space.logoColor512" :label="$t('Logo') + ' 512x512px'"></user-file-field>
|
|
||||||
<user-file-field v-model="space.logoColorSvg" :label="$t('Logo') + ' SVG'"></user-file-field>
|
|
||||||
<user-file-field v-model="space.customSpaceTheme" :label="$t('CustomTheme') + ' CSS'"></user-file-field>
|
|
||||||
|
|
||||||
|
|
||||||
<v-btn color="success" @click="updateSpace()" prepend-icon="$save">{{ $t('Save') }}</v-btn>
|
|
||||||
</v-form>
|
</v-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
|
||||||
import {onMounted, ref} from "vue";
|
|
||||||
import {ApiApi, Space} from "@/openapi";
|
|
||||||
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
|
||||||
import UserFileField from "@/components/inputs/UserFileField.vue";
|
|
||||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
|
||||||
import {isSpaceAboveRecipeLimit, isSpaceAboveStorageLimit, isSpaceAboveUserLimit} from "@/utils/logic_utils";
|
|
||||||
|
|
||||||
const space = ref({} as Space)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadSpace()
|
|
||||||
})
|
|
||||||
|
|
||||||
function loadSpace() {
|
|
||||||
let api = new ApiApi()
|
|
||||||
|
|
||||||
api.apiSpaceCurrentRetrieve().then(r => {
|
|
||||||
space.value = r
|
|
||||||
}).catch(err => {
|
|
||||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSpace() {
|
|
||||||
let api = new ApiApi()
|
|
||||||
api.apiSpacePartialUpdate({id: space.value.id, patchedSpace: space.value}).then(r => {
|
|
||||||
space.value = r
|
|
||||||
useUserPreferenceStore().activeSpace = Object.assign({}, space.value)
|
|
||||||
useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS, space.value)
|
|
||||||
}).catch(err => {
|
|
||||||
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||||
|
import SpaceLimitsInfo from "@/components/display/SpaceLimitsInfo.vue";
|
||||||
|
import SpaceEditor from "@/components/model_editors/SpaceEditor.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<p class="text-h6">
|
|
||||||
{{ $t('YourSpaces') }}
|
|
||||||
<v-btn color="create" prepend-icon="$add" class="float-right" size="small" @click="createNewSpace()">{{ $t('New') }}</v-btn>
|
|
||||||
</p>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="6" v-for="s in spaces" :key="s.id">
|
|
||||||
<v-card @click="useUserPreferenceStore().switchSpace(s)">
|
|
||||||
<v-img height="200px" cover :src="(s.image !== undefined) ? s.image?.preview : recipeDefaultImage" :alt="$t('Image')"></v-img>
|
|
||||||
<v-card-title>{{ s.name }}
|
|
||||||
<v-chip variant="tonal" density="compact" color="error" v-if="s.id == useUserPreferenceStore().activeSpace.id">{{ $t('active') }}</v-chip>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-subtitle>{{ $t('created_by') }} {{ s.createdBy.displayName }}</v-card-subtitle>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import {onMounted, ref} from "vue";
|
|
||||||
import {ApiApi, type FoodInheritField, Space} from "@/openapi";
|
|
||||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
|
|
||||||
import recipeDefaultImage from '../../assets/recipe_no_image.svg'
|
|
||||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
|
||||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
|
||||||
|
|
||||||
const {getDjangoUrl} = useDjangoUrls()
|
|
||||||
|
|
||||||
const spaces = ref([] as Space[])
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadSpaces()
|
|
||||||
})
|
|
||||||
|
|
||||||
function loadSpaces() {
|
|
||||||
const api = new ApiApi()
|
|
||||||
|
|
||||||
api.apiSpaceList().then(r => {
|
|
||||||
spaces.value = r.results
|
|
||||||
}).catch(err => {
|
|
||||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNewSpace() {
|
|
||||||
let api = new ApiApi()
|
|
||||||
api.apiSpaceCreate({space: {} as Space}).then(r => {
|
|
||||||
spaces.value.push(r)
|
|
||||||
}).catch(err => {
|
|
||||||
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -288,6 +288,7 @@
|
|||||||
"Show_as_header": "",
|
"Show_as_header": "",
|
||||||
"Single": "",
|
"Single": "",
|
||||||
"Size": "",
|
"Size": "",
|
||||||
|
"Skip": "",
|
||||||
"Sort_by_new": "",
|
"Sort_by_new": "",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
"SpaceHelp": "",
|
"SpaceHelp": "",
|
||||||
|
|||||||
@@ -281,6 +281,7 @@
|
|||||||
"Show_as_header": "Показване като заглавка",
|
"Show_as_header": "Показване като заглавка",
|
||||||
"Single": "Единичен",
|
"Single": "Единичен",
|
||||||
"Size": "Размер",
|
"Size": "Размер",
|
||||||
|
"Skip": "",
|
||||||
"Sort_by_new": "Сортиране по ново",
|
"Sort_by_new": "Сортиране по ново",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
"SpaceHelp": "",
|
"SpaceHelp": "",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@
|
|||||||
"Show_as_header": "Mostreu com a títol",
|
"Show_as_header": "Mostreu com a títol",
|
||||||
"Single": "Únic/a",
|
"Single": "Únic/a",
|
||||||
"Size": "Mida",
|
"Size": "Mida",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Identificació amb Xarxes Socials",
|
"Social_Authentication": "Identificació amb Xarxes Socials",
|
||||||
"Sort_by_new": "Ordenar a partir del més nou",
|
"Sort_by_new": "Ordenar a partir del més nou",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -360,6 +360,7 @@
|
|||||||
"Show_as_header": "Nastav jako nadpis",
|
"Show_as_header": "Nastav jako nadpis",
|
||||||
"Single": "Jednoduchý",
|
"Single": "Jednoduchý",
|
||||||
"Size": "Velikost",
|
"Size": "Velikost",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Přihlašování pomocí účtů sociálních sítí",
|
"Social_Authentication": "Přihlašování pomocí účtů sociálních sítí",
|
||||||
"Sort_by_new": "Seřadit od nejnovějšího",
|
"Sort_by_new": "Seřadit od nejnovějšího",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@
|
|||||||
"Show_as_header": "Vis som rubrik",
|
"Show_as_header": "Vis som rubrik",
|
||||||
"Single": "Enkel",
|
"Single": "Enkel",
|
||||||
"Size": "Størrelse",
|
"Size": "Størrelse",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Social authenticering",
|
"Social_Authentication": "Social authenticering",
|
||||||
"Sort_by_new": "Sorter efter nylige",
|
"Sort_by_new": "Sorter efter nylige",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -503,6 +503,7 @@
|
|||||||
"Show_as_header": "Als Überschrift",
|
"Show_as_header": "Als Überschrift",
|
||||||
"Single": "Einzeln",
|
"Single": "Einzeln",
|
||||||
"Size": "Größe",
|
"Size": "Größe",
|
||||||
|
"Skip": "Überspringen",
|
||||||
"Social_Authentication": "Login über Drittanbieter",
|
"Social_Authentication": "Login über Drittanbieter",
|
||||||
"Sort_by_new": "Nach Neueste sortieren",
|
"Sort_by_new": "Nach Neueste sortieren",
|
||||||
"Source": "Quelle",
|
"Source": "Quelle",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@
|
|||||||
"Show_as_header": "Εμφάνιση ως κεφαλίδα",
|
"Show_as_header": "Εμφάνιση ως κεφαλίδα",
|
||||||
"Single": "Ενικός",
|
"Single": "Ενικός",
|
||||||
"Size": "Μέγεθος",
|
"Size": "Μέγεθος",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Ταυτοποίηση μέσω κοινωνικών δικτύων",
|
"Social_Authentication": "Ταυτοποίηση μέσω κοινωνικών δικτύων",
|
||||||
"Sort_by_new": "Ταξινόμηση κατά νέο",
|
"Sort_by_new": "Ταξινόμηση κατά νέο",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -501,6 +501,7 @@
|
|||||||
"Show_as_header": "Show as header",
|
"Show_as_header": "Show as header",
|
||||||
"Single": "Single",
|
"Single": "Single",
|
||||||
"Size": "Size",
|
"Size": "Size",
|
||||||
|
"Skip": "Skip",
|
||||||
"Social_Authentication": "Social Authentication",
|
"Social_Authentication": "Social Authentication",
|
||||||
"Sort_by_new": "Sort by new",
|
"Sort_by_new": "Sort by new",
|
||||||
"Source": "Source",
|
"Source": "Source",
|
||||||
|
|||||||
@@ -484,6 +484,7 @@
|
|||||||
"Show_as_header": "Mostrar como encabezado",
|
"Show_as_header": "Mostrar como encabezado",
|
||||||
"Single": "Simple",
|
"Single": "Simple",
|
||||||
"Size": "Tamaño",
|
"Size": "Tamaño",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Autenticación Social",
|
"Social_Authentication": "Autenticación Social",
|
||||||
"Sort_by_new": "Ordenar por novedades",
|
"Sort_by_new": "Ordenar por novedades",
|
||||||
"SourceImportHelp": "Importar JSON en formato schema.org/recipe o páginas HTML con recetas en formato JSON+LD o microdatos.",
|
"SourceImportHelp": "Importar JSON en formato schema.org/recipe o páginas HTML con recetas en formato JSON+LD o microdatos.",
|
||||||
|
|||||||
@@ -353,6 +353,7 @@
|
|||||||
"Show_as_header": "Näytä otsikkona",
|
"Show_as_header": "Näytä otsikkona",
|
||||||
"Single": "Yksittäinen",
|
"Single": "Yksittäinen",
|
||||||
"Size": "Koko",
|
"Size": "Koko",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Sosiaalinen Todennus",
|
"Social_Authentication": "Sosiaalinen Todennus",
|
||||||
"Sort_by_new": "Lajittele uusien mukaan",
|
"Sort_by_new": "Lajittele uusien mukaan",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -498,6 +498,7 @@
|
|||||||
"Show_as_header": "Montrer comme en-tête",
|
"Show_as_header": "Montrer comme en-tête",
|
||||||
"Single": "Unique",
|
"Single": "Unique",
|
||||||
"Size": "Taille",
|
"Size": "Taille",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Authentification Sociale",
|
"Social_Authentication": "Authentification Sociale",
|
||||||
"Sort_by_new": "Trier par nouveautés",
|
"Sort_by_new": "Trier par nouveautés",
|
||||||
"Source": "Source",
|
"Source": "Source",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@
|
|||||||
"Show_as_header": "הצג בתור כותרת",
|
"Show_as_header": "הצג בתור כותרת",
|
||||||
"Single": "בודד",
|
"Single": "בודד",
|
||||||
"Size": "גודל",
|
"Size": "גודל",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "אימות חברתי",
|
"Social_Authentication": "אימות חברתי",
|
||||||
"Sort_by_new": "סדר ע\"י חדש",
|
"Sort_by_new": "סדר ע\"י חדש",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@
|
|||||||
"Show_as_header": "Prikaži kao zaglavlje",
|
"Show_as_header": "Prikaži kao zaglavlje",
|
||||||
"Single": "Jedna",
|
"Single": "Jedna",
|
||||||
"Size": "Veličina",
|
"Size": "Veličina",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Autentifikacija putem društvenih mreža",
|
"Social_Authentication": "Autentifikacija putem društvenih mreža",
|
||||||
"Sort_by_new": "Poredaj po novom",
|
"Sort_by_new": "Poredaj po novom",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@
|
|||||||
"Show_as_header": "Megjelenítés címként",
|
"Show_as_header": "Megjelenítés címként",
|
||||||
"Single": "Egyetlen",
|
"Single": "Egyetlen",
|
||||||
"Size": "Méret",
|
"Size": "Méret",
|
||||||
|
"Skip": "",
|
||||||
"Sort_by_new": "Rendezés legújabbak szerint",
|
"Sort_by_new": "Rendezés legújabbak szerint",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
"SpaceHelp": "",
|
"SpaceHelp": "",
|
||||||
|
|||||||
@@ -150,6 +150,7 @@
|
|||||||
"Shopping_list": "Գնումների ցուցակ",
|
"Shopping_list": "Գնումների ցուցակ",
|
||||||
"Show_as_header": "Ցույց տալ որպես խորագիր",
|
"Show_as_header": "Ցույց տալ որպես խորագիր",
|
||||||
"Size": "",
|
"Size": "",
|
||||||
|
"Skip": "",
|
||||||
"Sort_by_new": "Տեսակավորել ըստ նորերի",
|
"Sort_by_new": "Տեսակավորել ըստ նորերի",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
"SpaceHelp": "",
|
"SpaceHelp": "",
|
||||||
|
|||||||
@@ -309,6 +309,7 @@
|
|||||||
"Show_as_header": "Tampilkan sebagai tajuk",
|
"Show_as_header": "Tampilkan sebagai tajuk",
|
||||||
"Single": "",
|
"Single": "",
|
||||||
"Size": "Ukuran",
|
"Size": "Ukuran",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "",
|
"Social_Authentication": "",
|
||||||
"Sort_by_new": "Urutkan berdasarkan baru",
|
"Sort_by_new": "Urutkan berdasarkan baru",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -363,6 +363,7 @@
|
|||||||
"Show_as_header": "",
|
"Show_as_header": "",
|
||||||
"Single": "",
|
"Single": "",
|
||||||
"Size": "",
|
"Size": "",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "",
|
"Social_Authentication": "",
|
||||||
"Sort_by_new": "",
|
"Sort_by_new": "",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -500,6 +500,7 @@
|
|||||||
"Show_as_header": "Mostra come intestazione",
|
"Show_as_header": "Mostra come intestazione",
|
||||||
"Single": "Singolo",
|
"Single": "Singolo",
|
||||||
"Size": "Dimensione",
|
"Size": "Dimensione",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Autenticazione social",
|
"Social_Authentication": "Autenticazione social",
|
||||||
"Sort_by_new": "Prima i nuovi",
|
"Sort_by_new": "Prima i nuovi",
|
||||||
"Source": "Fonte",
|
"Source": "Fonte",
|
||||||
|
|||||||
@@ -337,6 +337,7 @@
|
|||||||
"Show_as_header": "Rodyti kaip antraštę",
|
"Show_as_header": "Rodyti kaip antraštę",
|
||||||
"Single": "",
|
"Single": "",
|
||||||
"Size": "",
|
"Size": "",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "",
|
"Social_Authentication": "",
|
||||||
"Sort_by_new": "Rūšiuoti pagal naujumą",
|
"Sort_by_new": "Rūšiuoti pagal naujumą",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@
|
|||||||
"Show_as_header": "",
|
"Show_as_header": "",
|
||||||
"Single": "",
|
"Single": "",
|
||||||
"Size": "",
|
"Size": "",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "",
|
"Social_Authentication": "",
|
||||||
"Sort_by_new": "",
|
"Sort_by_new": "",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -347,6 +347,7 @@
|
|||||||
"Show_as_header": "Vis som overskrift",
|
"Show_as_header": "Vis som overskrift",
|
||||||
"Single": "",
|
"Single": "",
|
||||||
"Size": "Størrelse",
|
"Size": "Størrelse",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "",
|
"Social_Authentication": "",
|
||||||
"Sort_by_new": "Sorter etter nyest",
|
"Sort_by_new": "Sorter etter nyest",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -501,6 +501,7 @@
|
|||||||
"Show_as_header": "Toon als koptekst",
|
"Show_as_header": "Toon als koptekst",
|
||||||
"Single": "Enkele",
|
"Single": "Enkele",
|
||||||
"Size": "Grootte",
|
"Size": "Grootte",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Authenticeren met sociale media-account",
|
"Social_Authentication": "Authenticeren met sociale media-account",
|
||||||
"Sort_by_new": "Sorteer op nieuw",
|
"Sort_by_new": "Sorteer op nieuw",
|
||||||
"Source": "Bron",
|
"Source": "Bron",
|
||||||
|
|||||||
@@ -391,6 +391,7 @@
|
|||||||
"Show_as_header": "Pokaż jako nagłówek",
|
"Show_as_header": "Pokaż jako nagłówek",
|
||||||
"Single": "Pojedynczy",
|
"Single": "Pojedynczy",
|
||||||
"Size": "Rozmiar",
|
"Size": "Rozmiar",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Uwierzytelnianie społecznościowe",
|
"Social_Authentication": "Uwierzytelnianie społecznościowe",
|
||||||
"Sort_by_new": "Sortuj według nowych",
|
"Sort_by_new": "Sortuj według nowych",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -299,6 +299,7 @@
|
|||||||
"Show_Week_Numbers": "Mostrar números das semanas?",
|
"Show_Week_Numbers": "Mostrar números das semanas?",
|
||||||
"Show_as_header": "Mostrar como cabeçalho",
|
"Show_as_header": "Mostrar como cabeçalho",
|
||||||
"Size": "Tamanho",
|
"Size": "Tamanho",
|
||||||
|
"Skip": "",
|
||||||
"Sort_by_new": "Ordenar por mais recente",
|
"Sort_by_new": "Ordenar por mais recente",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
"SpaceHelp": "",
|
"SpaceHelp": "",
|
||||||
|
|||||||
@@ -439,6 +439,7 @@
|
|||||||
"Show_as_header": "Mostrar como título",
|
"Show_as_header": "Mostrar como título",
|
||||||
"Single": "Simples",
|
"Single": "Simples",
|
||||||
"Size": "Tamanho",
|
"Size": "Tamanho",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Autenticação social",
|
"Social_Authentication": "Autenticação social",
|
||||||
"Sort_by_new": "Ordenar por novos",
|
"Sort_by_new": "Ordenar por novos",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -321,6 +321,7 @@
|
|||||||
"Show_as_header": "Afișare ca antet",
|
"Show_as_header": "Afișare ca antet",
|
||||||
"Single": "Singur",
|
"Single": "Singur",
|
||||||
"Size": "Marime",
|
"Size": "Marime",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Autentificare socială",
|
"Social_Authentication": "Autentificare socială",
|
||||||
"Sort_by_new": "Sortare după nou",
|
"Sort_by_new": "Sortare după nou",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -498,6 +498,7 @@
|
|||||||
"Show_as_header": "Показывать как заголовок",
|
"Show_as_header": "Показывать как заголовок",
|
||||||
"Single": "Одиночный",
|
"Single": "Одиночный",
|
||||||
"Size": "Размер",
|
"Size": "Размер",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Социальная аутентификация",
|
"Social_Authentication": "Социальная аутентификация",
|
||||||
"Sort_by_new": "Сортировка по новизне",
|
"Sort_by_new": "Сортировка по новизне",
|
||||||
"Source": "Источник",
|
"Source": "Источник",
|
||||||
|
|||||||
@@ -500,6 +500,7 @@
|
|||||||
"Show_as_header": "Prikaži kot glavo",
|
"Show_as_header": "Prikaži kot glavo",
|
||||||
"Single": "Ena",
|
"Single": "Ena",
|
||||||
"Size": "Velikost",
|
"Size": "Velikost",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Socialna avtentikacija",
|
"Social_Authentication": "Socialna avtentikacija",
|
||||||
"Sort_by_new": "Razvrsti po novih",
|
"Sort_by_new": "Razvrsti po novih",
|
||||||
"Source": "Vir",
|
"Source": "Vir",
|
||||||
|
|||||||
@@ -402,6 +402,7 @@
|
|||||||
"Show_as_header": "Visa som rubrik",
|
"Show_as_header": "Visa som rubrik",
|
||||||
"Single": "Enstaka",
|
"Single": "Enstaka",
|
||||||
"Size": "Storlek",
|
"Size": "Storlek",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Social autentisering",
|
"Social_Authentication": "Social autentisering",
|
||||||
"Sort_by_new": "Sortera efter ny",
|
"Sort_by_new": "Sortera efter ny",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@
|
|||||||
"Show_as_header": "Başlık olarak göster",
|
"Show_as_header": "Başlık olarak göster",
|
||||||
"Single": "Tek",
|
"Single": "Tek",
|
||||||
"Size": "Boyut",
|
"Size": "Boyut",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "Sosyal Kimlik Doğrulama",
|
"Social_Authentication": "Sosyal Kimlik Doğrulama",
|
||||||
"Sort_by_new": "Yeniye göre sırala",
|
"Sort_by_new": "Yeniye göre sırala",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -323,6 +323,7 @@
|
|||||||
"Show_as_header": "Показати як заголовок",
|
"Show_as_header": "Показати як заголовок",
|
||||||
"Single": "",
|
"Single": "",
|
||||||
"Size": "Розмір",
|
"Size": "Розмір",
|
||||||
|
"Skip": "",
|
||||||
"Sort_by_new": "Сортувати за новими",
|
"Sort_by_new": "Сортувати за новими",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
"SpaceHelp": "",
|
"SpaceHelp": "",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@
|
|||||||
"Show_as_header": "显示标题",
|
"Show_as_header": "显示标题",
|
||||||
"Single": "单个",
|
"Single": "单个",
|
||||||
"Size": "大小",
|
"Size": "大小",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "社交认证",
|
"Social_Authentication": "社交认证",
|
||||||
"Sort_by_new": "按新旧排序",
|
"Sort_by_new": "按新旧排序",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
|
|||||||
@@ -499,6 +499,7 @@
|
|||||||
"Show_as_header": "顯示為標題",
|
"Show_as_header": "顯示為標題",
|
||||||
"Single": "單一",
|
"Single": "單一",
|
||||||
"Size": "大小",
|
"Size": "大小",
|
||||||
|
"Skip": "",
|
||||||
"Social_Authentication": "社交認證",
|
"Social_Authentication": "社交認證",
|
||||||
"Sort_by_new": "按最新排序",
|
"Sort_by_new": "按最新排序",
|
||||||
"Source": "來源",
|
"Source": "來源",
|
||||||
|
|||||||
@@ -1803,6 +1803,11 @@ export interface ApiSpaceRetrieveRequest {
|
|||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiSpaceUpdateRequest {
|
||||||
|
id: number;
|
||||||
|
space?: Omit<Space, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'|'aiMonthlyCreditsUsed'>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiStepCreateRequest {
|
export interface ApiStepCreateRequest {
|
||||||
step: Omit<Step, 'instructionsMarkdown'|'stepRecipeData'|'numrecipe'>;
|
step: Omit<Step, 'instructionsMarkdown'|'stepRecipeData'|'numrecipe'>;
|
||||||
}
|
}
|
||||||
@@ -13265,6 +13270,46 @@ export class ApiApi extends runtime.BaseAPI {
|
|||||||
return await response.value();
|
return await response.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logs request counts to redis cache total/per user/
|
||||||
|
*/
|
||||||
|
async apiSpaceUpdateRaw(requestParameters: ApiSpaceUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Space>> {
|
||||||
|
if (requestParameters['id'] == null) {
|
||||||
|
throw new runtime.RequiredError(
|
||||||
|
'id',
|
||||||
|
'Required parameter "id" was null or undefined when calling apiSpaceUpdate().'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryParameters: any = {};
|
||||||
|
|
||||||
|
const headerParameters: runtime.HTTPHeaders = {};
|
||||||
|
|
||||||
|
headerParameters['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
if (this.configuration && this.configuration.apiKey) {
|
||||||
|
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.request({
|
||||||
|
path: `/api/space/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
|
||||||
|
method: 'PUT',
|
||||||
|
headers: headerParameters,
|
||||||
|
query: queryParameters,
|
||||||
|
body: SpaceToJSON(requestParameters['space']),
|
||||||
|
}, initOverrides);
|
||||||
|
|
||||||
|
return new runtime.JSONApiResponse(response, (jsonValue) => SpaceFromJSON(jsonValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logs request counts to redis cache total/per user/
|
||||||
|
*/
|
||||||
|
async apiSpaceUpdate(requestParameters: ApiSpaceUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Space> {
|
||||||
|
const response = await this.apiSpaceUpdateRaw(requestParameters, initOverrides);
|
||||||
|
return await response.value();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* logs request counts to redis cache total/per user/
|
* logs request counts to redis cache total/per user/
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,9 +13,7 @@
|
|||||||
<v-list-item :to="{name: 'SearchSettings'}" prepend-icon="$search">{{ $t('Search') }}</v-list-item>
|
<v-list-item :to="{name: 'SearchSettings'}" prepend-icon="$search">{{ $t('Search') }}</v-list-item>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-list-subheader>Space</v-list-subheader>
|
<v-list-subheader>Space</v-list-subheader>
|
||||||
<v-list-item :to="{name: 'UserSpaceSettings'}" prepend-icon="$spaces">{{ $t('YourSpaces') }}</v-list-item>
|
|
||||||
<v-list-item :to="{name: 'SpaceSettings'}" prepend-icon="$settings">{{ $t('SpaceSettings') }}</v-list-item>
|
<v-list-item :to="{name: 'SpaceSettings'}" prepend-icon="$settings">{{ $t('SpaceSettings') }}</v-list-item>
|
||||||
<v-list-item :to="{name: 'SpaceMemberSettings'}" prepend-icon="fa-solid fa-users">{{ $t('SpaceMembers') }}</v-list-item>
|
|
||||||
<v-list-item :to="{name: 'OpenDataImportSettings'}" prepend-icon="fa-solid fa-cloud-arrow-down">{{ $t('Open_Data_Import') }}</v-list-item>
|
<v-list-item :to="{name: 'OpenDataImportSettings'}" prepend-icon="fa-solid fa-cloud-arrow-down">{{ $t('Open_Data_Import') }}</v-list-item>
|
||||||
<v-list-item :to="{name: 'ExportDataSettings'}" prepend-icon="fa-solid fa-file-export">{{ $t('Export') }}</v-list-item>
|
<v-list-item :to="{name: 'ExportDataSettings'}" prepend-icon="fa-solid fa-file-export">{{ $t('Export') }}</v-list-item>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
</template>
|
</template>
|
||||||
<template #next>
|
<template #next>
|
||||||
|
<v-btn @click="finishWelcome()" color="warning" class="me-2" :loading="loading">{{ $t('Skip') }}</v-btn>
|
||||||
<v-btn @click="updateSpaceAndUserSettings()" :loading="loading" color="success">{{ $t('Next') }}</v-btn>
|
<v-btn @click="updateSpaceAndUserSettings()" :loading="loading" color="success">{{ $t('Next') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-stepper-actions>
|
</v-stepper-actions>
|
||||||
@@ -194,8 +195,10 @@ onMounted(() => {
|
|||||||
function finishWelcome(target: RouteLocationRaw = {name: 'StartPage'}) {
|
function finishWelcome(target: RouteLocationRaw = {name: 'StartPage'}) {
|
||||||
if (space.value) {
|
if (space.value) {
|
||||||
space.value.spaceSetupCompleted = true
|
space.value.spaceSetupCompleted = true
|
||||||
|
loading.value = true
|
||||||
updateSpace().then(() => {
|
updateSpace().then(() => {
|
||||||
router.push(target)
|
router.push(target)
|
||||||
|
loading.value = false
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
useMessageStore().addMessage(MessageType.ERROR, "Space not loaded yet", 5000)
|
useMessageStore().addMessage(MessageType.ERROR, "Space not loaded yet", 5000)
|
||||||
|
|||||||
Reference in New Issue
Block a user