Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/components/atoms/DragAndDropHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script setup lang="ts">
defineProps<{ title: string }>()
</script>

<template>
<div class="flex items-center gap-1 px-1">
<div class="flex items-center gap-1 px-1">
<span class="drag-handle size-4 cursor-grab active:cursor-grabbing text-muted-foreground hover:text-foreground transition-colors">
<Icon name="tabler:grip-vertical" />
</span>
<span
data-test-title
class="text-sm font-medium pt-0.5"
>
{{ title }}
</span>
</div>
<div
v-if="$slots.default"
data-test-actions
class="ml-auto flex items-center gap-1"
>
<slot />
</div>
</div>
</template>
6 changes: 2 additions & 4 deletions app/components/form/InitiativeSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
initiativeDefaultRows,
initiativePets,
initiativeWidgets,
widgetLabels,
} from '~~/constants/validation'
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
Expand Down Expand Up @@ -115,10 +116,7 @@ const onSubmit = form.handleSubmit(async (values) => {
<FormCheckboxGroup
name="widgets"
:label="$t('components.initiativeSettings.widgets')"
:options="[
{ label: $t('general.note'), value: 'note' },
{ label: $t('general.infoPins'), value: 'info-pins' },
]"
:options="initiativeWidgets.map(id => ({ label: $t(widgetLabels[id]), value: id }))"
list-class="sm:grid-cols-2 rounded-md border border-input bg-background px-3 py-2"
/>
<UiFormField
Expand Down
8 changes: 8 additions & 0 deletions app/components/initiative/Widgets/DiceRoll.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<Card
id="tour-2"
color="secondary"
>
<DiceRoller :styled="false" />
</Card>
</template>
8 changes: 8 additions & 0 deletions app/components/initiative/Widgets/FantasyNameGenerator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<Card color="secondary">
<FantasyNameGenerator
:amount="10"
compact
/>
</Card>
</template>
22 changes: 18 additions & 4 deletions app/components/initiative/Widgets/PinnedContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,25 @@ defineProps<{ value: DndItem[] }>()
</UiAccordionContent>
</UiAccordionItem>
</UiAccordion>
<p
<div
v-else
class="text-muted-foreground"
class="flex flex-col gap-2"
>
{{ $t("pages.encounter.pinnedContent.empty") }}
</p>
<span class="head-6">
{{ $t("pages.encounter.pinnedContent.empty.title") }}
</span>
<p class="text-muted-foreground">
{{ $t("pages.encounter.pinnedContent.empty.text") }}
<span class="inline-flex items-center gap-1">
<Icon
name="tabler:book"
class="size-4 min-w-4 text-help"
/>
<span>
{{ $t('components.navbar.dnd-content') }}
</span>
</span>
</p>
</div>
</Card>
</template>
127 changes: 87 additions & 40 deletions app/components/initiative/Widgets/index.vue
Original file line number Diff line number Diff line change
@@ -1,56 +1,76 @@
<script setup lang="ts">
import { VueDraggable } from 'vue-draggable-plus'
import { INITIATIVE_SHEET } from '~~/constants/provide-keys'
import { initiativeWidgets } from '~~/constants/validation'
import { initiativeWidgets, widgetLabels } from '~~/constants/validation'
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import * as z from 'zod'

const { sheet, update } = validateInject(INITIATIVE_SHEET)

const popoverOpen = shallowRef(false)
const definitions = initiativeWidgets.map(id => ({ id }))

const formSchema = toTypedSchema(z.object({
widgets: z.array(z.enum(initiativeWidgets)),
}))

const form = useForm({ validationSchema: formSchema })
const formError = ref<string>('')
const popoverOpen = shallowRef(false)
const localWidgets = ref<InitiativeWidget[]>([])
const isModified = computed(() => sheet.value?.settings?.modified ?? false)

const widgets = computed(() => {
const data = sheet.value?.settings?.widgets ?? []
return initiativeWidgets.filter(widget => data.includes(widget))
})
watch(
() => sheet.value?.settings,
(settings) => {
localWidgets.value = settings?.modified ? [...(settings.widgets ?? [])] : [...initiativeWidgets]
},
{ immediate: true },
)

watch(popoverOpen, (open) => {
if (!open) return

form.setValues({
widgets: isModified.value
? (sheet.value?.settings?.widgets || [])
: [...initiativeWidgets],
widgets: isModified.value ? (sheet.value?.settings?.widgets ?? []) : [...initiativeWidgets],
})
})

const formSchema = toTypedSchema(z.object({
widgets: z.array(z.enum(initiativeWidgets)),
}))

const form = useForm({
validationSchema: formSchema,
})

const formError = ref<string>('')

const onSubmit = form.handleSubmit(async (values) => {
if (!sheet.value) return

formError.value = ''

await update({
settings: {
...sheet.value?.settings,
...sheet.value.settings,
...values,
modified: true,
},
})

popoverOpen.value = false
})

function saveWidgets(widgets: InitiativeWidget[]) {
if (!sheet.value) return
update({
settings: {
...sheet.value.settings,
widgets,
modified: true,
},
})
}

function onDragEnd() {
saveWidgets(localWidgets.value)
}

function removeWidget(id: InitiativeWidget) {
const updated = localWidgets.value.filter(w => w !== id)
localWidgets.value = updated
saveWidgets(updated)
}
</script>

<template>
Expand All @@ -77,10 +97,7 @@ const onSubmit = form.handleSubmit(async (values) => {
<UiFormWrapper @submit="onSubmit">
<FormCheckboxGroup
name="widgets"
:options="[
{ label: $t('general.note'), value: 'note' },
{ label: $t('general.infoPins'), value: 'info-pins' },
]"
:options="definitions.map(d => ({ label: $t(widgetLabels[d.id]), value: d.id }))"
/>
<div
v-if="formError"
Expand All @@ -99,23 +116,53 @@ const onSubmit = form.handleSubmit(async (values) => {
</UiPopover>
</div>

<div
v-if="widgets.length || !isModified"
<VueDraggable
v-if="localWidgets.length"
v-model="localWidgets"
handle=".drag-handle"
:animation="150"
class="grid xl:grid-cols-2 gap-2 items-start"
@end="onDragEnd"
>
<LazyInitiativeWidgetsNote
v-if="widgets.includes('note') || !isModified"
hydrate-on-idle
:value="sheet?.info ?? ''"
@update="update({ info: $event })"
/>
<LazyInitiativeWidgetsPinnedContent
v-if="widgets.includes('info-pins') || !isModified"
hydrate-on-idle
:value="sheet?.infoCards ?? []"
@update="update({ infoCards: $event })"
/>
</div>
<div
v-for="widget in localWidgets"
:key="widget"
class="flex flex-col gap-1"
>
<DragAndDropHeader :title="$t(widgetLabels[widget])">
<UiButton
variant="destructive-ghost"
size="icon-sm"
:aria-label="$t('actions.remove')"
@click="removeWidget(widget)"
>
<Icon name="tabler:x" />
</UiButton>
</DragAndDropHeader>

<LazyInitiativeWidgetsNote
v-if="widget === 'note'"
hydrate-on-idle
:value="sheet?.info ?? ''"
@update="update({ info: $event })"
/>
<LazyInitiativeWidgetsPinnedContent
v-if="widget === 'info-pins'"
hydrate-on-idle
:value="sheet?.infoCards ?? []"
@update="update({ infoCards: $event })"
/>
<LazyInitiativeWidgetsFantasyNameGenerator
v-if="widget === 'fantasy-name-generator'"
hydrate-on-idle
/>
<LazyInitiativeWidgetsDiceRoll
v-if="widget === 'dice-roll'"
hydrate-on-idle
/>
</div>
</VueDraggable>

<p
v-else
class="text-muted-foreground text-sm"
Expand Down
79 changes: 1 addition & 78 deletions app/components/templates/EncounterSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,9 @@ const { sheet, update } = validateInject(INITIATIVE_SHEET)

type Modals = 'settings' | 'newHomebrew' | 'addHomebrew' | 'bestiary' | 'content' | undefined

const diceRollerOpen = ref(false)
const fantasyNameGeneratorOpen = ref(false)
const openModal = ref<Modals>(undefined)

onBeforeUnmount(() => {
openModal.value = undefined
diceRollerOpen.value = false
fantasyNameGeneratorOpen.value = false
})
onBeforeUnmount(() => openModal.value = undefined)

const maxCharacters = computed(() => hasMaxCharacters(sheet.value))
</script>
Expand All @@ -27,42 +21,6 @@ const maxCharacters = computed(() => hasMaxCharacters(sheet.value))
{{ $t('pages.encounter.options') }}
</UiSidebarGroupLabel>
<UiSidebarMenu>
<UiSidebarMenuItem>
<UiPopover v-model:open="diceRollerOpen">
<UiPopoverTrigger as-child>
<UiSidebarMenuButton as-child>
<button
id="tour-2"
v-tippy="{
content: $t('actions.roll'),
placement: 'right',
onShow: () => !isExpanded,
}"
:aria-label="$t('actions.roll')"
class="flex items-center gap-x-2 p-2 w-full text-sm"
>
<Icon
name="tabler:hexagon"
class="size-4 min-w-4 text-tertiary"
/>
<span class="group-data-[collapsible=icon]:hidden truncate">
{{ $t('actions.roll') }}
</span>
</button>
</UiSidebarMenuButton>
</UiPopoverTrigger>
<UiPopoverContent
align="center"
side="right"
prioritize-position
>
<DiceRoller
:styled="false"
@rolled="diceRollerOpen = false"
/>
</UiPopoverContent>
</UiPopover>
</UiSidebarMenuItem>
<UiSidebarMenuItem>
<UiDialog
:open="openModal === 'content'"
Expand Down Expand Up @@ -268,41 +226,6 @@ const maxCharacters = computed(() => hasMaxCharacters(sheet.value))
</UiDialog>
</UiSidebarMenuItem>
</template>
<UiSidebarMenuItem>
<UiPopover v-model:open="fantasyNameGeneratorOpen">
<UiPopoverTrigger as-child>
<UiSidebarMenuButton as-child>
<button
v-tippy="{
content: $t('components.navbar.fantasy'),
placement: 'right',
onShow: () => !isExpanded,
}"
:aria-label="$t('components.navbar.fantasy')"
class="flex items-center gap-x-2 p-2 w-full text-sm"
>
<Icon
name="tabler:signature"
class="size-4 min-w-4 text-success"
/>
<span class="group-data-[collapsible=icon]:hidden truncate">
{{ $t('components.navbar.fantasy') }}
</span>
</button>
</UiSidebarMenuButton>
</UiPopoverTrigger>
<UiPopoverContent
align="center"
side="right"
prioritize-position
>
<FantasyNameGenerator
:amount="10"
compact
/>
</UiPopoverContent>
</UiPopover>
</UiSidebarMenuItem>
<UiSidebarMenuItem>
<UiDialog
:open="openModal === 'settings'"
Expand Down
9 changes: 9 additions & 0 deletions constants/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,19 @@ export const initiativePets = [
] as const satisfies readonly InitiativePet[]

export const initiativeWidgets = [
'dice-roll',
'fantasy-name-generator',
'note',
'info-pins',
] as const satisfies readonly InitiativeWidget[]

export const widgetLabels: Record<InitiativeWidget, string> = {
'dice-roll': 'general.diceRoll',
'fantasy-name-generator': 'general.fantasyNameGenerator',
'note': 'general.note',
'info-pins': 'general.infoPins',
}

export const homebrewType = [
'player',
'summon',
Expand Down
Loading