Skip to content
Merged
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
4 changes: 2 additions & 2 deletions extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -481,10 +481,10 @@
"neowiki-layout-editor-schema",
"neowiki-layout-editor-view-type",
"neowiki-layout-editor-show-all-properties",
"neowiki-layout-editor-shown-properties",
"neowiki-layout-editor-hidden-properties",
"neowiki-layout-editor-shown-count",
"neowiki-layout-editor-show-property",
"neowiki-layout-editor-hide-property",
"neowiki-layout-editor-keep-one-shown",
"neowiki-layout-editor-summary-default",
"neowiki-layout-editor-success",
"neowiki-layout-editor-error",
Expand Down
4 changes: 2 additions & 2 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,10 @@
"neowiki-layout-editor-schema": "Schema",
"neowiki-layout-editor-view-type": "View type",
"neowiki-layout-editor-show-all-properties": "Show all properties",
"neowiki-layout-editor-shown-properties": "Shown · $1",
"neowiki-layout-editor-hidden-properties": "Hidden · $1",
"neowiki-layout-editor-shown-count": "$1 of $2 shown",
"neowiki-layout-editor-show-property": "Show property",
"neowiki-layout-editor-hide-property": "Hide property",
"neowiki-layout-editor-keep-one-shown": "At least one property must be shown",
"neowiki-layout-editor-summary-default": "Update layout via NeoWiki UI",
"neowiki-layout-editor-success": "Updated $1 layout",
"neowiki-layout-editor-error": "Failed to update $1 layout.",
Expand Down
10 changes: 5 additions & 5 deletions i18n/qqq.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@
"neowiki-layout-editor-description-placeholder": "Placeholder text for the description field in the layout editor.",
"neowiki-layout-editor-schema": "Label for the read-only schema field in the layout editor.",
"neowiki-layout-editor-view-type": "Label for the read-only view type field in the layout editor.",
"neowiki-layout-editor-show-all-properties": "Label for the toggle that controls whether all properties are shown or specific ones are chosen.",
"neowiki-layout-editor-shown-properties": "Section header for the list of shown properties in the display rule editor. $1 is the count.",
"neowiki-layout-editor-hidden-properties": "Section header for the list of hidden properties in the display rule editor. $1 is the count.",
"neowiki-layout-editor-show-property": "Aria label for the button that moves a property from the hidden section to the shown section.",
"neowiki-layout-editor-hide-property": "Aria label for the button that moves a property from the shown section to the hidden section.",
"neowiki-layout-editor-show-all-properties": "Label for the button that reveals all hidden properties, keeping the existing order of the shown ones.",
"neowiki-layout-editor-shown-count": "Header in the display rule editor showing how many properties are visible. $1 is the number shown; $2 is the total number of properties.",
"neowiki-layout-editor-show-property": "Aria label for the per-row toggle button that makes a hidden property visible in the display rule editor.",
"neowiki-layout-editor-hide-property": "Aria label for the per-row toggle button that hides a visible property in the display rule editor.",
"neowiki-layout-editor-keep-one-shown": "Tooltip on the disabled visibility toggle of the last shown property, explaining that at least one property must remain shown.",
"neowiki-layout-editor-summary-default": "Default edit summary used when updating a layout via the UI.",
"neowiki-layout-editor-success": "Success notification shown after updating a layout. $1 is the layout name.",
"neowiki-layout-editor-error": "Error notification title shown when updating a layout fails. $1 is the layout name.",
Expand Down
6 changes: 6 additions & 0 deletions resources/ext.neowiki/src/TestHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Schema } from '@/domain/Schema';
import { StatementList } from '@/domain/StatementList';
import { PropertyDefinitionList } from '@/domain/PropertyDefinitionList';
import { SubjectWithContext } from '@/domain/SubjectWithContext';
import { PropertyName } from '@/domain/PropertyDefinition';
import type { DisplayRule } from '@/domain/Layout';

export const DEFAULT_SUBJECT_ID = 's11111111111111';
export const DEFAULT_TEST_SUBJECT_LABEL = 'Test subject';
Expand Down Expand Up @@ -50,3 +52,7 @@ export function newSchema( {
properties ?? new PropertyDefinitionList( [] ),
);
}

