Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Timeline Filters #3284

Merged
Merged
8 changes: 6 additions & 2 deletions frontend/components/Domain/Recipe/RecipeExplorerPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,16 @@
<v-list>
<v-list-item @click="toggleOrderDirection()">
<v-icon left>
{{ $globals.icons.sort }}
{{
state.orderDirection === "asc" ?
$globals.icons.sortAscending : $globals.icons.sortDescending
}}
michael-genson marked this conversation as resolved.
Show resolved Hide resolved
</v-icon>
<v-list-item-title>
{{ state.orderDirection === "asc" ? "Sort Descending" : "Sort Ascending" }}
{{ state.orderDirection === "asc" ? $tc("general.sort-descending") : $tc("general.sort-ascending") }}
</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item
v-for="v in sortable"
:key="v.name"
Expand Down
83 changes: 75 additions & 8 deletions frontend/components/Domain/Recipe/RecipeTimeline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,52 @@
<v-row class="my-0 mx-7">
<v-spacer />
<v-col class="text-right">
<v-btn fab small color="info" @click="reverseSort">
<v-icon> {{ preferences.orderDirection === "asc" ? $globals.icons.sortCalendarAscending : $globals.icons.sortCalendarDescending }} </v-icon>
</v-btn>
<!-- Filters -->
<v-menu offset-y bottom left nudge-bottom="3" :close-on-content-click="false">
<template #activator="{ on, attrs }">
<v-badge :content="filterBadgeCount" :value="filterBadgeCount" bordered overlap>
<v-btn fab small color="info" v-bind="attrs" v-on="on">
<v-icon> {{ $globals.icons.filter }} </v-icon>
</v-btn>
</v-badge>
</template>
<v-card>
<v-list>
<v-list-item @click="reverseSort">
Kuchenpirat marked this conversation as resolved.
Show resolved Hide resolved
<v-icon left>
{{
preferences.orderDirection === "asc" ?
$globals.icons.sortCalendarAscending : $globals.icons.sortCalendarDescending
}}
</v-icon>
<v-list-item-title>
{{ preferences.orderDirection === "asc" ? $tc("general.sort-descending") : $tc("general.sort-ascending") }}
</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item class="pa-0">
<v-list class="py-0" style="width: 100%;">
<v-list-item
v-for="option, idx in eventTypeOptions"
:key="idx"
@click="toggleEventTypeOption(option.value)"
>
<v-checkbox
:input-value="preferences.types.includes(option.value)"
>
<template #label>
<v-icon left>
{{ option.icon }}
</v-icon>
{{ option.label }}
</template>
</v-checkbox>
</v-list-item>
</v-list>
</v-list-item>
</v-list>
</v-card>
</v-menu>
</v-col>
</v-row>
<v-divider class="mx-2"/>
Expand All @@ -31,7 +74,7 @@
</div>
<v-card v-else-if="!loading">
michael-genson marked this conversation as resolved.
Show resolved Hide resolved
<v-card-title class="justify-center pa-9">
{{ $t("recipe.timeline-is-empty") }}
{{ $t("recipe.timeline-no-events-found-try-adjusting-filters") }}
</v-card-title>
</v-card>
<div v-if="loading" class="mb-3 text-center">
Expand All @@ -41,14 +84,15 @@
</template>

<script lang="ts">
import { defineComponent, onMounted, ref, useAsync, useContext } from "@nuxtjs/composition-api";
import { computed, defineComponent, onMounted, ref, useAsync, useContext } from "@nuxtjs/composition-api";
import { useThrottleFn, whenever } from "@vueuse/core";
import RecipeTimelineItem from "./RecipeTimelineItem.vue"
import { useTimelinePreferences } from "~/composables/use-users/preferences";
import { useTimelineEventTypes } from "~/composables/recipes/use-recipe-timeline-events";
import { useAsyncKey } from "~/composables/use-utils";
import { alert } from "~/composables/use-toast";
import { useUserApi } from "~/composables/api";
import { Recipe, RecipeTimelineEventOut, RecipeTimelineEventUpdate } from "~/lib/api/types/recipe"
import { Recipe, RecipeTimelineEventOut, RecipeTimelineEventUpdate, TimelineEventType } from "~/lib/api/types/recipe";

