autoformat, fix lint errors

This commit is contained in:
Tomáš Mládek 2021-09-15 19:13:26 +02:00
parent 054f9a9218
commit 5734774366

View file

@ -1,207 +1,281 @@
<template> <template>
<div class="collage"> <div class="collage">
<div class="canvas"> <div class="canvas">
<canvas id="canvas" ref="canvas" :width="canvasSize.width" :height="canvasSize.height"></canvas> <canvas
<div class="canvas-size"> id="canvas"
<label> ref="canvas"
Width: :width="canvasSize.width"
<input type="number" step="16" min="128" v-model="canvasSize.width"> :height="canvasSize.height"
</label> ></canvas>
<label> <div class="canvas-size">
Height: <label>
<input type="number" step="16" min="128" v-model="canvasSize.height"> Width:
</label> <input type="number" step="16" min="128" v-model="canvasSize.width" />
</div> </label>
<label>
Height:
<input
type="number"
step="16"
min="128"
v-model="canvasSize.height"
/>
</label>
</div>
</div> </div>
<div class="controls"> <div class="controls">
<div class="modes"> <div class="modes">
<ul class="modes-list"> <ul class="modes-list">
<li v-for="(mode, idx) in modes.modes" <li
:class="['mode', { v-for="(mode, idx) in modes.modes"
disabled: images.length < mode.minImages, :class="[
excluded: excludedModes.includes(idx), 'mode',
selected: idx === currentModeType, {
lastActive: lastActiveModeTypes.includes(idx)}]"> disabled: images.length < mode.minImages,
<label class="handle">{{mode.name}}<input type="radio" :value="idx" excluded: excludedModes.includes(idx),
v-model="currentModeType"></label> selected: idx === currentModeType,
<span class="mode-plus" lastActive: lastActiveModeTypes.includes(idx),
v-if="['recursive', 'shuffle'].includes(currentModeType) && images.length >= minImages"> },
<span class="separator"></span> ]"
<label>{{ excludedModes.includes(idx) ? 'o' : 'x'}}<input type="checkbox" :value="idx" :key="idx"
v-model="excludedModes"></label> >
</span> <label class="handle"
</li> >{{ mode.name
</ul> }}<input type="radio" :value="idx" v-model="currentModeType"
<hr> /></label>
<label :class="{disabled: images.length < minImages, <span
selected: 'shuffle' === currentModeType}"> class="mode-plus"
!SHUFFLE ALL! v-if="
<input type="radio" value="shuffle" v-model="currentModeType"> ['recursive', 'shuffle'].includes(currentModeType) &&
</label> images.length >= minImages
<label :class="{disabled: images.length < minImages, "
selected: 'recursive' === currentModeType}"> >
#RECURSIVE# <span class="separator"></span>
<input type="radio" value="recursive" v-model="currentModeType"> <label
</label> >{{ excludedModes.includes(idx) ? "o" : "x"
</div> }}<input type="checkbox" :value="idx" v-model="excludedModes"
<button :disabled="images.length < minImages" @click="renderCollage">REPAINT</button> /></label>
<hr> </span>
<div class="config"> </li>
<template v-if="currentModeType !== 'recursive'"> </ul>
<label class="config-numimages"> <hr />
#N of images: <label
<input type="number" :min="minImages" :max="images.length" :class="{
placeholder="RND" disabled: images.length < minImages,
:disabled="Object.keys(forceConfig).includes('numImages')" selected: 'shuffle' === currentModeType,
v-model="forceConfig.numImages || collageConfig.numImages"> }"
</label> >
</template> !SHUFFLE ALL!
<template v-else> <input type="radio" value="shuffle" v-model="currentModeType" />
<label> </label>
Recursion levels: <label
<input type="number" :min="1" :max="10" v-model="recursiveConfig.level"> :class="{
</label> disabled: images.length < minImages,
<label> selected: 'recursive' === currentModeType,
<input type="checkbox" v-model="recursiveConfig.repeat"> }"
Repeat images? >
</label> #RECURSIVE#
</template> <input type="radio" value="recursive" v-model="currentModeType" />
</label>
</div> </div>
<button :disabled="images.length < minImages" @click="renderCollage">
REPAINT
</button>
<hr />
<div class="config">
<template v-if="currentModeType !== 'recursive'">
<label class="config-numimages">
#N of images:
<input
type="number"
:min="minImages"
:max="images.length"
placeholder="RND"
:disabled="Object.keys(forceConfig).includes('numImages')"
:value="forceConfig.numImages || collageConfig.numImages"
@input="(n) => (collageConfig.numImages = n)"
/>
</label>
</template>
<template v-else>
<label>
Recursion levels:
<input
type="number"
:min="1"
:max="10"
v-model="recursiveConfig.level"
/>
</label>
<label>
<input type="checkbox" v-model="recursiveConfig.repeat" />
Repeat images?
</label>
</template>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import {Component, Prop, Vue, Watch} from "vue-property-decorator"; import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import {CollageModeType} from "../common/collages";
import {CollageConfig, CollageMode, Segment} from "../common/types";
import {choice, shuffle} from "../common/utils";
import BrowserCollageModes from "../collages"; import BrowserCollageModes from "../collages";
import { CollageModeType } from "../common/collages";
import { CollageConfig, CollageMode, Segment } from "../common/types";
import { choice, shuffle } from "../common/utils";
type DisplayCollageModeType = CollageModeType | & "shuffle" | & "recursive"; type DisplayCollageModeType = CollageModeType | "shuffle" | "recursive";
@Component @Component
export default class Collage extends Vue { export default class Collage extends Vue {
@Prop({required: true}) private images!: ImageBitmap[]; @Prop({ required: true }) private images!: ImageBitmap[];
private context!: CanvasRenderingContext2D; private context!: CanvasRenderingContext2D;
private canvasSize = { private canvasSize = {
width: 640, width: 640,
height: 640 height: 640,
}; };
private collageConfig: CollageConfig = { private collageConfig: CollageConfig = {
numImages: undefined numImages: undefined,
}; };
private recursiveConfig = { private recursiveConfig = {
level: 2, level: 2,
repeat: true repeat: true,
}; };
private currentModeType: DisplayCollageModeType = "shuffle"; private currentModeType: DisplayCollageModeType = "shuffle";
private lastActiveModeTypes: CollageModeType[] = []; private lastActiveModeTypes: CollageModeType[] = [];
private excludedModes: CollageModeType[] = []; private excludedModes: CollageModeType[] = [];
private modes = new BrowserCollageModes(); private modes = new BrowserCollageModes();
private get minImages() { private get minImages() {
if (this.currentModeType === "shuffle" || this.currentModeType === "recursive") { if (
return Math.min(...Object.values(this.modes.modes).map((mode) => mode.minImages)); this.currentModeType === "shuffle" ||
this.currentModeType === "recursive"
) {
return Math.min(
...Object.values(this.modes.modes).map((mode) => mode.minImages)
);
} else {
return this.modes.modes[this.currentModeType].minImages;
}
}
private get lastMode() {
if (this.lastActiveModeTypes.length === 1) {
return this.modes.modes[this.lastActiveModeTypes[0]];
}
}
private get forceConfig() {
return this.lastMode ? this.lastMode.forceConfig || {} : {};
}
private mounted() {
const canvas = this.$refs.canvas as HTMLCanvasElement;
this.context = canvas.getContext("2d") as CanvasRenderingContext2D;
}
@Watch("images")
@Watch("currentModeType")
@Watch("collageConfig", { deep: true })
@Watch("recursiveConfig", { deep: true })
private renderCollage() {
if (this.images.length >= this.minImages) {
this.reset();
const permissibleModeKeys = (
Object.keys(this.modes.modes) as CollageModeType[]
).filter(
(k) =>
!this.excludedModes.includes(k) &&
this.modes.modes[k].minImages <= this.images.length
);
if (this.currentModeType !== "recursive") {
let mode: CollageMode<any, any>;
if (this.currentModeType === "shuffle") {
const randomModeType = choice(permissibleModeKeys);
this.lastActiveModeTypes = [randomModeType];
mode = this.modes.modes[randomModeType];
} else { } else {
return this.modes.modes[this.currentModeType].minImages; this.lastActiveModeTypes = [this.currentModeType];
mode = this.modes.modes[this.currentModeType];
} }
} const shuffledImages = shuffle(this.images);
const segments = mode.getSegments(
private get lastMode() { this.context,
if (this.lastActiveModeTypes.length === 1) { this.collageConfig,
return this.modes.modes[this.lastActiveModeTypes[0]]; shuffledImages
} );
} mode.place(this.context, shuffledImages, segments);
} else {
private get forceConfig() { this.lastActiveModeTypes = [];
return this.lastMode ? this.lastMode.forceConfig || {} : {}; const shuffledImages = shuffle(this.images);
} const rootSegment: Segment = {
x: 0,
private mounted() { y: 0,
const canvas = (this.$refs.canvas as HTMLCanvasElement); w: this.context.canvas.width,
this.context = canvas.getContext("2d") as CanvasRenderingContext2D; h: this.context.canvas.height,
} };
const processSegment = async (
@Watch("images") segment: Segment,
@Watch("currentModeType") level: number
@Watch("collageConfig", {deep: true}) ): Promise<ImageBitmap> => {
@Watch("recursiveConfig", {deep: true}) console.debug(segment, level);
private renderCollage() { if (
if (this.images.length >= this.minImages) { segment === rootSegment ||
this.reset(); level <= this.recursiveConfig.level - 1
) {
const permissibleModeKeys = (Object.keys(this.modes.modes) as CollageModeType[]) let canvas = document.createElement("canvas");
.filter(k => !this.excludedModes.includes(k) && this.modes.modes[k].minImages <= this.images.length); canvas.width = segment.w;
canvas.height = segment.h;
if (this.currentModeType !== "recursive") { let modeKey = choice(permissibleModeKeys);
let mode: CollageMode<any, any>; console.debug(modeKey);
if (this.currentModeType === "shuffle") { this.lastActiveModeTypes.push(modeKey);
const randomModeType = choice(permissibleModeKeys); let mode = this.modes.modes[modeKey];
this.lastActiveModeTypes = [randomModeType]; let ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
mode = this.modes.modes[randomModeType]; let segments = mode.getSegments(ctx);
} else { console.debug(segments);
this.lastActiveModeTypes = [this.currentModeType]; let bitmaps = await Promise.all(
mode = this.modes.modes[this.currentModeType]; segments.map((segment) => processSegment(segment, level + 1))
} );
const shuffledImages = shuffle(this.images); mode.place(ctx, bitmaps, segments);
const segments = mode.getSegments(this.context, this.collageConfig, shuffledImages); return await createImageBitmap(canvas);
mode.place(this.context, shuffledImages, segments); } else {
if (this.recursiveConfig.repeat) {
return choice(shuffledImages);
} else { } else {
this.lastActiveModeTypes = []; if (shuffledImages.length > 0) {
const shuffledImages = shuffle(this.images); return shuffledImages.pop() as ImageBitmap;
const rootSegment: Segment = {x: 0, y: 0, w: this.context.canvas.width, h: this.context.canvas.height}; } else {
const processSegment = async (segment: Segment, level: number): Promise<ImageBitmap> => { throw "RAN OUT OF IMAGES";
console.debug(segment, level); }
if (segment === rootSegment || level <= this.recursiveConfig.level - 1) {
let canvas = document.createElement("canvas");
canvas.width = segment.w;
canvas.height = segment.h;
let modeKey = choice(permissibleModeKeys);
console.debug(modeKey);
this.lastActiveModeTypes.push(modeKey);
let mode = this.modes.modes[modeKey];
let ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
let segments = mode.getSegments(ctx);
console.debug(segments);
let bitmaps = await Promise.all(segments.map((segment) => processSegment(segment, level + 1)));
mode.place(ctx, bitmaps, segments);
return await createImageBitmap(canvas);
} else {
if (this.recursiveConfig.repeat) {
return choice(shuffledImages);
} else {
if (shuffledImages.length > 0) {
return shuffledImages.pop() as ImageBitmap;
} else {
throw "RAN OUT OF IMAGES";
}
}
}
};
processSegment(rootSegment, 0).then((finalCollage) => {
console.debug(finalCollage);
this.context.drawImage(finalCollage, 0, 0);
}).catch((error) => {
alert(error);
});
} }
} }
};
processSegment(rootSegment, 0)
.then((finalCollage) => {
console.debug(finalCollage);
this.context.drawImage(finalCollage, 0, 0);
})
.catch((error) => {
alert(error);
});
}
} }
}
@Watch("canvasSize", {deep: true}) @Watch("canvasSize", { deep: true })
private onCanvasSizeChange() { private onCanvasSizeChange() {
this.$nextTick(() => { this.$nextTick(() => {
this.renderCollage(); this.renderCollage();
}); });
} }
private reset() { private reset() {
this.context.globalCompositeOperation = "source-over"; this.context.globalCompositeOperation = "source-over";
const canvas = (this.$refs.canvas as HTMLCanvasElement); const canvas = this.$refs.canvas as HTMLCanvasElement;
this.context.clearRect(0, 0, canvas.width, canvas.height); this.context.clearRect(0, 0, canvas.width, canvas.height);
} }
} }
</script> </script>
@ -209,112 +283,115 @@ export default class Collage extends Vue {
<!--suppress CssUnusedSymbol --> <!--suppress CssUnusedSymbol -->
<style scoped> <style scoped>
.collage { .collage {
margin: 2rem; margin: 2rem;
display: flex; display: flex;
justify-content: space-evenly; justify-content: space-evenly;
align-items: center; align-items: center;
} }
.canvas { .canvas {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
#canvas { #canvas {
border: 1px solid black; border: 1px solid black;
} }
.canvas-size { .canvas-size {
margin: 1rem; margin: 1rem;
} }
.controls button { .controls button {
width: 100%; width: 100%;
} }
.modes { .modes {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
.modes-list { .modes-list {
list-style: none; list-style: none;
padding: 0; padding: 0;
} }
.mode { .mode {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
user-select: none; user-select: none;
} }
.separator { .separator {
border: 1px solid gray; border: 1px solid gray;
width: 0; width: 0;
margin: 0 .5em; margin: 0 0.5em;
align-self: stretch; align-self: stretch;
} }
.mode .mode-plus, .mode .mode-plus label { .mode .mode-plus,
color: gray; .mode .mode-plus label {
font-size: 14px; color: gray;
font-size: 14px;
} }
.mode-plus { .mode-plus {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.controls .modes hr { .controls .modes hr {
margin-top: .5rem; margin-top: 0.5rem;
width: 100%; width: 100%;
color: lightgray; color: lightgray;
} }
.controls label { .controls label {
font-size: 14pt; font-size: 14pt;
cursor: pointer; cursor: pointer;
margin: .25rem; margin: 0.25rem;
} }
.modes input { .modes input {
display: none; display: none;
} }
.controls button, .controls hr, .controls .config { .controls button,
margin-top: 1rem; .controls hr,
.controls .config {
margin-top: 1rem;
} }
.controls .config { .controls .config {
user-select: none; user-select: none;
} }
.config label { .config label {
display: block; display: block;
} }
.config-numimages input { .config-numimages input {
width: 4em; width: 4em;
} }
.disabled { .disabled {
color: gray; color: gray;
pointer-events: none; pointer-events: none;
} }
.excluded .handle { .excluded .handle {
color: gray; color: gray;
text-decoration: line-through; text-decoration: line-through;
} }
.selected { .selected {
font-weight: bold; font-weight: bold;
} }
.lastActive .handle { .lastActive .handle {
text-decoration: underline; text-decoration: underline;
} }
</style> </style>