export function newDisplayRules( ...names: string[] ): DisplayRule[] {
return names.map( ( name ) => ( { property: new PropertyName( name ) } ) );
}
224 changes: 120 additions & 104 deletions resources/ext.neowiki/src/components/LayoutEditor/DisplayRuleList.vue
Original file line number Diff line number Diff line change
@@ -1,74 +1,66 @@
<template>
<div class="ext-neowiki-display-rule-list">
<div class="ext-neowiki-display-rule-list__section-header">
{{ $i18n( 'neowiki-layout-editor-shown-properties', enabledCount ).text() }}
<div class="ext-neowiki-display-rule-list__header">
<span class="ext-neowiki-display-rule-list__header__count">
{{ $i18n( 'neowiki-layout-editor-shown-count', shownCount, totalCount ).text() }}
</span>
<span
v-if="isDefault"
class="ext-neowiki-display-rule-list__header__note"
>
{{ $i18n( 'neowiki-layout-display-no-rules' ).text() }}
</span>
<CdxButton
v-if="hasHidden"
class="ext-neowiki-display-rule-list__reset"
weight="quiet"
@click="onShowAll"
>
{{ $i18n( 'neowiki-layout-editor-show-all-properties' ).text() }}
</CdxButton>
</div>
<ul
ref="listRef"
class="ext-neowiki-display-rule-list__items"
>
<li
v-for="property in enabledProperties"
:key="property.name.toString()"
class="ext-neowiki-display-rule-list__item ext-neowiki-display-rule-list__item--enabled"
v-for="row in rows"
:key="row.property.name.toString()"
class="ext-neowiki-display-rule-list__item"
:class="{ 'ext-neowiki-display-rule-list__item--hidden': !row.shown }"
>
<span class="ext-neowiki-display-rule-list__item__drag-handle">
<span
v-if="row.shown"
class="ext-neowiki-display-rule-list__item__drag-handle"
>
<CdxIcon
:icon="cdxIconDraggable"
:aria-hidden="true"
/>
</span>
<CdxButton
class="ext-neowiki-display-rule-list__item__action"
weight="quiet"
:aria-label="$i18n( 'neowiki-layout-editor-hide-property' ).text()"
@click="onHide( property.name.toString() )"
<span
v-else
class="ext-neowiki-display-rule-list__item__drag-placeholder"
:aria-hidden="true"
>
<CdxIcon :icon="cdxIconEye" />
</CdxButton>
<span class="ext-neowiki-display-rule-list__item__name">
{{ property.name.toString() }}
<CdxIcon :icon="cdxIconDraggable" />
</span>
</li>
</ul>
<p
v-if="enabledCount === 0"
class="ext-neowiki-display-rule-list__empty"
>
{{ $i18n( 'neowiki-layout-display-no-rules' ).text() }}
</p>

<div
v-if="disabledCount > 0"
class="ext-neowiki-display-rule-list__section-header"
>
{{ $i18n( 'neowiki-layout-editor-hidden-properties', disabledCount ).text() }}
</div>
<ul
v-if="disabledCount > 0"
class="ext-neowiki-display-rule-list__items"
>
<li
v-for="property in disabledProperties"
:key="property.name.toString()"
class="ext-neowiki-display-rule-list__item"
>
<span class="ext-neowiki-display-rule-list__item__drag-handle ext-neowiki-display-rule-list__item__drag-handle--spacer">
<CdxIcon
:icon="cdxIconDraggable"
:aria-hidden="true"
/>
</span>
<CdxButton
class="ext-neowiki-display-rule-list__item__action"
weight="quiet"
:aria-label="$i18n( 'neowiki-layout-editor-show-property' ).text()"
@click="onShow( property.name.toString() )"
<span
class="ext-neowiki-display-rule-list__item__action-tooltip"
:title="$i18n( toggleMessageKey( row.shown ) ).text()"
>
<CdxIcon :icon="cdxIconEyeClosed" />
</CdxButton>
<CdxButton
class="ext-neowiki-display-rule-list__item__action"
weight="quiet"
:disabled="lastShown( row.shown )"
:aria-label="$i18n( toggleMessageKey( row.shown ) ).text()"
@click="onToggle( row.property.name.toString() )"
>
<CdxIcon :icon="row.shown ? cdxIconEye : cdxIconEyeClosed" />
</CdxButton>
</span>
<span class="ext-neowiki-display-rule-list__item__name">
{{ property.name.toString() }}
{{ row.property.name.toString() }}
</span>
</li>
</ul>
Expand All @@ -80,9 +72,9 @@ import { ref, computed } from 'vue';
import { CdxButton, CdxIcon } from '@wikimedia/codex';
import { cdxIconDraggable, cdxIconEye, cdxIconEyeClosed } from '@wikimedia/codex-icons';
import type { PropertyDefinition } from '@/domain/PropertyDefinition.ts';
import { PropertyName } from '@/domain/PropertyDefinition.ts';
import type { DisplayRule } from '@/domain/Layout.ts';
import { useSortable } from '@/composables/useSortable.ts';
import { unifiedRows, rulesAfterToggle, rulesAfterShowingAll, rulesAfterReorder } from './displayRuleEditing.ts';

