Skip to content

Commit

Permalink
[#31] dnd5e 2.4.0 Compatibility - Item Descriptions with Accordion (#50)
Browse files Browse the repository at this point in the history
* Added basic accordion.

Added createOpenEditorHtml for the purpose of separating editor from HTML preview for more advanced editing scenarios.

Created `ItemDescriptions` component to contain the three description editors.

Swapped the `ItemDescriptionWithSidebarTab` to use `ItemDescriptions`.

* Got the editor working and laying out properly.

* Cleaned up Accordion appearance a bit.

Enabled support for having an accordion item start as opened.

Added some missing background colors for prose mirror light mode, as a temporary measure, to prevent transparent menu backgrounds.

* Made the item descriptions data-driven.

Added itemDescriptions and originalContext array to the item sheet context implementation and type definition.

Ordered prop names alphabetically in the ItemSheetContext type definition.

* Made the edit button for item descriptions more clickable by applying some horizontal padding to the button.
  • Loading branch information
kgar authored Nov 18, 2023
1 parent 5e38ff3 commit 9c56243
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 9 deletions.
22 changes: 22 additions & 0 deletions src/components/accordion/Accordion.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script context="module" lang="ts">
import { setContext } from 'svelte';
import { writable, type Writable } from 'svelte/store';
export interface AccordionCtxType {
selected?: Writable<object>;
}
</script>

<script lang="ts">
export let multiple: boolean = false;
$: setContext<AccordionCtxType>('ctx', {
selected: multiple ? undefined : writable(),
});
</script>

{#key multiple}
<div class={$$props.class}>
<slot />
</div>
{/key}
88 changes: 88 additions & 0 deletions src/components/accordion/AccordionItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import type { AccordionCtxType } from './Accordion.svelte';
import { writable } from 'svelte/store';
import { slide } from 'svelte/transition';
import { quadInOut } from 'svelte/easing';
export let open: boolean = false;
const ctx = getContext<AccordionCtxType>('ctx') ?? {};
const self = {};
const selected = ctx.selected ?? writable();
function toggle() {
selected.set(open ? {} : self);
}
onMount(() => {
if (open) {
$selected = self;
}
return selected.subscribe((x) => (open = x === self));
});
</script>

<section class="accordion-item">
<h2 class="accordion-item-header" class:open>
<button
class="accordion-item-toggle transparent-button"
type="button"
on:click={() => toggle()}
>
<span class="accordion-arrow" class:open
><i class="fas fa-chevron-right" /></span
>
<slot name="header" />
</button>
</h2>

{#if open}
<div
class="accordion-item-content"
transition:slide={{ duration: 200, easing: quadInOut }}
>
<slot />
</div>
{:else}
<div class="accordion-item-content hidden">
<slot />
</div>
{/if}
</section>

<style lang="scss">
.accordion-item {
margin-block-end: 0.5rem;
}
.accordion-item-header {
padding: 0.25rem;
background-color: var(--t5ek-faintest-color);
margin: 0;
}
.accordion-item-toggle {
display: flex;
gap: 0.5rem;
align-items: center;
.accordion-arrow {
font-size: 0.75rem;
transition: transform 0.2s;
&.open {
transform: rotate(90deg);
}
}
}
.accordion-item-content {
overflow-y: hidden;
&.hidden {
display: none;
}
}
</style>
14 changes: 14 additions & 0 deletions src/components/editor/OpenSheetEditor.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script lang="ts">
import { FoundryAdapter } from 'src/foundry/foundry-adapter';
/**
* The HTML to render or to edit.
*/
export let content: string;
/**
* The data field to update via form post, which will be applied to the entity the form represents (e.g., to the Actor).
*/
export let target: string;
</script>

{@html FoundryAdapter.createOpenEditorHtml(content, target)}
12 changes: 12 additions & 0 deletions src/foundry/foundry-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ export const FoundryAdapter = {
},
});
},
createOpenEditorHtml(content: string, targetDataField: string, textEditorOptions?: Record<string, any>) {
return HandlebarsHelpers.editor(content, {
hash: {
target: targetDataField,
button: false,
engine: 'prosemirror',
collaborate: false,
editable: true,
...textEditorOptions
},
});
},
mergeObject<T>(original: T, ...args: any[]) {
return mergeObject(original, ...args) as T;
},
Expand Down
2 changes: 1 addition & 1 deletion src/scss/partials/_items.scss
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ input[type='checkbox'] {
&:has(> input, > select) {
padding: 0;
}

input {
height: 1.25rem;
background: transparent;
Expand Down
26 changes: 24 additions & 2 deletions src/sheets/Tidy5eItemSheet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CONSTANTS } from 'src/constants';
import { FoundryAdapter } from 'src/foundry/foundry-adapter';
import type { Item5e, ItemSheetContext } from 'src/types/item';
import type { Item5e, ItemDescription, ItemSheetContext } from 'src/types/item';
import { get, writable } from 'svelte/store';
import TypeNotFoundSheet from './item/TypeNotFoundSheet.svelte';
import EquipmentSheet from './item/EquipmentSheet.svelte';
Expand Down Expand Up @@ -192,8 +192,28 @@ export class Tidy5eKgarItemSheet
}

