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

Add card search to deck page #93

Merged
merged 13 commits into from
Jan 10, 2025
16 changes: 15 additions & 1 deletion database/factories/CardFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
*/
class CardFactory extends Factory
{
protected function createTextBlock(string $text): array
{
return [
'type' => 'text',
'content' => $text,
];
}

/**
* Define the model's default state.
*
Expand All @@ -17,7 +25,13 @@ class CardFactory extends Factory
public function definition(): array
{
return [
//
'front' => [
$this->createTextBlock($this->faker->sentence),
],
'back' => [
$this->createTextBlock($this->faker->sentence),
],

];
}
}
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"pinia": "^2.1.7",
"quill": "^2.0.2",
"quill-paste-smart": "^2.0.0",
"radix-vue": "^1.8.4",
"radix-vue": "^1.9.12",
"ramda": "^0.30.1",
"uuid": "^10.0.0",
"vue": "^3.4.21",
Expand Down
7 changes: 4 additions & 3 deletions resources/client/components/DeckMembership.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
<div>
<p
v-if="
membership.role === 'owner' || !membership.capabilities.canUpdate
membership.role === T.MembershipRole.OWNER ||
!membership.capabilities.canUpdate
"
class="px-4 py-3 border border-black/10 rounded-lg text-sm capitalize leading-none"
>
Expand All @@ -24,8 +25,8 @@
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="viewer">Viewer</SelectItem>
<SelectItem value="editor">Editor</SelectItem>
<SelectItem :value="T.MembershipRole.VIEWER">Viewer</SelectItem>
<SelectItem :value="T.MembershipRole.EDITOR">Editor</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
Expand Down
19 changes: 19 additions & 0 deletions resources/client/components/icons/IconSearch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
>
<path
fill="currentColor"
d="m228.24 219.76l-51.38-51.38a86.15 86.15 0 1 0-8.48 8.48l51.38 51.38a6 6 0 0 0 8.48-8.48M38 112a74 74 0 1 1 74 74a74.09 74.09 0 0 1-74-74"
></path>
</svg>
</template>

<script lang="ts">
export default {
name: "PhMagnifyingGlassLight",
};
</script>
1 change: 1 addition & 0 deletions resources/client/components/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export { default as IconPencil } from "./IconPencil.vue";
export { default as IconPlusFilled } from "./IconPlusFilled.vue";
export { default as IconUser } from "./IconUser.vue";
export { default as IconRefresh } from "./IconRefresh.vue";
export { default as IconSearch } from "./IconSearch.vue";
export { default as IconSettings } from "./IconSettings.vue";
export { default as IconSpinner } from "./IconSpinner.vue";
export { default as IconSound } from "./IconSound.vue";
Expand Down
6 changes: 3 additions & 3 deletions resources/client/components/ui/button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ export const buttonVariants = cva(
variants: {
variant: {
default:
"bg-brand-maroon-800 text-neutral-50 shadow hover:bg-brand-maroon-800/90 dark:bg-neutral-50 dark:text-brand-maroon-800 dark:hover:bg-neutral-50/90",
"bg-brand-maroon-800 text-neutral-50 hover:bg-brand-maroon-800/90 dark:bg-neutral-50 dark:text-brand-maroon-800 dark:hover:bg-neutral-50/90",
destructive:
"bg-red-500 text-neutral-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90",
"bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90",
outline:
"border border-brand-maroon-800/20 hover:bg-white brand-maroon-800/20 hover:text-brand-maroon-800 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
secondary:
"bg-brand-maroon-800/5 text-brand-maroon-800 shadow-sm hover:bg-brand-maroon-800/80 hover:text-white dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
"bg-brand-maroon-800/5 text-brand-maroon-800 hover:bg-brand-maroon-800/80 hover:text-white dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
ghost:
"hover:bg-neutral-100 hover:text-brand-maroon-800 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
link: "text-brand-maroon-800 underline-offset-4 hover:underline dark:text-neutral-50",
Expand Down
2 changes: 2 additions & 0 deletions resources/client/components/ui/dialog/DialogContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
/>
<DialogContent
v-bind="forwarded"
data-cy="dialog-content"
:class="
cn(
'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border border-stone-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-stone-800 dark:bg-stone-950',
Expand All @@ -44,6 +45,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);

<DialogClose
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-stone-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-stone-100 data-[state=open]:text-stone-500 dark:ring-offset-stone-950 dark:focus:ring-stone-300 dark:data-[state=open]:bg-stone-800 dark:data-[state=open]:text-stone-400"
data-cy="dialog-close"
>
<Cross2Icon class="w-4 h-4" />
<span class="sr-only">Close</span>
Expand Down
13 changes: 9 additions & 4 deletions resources/client/components/ui/dropdown-menu/DropdownMenu.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
<script setup lang="ts">
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useForwardPropsEmits } from 'radix-vue'
import {
DropdownMenuRoot,
type DropdownMenuRootEmits,
type DropdownMenuRootProps,
useForwardPropsEmits,
} from "radix-vue";

const props = defineProps<DropdownMenuRootProps>()
const emits = defineEmits<DropdownMenuRootEmits>()
const props = defineProps<DropdownMenuRootProps>();
const emits = defineEmits<DropdownMenuRootEmits>();

const forwarded = useForwardPropsEmits(props, emits)
const forwarded = useForwardPropsEmits(props, emits);
</script>

<template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,17 @@ const { data: decks } = useAllDecksQuery();

const myDecks = computed((): T.Deck[] => {
return (
decks.value?.filter((deck) => deck.current_user_role === "owner") ?? []
decks.value?.filter(
(deck) => deck.current_user_role === T.MembershipRole.OWNER,
) ?? []
);
});

const sharedDecks = computed((): T.Deck[] => {
return (
decks.value?.filter((deck) => deck.current_user_role !== "owner") ?? []
decks.value?.filter(
(deck) => deck.current_user_role !== T.MembershipRole.OWNER,
) ?? []
);
});
</script>
8 changes: 6 additions & 2 deletions resources/client/pages/Decks/DeckIndexPage/DecksIndexPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,17 @@ const { data: decks } = useAllDecksQuery();

const myDecks = computed((): T.Deck[] => {
return (
decks.value?.filter((deck) => deck.current_user_role === "owner") ?? []
decks.value?.filter(
(deck) => deck.current_user_role === T.MembershipRole.OWNER,
) ?? []
);
});

const sharedDecks = computed((): T.Deck[] => {
return (
decks.value?.filter((deck) => deck.current_user_role !== "owner") ?? []
decks.value?.filter(
(deck) => deck.current_user_role !== T.MembershipRole.OWNER,
) ?? []
);
});
</script>
Expand Down
40 changes: 38 additions & 2 deletions resources/client/pages/Decks/DeckShowPage/DeckShowPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,23 @@
class="flex justify-between items-baseline sticky top-16 lg:top-0 z-10 bg-brand-oatmeal-100 py-4"
>
<h3 class="text-3xl font-bold">Cards</h3>
<Button @click="flipAllCards" variant="secondary"> Flip All </Button>
<div class="flex gap-4">
<form class="relative">
<IconSearch
class="absolute left-2 top-1/2 -translate-y-1/2 size-4 z-10"
/>
<Input
v-model="cardSearch"
placeholder="Search cards"
type="search"
class="pl-8 placeholder:text-black/40 h-full"
data-cy="card-search-input"
/>
</form>
<Button @click="flipAllCards" variant="secondary">
Flip All
</Button>
</div>
</header>
<div class="card-grid">
<RouterLink
Expand All @@ -79,7 +95,7 @@
<span>Create Card</span>
</RouterLink>

<template v-for="card in deck.cards" :key="card.id">
<template v-for="card in filteredCards" :key="card.id">
<FlippableCard
data-cy="flippable-card"
:front="card.front"
Expand Down Expand Up @@ -128,11 +144,14 @@ import LevelProgress from "@/components/LevelProgress.vue";
import { useActivityTypesQuery } from "@/queries/activityTypes/useActivityTypesQuery";
import { useIsDeckTTSEnabled } from "@/composables/useIsDeckTTSEnabled";
import { IS_DECK_TTS_ENABLED_INJECTION_KEY } from "@/constants";
import { Input } from "@/components/ui/input";
import { IconSearch } from "@/components/icons";

const props = defineProps<{
deckId: number;
}>();

const cardSearch = ref("");
const deckIdRef = computed(() => props.deckId);

const canEdit = computed(() => {
Expand Down Expand Up @@ -174,6 +193,23 @@ function handleDeleteCard(card: T.Card) {

const initialCardSide = ref<T.CardSideName>("front");

function doesBlockContainText(block: T.ContentBlock, text: string) {
if (block.type !== "text") return false;
return (block as T.TextContentBlock).content
.toLowerCase()
.includes(text.toLowerCase());
}

const filteredCards = computed((): T.Card[] => {
if (!deck.value?.cards) return [];

return deck.value.cards.filter((card) => {
return [...card.front, ...card.back].some((block) => {
return doesBlockContainText(block, cardSearch.value);
});
});
});

function flipAllCards() {
initialCardSide.value = initialCardSide.value === "front" ? "back" : "front";
}
Expand Down
4 changes: 2 additions & 2 deletions resources/client/pages/Decks/DeckShowPage/MoreCardActions.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<template>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Button variant="ghost" size="icon" data-cy="more-card-actions-button">
<IconEllipsesVertical />
<span class="sr-only">More</span>
<span class="sr-only">More actions</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
Expand Down
6 changes: 5 additions & 1 deletion resources/client/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export interface User {
};
}

type MembershipRole = "viewer" | "editor" | "owner";
export enum MembershipRole {
VIEWER = "viewer",
EDITOR = "editor",
OWNER = "owner",
}

export interface DeckMembership {
id: number;
Expand Down
Loading
Loading