Refactor Vue components using Composition API #230

- Migrate `StatefulVue`:
  - Introduce `UseCollectionState` that replaces its behavior and acts
    as a shared state store.
  - Add more encapsulated, granular functions based on read or write
    access to state in CollectionState.
- Some linting rules get activates due to new code-base compability to
  modern parses, fix linting errors.
  - Rename Dialog to ModalDialog as after refactoring,
    eslintvue/no-reserved-component-names does not allow name Dialog.
  - To comply with `vue/multi-word-component-names`, rename:
    - `Code`          -> `CodeInstruction`
    - `Handle`        -> `SliderHandle`
    - `Documentable`  -> `DocumentableNode`
    - `Node`          -> `NodeContent`
    - `INode`         -> `INodeContent`
    - `Responsive`    -> `SizeObserver`
- Remove `vue-property-decorator` and `vue-class-component`
  dependencies.
- Refactor `watch` with computed properties when possible for cleaner
  code.
  - Introduce `UseApplication` to reduce repeated code in new components
    that use `computed` more heavily than before.
- Change TypeScript target to `es2017` to allow top level async calls
  for getting application context/state/instance to simplify the code by
  removing async calls. However, mocha (unit and integration) tests do
  not run with top level awaits, so a workaround is used.
This commit is contained in:
undergroundwires
2023-08-07 13:16:39 +02:00
parent 3a594ac7fd
commit 1b9be8fe2d
67 changed files with 2135 additions and 1267 deletions

View File

@@ -1,7 +1,7 @@
<template>
<div
class="card"
v-on:click="onSelected(!isExpanded)"
v-on:click="isExpanded = !isExpanded"
v-bind:class="{
'is-collapsed': !isExpanded,
'is-inactive': activeCategoryId && activeCategoryId != categoryId,
@@ -40,7 +40,7 @@
<div class="card__expander__close-button">
<font-awesome-icon
:icon="['fas', 'times']"
v-on:click="onSelected(false)"
v-on:click="collapse()"
/>
</div>
</div>
@@ -49,74 +49,97 @@
<script lang="ts">
import {
Component, Prop, Watch, Emit,
} from 'vue-property-decorator';
defineComponent, ref, watch, computed,
} from 'vue';
import { useCollectionState } from '@/presentation/components/Shared/Hooks/UseCollectionState';
import ScriptsTree from '@/presentation/components/Scripts/View/ScriptsTree/ScriptsTree.vue';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
@Component({
export default defineComponent({
components: {
ScriptsTree,
},
})
export default class CardListItem extends StatefulVue {
@Prop() public categoryId!: number;
props: {
categoryId: {
type: Number,
required: true,
},
activeCategoryId: {
type: Number,
default: undefined,
},
},
emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */
cardExpansionChanged: (isExpanded: boolean) => true,
/* eslint-enable @typescript-eslint/no-unused-vars */
},
setup(props, { emit }) {
const { events, onStateChange, currentState } = useCollectionState();
@Prop() public activeCategoryId!: number;
const isExpanded = computed({
get: () => {
return props.activeCategoryId === props.categoryId;
},
set: (newValue) => {
if (newValue) {
scrollToCard();
}
emit('cardExpansionChanged', newValue);
},
});
public cardTitle = '';
const isAnyChildSelected = ref(false);
const areAllChildrenSelected = ref(false);
const cardElement = ref<HTMLElement>();
public isExpanded = false;
const cardTitle = computed<string | undefined>(() => {
if (!props.categoryId || !currentState.value) {
return undefined;
}
const category = currentState.value.collection.findCategory(props.categoryId);
return category?.name;
});
public isAnyChildSelected = false;
public areAllChildrenSelected = false;
public async mounted() {
const context = await this.getCurrentContext();
this.events.register(context.state.selection.changed.on(
() => this.updateSelectionIndicators(this.categoryId),
));
await this.updateState(this.categoryId);
}
@Emit('selected')
public onSelected(isExpanded: boolean) {
this.isExpanded = isExpanded;
}
@Watch('activeCategoryId')
public async onActiveCategoryChanged(value?: number) {
this.isExpanded = value === this.categoryId;
}
@Watch('isExpanded')
public async onExpansionChanged(newValue: number, oldValue: number) {
if (!oldValue && newValue) {
await new Promise((resolve) => { setTimeout(resolve, 400); });
const focusElement = this.$refs.cardElement as HTMLElement;
focusElement.scrollIntoView({ behavior: 'smooth' });
function collapse() {
isExpanded.value = false;
}
}
@Watch('categoryId')
public async updateState(value?: number) {
const context = await this.getCurrentContext();
const category = !value ? undefined : context.state.collection.findCategory(value);
this.cardTitle = category ? category.name : undefined;
await this.updateSelectionIndicators(value);
}
onStateChange(async (state) => {
events.unsubscribeAll();
events.register(state.selection.changed.on(
() => updateSelectionIndicators(props.categoryId),
));
await updateSelectionIndicators(props.categoryId);
}, { immediate: true });
protected handleCollectionState(): void { /* do nothing */ }
watch(
() => props.categoryId,
(categoryId) => updateSelectionIndicators(categoryId),
);
private async updateSelectionIndicators(categoryId: number) {
const context = await this.getCurrentContext();
const { selection } = context.state;
const category = context.state.collection.findCategory(categoryId);
this.isAnyChildSelected = category ? selection.isAnySelected(category) : false;
this.areAllChildrenSelected = category ? selection.areAllSelected(category) : false;
}
}
async function scrollToCard() {
await sleep(400); // wait a bit to allow GUI to render the expanded card
cardElement.value.scrollIntoView({ behavior: 'smooth' });
}
async function updateSelectionIndicators(categoryId: number) {
const category = currentState.value.collection.findCategory(categoryId);
const { selection } = currentState.value;
isAnyChildSelected.value = category ? selection.isAnySelected(category) : false;
areAllChildrenSelected.value = category ? selection.areAllSelected(category) : false;
}
return {
cardTitle,
isExpanded,
isAnyChildSelected,
areAllChildrenSelected,
cardElement,
collapse,
};
},
});
</script>