export default defineComponent({
components: { RecipeTimelineItem },
Expand Down Expand Up @@ -76,6 +120,7 @@ export default defineComponent({
const api = useUserApi();
const { i18n } = useContext();
const preferences = useTimelinePreferences();
const { eventTypeOptions } = useTimelineEventTypes();
const loading = ref(true);
const ready = ref(false);

Expand All @@ -85,6 +130,7 @@ export default defineComponent({

const timelineEvents = ref([] as RecipeTimelineEventOut[]);
const recipes = new Map<string, Recipe>();
const filterBadgeCount = computed(() => eventTypeOptions.value.length - preferences.value.types.length);

interface ScrollEvent extends Event {
target: HTMLInputElement;
Expand Down Expand Up @@ -112,7 +158,7 @@ export default defineComponent({
}
);

// Sorting
// Preferences
function reverseSort() {
if (loading.value) {
return;
Expand All @@ -122,6 +168,21 @@ export default defineComponent({
initializeTimelineEvents();
}

function toggleEventTypeOption(option: TimelineEventType) {
if (loading.value) {
return;
}

const index = preferences.value.types.indexOf(option);
if (index === -1) {
preferences.value.types.push(option);
} else {
preferences.value.types.splice(index, 1);
}

initializeTimelineEvents();
}

// Timeline Actions
async function updateTimelineEvent(index: number) {
const event = timelineEvents.value[index]
Expand Down Expand Up @@ -179,8 +240,11 @@ export default defineComponent({
async function scrollTimelineEvents() {
const orderBy = "timestamp";
const orderDirection = preferences.value.orderDirection === "asc" ? "asc" : "desc";
// eslint-disable-next-line quotes
const eventTypeValue = `["${preferences.value.types.join('", "')}"]`;
const queryFilter = `(${props.queryFilter}) AND eventType IN ${eventTypeValue}`

const response = await api.recipes.getAllTimelineEvents(page.value, perPage, { orderBy, orderDirection, queryFilter: props.queryFilter });
const response = await api.recipes.getAllTimelineEvents(page.value, perPage, { orderBy, orderDirection, queryFilter });
page.value += 1;
if (!response?.data) {
return;
Expand Down Expand Up @@ -256,11 +320,14 @@ export default defineComponent({

return {
deleteTimelineEvent,
filterBadgeCount,
loading,
onScroll,
preferences,
eventTypeOptions,
recipes,
reverseSort,
toggleEventTypeOption,
timelineEvents,
updateTimelineEvent,
};
Expand Down
21 changes: 6 additions & 15 deletions frontend/components/Domain/Recipe/RecipeTimelineItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import { computed, defineComponent, ref, useContext, useRoute } from "@nuxtjs/co
import RecipeCardMobile from "./RecipeCardMobile.vue";
import RecipeTimelineContextMenu from "./RecipeTimelineContextMenu.vue";
import { useStaticRoutes } from "~/composables/api";
import { useTimelineEventTypes } from "~/composables/recipes/use-recipe-timeline-events";
import { Recipe, RecipeTimelineEventOut } from "~/lib/api/types/recipe"
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
import SafeMarkdown from "~/components/global/SafeMarkdown.vue";
Expand All @@ -124,6 +125,7 @@ export default defineComponent({
setup(props) {
const { $auth, $globals, $vuetify } = useContext();
const { recipeTimelineEventImage } = useStaticRoutes();
const { eventTypeOptions } = useTimelineEventTypes();
const timelineEvents = ref([] as RecipeTimelineEventOut[]);

const route = useRoute();
Expand Down Expand Up @@ -164,21 +166,10 @@ export default defineComponent({
}
})

const icon = computed( () => {
switch (props.event.eventType) {
case "comment":
return $globals.icons.commentTextMultiple;

case "info":
return $globals.icons.informationVariant;

case "system":
return $globals.icons.cog;

default:
return $globals.icons.informationVariant;
};
})
const icon = computed(() => {
const option = eventTypeOptions.value.find((option) => option.value === props.event.eventType);
return option ? option.icon : $globals.icons.informationVariant;
});

const hideImage = ref(false);
const eventImageUrl = computed<string>( () => {
Expand Down
35 changes: 35 additions & 0 deletions frontend/composables/recipes/use-recipe-timeline-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { computed, useContext } from "@nuxtjs/composition-api";
import { TimelineEventType } from "~/lib/api/types/recipe";

export interface TimelineEventTypeData {
value: TimelineEventType;
label: string;
icon: string;
}

export const useTimelineEventTypes = () => {
const { $globals, i18n } = useContext();
const eventTypeOptions = computed<TimelineEventTypeData[]>(() => {
return [
{
value: "comment",
label: i18n.tc("recipe.comment"),
icon: $globals.icons.commentTextMultiple,
},
{
value: "info",
label: i18n.tc("settings.theme.info"),
icon: $globals.icons.informationVariant,
},
{
value: "system",
label: i18n.tc("general.system"),
icon: $globals.icons.cog,
},
];
});

return {
eventTypeOptions,
}
}
3 changes: 3 additions & 0 deletions frontend/composables/use-users/preferences.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Ref, useContext } from "@nuxtjs/composition-api";
import { useLocalStorage } from "@vueuse/core";
import { TimelineEventType } from "~/lib/api/types/recipe";

export interface UserPrintPreferences {
imagePosition: string;
Expand Down Expand Up @@ -28,6 +29,7 @@ export interface UserShoppingListPreferences {

export interface UserTimelinePreferences {
orderDirection: string;
types: TimelineEventType[];
}

export function useUserPrintPreferences(): Ref<UserPrintPreferences> {
Expand Down Expand Up @@ -87,6 +89,7 @@ export function useTimelinePreferences(): Ref<UserTimelinePreferences> {
"timeline-preferences",
{
orderDirection: "asc",
types: ["info", "system", "comment"] as TimelineEventType[],
},
{ mergeDefaults: true }
// we cast to a Ref because by default it will return an optional type ref
Expand Down
4 changes: 4 additions & 0 deletions frontend/lang/messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,15 @@
"show-all": "Show All",
"shuffle": "Shuffle",
"sort": "Sort",
"sort-ascending": "Sort Ascending",
"sort-descending": "Sort Descending",
"sort-alphabetically": "Alphabetical",
"status": "Status",
"subject": "Subject",
"submit": "Submit",
"success-count": "Success: {count}",
"sunday": "Sunday",
"system": "System",
"templates": "Templates:",
"test": "Test",
"themes": "Themes",
Expand Down Expand Up @@ -513,6 +516,7 @@
"edit-timeline-event": "Edit Timeline Event",
"timeline": "Timeline",
"timeline-is-empty": "Nothing on the timeline yet. Try making this recipe!",
"timeline-no-events-found-try-adjusting-filters": "No events found. Try adjusting your search filters.",
"group-global-timeline": "{groupName} Global Timeline",
"open-timeline": "Open Timeline",
"made-this": "I Made This",
Expand Down
1 change: 0 additions & 1 deletion frontend/pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ export default defineComponent({
const allowSignup = computed(() => appInfo.value?.allowSignup || false);
const allowOidc = computed(() => appInfo.value?.enableOidc || false);
const oidcRedirect = computed(() => appInfo.value?.oidcRedirect || false);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
const oidcProviderName = computed(() => appInfo.value?.oidcProviderName || "OAuth")

whenever(
Expand Down