restructure presentation layer
- Move most GUI related code to /presentation - Move components to /components (separate from bootstrap and style) - Move shared components helpers to /components/shared - Rename Bootstrapping to bootstrapping to enforce same naming convention in /presentation
This commit is contained in:
75
src/presentation/components/Shared/Responsive.vue
Normal file
75
src/presentation/components/Shared/Responsive.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div ref="containerElement" class="container">
|
||||
<slot ref="containerElement"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Emit } from 'vue-property-decorator';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import { throttle } from './Throttle';
|
||||
|
||||
@Component
|
||||
export default class Responsive extends Vue {
|
||||
private width: number;
|
||||
private height: number;
|
||||
private observer: ResizeObserver;
|
||||
private get container(): HTMLElement { return this.$refs.containerElement as HTMLElement; }
|
||||
|
||||
public mounted() {
|
||||
this.width = this.container.offsetWidth;
|
||||
this.height = this.container.offsetHeight;
|
||||
const resizeCallback = throttle(() => this.updateSize(), 200);
|
||||
this.observer = new ResizeObserver(resizeCallback);
|
||||
this.observer.observe(this.container);
|
||||
this.fireChangeEvents();
|
||||
}
|
||||
public updateSize() {
|
||||
let sizeChanged = false;
|
||||
if (this.isWidthChanged()) {
|
||||
this.updateWidth(this.container.offsetWidth);
|
||||
sizeChanged = true;
|
||||
}
|
||||
if (this.isHeightChanged()) {
|
||||
this.updateHeight(this.container.offsetHeight);
|
||||
sizeChanged = true;
|
||||
}
|
||||
if (sizeChanged) {
|
||||
this.$emit('sizeChanged');
|
||||
}
|
||||
}
|
||||
@Emit('widthChanged') public updateWidth(width: number) {
|
||||
this.width = width;
|
||||
}
|
||||
@Emit('heightChanged') public updateHeight(height: number) {
|
||||
this.height = height;
|
||||
}
|
||||
public destroyed() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private fireChangeEvents() {
|
||||
this.updateWidth(this.container.offsetWidth);
|
||||
this.updateHeight(this.container.offsetHeight);
|
||||
this.$emit('sizeChanged');
|
||||
}
|
||||
private isWidthChanged(): boolean {
|
||||
return this.width !== this.container.offsetWidth;
|
||||
}
|
||||
private isHeightChanged(): boolean {
|
||||
return this.height !== this.container.offsetHeight;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline-block; // if inline then it has no height or weight
|
||||
}
|
||||
</style>
|
||||
|
||||
37
src/presentation/components/Shared/StatefulVue.ts
Normal file
37
src/presentation/components/Shared/StatefulVue.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
||||
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
||||
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
|
||||
import { IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
|
||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||
import { EventSubscriptionCollection } from '@/infrastructure/Events/EventSubscriptionCollection';
|
||||
|
||||
// @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91
|
||||
@Component
|
||||
export abstract class StatefulVue extends Vue {
|
||||
private static readonly instance = new AsyncLazy<IApplicationContext>(() => buildContextAsync());
|
||||
|
||||
protected readonly events = new EventSubscriptionCollection();
|
||||
|
||||
private readonly ownEvents = new EventSubscriptionCollection();
|
||||
|
||||
public async mounted() {
|
||||
const context = await this.getCurrentContextAsync();
|
||||
this.ownEvents.register(context.contextChanged.on((event) => this.handleStateChangedEvent(event)));
|
||||
this.handleCollectionState(context.state, undefined);
|
||||
}
|
||||
public destroyed() {
|
||||
this.ownEvents.unsubscribeAll();
|
||||
this.events.unsubscribeAll();
|
||||
}
|
||||
|
||||
protected abstract handleCollectionState(
|
||||
newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void;
|
||||
protected getCurrentContextAsync(): Promise<IApplicationContext> {
|
||||
return StatefulVue.instance.getValueAsync();
|
||||
}
|
||||
|
||||
private handleStateChangedEvent(event: IApplicationContextChangedEvent) {
|
||||
this.handleCollectionState(event.newState, event.oldState);
|
||||
}
|
||||
}
|
||||
30
src/presentation/components/Shared/Throttle.ts
Normal file
30
src/presentation/components/Shared/Throttle.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export function throttle<T extends []>(
|
||||
callback: (..._: T) => void, wait: number,
|
||||
timer: ITimer = NodeTimer): (..._: T) => void {
|
||||
let queuedToRun: ReturnType<typeof setTimeout>;
|
||||
let previouslyRun: number;
|
||||
return function invokeFn(...args: T) {
|
||||
const now = timer.dateNow();
|
||||
if (queuedToRun) {
|
||||
queuedToRun = timer.clearTimeout(queuedToRun) as undefined;
|
||||
}
|
||||
if (!previouslyRun || (now - previouslyRun >= wait)) {
|
||||
callback(...args);
|
||||
previouslyRun = now;
|
||||
} else {
|
||||
queuedToRun = timer.setTimeout(invokeFn.bind(null, ...args), wait - (now - previouslyRun));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITimer {
|
||||
setTimeout: (callback: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
||||
clearTimeout: (timeoutId: ReturnType<typeof setTimeout>) => void;
|
||||
dateNow(): number;
|
||||
}
|
||||
|
||||
const NodeTimer: ITimer = {
|
||||
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
||||
clearTimeout: (timeoutId) => clearTimeout(timeoutId),
|
||||
dateNow: () => Date.now(),
|
||||
};
|
||||
Reference in New Issue
Block a user