mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-06-16 14:00:13 -04:00
chore: script setup components (#7299)
This commit is contained in:
@@ -37,9 +37,7 @@
|
|||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
|
||||||
setup() {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const auth = useMealieAuth();
|
const auth = useMealieAuth();
|
||||||
const groupSlug = computed(() => auth.user.value?.groupSlug);
|
const groupSlug = computed(() => auth.user.value?.groupSlug);
|
||||||
@@ -101,9 +99,6 @@ export default defineNuxtComponent({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
return { sections };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -25,15 +25,12 @@
|
|||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipeExplorerPageSearch from "./RecipeExplorerPageParts/RecipeExplorerPageSearch.vue";
|
import RecipeExplorerPageSearch from "./RecipeExplorerPageParts/RecipeExplorerPageSearch.vue";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
import { useLazyRecipes } from "~/composables/recipes";
|
import { useLazyRecipes } from "~/composables/recipes";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
components: { RecipeCardSection, RecipeExplorerPageSearch },
|
|
||||||
setup() {
|
|
||||||
const auth = useMealieAuth();
|
const auth = useMealieAuth();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
@@ -56,17 +53,4 @@ export default defineNuxtComponent({
|
|||||||
function onItemSelected(item: any, urlPrefix: string) {
|
function onItemSelected(item: any, urlPrefix: string) {
|
||||||
searchComponent.value?.filterItems(item, urlPrefix);
|
searchComponent.value?.filterItems(item, urlPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
ready,
|
|
||||||
searchComponent,
|
|
||||||
searchQuery,
|
|
||||||
recipes,
|
|
||||||
appendRecipes,
|
|
||||||
replaceRecipes,
|
|
||||||
onSearchReady,
|
|
||||||
onItemSelected,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
nudge-bottom="3"
|
nudge-bottom="3"
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: menuProps }">
|
||||||
<v-badge
|
<v-badge
|
||||||
v-memo="[selectedCount]"
|
v-memo="[selectedCount]"
|
||||||
:model-value="selectedCount > 0"
|
:model-value="selectedCount > 0"
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
color="accent"
|
color="accent"
|
||||||
dark
|
dark
|
||||||
v-bind="props"
|
v-bind="menuProps"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -145,20 +145,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ISearchableItem } from "~/composables/use-search";
|
import type { ISearchableItem } from "~/composables/use-search";
|
||||||
import { useSearch } from "~/composables/use-search";
|
import { useSearch } from "~/composables/use-search";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
items: {
|
items: {
|
||||||
type: Array as () => ISearchableItem[],
|
type: Array as () => ISearchableItem[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
modelValue: {
|
|
||||||
type: Array as () => any[],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
requireAll: {
|
requireAll: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
@@ -167,9 +162,14 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["update:requireAll", "update:modelValue"],
|
|
||||||
setup(props, context) {
|
const modelValue = defineModel<ISearchableItem[]>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "update:requireAll", value: boolean | undefined): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
menu: false,
|
menu: false,
|
||||||
});
|
});
|
||||||
@@ -179,30 +179,28 @@ export default defineNuxtComponent({
|
|||||||
|
|
||||||
const combinator = computed({
|
const combinator = computed({
|
||||||
get: () => (props.requireAll ? "hasAll" : "hasAny"),
|
get: () => (props.requireAll ? "hasAll" : "hasAny"),
|
||||||
set: (value) => {
|
set: (value: string) => {
|
||||||
context.emit("update:requireAll", value === "hasAll");
|
emit("update:requireAll", value === "hasAll");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use shallowRef to prevent deep reactivity on large arrays
|
const selected = computed<ISearchableItem[]>({
|
||||||
const selected = computed({
|
get: () => modelValue.value ?? [],
|
||||||
get: () => props.modelValue as ISearchableItem[],
|
set: (value: ISearchableItem[]) => {
|
||||||
set: (value) => {
|
modelValue.value = value;
|
||||||
context.emit("update:modelValue", value);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedRadio = computed({
|
const selectedRadio = computed<null | ISearchableItem>({
|
||||||
get: () => (selected.value.length > 0 ? selected.value[0] : null),
|
get: () => (selected.value.length > 0 ? selected.value[0] : null),
|
||||||
set: (value) => {
|
set: (value: ISearchableItem | null) => {
|
||||||
context.emit("update:modelValue", value ? [value] : []);
|
const next = value ? [value] : [];
|
||||||
|
selected.value = next;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedCount = computed(() => selected.value.length);
|
const selectedCount = computed(() => selected.value.length);
|
||||||
const selectedIds = computed(() => {
|
const selectedIds = computed(() => new Set(selected.value.map(item => item.id)));
|
||||||
return new Set(selected.value.map(item => item.id));
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleRadioClick = (item: ISearchableItem) => {
|
const handleRadioClick = (item: ISearchableItem) => {
|
||||||
if (selectedRadio.value === item) {
|
if (selectedRadio.value === item) {
|
||||||
@@ -215,19 +213,4 @@ export default defineNuxtComponent({
|
|||||||
selectedRadio.value = null;
|
selectedRadio.value = null;
|
||||||
searchInput.value = "";
|
searchInput.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
combinator,
|
|
||||||
state,
|
|
||||||
searchInput,
|
|
||||||
selected,
|
|
||||||
selectedRadio,
|
|
||||||
selectedCount,
|
|
||||||
selectedIds,
|
|
||||||
filtered,
|
|
||||||
handleRadioClick,
|
|
||||||
clearSelection,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,23 +12,13 @@
|
|||||||
</v-chip>
|
</v-chip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { getTextColor } from "~/composables/use-text-color";
|
import { getTextColor } from "~/composables/use-text-color";
|
||||||
import type { MultiPurposeLabelSummary } from "~/lib/api/types/recipe";
|
import type { MultiPurposeLabelSummary } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps<{
|
||||||
props: {
|
label: MultiPurposeLabelSummary;
|
||||||
label: {
|
}>();
|
||||||
type: Object as () => MultiPurposeLabelSummary,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const textColor = computed(() => getTextColor(props.label.color));
|
|
||||||
|
|
||||||
return {
|
const textColor = computed(() => getTextColor(props.label.color));
|
||||||
textColor,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -17,13 +17,13 @@
|
|||||||
start
|
start
|
||||||
min-width="125px"
|
min-width="125px"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: hoverProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
size="small"
|
size="small"
|
||||||
variant="text"
|
variant="text"
|
||||||
class="ml-2 handle"
|
class="ml-2 handle"
|
||||||
icon
|
icon
|
||||||
v-bind="props"
|
v-bind="hoverProps"
|
||||||
>
|
>
|
||||||
<v-icon>
|
<v-icon>
|
||||||
{{ $globals.icons.arrowUpDown }}
|
{{ $globals.icons.arrowUpDown }}
|
||||||
@@ -35,31 +35,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/household";
|
import type { ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/household";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps<{
|
||||||
props: {
|
useColor?: boolean;
|
||||||
modelValue: {
|
}>();
|
||||||
type: Object as () => ShoppingListMultiPurposeLabelOut,
|
const modelValue = defineModel<ShoppingListMultiPurposeLabelOut>({ required: true });
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
useColor: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, context) {
|
|
||||||
const labelColor = ref<string | undefined>(props.useColor ? props.modelValue.label.color : undefined);
|
|
||||||
|
|
||||||
function contextHandler(event: string) {
|
const labelColor = ref<string | undefined>(props.useColor ? modelValue.value.label.color : undefined);
|
||||||
context.emit(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
contextHandler,
|
|
||||||
labelColor,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,15 +10,12 @@
|
|||||||
<v-col :cols="itemLabelCols">
|
<v-col :cols="itemLabelCols">
|
||||||
<div class="d-flex align-center flex-nowrap">
|
<div class="d-flex align-center flex-nowrap">
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
v-model="listItem.checked"
|
:model-value="listItem.checked"
|
||||||
hide-details
|
hide-details
|
||||||
density="compact"
|
density="compact"
|
||||||
class="mt-0 flex-shrink-0"
|
class="mt-0 flex-shrink-0"
|
||||||
color="null"
|
color="null"
|
||||||
@click="() => {
|
@click="toggleChecked"
|
||||||
listItem.checked = !listItem.checked
|
|
||||||
$emit('checked', listItem)
|
|
||||||
}"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="ml-2 text-truncate"
|
class="ml-2 text-truncate"
|
||||||
@@ -43,7 +40,7 @@
|
|||||||
start
|
start
|
||||||
min-width="125px"
|
min-width="125px"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: hoverProps }">
|
||||||
<v-tooltip
|
<v-tooltip
|
||||||
v-if="recipeList && recipeList.length"
|
v-if="recipeList && recipeList.length"
|
||||||
open-delay="200"
|
open-delay="200"
|
||||||
@@ -84,7 +81,7 @@
|
|||||||
variant="text"
|
variant="text"
|
||||||
class="handle"
|
class="handle"
|
||||||
icon
|
icon
|
||||||
v-bind="props"
|
v-bind="hoverProps"
|
||||||
>
|
>
|
||||||
<v-icon>
|
<v-icon>
|
||||||
{{ $globals.icons.arrowUpDown }}
|
{{ $globals.icons.arrowUpDown }}
|
||||||
@@ -155,27 +152,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { useOnline } from "@vueuse/core";
|
import { useOnline } from "@vueuse/core";
|
||||||
import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue";
|
import RecipeIngredientListItem from "../Recipe/RecipeIngredientListItem.vue";
|
||||||
import ShoppingListItemEditor from "./ShoppingListItemEditor.vue";
|
import ShoppingListItemEditor from "./ShoppingListItemEditor.vue";
|
||||||
import type { ShoppingListItemOut } from "~/lib/api/types/household";
|
import type { ShoppingListItemOut } from "~/lib/api/types/household";
|
||||||
import type { MultiPurposeLabelOut, MultiPurposeLabelSummary } from "~/lib/api/types/labels";
|
import type { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
||||||
import type { IngredientFood, IngredientUnit, RecipeSummary } from "~/lib/api/types/recipe";
|
import type { IngredientFood, IngredientUnit, RecipeSummary } from "~/lib/api/types/recipe";
|
||||||
import RecipeList from "~/components/Domain/Recipe/RecipeList.vue";
|
import RecipeList from "~/components/Domain/Recipe/RecipeList.vue";
|
||||||
|
|
||||||
interface actions {
|
const model = defineModel<ShoppingListItemOut>({ type: Object as () => ShoppingListItemOut, required: true });
|
||||||
text: string;
|
|
||||||
event: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
components: { ShoppingListItemEditor, RecipeList, RecipeIngredientListItem },
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Object as () => ShoppingListItemOut,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
labels: {
|
labels: {
|
||||||
type: Array as () => MultiPurposeLabelOut[],
|
type: Array as () => MultiPurposeLabelOut[],
|
||||||
required: true,
|
required: true,
|
||||||
@@ -189,124 +177,74 @@ export default defineNuxtComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
recipes: {
|
recipes: {
|
||||||
type: Map<string, RecipeSummary>,
|
type: Map as unknown as () => Map<string, RecipeSummary>,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["checked", "update:modelValue", "save", "delete"],
|
|
||||||
setup(props, context) {
|
const emit = defineEmits<{
|
||||||
|
(e: "checked" | "save", item: ShoppingListItemOut): void;
|
||||||
|
(e: "delete"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const displayRecipeRefs = ref(false);
|
const displayRecipeRefs = ref(false);
|
||||||
const itemLabelCols = ref<string>(props.modelValue.checked ? "auto" : "6");
|
const itemLabelCols = computed<string>(() => (model.value?.checked ? "auto" : "6"));
|
||||||
const isOffline = computed(() => useOnline().value === false);
|
const online = useOnline();
|
||||||
|
const isOffline = computed(() => online.value === false);
|
||||||
|
|
||||||
const contextMenu: actions[] = [
|
type actions = { text: string; event: string };
|
||||||
{
|
const contextMenu = ref<actions[]>([
|
||||||
text: i18n.t("general.edit") as string,
|
{ text: i18n.t("general.edit") as string, event: "edit" },
|
||||||
event: "edit",
|
{ text: i18n.t("general.delete") as string, event: "delete" },
|
||||||
},
|
]);
|
||||||
{
|
|
||||||
text: i18n.t("general.delete") as string,
|
|
||||||
event: "delete",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// copy prop value so a refresh doesn't interrupt the user
|
// copy prop value so a refresh doesn't interrupt the user
|
||||||
const localListItem = ref(Object.assign({}, props.modelValue));
|
const localListItem = ref(Object.assign({}, model.value));
|
||||||
const listItem = computed({
|
|
||||||
get: () => {
|
const listItem = computed<ShoppingListItemOut>({
|
||||||
return props.modelValue;
|
get: () => model.value,
|
||||||
},
|
set: (val: ShoppingListItemOut) => {
|
||||||
set: (val) => {
|
|
||||||
// keep local copy in sync
|
|
||||||
localListItem.value = val;
|
localListItem.value = val;
|
||||||
context.emit("update:modelValue", val);
|
model.value = val;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const edit = ref(false);
|
const edit = ref(false);
|
||||||
function toggleEdit(val = !edit.value) {
|
function toggleEdit(val = !edit.value) {
|
||||||
if (edit.value === val) {
|
if (edit.value === val) return;
|
||||||
return;
|
if (val) localListItem.value = model.value;
|
||||||
}
|
|
||||||
|
|
||||||
if (val) {
|
|
||||||
// update local copy of item with the current value
|
|
||||||
localListItem.value = props.modelValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
edit.value = val;
|
edit.value = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleChecked() {
|
||||||
|
const updated = { ...model.value, checked: !model.value.checked } as ShoppingListItemOut;
|
||||||
|
model.value = updated;
|
||||||
|
emit("checked", updated);
|
||||||
|
}
|
||||||
|
|
||||||
function contextHandler(event: string) {
|
function contextHandler(event: string) {
|
||||||
if (event === "edit") {
|
if (event === "edit") {
|
||||||
toggleEdit(true);
|
toggleEdit(true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
context.emit(event);
|
emit(event as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
context.emit("save", localListItem.value);
|
emit("save", localListItem.value);
|
||||||
edit.value = false;
|
edit.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedLabels = computed(() => {
|
|
||||||
return props.labels.map((label) => {
|
|
||||||
return {
|
|
||||||
id: label.id,
|
|
||||||
text: label.name,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the label for the shopping list item. Either the label assign to the item
|
|
||||||
* or the label of the food applied.
|
|
||||||
*/
|
|
||||||
const label = computed<MultiPurposeLabelSummary | undefined>(() => {
|
|
||||||
if (listItem.value.label) {
|
|
||||||
return listItem.value.label as MultiPurposeLabelSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listItem.value.food?.label) {
|
|
||||||
return listItem.value.food.label;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
const recipeList = computed<RecipeSummary[]>(() => {
|
const recipeList = computed<RecipeSummary[]>(() => {
|
||||||
const recipeList: RecipeSummary[] = [];
|
const ret: RecipeSummary[] = [];
|
||||||
if (!listItem.value.recipeReferences) {
|
if (!listItem.value.recipeReferences) return ret;
|
||||||
return recipeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
listItem.value.recipeReferences.forEach((ref) => {
|
listItem.value.recipeReferences.forEach((ref) => {
|
||||||
const recipe = props.recipes?.get(ref.recipeId);
|
const recipe = props.recipes?.get(ref.recipeId);
|
||||||
if (recipe) {
|
if (recipe) ret.push(recipe);
|
||||||
recipeList.push(recipe);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
return ret;
|
||||||
return recipeList;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
updatedLabels,
|
|
||||||
save,
|
|
||||||
contextHandler,
|
|
||||||
displayRecipeRefs,
|
|
||||||
edit,
|
|
||||||
contextMenu,
|
|
||||||
itemLabelCols,
|
|
||||||
listItem,
|
|
||||||
localListItem,
|
|
||||||
label,
|
|
||||||
recipeList,
|
|
||||||
toggleEdit,
|
|
||||||
isOffline,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -102,18 +102,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/household";
|
import type { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/household";
|
||||||
import type { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
import type { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
||||||
import type { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
import type { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||||
import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store";
|
import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
// modelValue as reactive v-model
|
||||||
props: {
|
const listItem = defineModel<ShoppingListItemCreate | ShoppingListItemOut>({ required: true });
|
||||||
modelValue: {
|
|
||||||
type: Object as () => ShoppingListItemCreate | ShoppingListItemOut,
|
defineProps({
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
labels: {
|
labels: {
|
||||||
type: Array as () => MultiPurposeLabelOut[],
|
type: Array as () => MultiPurposeLabelOut[],
|
||||||
required: true,
|
required: true,
|
||||||
@@ -131,47 +129,43 @@ export default defineNuxtComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["update:modelValue", "save", "cancel", "delete"],
|
|
||||||
setup(props, context) {
|
// const emit = defineEmits<["save", "cancel", "delete"]>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "save", item: ShoppingListItemOut): void;
|
||||||
|
(e: "cancel" | "delete"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const foodStore = useFoodStore();
|
const foodStore = useFoodStore();
|
||||||
const foodData = useFoodData();
|
const foodData = useFoodData();
|
||||||
|
|
||||||
const unitStore = useUnitStore();
|
const unitStore = useUnitStore();
|
||||||
const unitData = useUnitData();
|
const unitData = useUnitData();
|
||||||
|
|
||||||
const listItem = computed({
|
|
||||||
get: () => {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("update:modelValue", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue.quantity,
|
() => listItem.value.quantity,
|
||||||
() => {
|
(newQty) => {
|
||||||
if (!props.modelValue.quantity) {
|
if (!newQty) {
|
||||||
listItem.value.quantity = 0;
|
listItem.value.quantity = 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue.food,
|
() => listItem.value.food,
|
||||||
(newFood) => {
|
(newFood) => {
|
||||||
listItem.value.label = newFood?.label || null;
|
listItem.value.label = newFood?.label || null;
|
||||||
listItem.value.labelId = listItem.value.label?.id || null;
|
listItem.value.labelId = listItem.value.label?.id || null;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const autoFocus = !listItem.value.food && listItem.value.note ? "note" : "food";
|
const autoFocus = computed(() => (!listItem.value.food && listItem.value.note ? "note" : "food"));
|
||||||
|
|
||||||
async function createAssignFood(val: string) {
|
async function createAssignFood(val: string) {
|
||||||
// keep UI reactive
|
// keep UI reactive
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
listItem.value.food ? (listItem.value.food.name = val) : (listItem.value.food = { name: val });
|
listItem.value.food ? (listItem.value.food.name = val) : (listItem.value.food = { name: val } as any);
|
||||||
|
|
||||||
foodData.data.name = val;
|
foodData.data.name = val;
|
||||||
const newFood = await foodStore.actions.createOne(foodData.data);
|
const newFood = await foodStore.actions.createOne(foodData.data);
|
||||||
@@ -185,7 +179,7 @@ export default defineNuxtComponent({
|
|||||||
async function createAssignUnit(val: string) {
|
async function createAssignUnit(val: string) {
|
||||||
// keep UI reactive
|
// keep UI reactive
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
listItem.value.unit ? (listItem.value.unit.name = val) : (listItem.value.unit = { name: val });
|
listItem.value.unit ? (listItem.value.unit.name = val) : (listItem.value.unit = { name: val } as any);
|
||||||
|
|
||||||
unitData.data.name = val;
|
unitData.data.name = val;
|
||||||
const newUnit = await unitStore.actions.createOne(unitData.data);
|
const newUnit = await unitStore.actions.createOne(unitData.data);
|
||||||
@@ -205,22 +199,11 @@ export default defineNuxtComponent({
|
|||||||
await foodStore.actions.updateOne(listItem.value.food);
|
await foodStore.actions.updateOne(listItem.value.food);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
function handleNoteKeyPress(event: KeyboardEvent) {
|
||||||
listItem,
|
const e = event as KeyboardEvent & { key: string; shiftKey: boolean };
|
||||||
autoFocus,
|
if (!e.shiftKey && e.key === "Enter") {
|
||||||
createAssignFood,
|
e.preventDefault();
|
||||||
createAssignUnit,
|
emit("save");
|
||||||
assignLabelToFood,
|
}
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleNoteKeyPress(event) {
|
|
||||||
// Save on Enter
|
|
||||||
if (!event.shiftKey && event.key === "Enter") {
|
|
||||||
event.preventDefault();
|
|
||||||
this.$emit("save");
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
:disabled="!user || !tooltip"
|
:disabled="!user || !tooltip"
|
||||||
location="end"
|
location="end"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: tooltipProps }">
|
||||||
<v-avatar
|
<v-avatar
|
||||||
v-if="list"
|
v-if="list"
|
||||||
v-bind="props"
|
v-bind="tooltipProps"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img
|
||||||
:src="imageURL"
|
:src="imageURL"
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<v-avatar
|
<v-avatar
|
||||||
v-else
|
v-else
|
||||||
:size="size"
|
:size="size"
|
||||||
v-bind="props"
|
v-bind="tooltipProps"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img
|
||||||
:src="imageURL"
|
:src="imageURL"
|
||||||
@@ -35,11 +35,10 @@
|
|||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserStore } from "~/composables/store/use-user-store";
|
import { useUserStore } from "~/composables/store/use-user-store";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
userId: {
|
userId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -56,12 +55,10 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const state = reactive({
|
|
||||||
error: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const error = ref(false);
|
||||||
|
|
||||||
const auth = useMealieAuth();
|
const auth = useMealieAuth();
|
||||||
const { store: users } = useUserStore();
|
const { store: users } = useUserStore();
|
||||||
const user = computed(() => {
|
const user = computed(() => {
|
||||||
@@ -74,12 +71,4 @@ export default defineNuxtComponent({
|
|||||||
const key = authUser?.cacheKey ?? "";
|
const key = authUser?.cacheKey ?? "";
|
||||||
return `/api/media/users/${props.userId}/profile.webp?cacheKey=${key}`;
|
return `/api/media/users/${props.userId}/profile.webp?cacheKey=${key}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
imageURL,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -73,8 +73,7 @@
|
|||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { watchEffect } from "vue";
|
|
||||||
import { useUserApi } from "@/composables/api";
|
import { useUserApi } from "@/composables/api";
|
||||||
import BaseDialog from "~/components/global/BaseDialog.vue";
|
import BaseDialog from "~/components/global/BaseDialog.vue";
|
||||||
import AppButtonCopy from "~/components/global/AppButtonCopy.vue";
|
import AppButtonCopy from "~/components/global/AppButtonCopy.vue";
|
||||||
@@ -86,21 +85,8 @@ import type { HouseholdInDB } from "~/lib/api/types/household";
|
|||||||
import { useGroups } from "~/composables/use-groups";
|
import { useGroups } from "~/composables/use-groups";
|
||||||
import { useAdminHouseholds } from "~/composables/use-households";
|
import { useAdminHouseholds } from "~/composables/use-households";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const inviteDialog = defineModel<boolean>("modelValue", { type: Boolean, default: false });
|
||||||
name: "UserInviteDialog",
|
|
||||||
components: {
|
|
||||||
BaseDialog,
|
|
||||||
AppButtonCopy,
|
|
||||||
BaseButton,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const auth = useMealieAuth();
|
const auth = useMealieAuth();
|
||||||
|
|
||||||
@@ -123,15 +109,6 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const inviteDialog = computed<boolean>({
|
|
||||||
get() {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
context.emit("update:modelValue", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getSignupLink(group: string | null = null, household: string | null = null) {
|
async function getSignupLink(group: string | null = null, household: string | null = null) {
|
||||||
const payload = (group && household) ? { uses: 1, group_id: group, household_id: household } : { uses: 1 };
|
const payload = (group && household) ? { uses: 1, group_id: group, household_id: household } : { uses: 1 };
|
||||||
const { data } = await api.households.createInvitation(payload);
|
const { data } = await api.households.createInvitation(payload);
|
||||||
@@ -145,20 +122,18 @@ export default defineNuxtComponent({
|
|||||||
return households.value?.filter(household => household.groupId === selectedGroup.value);
|
return households.value?.filter(household => household.groupId === selectedGroup.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
function constructLink(token: string) {
|
function constructLink(tokenVal: string) {
|
||||||
return token ? `${window.location.origin}/register?token=${token}` : "";
|
return tokenVal ? `${window.location.origin}/register?token=${tokenVal}` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatedSignupLink = computed(() => {
|
const generatedSignupLink = computed(() => constructLink(token.value));
|
||||||
return constructLink(token.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// =================================================
|
|
||||||
// Email Invitation
|
// Email Invitation
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
loading: false,
|
loading: false,
|
||||||
sendTo: "",
|
sendTo: "",
|
||||||
});
|
});
|
||||||
|
const { loading, sendTo } = toRefs(state);
|
||||||
|
|
||||||
async function sendInvite() {
|
async function sendInvite() {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
@@ -181,52 +156,24 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validEmail = computed(() => {
|
const validEmail = computed(() => {
|
||||||
if (state.sendTo === "") {
|
if (sendTo.value === "") return false;
|
||||||
return false;
|
const valid = validators.email(sendTo.value);
|
||||||
}
|
return valid === true;
|
||||||
const valid = validators.email(state.sendTo);
|
|
||||||
|
|
||||||
// Explicit bool check because validators.email sometimes returns a string
|
|
||||||
if (valid === true) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
// Watchers (replacing options API watchers)
|
||||||
sendInvite,
|
watch(inviteDialog, (val) => {
|
||||||
validators,
|
if (val && !isAdmin.value) {
|
||||||
validEmail,
|
getSignupLink();
|
||||||
inviteDialog,
|
|
||||||
getSignupLink,
|
|
||||||
generatedSignupLink,
|
|
||||||
selectedGroup,
|
|
||||||
selectedHousehold,
|
|
||||||
filteredHouseholds,
|
|
||||||
groups,
|
|
||||||
households,
|
|
||||||
fetchGroupsAndHouseholds,
|
|
||||||
...toRefs(state),
|
|
||||||
isAdmin,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
modelValue: {
|
|
||||||
immediate: false,
|
|
||||||
handler(val) {
|
|
||||||
if (val && !this.isAdmin) {
|
|
||||||
this.getSignupLink();
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
selectedHousehold(newVal) {
|
|
||||||
if (newVal && this.selectedGroup) {
|
|
||||||
this.getSignupLink(this.selectedGroup, this.selectedHousehold);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fetchGroupsAndHouseholds();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(selectedHousehold, (newVal) => {
|
||||||
|
if (newVal && selectedGroup.value) {
|
||||||
|
getSignupLink(selectedGroup.value, selectedHousehold.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// initial fetch
|
||||||
|
fetchGroupsAndHouseholds();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -52,14 +52,14 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script setup lang="ts">
|
||||||
interface LinkProp {
|
interface LinkProp {
|
||||||
text: string;
|
text: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
to: string;
|
to: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
defineProps({
|
||||||
link: {
|
link: {
|
||||||
type: Object as () => LinkProp,
|
type: Object as () => LinkProp,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -70,6 +70,4 @@ const props = defineProps({
|
|||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Props", props);
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -78,29 +78,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDark } from "@vueuse/core";
|
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
import { useUserRegistrationForm } from "~/composables/use-users/user-registration-form";
|
import { useUserRegistrationForm } from "~/composables/use-users/user-registration-form";
|
||||||
import { usePasswordField } from "~/composables/use-passwords";
|
import { usePasswordField } from "~/composables/use-passwords";
|
||||||
import UserPasswordStrength from "~/components/Domain/User/UserPasswordStrength.vue";
|
import UserPasswordStrength from "~/components/Domain/User/UserPasswordStrength.vue";
|
||||||
|
|
||||||
|
definePageMeta({ layout: "blank" });
|
||||||
|
|
||||||
const inputAttrs = {
|
const inputAttrs = {
|
||||||
validateOnBlur: true,
|
validateOnBlur: true,
|
||||||
class: "pb-1",
|
class: "pb-1",
|
||||||
variant: "solo-filled" as any,
|
variant: "solo-filled" as any,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
components: { UserPasswordStrength },
|
|
||||||
setup() {
|
|
||||||
definePageMeta({
|
|
||||||
layout: "blank",
|
|
||||||
});
|
|
||||||
|
|
||||||
const isDark = useDark();
|
|
||||||
const langDialog = ref(false);
|
|
||||||
|
|
||||||
const pwFields = usePasswordField();
|
const pwFields = usePasswordField();
|
||||||
const {
|
const {
|
||||||
accountDetails,
|
accountDetails,
|
||||||
@@ -109,26 +100,7 @@ export default defineNuxtComponent({
|
|||||||
usernameErrorMessages,
|
usernameErrorMessages,
|
||||||
validateUsername,
|
validateUsername,
|
||||||
validateEmail,
|
validateEmail,
|
||||||
domAccountForm,
|
|
||||||
} = useUserRegistrationForm();
|
} = useUserRegistrationForm();
|
||||||
return {
|
|
||||||
accountDetails,
|
|
||||||
credentials,
|
|
||||||
emailErrorMessages,
|
|
||||||
inputAttrs,
|
|
||||||
isDark,
|
|
||||||
langDialog,
|
|
||||||
pwFields,
|
|
||||||
usernameErrorMessages,
|
|
||||||
validators,
|
|
||||||
// Validators
|
|
||||||
validateUsername,
|
|
||||||
validateEmail,
|
|
||||||
// Dom Refs
|
|
||||||
domAccountForm,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|||||||
@@ -94,15 +94,13 @@
|
|||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import type { SideBarLink } from "~/types/application-types";
|
import type { SideBarLink } from "~/types/application-types";
|
||||||
import { useCookbookPreferences } from "~/composables/use-users/preferences";
|
import { useCookbookPreferences } from "~/composables/use-users/preferences";
|
||||||
import { useCookbookStore, usePublicCookbookStore } from "~/composables/store/use-cookbook-store";
|
import { useCookbookStore, usePublicCookbookStore } from "~/composables/store/use-cookbook-store";
|
||||||
import type { ReadCookBook } from "~/lib/api/types/cookbook";
|
import type { ReadCookBook } from "~/lib/api/types/cookbook";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
setup() {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { $appInfo, $globals } = useNuxtApp();
|
const { $appInfo, $globals } = useNuxtApp();
|
||||||
const display = useDisplay();
|
const display = useDisplay();
|
||||||
@@ -135,7 +133,6 @@ export default defineNuxtComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showImageImport = computed(() => $appInfo.enableOpenaiImageServices);
|
const showImageImport = computed(() => $appInfo.enableOpenaiImageServices);
|
||||||
const languageDialog = ref<boolean>(false);
|
|
||||||
|
|
||||||
const sidebar = ref<boolean>(false);
|
const sidebar = ref<boolean>(false);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -288,16 +285,4 @@ export default defineNuxtComponent({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
|
||||||
groupSlug,
|
|
||||||
cookbookLinks,
|
|
||||||
createLinks,
|
|
||||||
topLinks,
|
|
||||||
isOwnGroup,
|
|
||||||
languageDialog,
|
|
||||||
sidebar,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,11 +29,3 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
</v-footer>
|
</v-footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
setup() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
class="d-print-none"
|
class="d-print-none"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<router-link :to="routerLink">
|
<RouterLink :to="routerLink">
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
color="white"
|
color="white"
|
||||||
>
|
>
|
||||||
<v-icon size="40"> {{ $globals.icons.primary }} </v-icon>
|
<v-icon size="40"> {{ $globals.icons.primary }} </v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</router-link>
|
</RouterLink>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
btn
|
btn
|
||||||
@@ -84,19 +84,16 @@
|
|||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import RecipeDialogSearch from "~/components/Domain/Recipe/RecipeDialogSearch.vue";
|
import type RecipeDialogSearch from "~/components/Domain/Recipe/RecipeDialogSearch.vue";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
components: { RecipeDialogSearch },
|
|
||||||
props: {
|
|
||||||
menu: {
|
menu: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup() {
|
|
||||||
const auth = useMealieAuth();
|
const auth = useMealieAuth();
|
||||||
const { loggedIn } = useLoggedInState();
|
const { loggedIn } = useLoggedInState();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -134,17 +131,6 @@ export default defineNuxtComponent({
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
activateSearch,
|
|
||||||
domSearchDialog,
|
|
||||||
routerLink,
|
|
||||||
loggedIn,
|
|
||||||
logout,
|
|
||||||
xs, smAndUp,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-navigation-drawer v-model="showDrawer" class="d-flex flex-column d-print-none position-fixed" touchless>
|
<v-navigation-drawer v-model="modelValue" class="d-flex flex-column d-print-none position-fixed" touchless>
|
||||||
<LanguageDialog v-model="languageDialog" />
|
<LanguageDialog v-model="state.languageDialog" />
|
||||||
<!-- User Profile -->
|
<!-- User Profile -->
|
||||||
<template v-if="loggedIn">
|
<template v-if="loggedIn && sessionUser">
|
||||||
<v-list-item lines="two" :to="userProfileLink" exact>
|
<v-list-item lines="two" :to="userProfileLink" exact>
|
||||||
<div class="d-flex align-center ga-2">
|
<div class="d-flex align-center ga-2">
|
||||||
<UserAvatar list :user-id="sessionUser.id" :tooltip="false" />
|
<UserAvatar list :user-id="sessionUser.id" :tooltip="false" />
|
||||||
@@ -29,20 +29,20 @@
|
|||||||
|
|
||||||
<!-- Primary Links -->
|
<!-- Primary Links -->
|
||||||
<template v-if="topLink">
|
<template v-if="topLink">
|
||||||
<v-list v-model:selected="secondarySelected" nav density="comfortable" color="primary">
|
<v-list v-model:selected="state.secondarySelected" nav density="comfortable" color="primary">
|
||||||
<template v-for="nav in topLink">
|
<template v-for="nav in topLink">
|
||||||
<div v-if="!nav.restricted || isOwnGroup" :key="nav.key || nav.title">
|
<div v-if="!nav.restricted || isOwnGroup" :key="nav.key || nav.title">
|
||||||
<!-- Multi Items -->
|
<!-- Multi Items -->
|
||||||
<v-list-group
|
<v-list-group
|
||||||
v-if="nav.children"
|
v-if="nav.children"
|
||||||
:key="(nav.key || nav.title) + 'multi-item'"
|
:key="(nav.key || nav.title) + 'multi-item'"
|
||||||
v-model="dropDowns[nav.title]"
|
v-model="state.dropDowns[nav.title]"
|
||||||
color="primary"
|
color="primary"
|
||||||
:prepend-icon="nav.icon"
|
:prepend-icon="nav.icon"
|
||||||
:fluid="true"
|
:fluid="true"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: hoverProps }">
|
||||||
<v-list-item v-bind="props" :prepend-icon="nav.icon" :title="nav.title" />
|
<v-list-item v-bind="hoverProps" :prepend-icon="nav.icon" :title="nav.title" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list-item
|
<v-list-item
|
||||||
@@ -75,20 +75,20 @@
|
|||||||
<!-- Secondary Links -->
|
<!-- Secondary Links -->
|
||||||
<template v-if="secondaryLinks.length > 0">
|
<template v-if="secondaryLinks.length > 0">
|
||||||
<v-divider class="mt-2" />
|
<v-divider class="mt-2" />
|
||||||
<v-list v-model:selected="secondarySelected" nav density="compact" exact>
|
<v-list v-model:selected="state.secondarySelected" nav density="compact" exact>
|
||||||
<template v-for="nav in secondaryLinks">
|
<template v-for="nav in secondaryLinks">
|
||||||
<div v-if="!nav.restricted || isOwnGroup" :key="nav.key || nav.title">
|
<div v-if="!nav.restricted || isOwnGroup" :key="nav.key || nav.title">
|
||||||
<!-- Multi Items -->
|
<!-- Multi Items -->
|
||||||
<v-list-group
|
<v-list-group
|
||||||
v-if="nav.children"
|
v-if="nav.children"
|
||||||
:key="(nav.key || nav.title) + 'multi-item'"
|
:key="(nav.key || nav.title) + 'multi-item'"
|
||||||
v-model="dropDowns[nav.title]"
|
v-model="state.dropDowns[nav.title]"
|
||||||
color="primary"
|
color="primary"
|
||||||
:prepend-icon="nav.icon"
|
:prepend-icon="nav.icon"
|
||||||
fluid
|
fluid
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: hoverProps }">
|
||||||
<v-list-item v-bind="props" :prepend-icon="nav.icon" :title="nav.title" />
|
<v-list-item v-bind="hoverProps" :prepend-icon="nav.icon" :title="nav.title" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list-item
|
<v-list-item
|
||||||
@@ -116,13 +116,13 @@
|
|||||||
|
|
||||||
<!-- Bottom Navigation Links -->
|
<!-- Bottom Navigation Links -->
|
||||||
<template #append>
|
<template #append>
|
||||||
<v-list v-model:selected="bottomSelected" nav density="comfortable">
|
<v-list v-model:selected="state.bottomSelected" nav density="comfortable">
|
||||||
<v-menu location="end bottom" :offset="15">
|
<v-menu location="end bottom" :offset="15">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: hoverProps }">
|
||||||
<v-list-item v-bind="props" :prepend-icon="$globals.icons.cog" :title="$t('general.settings')" />
|
<v-list-item v-bind="hoverProps" :prepend-icon="$globals.icons.cog" :title="$t('general.settings')" />
|
||||||
</template>
|
</template>
|
||||||
<v-list density="comfortable" color="primary">
|
<v-list density="comfortable" color="primary">
|
||||||
<v-list-item :prepend-icon="$globals.icons.translate" :title="$t('sidebar.language')" @click="languageDialog=true" />
|
<v-list-item :prepend-icon="$globals.icons.translate" :title="$t('sidebar.language')" @click="state.languageDialog=true" />
|
||||||
<v-list-item :prepend-icon="$vuetify.theme.current.dark ? $globals.icons.weatherSunny : $globals.icons.weatherNight" :title="$vuetify.theme.current.dark ? $t('settings.theme.light-mode') : $t('settings.theme.dark-mode')" @click="toggleDark" />
|
<v-list-item :prepend-icon="$vuetify.theme.current.dark ? $globals.icons.weatherSunny : $globals.icons.weatherNight" :title="$vuetify.theme.current.dark ? $t('settings.theme.light-mode') : $t('settings.theme.dark-mode')" @click="toggleDark" />
|
||||||
<v-divider v-if="loggedIn" class="my-2" />
|
<v-divider v-if="loggedIn" class="my-2" />
|
||||||
<v-list-item v-if="loggedIn" :prepend-icon="$globals.icons.cog" :title="$t('profile.user-settings')" to="/user/profile" />
|
<v-list-item v-if="loggedIn" :prepend-icon="$globals.icons.cog" :title="$t('profile.user-settings')" to="/user/profile" />
|
||||||
@@ -136,22 +136,13 @@
|
|||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import type { SidebarLinks } from "~/types/application-types";
|
import type { SidebarLinks } from "~/types/application-types";
|
||||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||||
import { useToggleDarkMode } from "~/composables/use-utils";
|
import { useToggleDarkMode } from "~/composables/use-utils";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
components: {
|
|
||||||
UserAvatar,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
user: {
|
user: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
@@ -165,10 +156,12 @@ export default defineNuxtComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
const modelValue = defineModel<boolean>({ default: false });
|
||||||
|
|
||||||
const auth = useMealieAuth();
|
const auth = useMealieAuth();
|
||||||
|
const sessionUser = computed(() => auth.user.value);
|
||||||
const { loggedIn, isOwnGroup } = useLoggedInState();
|
const { loggedIn, isOwnGroup } = useLoggedInState();
|
||||||
const isAdmin = computed(() => auth.user.value?.admin);
|
const isAdmin = computed(() => auth.user.value?.admin);
|
||||||
const canManage = computed(() => auth.user.value?.canManage);
|
const canManage = computed(() => auth.user.value?.canManage);
|
||||||
@@ -180,17 +173,10 @@ export default defineNuxtComponent({
|
|||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dropDowns: {} as Record<string, boolean>,
|
dropDowns: {} as Record<string, boolean>,
|
||||||
topSelected: null as string[] | null,
|
|
||||||
secondarySelected: null as string[] | null,
|
secondarySelected: null as string[] | null,
|
||||||
bottomSelected: null as string[] | null,
|
bottomSelected: null as string[] | null,
|
||||||
hasOpenedBefore: false as boolean,
|
|
||||||
languageDialog: false as boolean,
|
languageDialog: false as boolean,
|
||||||
});
|
});
|
||||||
// model to control the drawer
|
|
||||||
const showDrawer = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: value => context.emit("update:modelValue", value),
|
|
||||||
});
|
|
||||||
|
|
||||||
const allLinks = computed(() => [...props.topLink, ...(props.secondaryLinks || [])]);
|
const allLinks = computed(() => [...props.topLink, ...(props.secondaryLinks || [])]);
|
||||||
function initDropdowns() {
|
function initDropdowns() {
|
||||||
@@ -207,21 +193,6 @@ export default defineNuxtComponent({
|
|||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
userFavoritesLink,
|
|
||||||
userProfileLink,
|
|
||||||
showDrawer,
|
|
||||||
loggedIn,
|
|
||||||
isAdmin,
|
|
||||||
canManage,
|
|
||||||
isOwnGroup,
|
|
||||||
sessionUser: auth.user,
|
|
||||||
toggleDark,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -49,12 +49,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useNuxtApp } from "#app";
|
import { useNuxtApp } from "#app";
|
||||||
import { toastAlert, toastLoading } from "~/composables/use-toast";
|
import { toastAlert, toastLoading } from "~/composables/use-toast";
|
||||||
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
const { $globals } = useNuxtApp();
|
const { $globals } = useNuxtApp();
|
||||||
const icon = computed(() => {
|
const icon = computed(() => {
|
||||||
switch (toastAlert.color) {
|
switch (toastAlert.color) {
|
||||||
@@ -68,8 +66,4 @@ export default {
|
|||||||
return $globals.icons.alertOutline;
|
return $globals.icons.alertOutline;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { icon, toastAlert, toastLoading };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div scoped-slot />
|
<slot v-if="advanced" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* Renderless component that only renders if the user is logged in.
|
* Renderless component that only renders if the user is logged in.
|
||||||
* and has advanced options toggled.
|
* and has advanced options toggled.
|
||||||
*/
|
*/
|
||||||
export default defineNuxtComponent({
|
|
||||||
setup(_, ctx) {
|
|
||||||
const auth = useMealieAuth();
|
const auth = useMealieAuth();
|
||||||
|
|
||||||
const r = auth.user.value?.advanced || false;
|
const advanced = auth.user.value?.advanced || false;
|
||||||
|
|
||||||
return () => {
|
|
||||||
return r ? ctx.slots.default?.() : null;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
close-delay="500"
|
close-delay="500"
|
||||||
transition="slide-y-transition"
|
transition="slide-y-transition"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: hoverProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
variant="flat"
|
variant="flat"
|
||||||
:icon="icon"
|
:icon="icon"
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
retain-focus-on-click
|
retain-focus-on-click
|
||||||
:class="btnClass"
|
:class="btnClass"
|
||||||
:disabled="copyText !== '' ? false : true"
|
:disabled="copyText !== '' ? false : true"
|
||||||
v-bind="props"
|
v-bind="hoverProps"
|
||||||
@click="textToClipboard()"
|
@click="textToClipboard()"
|
||||||
>
|
>
|
||||||
<v-icon>{{ $globals.icons.contentCopy }}</v-icon>
|
<v-icon>{{ $globals.icons.contentCopy }}</v-icon>
|
||||||
@@ -33,11 +33,10 @@
|
|||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
copyText: {
|
copyText: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -54,11 +53,10 @@ export default defineNuxtComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup(props) {
|
|
||||||
const { copy, copied, isSupported } = useClipboard();
|
const { copy, copied, isSupported } = useClipboard();
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const copyToolTip = ref<VTooltip | null>(null);
|
|
||||||
const copiedSuccess = ref<boolean | null>(null);
|
const copiedSuccess = ref<boolean | null>(null);
|
||||||
|
|
||||||
async function textToClipboard() {
|
async function textToClipboard() {
|
||||||
@@ -82,17 +80,6 @@ export default defineNuxtComponent({
|
|||||||
show.value = false;
|
show.value = false;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
show,
|
|
||||||
copyToolTip,
|
|
||||||
textToClipboard,
|
|
||||||
copied,
|
|
||||||
isSupported,
|
|
||||||
copiedSuccess,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-form ref="files">
|
<v-form ref="form">
|
||||||
<input
|
<input
|
||||||
ref="uploader"
|
ref="uploader"
|
||||||
class="d-none"
|
class="d-none"
|
||||||
@@ -26,13 +26,12 @@
|
|||||||
</v-form>
|
</v-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
const UPLOAD_EVENT = "uploaded";
|
const UPLOAD_EVENT = "uploaded";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
small: {
|
small: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -77,9 +76,13 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup(props, context) {
|
|
||||||
const files = ref<File[]>([]);
|
const emit = defineEmits<{
|
||||||
|
(e: "uploaded", payload: File | File[] | unknown | null): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const selectedFiles = ref<File[]>([]);
|
||||||
const uploader = ref<HTMLInputElement | null>(null);
|
const uploader = ref<HTMLInputElement | null>(null);
|
||||||
const isSelecting = ref(false);
|
const isSelecting = ref(false);
|
||||||
|
|
||||||
@@ -91,42 +94,36 @@ export default defineNuxtComponent({
|
|||||||
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
async function upload() {
|
async function upload() {
|
||||||
if (files.value.length === 0) {
|
if (selectedFiles.value.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelecting.value = true;
|
isSelecting.value = true;
|
||||||
|
|
||||||
if (!props.post) {
|
if (!props.post) {
|
||||||
// NOTE: To preserve behaviour for other parents of this component,
|
emit(UPLOAD_EVENT, props.multiple ? selectedFiles.value : selectedFiles.value[0]);
|
||||||
// we emit a single File if !props.multiple.
|
|
||||||
context.emit(UPLOAD_EVENT, props.multiple ? files.value : files.value[0]);
|
|
||||||
isSelecting.value = false;
|
isSelecting.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARN: My change is only for !props.post.
|
if (props.multiple && selectedFiles.value.length > 1) {
|
||||||
// I have not added support for multiple files in the API.
|
|
||||||
// Existing call-sites never passed the `multiple` prop,
|
|
||||||
// so this case will only be hit if the prop is set to true.
|
|
||||||
if (props.multiple && files.value.length > 1) {
|
|
||||||
console.warn("Multiple file uploads are not supported by the API.");
|
console.warn("Multiple file uploads are not supported by the API.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = files.value[0];
|
const file = selectedFiles.value[0];
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append(props.fileName, file);
|
formData.append(props.fileName, file);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.upload.file(props.url, formData);
|
const response = await api.upload.file(props.url, formData);
|
||||||
if (response) {
|
if (response) {
|
||||||
context.emit(UPLOAD_EVENT, response);
|
emit(UPLOAD_EVENT, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
context.emit(UPLOAD_EVENT, null);
|
emit(UPLOAD_EVENT, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelecting.value = false;
|
isSelecting.value = false;
|
||||||
@@ -136,7 +133,7 @@ export default defineNuxtComponent({
|
|||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
if (target.files !== null && target.files.length > 0) {
|
if (target.files !== null && target.files.length > 0) {
|
||||||
files.value = Array.from(target.files);
|
selectedFiles.value = Array.from(target.files);
|
||||||
upload();
|
upload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,18 +149,6 @@ export default defineNuxtComponent({
|
|||||||
);
|
);
|
||||||
uploader.value?.click();
|
uploader.value?.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
files,
|
|
||||||
uploader,
|
|
||||||
isSelecting,
|
|
||||||
effIcon,
|
|
||||||
defaultText,
|
|
||||||
onFileChanged,
|
|
||||||
onButtonClick,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|||||||
@@ -39,9 +39,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
@@ -66,8 +65,8 @@ export default defineNuxtComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup(props) {
|
|
||||||
const size = computed(() => {
|
const size = computed(() => {
|
||||||
if (props.tiny) {
|
if (props.tiny) {
|
||||||
return {
|
return {
|
||||||
@@ -99,11 +98,4 @@ export default defineNuxtComponent({
|
|||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const waitingTextCalculated = props.waitingText == null ? i18n.t("general.loading-recipes") : props.waitingText;
|
const waitingTextCalculated = props.waitingText == null ? i18n.t("general.loading-recipes") : props.waitingText;
|
||||||
|
|
||||||
return {
|
|
||||||
size,
|
|
||||||
waitingTextCalculated,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,14 +18,12 @@
|
|||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
props: {
|
|
||||||
back: {
|
back: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,12 @@
|
|||||||
</BannerWarning>
|
</BannerWarning>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default {
|
defineProps({
|
||||||
props: {
|
|
||||||
issue: {
|
issue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-alert
|
<v-alert
|
||||||
border="start"
|
border="start"
|
||||||
border-color
|
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
type="warning"
|
type="warning"
|
||||||
elevation="2"
|
elevation="2"
|
||||||
@@ -20,9 +19,8 @@
|
|||||||
</v-alert>
|
</v-alert>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default {
|
defineProps({
|
||||||
props: {
|
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -33,6 +31,5 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -32,13 +32,10 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
name: "BaseButton",
|
|
||||||
props: {
|
|
||||||
// Types
|
|
||||||
cancel: {
|
cancel: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -61,9 +58,7 @@ export default defineNuxtComponent({
|
|||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false },
|
||||||
},
|
|
||||||
// Download
|
|
||||||
download: {
|
download: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -72,7 +67,6 @@ export default defineNuxtComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
// Property
|
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -81,7 +75,6 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
// Styles
|
|
||||||
small: {
|
small: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -118,10 +111,11 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup(props) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { $globals } = useNuxtApp();
|
const { $globals } = useNuxtApp();
|
||||||
|
|
||||||
const buttonOptions = {
|
const buttonOptions = {
|
||||||
create: {
|
create: {
|
||||||
text: i18n.t("general.create"),
|
text: i18n.t("general.create"),
|
||||||
@@ -164,44 +158,35 @@ export default defineNuxtComponent({
|
|||||||
if (props.delete) {
|
if (props.delete) {
|
||||||
return buttonOptions.delete;
|
return buttonOptions.delete;
|
||||||
}
|
}
|
||||||
else if (props.update) {
|
if (props.update) {
|
||||||
return buttonOptions.update;
|
return buttonOptions.update;
|
||||||
}
|
}
|
||||||
else if (props.edit) {
|
if (props.edit) {
|
||||||
return buttonOptions.edit;
|
return buttonOptions.edit;
|
||||||
}
|
}
|
||||||
else if (props.cancel) {
|
if (props.cancel) {
|
||||||
return buttonOptions.cancel;
|
return buttonOptions.cancel;
|
||||||
}
|
}
|
||||||
else if (props.save) {
|
if (props.save) {
|
||||||
return buttonOptions.save;
|
return buttonOptions.save;
|
||||||
}
|
}
|
||||||
else if (props.download) {
|
if (props.download) {
|
||||||
return buttonOptions.download;
|
return buttonOptions.download;
|
||||||
}
|
}
|
||||||
return buttonOptions.create;
|
return buttonOptions.create;
|
||||||
});
|
});
|
||||||
|
|
||||||
const buttonStyles = {
|
const buttonStyles = {
|
||||||
defaults: {
|
defaults: { text: false, outlined: false },
|
||||||
text: false,
|
secondary: { text: false, outlined: true },
|
||||||
outlined: false,
|
minor: { text: true, outlined: false },
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
text: false,
|
|
||||||
outlined: true,
|
|
||||||
},
|
|
||||||
minor: {
|
|
||||||
text: true,
|
|
||||||
outlined: false,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnStyle = computed(() => {
|
const btnStyle = computed(() => {
|
||||||
if (props.secondary) {
|
if (props.secondary) {
|
||||||
return buttonStyles.secondary;
|
return buttonStyles.secondary;
|
||||||
}
|
}
|
||||||
else if (props.minor || props.cancel) {
|
if (props.minor || props.cancel) {
|
||||||
return buttonStyles.minor;
|
return buttonStyles.minor;
|
||||||
}
|
}
|
||||||
return buttonStyles.defaults;
|
return buttonStyles.defaults;
|
||||||
@@ -211,12 +196,4 @@ export default defineNuxtComponent({
|
|||||||
function downloadFile() {
|
function downloadFile() {
|
||||||
api.utils.download(props.downloadUrl);
|
api.utils.download(props.downloadUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
btnAttrs,
|
|
||||||
btnStyle,
|
|
||||||
downloadFile,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
start
|
start
|
||||||
:style="stretch ? 'width: 100%;' : ''"
|
:style="stretch ? 'width: 100%;' : ''"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: hoverProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
tile
|
tile
|
||||||
:large="large"
|
:large="large"
|
||||||
icon
|
icon
|
||||||
variant="plain"
|
variant="plain"
|
||||||
v-bind="props"
|
v-bind="hoverProps"
|
||||||
>
|
>
|
||||||
<v-icon>
|
<v-icon>
|
||||||
{{ btn.icon }}
|
{{ btn.icon }}
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
location="bottom"
|
location="bottom"
|
||||||
content-class="text-caption"
|
content-class="text-caption"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: tooltipProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
tile
|
tile
|
||||||
icon
|
icon
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
:disabled="btn.disabled"
|
:disabled="btn.disabled"
|
||||||
:style="stretch ? `width: ${maxButtonWidth};` : ''"
|
:style="stretch ? `width: ${maxButtonWidth};` : ''"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
v-bind="props"
|
v-bind="tooltipProps"
|
||||||
@click="$emit(btn.event)"
|
@click="$emit(btn.event)"
|
||||||
>
|
>
|
||||||
<v-icon> {{ btn.icon }} </v-icon>
|
<v-icon> {{ btn.icon }} </v-icon>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</v-item-group>
|
</v-item-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export interface ButtonOption {
|
export interface ButtonOption {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
@@ -83,8 +83,7 @@ export interface ButtonOption {
|
|||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
buttons: {
|
buttons: {
|
||||||
type: Array as () => ButtonOption[],
|
type: Array as () => ButtonOption[],
|
||||||
required: true,
|
required: true,
|
||||||
@@ -97,12 +96,7 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const maxButtonWidth = computed(() => `${100 / props.buttons.length}%`);
|
|
||||||
return {
|
|
||||||
maxButtonWidth,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const maxButtonWidth = computed(() => `${100 / props.buttons.length}%`);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,9 +29,8 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
props: {
|
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -44,6 +43,5 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,9 +6,8 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
props: {
|
|
||||||
width: {
|
width: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "100px",
|
default: "100px",
|
||||||
@@ -21,6 +20,5 @@ export default defineNuxtComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "accent",
|
default: "accent",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-menu offset-y>
|
<v-menu offset-y>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: hoverProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
v-bind="{ ...props, ...$attrs }"
|
v-bind="{ ...hoverProps, ...$attrs }"
|
||||||
:class="btnClass"
|
:class="btnClass"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
</v-menu>
|
</v-menu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
const MODES = {
|
const MODES = {
|
||||||
model: "model",
|
model: "model",
|
||||||
link: "link",
|
link: "link",
|
||||||
@@ -124,8 +124,7 @@ export interface MenuItem {
|
|||||||
hide?: boolean;
|
hide?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
mode: {
|
mode: {
|
||||||
type: String as () => modes,
|
type: String as () => modes,
|
||||||
default: "model",
|
default: "model",
|
||||||
@@ -139,11 +138,6 @@ export default defineNuxtComponent({
|
|||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
btnClass: {
|
btnClass: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -156,9 +150,14 @@ export default defineNuxtComponent({
|
|||||||
return useI18n().t("general.actions");
|
return useI18n().t("general.actions");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
const modelValue = defineModel({
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: "",
|
||||||
|
});
|
||||||
|
|
||||||
const activeObj = ref<MenuItem>({
|
const activeObj = ref<MenuItem>({
|
||||||
text: "DEFAULT",
|
text: "DEFAULT",
|
||||||
value: "",
|
value: "",
|
||||||
@@ -166,7 +165,7 @@ export default defineNuxtComponent({
|
|||||||
|
|
||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
props.items.forEach((item, index) => {
|
props.items.forEach((item, index) => {
|
||||||
if (item.value === props.modelValue) {
|
if (item.value === modelValue.value) {
|
||||||
startIndex = index;
|
startIndex = index;
|
||||||
|
|
||||||
activeObj.value = item;
|
activeObj.value = item;
|
||||||
@@ -175,16 +174,7 @@ export default defineNuxtComponent({
|
|||||||
const itemGroup = ref(startIndex);
|
const itemGroup = ref(startIndex);
|
||||||
|
|
||||||
function setValue(v: MenuItem) {
|
function setValue(v: MenuItem) {
|
||||||
context.emit("update:modelValue", v.value);
|
modelValue.value = v.value || "";
|
||||||
activeObj.value = v;
|
activeObj.value = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
MODES,
|
|
||||||
activeObj,
|
|
||||||
itemGroup,
|
|
||||||
setValue,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -22,14 +22,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
props: {
|
|
||||||
divider: {
|
divider: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card
|
|
||||||
v-bind="$attrs"
|
|
||||||
:class="classes"
|
|
||||||
class="v-card--material pa-3"
|
|
||||||
>
|
|
||||||
<div class="d-flex grow flex-wrap">
|
|
||||||
<slot name="avatar">
|
|
||||||
<v-sheet
|
|
||||||
:color="color"
|
|
||||||
:max-height="icon ? 90 : undefined"
|
|
||||||
:width="icon ? 'auto' : '100%'"
|
|
||||||
elevation="6"
|
|
||||||
class="text-start v-card--material__heading mb-n6 mt-n10 pa-7"
|
|
||||||
dark
|
|
||||||
>
|
|
||||||
<v-icon
|
|
||||||
v-if="icon"
|
|
||||||
size="40"
|
|
||||||
>
|
|
||||||
{{ icon }}
|
|
||||||
</v-icon>
|
|
||||||
<div
|
|
||||||
v-if="text"
|
|
||||||
class="headline font-weight-thin"
|
|
||||||
v-text="text"
|
|
||||||
/>
|
|
||||||
</v-sheet>
|
|
||||||
</slot>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="$slots['after-heading']"
|
|
||||||
class="ml-auto"
|
|
||||||
>
|
|
||||||
<slot name="after-heading" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<slot />
|
|
||||||
|
|
||||||
<template v-if="$slots.actions">
|
|
||||||
<v-divider class="mt-2" />
|
|
||||||
|
|
||||||
<v-card-actions class="pb-0">
|
|
||||||
<slot name="actions" />
|
|
||||||
</v-card-actions>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="$slots.bottom">
|
|
||||||
<v-divider
|
|
||||||
v-if="!$slots.actions"
|
|
||||||
class="mt-2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="pb-0">
|
|
||||||
<slot name="bottom" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
name: "MaterialCard",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
avatar: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: "primary",
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const display = useDisplay();
|
|
||||||
const hasHeading = computed(() => false);
|
|
||||||
const hasAltHeading = computed(() => false);
|
|
||||||
const classes = computed(() => {
|
|
||||||
return {
|
|
||||||
"v-card--material--has-heading": hasHeading,
|
|
||||||
"mt-3": display.name.value === "xs" || display.name.value === "sm",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasHeading,
|
|
||||||
hasAltHeading,
|
|
||||||
classes,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.v-card--material
|
|
||||||
&__avatar
|
|
||||||
position: relative
|
|
||||||
top: -64px
|
|
||||||
margin-bottom: -32px
|
|
||||||
|
|
||||||
&__heading
|
|
||||||
position: relative
|
|
||||||
top: -40px
|
|
||||||
transition: .3s ease
|
|
||||||
z-index: 1
|
|
||||||
</style>
|
|
||||||
@@ -16,9 +16,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
props: {
|
|
||||||
to: {
|
to: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -31,6 +30,5 @@ export default defineNuxtComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -40,11 +40,10 @@
|
|||||||
</v-menu>
|
</v-menu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ContextMenuItem } from "~/composables/use-context-presents";
|
import type { ContextMenuItem } from "~/composables/use-context-presents";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
props: {
|
|
||||||
items: {
|
items: {
|
||||||
type: Array as () => ContextMenuItem[],
|
type: Array as () => ContextMenuItem[],
|
||||||
required: true,
|
required: true,
|
||||||
@@ -61,6 +60,5 @@ export default defineNuxtComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "grey-darken-2",
|
default: "grey-darken-2",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,20 +4,13 @@
|
|||||||
</pre>
|
</pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const prettyJson = JSON.stringify(props.data, null, 2);
|
|
||||||
|
|
||||||
return {
|
|
||||||
prettyJson,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const prettyJson = JSON.stringify(props.data, null, 2);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -15,21 +15,16 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
link: {
|
link: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const href = computed(() => {
|
|
||||||
// TODO: dynamically set docs link based off env
|
|
||||||
return `https://nightly.mealie.io${props.link}`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { href };
|
const href = computed(() => {
|
||||||
},
|
// TODO: dynamically set docs link based off env
|
||||||
|
return `https://docs.mealie.io${props.link}`;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -19,25 +19,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDropZone } from "@vueuse/core";
|
import { useDropZone } from "@vueuse/core";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
emits: ["drop"],
|
disabled: {
|
||||||
setup(_, context) {
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["drop"]);
|
||||||
|
|
||||||
const el = ref<HTMLDivElement>();
|
const el = ref<HTMLDivElement>();
|
||||||
|
|
||||||
function onDrop(files: File[] | null) {
|
function onDrop(files: File[] | null) {
|
||||||
if (files) {
|
if (files) {
|
||||||
context.emit("drop", files);
|
emit("drop", files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isOverDropZone } = useDropZone(el, files => onDrop(files));
|
const { isOverDropZone } = useDropZone(el, files => onDrop(files));
|
||||||
|
|
||||||
return { el, isOverDropZone };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css">
|
<style lang="css">
|
||||||
|
|||||||
@@ -29,9 +29,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
props: {
|
|
||||||
small: {
|
small: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -40,7 +39,6 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
color="success"
|
color="success"
|
||||||
:icon="$globals.icons.save"
|
:icon="$globals.icons.save"
|
||||||
:disabled="submitted"
|
:disabled="submitted"
|
||||||
@click="() => save()"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
<v-menu offset-y :close-on-content-click="false" location="bottom center">
|
<v-menu offset-y :close-on-content-click="false" location="bottom center">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: slotProps }">
|
||||||
<v-btn color="info" v-bind="props" :icon="$globals.icons.edit" :disabled="submitted" />
|
<v-btn color="info" v-bind="slotProps" :icon="$globals.icons.edit" :disabled="submitted" />
|
||||||
</template>
|
</template>
|
||||||
<v-list class="mt-1">
|
<v-list class="mt-1">
|
||||||
<template v-for="(row, keyRow) in controls" :key="keyRow">
|
<template v-for="(row, keyRow) in controls" :key="keyRow">
|
||||||
@@ -55,13 +55,11 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Cropper } from "vue-advanced-cropper";
|
import { Cropper } from "vue-advanced-cropper";
|
||||||
import "vue-advanced-cropper/dist/style.css";
|
import "vue-advanced-cropper/dist/style.css";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
components: { Cropper },
|
|
||||||
props: {
|
|
||||||
img: {
|
img: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -78,17 +76,33 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["save", "delete"],
|
|
||||||
setup(_, context) {
|
const emit = defineEmits<{
|
||||||
const cropper = ref<any>();
|
(e: "save", item: Blob): void;
|
||||||
|
(e: "delete"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const cropper = ref<any>(null);
|
||||||
const changed = ref(0);
|
const changed = ref(0);
|
||||||
const { $globals } = useNuxtApp();
|
const { $globals } = useNuxtApp();
|
||||||
|
|
||||||
interface Control {
|
type Control = {
|
||||||
color: string;
|
color: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
callback: CallableFunction;
|
callback: CallableFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
function flip(hortizontal: boolean, vertical?: boolean) {
|
||||||
|
if (!cropper.value) return;
|
||||||
|
cropper.value.flip(hortizontal, vertical);
|
||||||
|
changed.value = changed.value + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotate(angle: number) {
|
||||||
|
if (!cropper.value) return;
|
||||||
|
cropper.value.rotate(angle);
|
||||||
|
changed.value = changed.value + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const controls = ref<Control[][]>([
|
const controls = ref<Control[][]>([
|
||||||
@@ -118,54 +132,21 @@ export default defineNuxtComponent({
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function flip(hortizontal: boolean, vertical?: boolean) {
|
|
||||||
if (!cropper.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cropper.value.flip(hortizontal, vertical);
|
|
||||||
changed.value = changed.value + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rotate(angle: number) {
|
|
||||||
if (!cropper.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cropper.value.rotate(angle);
|
|
||||||
changed.value = changed.value + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
if (!cropper.value) {
|
if (!cropper.value) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { canvas } = cropper.value.getResult();
|
const { canvas } = cropper.value.getResult();
|
||||||
if (!canvas) {
|
if (!canvas) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
canvas.toBlob((blob) => {
|
canvas.toBlob((blob) => {
|
||||||
if (blob) {
|
if (blob) {
|
||||||
context.emit("save", blob);
|
emit("save", blob);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
function defaultSize({ imageSize, visibleArea }: any) {
|
||||||
cropper,
|
|
||||||
controls,
|
|
||||||
flip,
|
|
||||||
rotate,
|
|
||||||
save,
|
|
||||||
changed,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
defaultSize({ imageSize, visibleArea }) {
|
|
||||||
return {
|
return {
|
||||||
width: (visibleArea || imageSize).width,
|
width: (visibleArea || imageSize).width,
|
||||||
height: (visibleArea || imageSize).height,
|
height: (visibleArea || imageSize).height,
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="inputVal"
|
v-model="modelValue"
|
||||||
:label="$t('general.color')"
|
:label="$t('general.color')"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
height="30px"
|
height="30px"
|
||||||
width="30px"
|
width="30px"
|
||||||
:color="inputVal || 'grey'"
|
:color="modelValue || 'grey'"
|
||||||
@click="setRandomHex"
|
@click="setRandomHex"
|
||||||
>
|
>
|
||||||
<v-icon color="white">
|
<v-icon color="white">
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<v-card>
|
<v-card>
|
||||||
<v-card-text class="pa-0">
|
<v-card-text class="pa-0">
|
||||||
<v-color-picker
|
<v-color-picker
|
||||||
v-model="inputVal"
|
v-model="modelValue"
|
||||||
flat
|
flat
|
||||||
hide-inputs
|
hide-inputs
|
||||||
show-swatches
|
show-swatches
|
||||||
@@ -46,27 +46,14 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
const modelValue = defineModel({
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
|
||||||
const menu = ref(false);
|
|
||||||
|
|
||||||
const inputVal = computed({
|
|
||||||
get: () => {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("update:modelValue", val);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const menu = ref(false);
|
||||||
|
|
||||||
function getRandomHex() {
|
function getRandomHex() {
|
||||||
return "#000000".replace(/0/g, function () {
|
return "#000000".replace(/0/g, function () {
|
||||||
return (~~(Math.random() * 16)).toString(16);
|
return (~~(Math.random() * 16)).toString(16);
|
||||||
@@ -74,14 +61,6 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setRandomHex() {
|
function setRandomHex() {
|
||||||
inputVal.value = getRandomHex();
|
modelValue.value = getRandomHex();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
menu,
|
|
||||||
setRandomHex,
|
|
||||||
inputVal,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -30,48 +30,22 @@
|
|||||||
</v-autocomplete>
|
</v-autocomplete>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
|
||||||
* The InputLabelType component is a wrapper for v-autocomplete. It is used to abstract the selection functionality
|
|
||||||
* of some common types within Mealie. This can mostly be used with any type of object provided it has a name and id
|
|
||||||
* property. The name property is used to display the name of the object in the autocomplete dropdown. The id property
|
|
||||||
* is used to store the id of the object in the itemId property.
|
|
||||||
*
|
|
||||||
* Supported Types
|
|
||||||
* - MultiPurposeLabel
|
|
||||||
* - RecipeIngredientFood
|
|
||||||
* - RecipeIngredientUnit
|
|
||||||
*
|
|
||||||
* TODO: Add RecipeTag / Category to this selector
|
|
||||||
* Future Supported Types
|
|
||||||
* - RecipeTags
|
|
||||||
* - RecipeCategories
|
|
||||||
*
|
|
||||||
* Both the ID and Item can be synced. The item can be synced using the v-model syntax and the itemId can be synced
|
|
||||||
* using the .sync syntax `item-id.sync="item.labelId"`
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { MultiPurposeLabelSummary } from "~/lib/api/types/labels";
|
import type { MultiPurposeLabelSummary } from "~/lib/api/types/labels";
|
||||||
import type { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
import type { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||||
import { useSearch } from "~/composables/use-search";
|
import { useSearch } from "~/composables/use-search";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
// v-model for the selected item
|
||||||
props: {
|
const modelValue = defineModel<MultiPurposeLabelSummary | IngredientFood | IngredientUnit | null>({ default: () => null });
|
||||||
modelValue: {
|
|
||||||
type: Object as () => MultiPurposeLabelSummary | IngredientFood | IngredientUnit,
|
// support v-model:item-id binding
|
||||||
required: false,
|
const itemId = defineModel<string | undefined>("item-id", { default: undefined });
|
||||||
default: () => {
|
|
||||||
return {};
|
const props = defineProps({
|
||||||
},
|
|
||||||
},
|
|
||||||
items: {
|
items: {
|
||||||
type: Array as () => Array<MultiPurposeLabelSummary | IngredientFood | IngredientUnit>,
|
type: Array as () => Array<MultiPurposeLabelSummary | IngredientFood | IngredientUnit>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
itemId: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -81,35 +55,27 @@ export default defineNuxtComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["update:modelValue", "update:item-id", "create"],
|
|
||||||
setup(props, context) {
|
const emit = defineEmits<{
|
||||||
|
(e: "create", val: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const autocompleteRef = ref<HTMLInputElement>();
|
const autocompleteRef = ref<HTMLInputElement>();
|
||||||
|
|
||||||
// Use the search composable
|
// Use the search composable
|
||||||
const { search: searchInput, filtered: filteredItems } = useSearch(computed(() => props.items));
|
const { search: searchInput, filtered: filteredItems } = useSearch(computed(() => props.items));
|
||||||
|
|
||||||
const itemIdVal = computed({
|
|
||||||
get: () => {
|
|
||||||
return props.itemId || undefined;
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("update:item-id", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const itemVal = computed({
|
const itemVal = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
try {
|
if (!modelValue.value || Object.keys(modelValue.value).length === 0) {
|
||||||
return Object.keys(props.modelValue).length !== 0 ? props.modelValue : null;
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return modelValue.value;
|
||||||
},
|
},
|
||||||
set: (val) => {
|
set: (val) => {
|
||||||
itemIdVal.value = val?.id || undefined;
|
itemId.value = val?.id || "";
|
||||||
context.emit("update:modelValue", val);
|
modelValue.value = val;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,18 +83,7 @@ export default defineNuxtComponent({
|
|||||||
if (props.items.some(item => item.name === searchInput.value)) {
|
if (props.items.some(item => item.name === searchInput.value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.emit("create", searchInput.value);
|
emit("create", searchInput.value);
|
||||||
autocompleteRef.value?.blur();
|
autocompleteRef.value?.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
autocompleteRef,
|
|
||||||
itemVal,
|
|
||||||
itemIdVal,
|
|
||||||
searchInput,
|
|
||||||
filteredItems,
|
|
||||||
emitCreate,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
v-model="dialog"
|
v-model="modelValue"
|
||||||
:icon="$globals.icons.translate"
|
:icon="$globals.icons.translate"
|
||||||
:title="$t('language-dialog.choose-language')"
|
:title="$t('language-dialog.choose-language')"
|
||||||
>
|
>
|
||||||
@@ -43,23 +43,11 @@
|
|||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLocales } from "~/composables/use-locales";
|
import { useLocales } from "~/composables/use-locales";
|
||||||
import { normalizeFilter } from "~/composables/use-utils";
|
import { normalizeFilter } from "~/composables/use-utils";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const modelValue = defineModel<boolean>({ default: () => false });
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const dialog = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: value => emit("update:modelValue", value),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { locales: LOCALES, locale, i18n } = useLocales();
|
const { locales: LOCALES, locale, i18n } = useLocales();
|
||||||
|
|
||||||
@@ -71,22 +59,10 @@ export default defineNuxtComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
watch(locale, () => {
|
watch(locale, () => {
|
||||||
dialog.value = false; // Close dialog when locale changes
|
modelValue.value = false; // Close dialog when locale changes
|
||||||
});
|
});
|
||||||
|
|
||||||
const locales = LOCALES.filter(lc =>
|
const locales = LOCALES.filter(lc =>
|
||||||
i18n.locales.value.map(i18nLocale => i18nLocale.code).includes(lc.value as any),
|
i18n.locales.value.map(i18nLocale => i18nLocale.code).includes(lc.value as any),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
|
||||||
dialog,
|
|
||||||
i18n,
|
|
||||||
locales,
|
|
||||||
locale,
|
|
||||||
selectedLocale,
|
|
||||||
onLocaleSelect,
|
|
||||||
normalizeFilter,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<v-textarea
|
<v-textarea
|
||||||
v-if="!previewState"
|
v-if="!previewState"
|
||||||
v-bind="textarea"
|
v-bind="textarea"
|
||||||
v-model="inputVal"
|
v-model="modelValue"
|
||||||
:class="label == '' ? '' : 'mt-5'"
|
:class="label == '' ? '' : 'mt-5'"
|
||||||
:label="label"
|
:label="label"
|
||||||
auto-grow
|
auto-grow
|
||||||
@@ -33,14 +33,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
name: "MarkdownEditor",
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
@@ -57,36 +51,24 @@ export default defineNuxtComponent({
|
|||||||
type: Object as () => unknown,
|
type: Object as () => unknown,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["update:modelValue", "input:preview"],
|
|
||||||
setup(props, context) {
|
const emit = defineEmits<{
|
||||||
|
(e: "input:preview", value: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const modelValue = defineModel<string>("modelValue");
|
||||||
|
|
||||||
const fallbackPreview = ref(false);
|
const fallbackPreview = ref(false);
|
||||||
const previewState = computed({
|
const previewState = computed({
|
||||||
get: () => {
|
get: () => props.preview ?? fallbackPreview.value,
|
||||||
return props.preview ?? fallbackPreview.value;
|
set: (val: boolean) => {
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
if (props.preview) {
|
if (props.preview) {
|
||||||
context.emit("input:preview", val);
|
emit("input:preview", val);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fallbackPreview.value = val;
|
fallbackPreview.value = val;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const inputVal = computed({
|
|
||||||
get: () => {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("update:modelValue", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
previewState,
|
|
||||||
inputVal,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,28 +8,20 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import JsonEditorVue from "json-editor-vue";
|
import JsonEditorVue from "json-editor-vue";
|
||||||
|
|
||||||
export default defineComponent({
|
const modelValue = defineModel<object>("modelValue", { default: () => ({}) });
|
||||||
name: "RecipeJsonEditor",
|
defineProps({
|
||||||
components: { JsonEditorVue },
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
height: {
|
height: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "1500px",
|
default: "1500px",
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
function parseEvent(event: any): object {
|
function parseEvent(event: any): object {
|
||||||
if (!event) {
|
if (!event) {
|
||||||
return props.modelValue || {};
|
return modelValue.value || {};
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (event.json) {
|
if (event.json) {
|
||||||
@@ -43,18 +35,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
return props.modelValue || {};
|
return modelValue.value || {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onChange(event: any) {
|
function onChange(event: any) {
|
||||||
const parsed = parseEvent(event);
|
const parsed = parseEvent(event);
|
||||||
if (parsed !== props.modelValue) {
|
if (parsed !== modelValue.value) {
|
||||||
emit("update:modelValue", parsed);
|
modelValue.value = parsed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
onChange,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -27,19 +27,20 @@
|
|||||||
</v-data-table>
|
</v-data-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ReportSummary } from "~/lib/api/types/reports";
|
import type { ReportSummary } from "~/lib/api/types/reports";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
defineProps({
|
||||||
props: {
|
|
||||||
items: {
|
items: {
|
||||||
required: true,
|
|
||||||
type: Array as () => Array<ReportSummary>,
|
type: Array as () => Array<ReportSummary>,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["delete"],
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "delete", id: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
setup(_, context) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -64,17 +65,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function deleteReport(id: string) {
|
function deleteReport(id: string) {
|
||||||
context.emit("delete", id);
|
emit("delete", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
headers,
|
|
||||||
handleRowClick,
|
|
||||||
capitalize,
|
|
||||||
deleteReport,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div v-html="value" />
|
<div v-html="value" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import DOMPurify from "isomorphic-dompurify";
|
import DOMPurify from "isomorphic-dompurify";
|
||||||
import { marked } from "marked";
|
import { marked } from "marked";
|
||||||
|
|
||||||
@@ -11,14 +11,13 @@ enum DOMPurifyHook {
|
|||||||
UponSanitizeAttribute = "uponSanitizeAttribute",
|
UponSanitizeAttribute = "uponSanitizeAttribute",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
source: {
|
source: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup(props) {
|
|
||||||
const ALLOWED_STYLE_TAGS = [
|
const ALLOWED_STYLE_TAGS = [
|
||||||
"background-color", "color", "font-style", "font-weight", "text-decoration", "text-align",
|
"background-color", "color", "font-style", "font-weight", "text-decoration", "text-align",
|
||||||
];
|
];
|
||||||
@@ -62,12 +61,6 @@ export default defineNuxtComponent({
|
|||||||
const rawHtml = marked.parse(props.source || "", { async: false, breaks: true });
|
const rawHtml = marked.parse(props.source || "", { async: false, breaks: true });
|
||||||
return sanitizeMarkdown(rawHtml);
|
return sanitizeMarkdown(rawHtml);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -30,9 +30,8 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
@@ -45,19 +44,13 @@ export default defineNuxtComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup(props) {
|
|
||||||
const { $globals } = useNuxtApp();
|
const { $globals } = useNuxtApp();
|
||||||
|
|
||||||
const activeIcon = computed(() => {
|
const activeIcon = computed(() => {
|
||||||
return props.icon ?? $globals.icons.primary;
|
return props.icon ?? $globals.icons.primary;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
activeIcon,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -2,40 +2,26 @@
|
|||||||
<component :is="tag">
|
<component :is="tag">
|
||||||
<slot
|
<slot
|
||||||
name="activator"
|
name="activator"
|
||||||
v-bind="{ toggle, state }"
|
v-bind="{ toggle, modelValue }"
|
||||||
/>
|
/>
|
||||||
<slot v-bind="{ state, toggle }" />
|
<slot v-bind="{ modelValue, toggle }" />
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
const modelValue = defineModel({
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
});
|
||||||
|
|
||||||
|
defineProps({
|
||||||
tag: {
|
tag: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "div",
|
default: "div",
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
|
||||||
const state = ref(false);
|
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => {
|
||||||
state.value = !state.value;
|
modelValue.value = !modelValue.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(state, () => {
|
|
||||||
context.emit("update:modelValue", state.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
state,
|
|
||||||
toggle,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,11 +12,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useWakeLock } from "@vueuse/core";
|
import { useWakeLock } from "@vueuse/core";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
setup() {
|
|
||||||
const { isSupported: wakeIsSupported, isActive, request, release } = useWakeLock();
|
const { isSupported: wakeIsSupported, isActive, request, release } = useWakeLock();
|
||||||
const wakeLock = computed({
|
const wakeLock = computed({
|
||||||
get: () => isActive.value,
|
get: () => isActive.value,
|
||||||
@@ -43,11 +41,4 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
onMounted(() => lockScreen());
|
onMounted(() => lockScreen());
|
||||||
onUnmounted(() => unlockScreen());
|
onUnmounted(() => unlockScreen());
|
||||||
|
|
||||||
return {
|
|
||||||
wakeLock,
|
|
||||||
wakeIsSupported,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user