const props = defineProps<{
schemaProperties: PropertyDefinition[];
Expand All @@ -95,43 +87,41 @@ const emit = defineEmits<{

const listRef = ref<HTMLElement | null>( null );

const enabledNames = computed( () =>
new Set( props.displayRules.map( ( r ) => r.property.toString() ) )
);

const enabledProperties = computed( () =>
props.displayRules
.map( ( rule ) =>
props.schemaProperties.find( ( p ) => p.name.toString() === rule.property.toString() )
)
.filter( Boolean ) as PropertyDefinition[]
);

const disabledProperties = computed( () =>
props.schemaProperties.filter(
( p ) => !enabledNames.value.has( p.name.toString() )
)
);

const enabledCount = computed( () => enabledProperties.value.length );
const disabledCount = computed( () => disabledProperties.value.length );

function onShow( name: string ): void {
emit( 'update:display-rules', [ ...props.displayRules, { property: new PropertyName( name ) } ] );
const rows = computed( () => unifiedRows( props.schemaProperties, props.displayRules ) );
const isDefault = computed( () => props.displayRules.length === 0 );
const shownCount = computed( () => rows.value.filter( ( row ) => row.shown ).length );
const totalCount = computed( () => props.schemaProperties.length );
const hasHidden = computed( () => shownCount.value < totalCount.value );

function onToggle( name: string ): void {
emit( 'update:display-rules', rulesAfterToggle( props.schemaProperties, props.displayRules, name ) );
}

function onShowAll(): void {
emit( 'update:display-rules', rulesAfterShowingAll( props.schemaProperties, props.displayRules ) );
}

function toggleMessageKey( shown: boolean ): string {
if ( lastShown( shown ) ) {
return 'neowiki-layout-editor-keep-one-shown';
}

return shown ? 'neowiki-layout-editor-hide-property' : 'neowiki-layout-editor-show-property';
}

function onHide( name: string ): void {
emit( 'update:display-rules', props.displayRules.filter( ( r ) => r.property.toString() !== name ) );
function lastShown( shown: boolean ): boolean {
return shown && shownCount.value === 1;
}

useSortable( listRef, {
handle: '.ext-neowiki-display-rule-list__item__drag-handle',
// Only shown rows take part in sorting. Hidden rows have no handle (can't be
// grabbed) and excluding them here stops a shown row from being dropped into
// the hidden region, where it would otherwise snap back on the next render.
draggable: '.ext-neowiki-display-rule-list__item:not(.ext-neowiki-display-rule-list__item--hidden)',
ghostClass: 'ext-neowiki-display-rule-list__item--ghost',
onReorder( oldIndex: number, newIndex: number ): void {
const enabledRules = [ ...props.displayRules ];
const [ moved ] = enabledRules.splice( oldIndex, 1 );
enabledRules.splice( newIndex, 0, moved );
emit( 'update:display-rules', enabledRules );
emit( 'update:display-rules', rulesAfterReorder( props.schemaProperties, props.displayRules, oldIndex, newIndex ) );
}
} );
</script>
Expand All @@ -140,10 +130,25 @@ useSortable( listRef, {
@import ( reference ) '@wikimedia/codex-design-tokens/theme-wikimedia-ui.less';

.ext-neowiki-display-rule-list {
&__section-header {
&__header {
display: flex;
align-items: center;
gap: @spacing-50;
padding: @spacing-25 @spacing-75;
font-size: @font-size-small;
color: @color-subtle;
padding: @spacing-75 @spacing-75 @spacing-25;

&__count {
font-weight: @font-weight-bold;
}

&__note {
flex-grow: 1;
}
}

&__reset {
margin-inline-start: auto;
}

&__items {
Expand All @@ -152,12 +157,6 @@ useSortable( listRef, {
padding: 0;
}

&__empty {
color: @color-subtle;
padding: @spacing-50 @spacing-75;
font-size: @font-size-small;
}

&__item {
display: flex;
align-items: center;
Expand All @@ -167,42 +166,59 @@ useSortable( listRef, {
user-select: none;

&--ghost {
opacity: 0.5;
opacity: @opacity-low;
background-color: @background-color-interactive-subtle;
}

&:hover {
background-color: @background-color-interactive-subtle;
}

&--hidden &__name {
opacity: @opacity-medium;
}

&__name {
flex-grow: 1;
}

&__drag-handle {
&__drag-handle,
&__drag-placeholder {
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity @transition-duration-medium @transition-timing-function-system;
cursor: grab;

.ext-neowiki-display-rule-list__item:hover &,
.ext-neowiki-display-rule-list__item:focus-within & {
opacity: 1;
}

.cdx-icon {
color: @color-placeholder;
}
}

&__drag-placeholder {
visibility: hidden;
}

&__drag-handle {
opacity: @opacity-transparent;
transition: opacity @transition-duration-medium @transition-timing-function-system;
cursor: grab;

&--spacer {
visibility: hidden;
.ext-neowiki-display-rule-list__item:hover &,
.ext-neowiki-display-rule-list__item:focus-within & {
opacity: @opacity-base;
}
}

&__action {
&__action-tooltip {
display: inline-flex;
flex-shrink: 0;

// A disabled button receives no pointer events, so the browser never
// surfaces its title tooltip on hover. Letting events fall through to
// this wrapper makes its title the hover target, so the "at least one
// must stay shown" explanation shows even while the toggle is disabled.
.cdx-button:disabled {
pointer-events: none;
}
}
}
}
Expand Down
Loading
Loading