private async getContext(): Promise<ItemSheetContext> {
const defaultCharacterContext = await super.getData(this.options);

const itemDescriptions: ItemDescription[] = [
{
content: defaultCharacterContext.enriched.description,
field: 'system.description.value',
label: FoundryAdapter.localize('DND5E.Description'),
},
{
content: defaultCharacterContext.enriched.unidentified,
field: 'system.description.unidentified',
label: FoundryAdapter.localize('DND5E.DescriptionUnidentified'),
},
{
content: defaultCharacterContext.enriched.chat,
field: 'system.description.chat',
label: FoundryAdapter.localize('DND5E.DescriptionChat'),
},
];

const context = {
...(await super.getData(this.options)),
...defaultCharacterContext,
appId: this.appId,
activateFoundryJQueryListeners: (node: HTMLElement) => {
this._activateCoreListeners($(node));
Expand All @@ -205,6 +225,8 @@ export class Tidy5eKgarItemSheet
this.item?.system?.hp?.value,
this.item?.system?.hp?.max
),
itemDescriptions,
originalContext: defaultCharacterContext,
};

debug(`${this.item?.type ?? 'Unknown Item Type'} context data`, context);
Expand Down
85 changes: 85 additions & 0 deletions src/sheets/item/parts/ItemDescriptions.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script lang="ts">
import RerenderAfterFormSubmission from 'src/components/utility/RerenderAfterFormSubmission.svelte';
import { FoundryAdapter } from 'src/foundry/foundry-adapter';
import type { ItemSheetContext } from 'src/types/item';
import { getContext } from 'svelte';
import type { Readable } from 'svelte/store';
import Accordion from 'src/components/accordion/Accordion.svelte';
import AccordionItem from 'src/components/accordion/AccordionItem.svelte';
import OpenSheetEditor from 'src/components/editor/OpenSheetEditor.svelte';
let context = getContext<Readable<ItemSheetContext>>('context');
const localize = FoundryAdapter.localize;
function onEditorActivation(node: HTMLElement) {
if (editorIsActive) {
editing = false;
editorIsActive = false;
return;
}
$context.activateFoundryJQueryListeners(node);
editorIsActive = true;
}
let editing = false;
let editorIsActive = false;
let valueToEdit: string;
let fieldToEdit: string;
function edit(value: string, field: string) {
valueToEdit = value;
fieldToEdit = field;
editing = true;
}
let accordionItemOpenStates = $context.itemDescriptions.map(
(_, i) => i === 0
);
</script>

<div class="item-descriptions-container">
<Accordion multiple class={editing ? 'hidden' : ''}>
{#each $context.itemDescriptions as itemDescription, i (itemDescription.field)}
<AccordionItem bind:open={accordionItemOpenStates[i]}>
<span
slot="header"
class="flex-1 flex-row justify-content-space-between"
>
{itemDescription.label}

{#if $context.owner}
<button
type="button"
class="inline-icon-button edit-item-description"
on:click|stopPropagation={() =>
edit(itemDescription.content, itemDescription.field)}
><i class="fas fa-edit" /></button
>
{/if}
</span>
{@html itemDescription.content}
</AccordionItem>
{/each}
</Accordion>

{#if editing}
<RerenderAfterFormSubmission andOnValueChange={valueToEdit}>
<article class="editor-container" use:onEditorActivation>
<OpenSheetEditor content={valueToEdit} target={fieldToEdit} />
</article>
</RerenderAfterFormSubmission>
{/if}
</div>

<style lang="scss">
.item-descriptions-container {
padding-right: 0.3125rem;
.edit-item-description {
padding-left: 2rem;
padding-right: 0.125rem;
}
}
</style>
5 changes: 2 additions & 3 deletions src/sheets/item/tabs/ItemDescriptionWithSidebarTab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
import type { ItemSheetContext } from 'src/types/item';
import { getContext } from 'svelte';
import type { Readable } from 'svelte/store';
import ItemDescription from './ItemDescriptionTab.svelte';
import HorizontalLineSeparator from 'src/components/layout/HorizontalLineSeparator.svelte';
import VerticalLineSeparator from 'src/components/layout/VerticalLineSeparator.svelte';
import { settingStore } from 'src/settings/settings';
import ItemDescriptions from '../parts/ItemDescriptions.svelte';
let context = getContext<Readable<ItemSheetContext>>('context');
Expand Down Expand Up @@ -119,7 +118,7 @@

<VerticalLineSeparator />

<ItemDescription />
<ItemDescriptions />
</div>

<style lang="scss">
Expand Down
2 changes: 2 additions & 0 deletions src/theme/default-light-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export const defaultLightTheme: Tidy5eTheme = {
'--t5ek-exhaustion-severity2-color': 'rgba(255, 255, 255, 0.7)',
'--t5ek-exhaustion-severity3-color': 'rgba(255, 255, 255, 0.7)',
'--t5ek-tinymce-toolbar-background': 'transparent',
'--t5ek-prosemirror-button-color': '#deded3',
'--t5ek-prosemirror-dropdown-item-hover-color': 'var(--t5ek-faint-color)',
'--t5ek-item-info-card-box-shadow-color': 'rgba(0, 0, 0, 0.5)',
'--t5ek-sheet-lock-icon-color': 'rgba(255, 255, 255, 0.6)',
'--t5ek-grid-pane-favorite-icon-color': 'rgba(0, 200, 100, 1)',
Expand Down
14 changes: 11 additions & 3 deletions src/types/item.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import type { ComponentType, SvelteComponent } from 'svelte';

export type ItemSheetContext = {
lockItemQuantity: boolean;
owner: boolean;
/**
* Represents remaining health as a percentage within the range of `0` to `100`.
*/
healthPercentage: number;
itemDescriptions: ItemDescription[];
lockItemQuantity: boolean;
originalContext: unknown;
owner: boolean;
} & Record<string, any>;

export type ItemDescription = {
field: string;
content: string;
label: string;
};

export type Item5e = any;

export type ItemChatData = {
Expand All @@ -25,4 +33,4 @@ export type ItemCardContentComponent = ComponentType<
any,
any
>
>;
>;

0 comments on commit 9c56243

Please sign in to comment.