Merge branch 'feature/drawrects' into develop

This commit is contained in:
Jörn-Michael Miehe 2023-09-04 22:20:58 +02:00
commit e097e3011e
19 changed files with 1898 additions and 59 deletions

View file

@ -1,18 +1,34 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/typescript/recommended'
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
},
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)'
],
env: {
mocha: true
}
}
]
}

View file

@ -5,6 +5,8 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:unit-watch": "vue-cli-service test:unit --watch",
"lint": "vue-cli-service lint"
},
"dependencies": {
@ -13,15 +15,24 @@
"vue-class-component": "^8.0.0-0"
},
"devDependencies": {
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@fortawesome/vue-fontawesome": "^3.0.2",
"@types/chai": "^4.3.5",
"@types/mocha": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-plugin-unit-mocha": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/test-utils": "^2.4.1",
"axios": "^1.5.0",
"bulma": "^0.9.4",
"chai": "^4.2.0",
"eslint": "^8.48.0",
"eslint-plugin-vue": "^9.17.0",
"sass": "^1.66.1",

View file

@ -9,6 +9,8 @@
</h1>
<h2 class="subtitle has-text-centered">Der Gelöt</h2>
<DoorMapEditor />
<CalendarDoor
v-for="(_, index) in 24"
:key="index"
@ -29,12 +31,14 @@
import { Options, Vue } from "vue-class-component";
import CalendarDoor from "./components/CalendarDoor.vue";
import DoorMapEditor from "./components/DoorMapEditor.vue";
import ImageModal from "./components/ImageModal.vue";
import LoginModal from "./components/LoginModal.vue";
@Options({
components: {
CalendarDoor,
DoorMapEditor,
ImageModal,
LoginModal,
},

BIN
ui/src/assets/adventskalender.jpg (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,48 @@
<template>
<nav class="breadcrumb has-succeeds-separator">
<ul>
<li
v-for="(step, index) in steps"
:key="`step-${index}`"
:class="modelValue === index ? 'is-active' : ''"
@click="change_step(index)"
>
<a>
<span class="icon is-small">
<font-awesome-icon :icon="step.icon" />
</span>
<span>{{ step.label }}</span>
</a>
</li>
</ul>
</nav>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
export interface Step {
label: string;
icon: string;
}
@Options({
props: {
steps: Array,
modelValue: Number,
},
emits: ["update:modelValue"],
})
export default class extends Vue {
private steps!: Step[];
private modelValue!: number;
private change_step(next_step: number) {
if (next_step === this.modelValue) {
return;
}
this.$emit("update:modelValue", next_step);
}
}
</script>

View file

@ -0,0 +1,37 @@
<template>
<div class="box">
<p class="title is-4">Türchen bearbeiten</p>
<BulmaBreadcrumbs :steps="steps" v-model="current_step" />
<DoorPlacer v-if="current_step === 0" v-model:doors="doors" />
<DoorChooser v-if="current_step === 1" v-model:doors="doors" />
<p v-if="current_step === 2">Bar</p>
</div>
</template>
<script lang="ts">
import { Vue, Options } from "vue-class-component";
import { Door } from "./door_map/calendar";
import BulmaBreadcrumbs, { Step } from "./BulmaBreadcrumbs.vue";
import DoorPlacer from "./door_map/DoorPlacer.vue";
import DoorChooser from "./door_map/DoorChooser.vue";
@Options({
components: {
BulmaBreadcrumbs,
DoorPlacer,
DoorChooser,
},
})
export default class extends Vue {
private readonly steps: Step[] = [
{ label: "Platzieren", icon: "fa-solid fa-crosshairs" },
{ label: "Ordnen", icon: "fa-solid fa-list-ol" },
{ label: "Überprüfen", icon: "fa-solid fa-check" },
];
private current_step = 0;
private doors: Door[] = [];
}
</script>

View file

@ -0,0 +1,56 @@
<template>
<section>
<div class="content">
<p class="title is-5">Steuerung</p>
<ul>
<li>Linksklick: Türchen bearbeiten</li>
<li>Tastatur: Tag eingeben</li>
<li>[Enter]: Tag speichern</li>
<li>[Esc]: Eingabe Abbrechen</li>
<li>[Entf]: Tag entfernen</li>
</ul>
</div>
<figure class="image">
<img src="@/assets/adventskalender.jpg" />
<ThouCanvas>
<PreviewDoor
v-for="(_, index) in doors"
:key="`door-${index}`"
v-model:door="doors[index]"
/>
</ThouCanvas>
</figure>
</section>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { Door } from "./calendar";
import ThouCanvas from "../rects/ThouCanvas.vue";
import PreviewDoor from "./PreviewDoor.vue";
@Options({
components: {
ThouCanvas,
PreviewDoor,
},
props: {
doors: Array,
},
emits: ["update:doors"],
})
export default class extends Vue {
private doors!: Door[];
public beforeUnmount() {
this.$emit("update:doors", this.doors);
}
}
</script>
<style lang="scss" scoped>
section > figure {
user-select: none;
}
</style>

View file

@ -0,0 +1,83 @@
<template>
<section>
<div class="content">
<p class="title is-5">Steuerung</p>
<ul>
<li>Linksklick + Ziehen: Neues Türchen erstellen</li>
<li>Rechtsklick + Ziehen: Türchen verschieben</li>
<li>Doppel- oder Mittelklick: Türchen löschen</li>
</ul>
</div>
<figure class="image">
<img src="@/assets/adventskalender.jpg" />
<RectangleCanvas
:rectangles="rectangles"
@draw="on_draw"
@drag="on_drag"
@remove="on_remove"
/>
</figure>
</section>
</template>
<script lang="ts">
import { Vue, Options } from "vue-class-component";
import { Rectangle } from "../rects/rectangles";
import { Door } from "./calendar";
import RectangleCanvas from "./RectangleCanvas.vue";
@Options({
components: {
RectangleCanvas,
},
props: {
doors: Array,
},
emits: ["update:doors"],
})
export default class extends Vue {
private doors!: Door[];
private get rectangles() {
return this.doors.map((door) => door.position);
}
private on_draw(position: Rectangle) {
this.doors.push(new Door(position));
}
private find_door_index(position: Rectangle): number {
return this.doors.findIndex((door) => door.position.equals(position));
}
private on_drag(old_pos: Rectangle, new_pos: Rectangle) {
const idx = this.find_door_index(old_pos);
if (idx === -1) {
return;
}
this.doors[idx].position = new_pos;
}
private on_remove(position: Rectangle) {
const idx = this.find_door_index(position);
if (idx === -1) {
return;
}
this.doors.splice(idx, 1);
}
public beforeUnmount() {
this.$emit("update:doors", this.doors);
}
}
</script>
<style lang="scss" scoped>
section > figure {
user-select: none;
}
</style>

View file

@ -0,0 +1,155 @@
<template>
<SVGRect :rectangle="door.position" :focused="editing" />
<foreignObject
:x="Math.round(parent_aspect_ratio * door.position.left)"
:y="door.position.top"
:width="Math.round(parent_aspect_ratio * door.position.width)"
:height="door.position.height"
:style="`transform: scaleX(${1 / parent_aspect_ratio})`"
>
<div
xmlns="http://www.w3.org/1999/xhtml"
class="is-flex is-align-items-center is-justify-content-center"
@click.left="on_click"
>
<input
v-if="editing"
v-model="day_str"
ref="day_input"
class="input is-large"
type="number"
min="-1"
placeholder="Tag"
@keydown="on_keydown"
/>
<div v-else class="is-size-1 has-text-weight-bold">
<template v-if="door.day >= 0">{{ door.day }}</template>
</div>
</div>
</foreignObject>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { Door } from "./calendar";
import SVGRect from "../rects/SVGRect.vue";
@Options({
components: {
SVGRect,
},
props: {
door: Door,
editable: {
type: Boolean,
default: true,
},
},
emits: ["update:door"],
})
export default class extends Vue {
private door!: Door;
private editable!: boolean;
private day_str = "";
private editing = false;
private refreshKey = 0;
declare $refs: {
day_input: HTMLInputElement | null | undefined;
};
private refresh() {
window.setTimeout(() => {
// don't loop endlessly
if (this.refreshKey < 10000) {
this.refreshKey++;
}
}, 100);
}
private get parent_aspect_ratio(): number {
this.refreshKey; // read it just to force recompute on change
if (!(this.$el instanceof Text) || this.$el.parentElement === null) {
this.refresh();
return 1;
}
const parent = this.$el.parentElement;
const result = parent.clientWidth / parent.clientHeight;
// force recompute for suspicious results
if (result === 0 || result === Infinity) {
this.refresh();
return 1;
}
return result;
}
private get day_num(): number {
const result = Number(this.day_str);
return isNaN(result) ? -1 : result;
}
private toggle_editing() {
this.day_str = String(this.door.day);
this.editing = this.editable && !this.editing;
}
private on_click(event: MouseEvent) {
if (event.target === null || !(event.target instanceof HTMLDivElement)) {
return;
}
if (!this.editing) {
const day_input_focus = () => {
if (this.$refs.day_input instanceof HTMLInputElement) {
this.$refs.day_input.focus();
return;
}
this.$nextTick(day_input_focus);
};
day_input_focus();
} else {
this.door.day = this.day_num;
}
this.toggle_editing();
}
private on_keydown(event: KeyboardEvent) {
if (!this.editing) {
return;
}
if (event.key === "Enter") {
this.door.day = this.day_num;
this.toggle_editing();
} else if (event.key === "Delete") {
this.door.day = -1;
this.toggle_editing();
} else if (event.key === "Escape") {
this.toggle_editing();
}
}
public beforeUnmount() {
this.$emit("update:door", this.door);
}
}
</script>
<style lang="scss" scoped>
foreignObject {
cursor: pointer;
> div {
color: red;
height: inherit;
}
}
</style>

View file

@ -0,0 +1,151 @@
<template>
<ThouCanvas
@mousedown.left="draw_start"
@mouseup.left="draw_finish"
@mousedown.right="drag_start"
@mouseup.right="drag_finish"
@mousemove="on_mousemove"
@click.middle="remove_rect"
@dblclick.left="remove_rect"
>
<SVGRect
v-for="(rect, index) in rectangles"
:key="`rect-${index}`"
:rectangle="rect"
/>
<SVGRect v-if="preview_visible" :focused="true" :rectangle="preview_rect" />
</ThouCanvas>
</template>
<script lang="ts">
import { Vue, Options } from "vue-class-component";
import { Vector2D, Rectangle } from "../rects/rectangles";
import ThouCanvas from "../rects/ThouCanvas.vue";
import SVGRect from "../rects/SVGRect.vue";
enum CanvasState {
Idle,
Drawing,
Dragging,
}
@Options({
components: {
ThouCanvas,
SVGRect,
},
props: {
rectangles: Array,
},
emits: ["draw", "drag", "remove"],
})
export default class extends Vue {
private readonly min_rect_area = 300;
private state = CanvasState.Idle;
private preview_rect = new Rectangle();
private drag_rect?: Rectangle;
private drag_origin = new Vector2D();
private rectangles!: Rectangle[];
private get preview_visible(): boolean {
return this.state !== CanvasState.Idle;
}
private pop_rectangle(point: Vector2D): Rectangle | undefined {
const idx = this.rectangles.findIndex((rect) => rect.contains(point));
if (idx === -1) {
return;
}
return this.rectangles.splice(idx, 1)[0];
}
private draw_start(event: MouseEvent, point: Vector2D) {
if (this.preview_visible) {
return;
}
this.state = CanvasState.Drawing;
this.preview_rect = new Rectangle(point, point);
}
private draw_finish() {
if (this.state !== CanvasState.Drawing || this.preview_rect === undefined) {
return;
}
this.state = CanvasState.Idle;
if (this.preview_rect.area < this.min_rect_area) {
return;
}
this.rectangles.push(this.preview_rect);
this.$emit("draw", this.preview_rect);
}
private drag_start(event: MouseEvent, point: Vector2D) {
if (this.preview_visible) {
return;
}
this.drag_rect = this.pop_rectangle(point);
if (this.drag_rect === undefined) {
return;
}
this.state = CanvasState.Dragging;
this.drag_origin = point;
this.preview_rect = this.drag_rect;
}
private drag_finish() {
if (
this.state !== CanvasState.Dragging ||
this.preview_rect === undefined
) {
return;
}
this.state = CanvasState.Idle;
this.rectangles.push(this.preview_rect);
this.$emit("drag", this.drag_rect, this.preview_rect);
}
private on_mousemove(event: MouseEvent, point: Vector2D) {
if (this.preview_rect === undefined) {
return;
}
if (this.state === CanvasState.Drawing) {
this.preview_rect = this.preview_rect.update(undefined, point);
} else if (this.state === CanvasState.Dragging && this.drag_rect) {
const movement = point.minus(this.drag_origin);
this.preview_rect = this.drag_rect.move(movement);
}
}
private remove_rect(event: MouseEvent, point: Vector2D) {
if (this.preview_visible) {
return;
}
const rect = this.pop_rectangle(point);
if (rect === undefined) {
return;
}
this.$emit("remove", rect);
}
}
</script>
<style scoped>
* {
cursor: crosshair;
}
</style>

View file

@ -0,0 +1,19 @@
import { Rectangle } from "../rects/rectangles";
export class Door {
private _day = -1;
public position: Rectangle;
constructor(position: Rectangle, day = -1) {
this.day = day;
this.position = position;
}
public get day(): number {
return this._day
}
public set day(day: number) {
this._day = Math.max(day, -1);
}
}

View file

@ -0,0 +1,43 @@
<template>
<rect
:class="focused ? 'focus' : ''"
:x="rectangle.left"
:y="rectangle.top"
:width="rectangle.width"
:height="rectangle.height"
/>
</template>
<script lang="ts">
import { Vue, Options } from "vue-class-component";
import { Rectangle } from "./rectangles";
@Options({
props: {
focused: {
type: Boolean,
default: false,
},
rectangle: Rectangle,
},
})
export default class extends Vue {
private focused!: boolean;
private rectangle!: Rectangle;
}
</script>
<style lang="scss" scoped>
rect {
fill: lightgreen;
stroke: green;
fill-opacity: 0.2;
stroke-opacity: 0.9;
stroke-width: 1;
&.focus {
fill: gold;
stroke: yellow;
}
}
</style>

View file

@ -0,0 +1,77 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 1000 1000"
preserveAspectRatio="none"
@contextmenu.prevent
@mousedown="transform_mouse_event"
@mousemove="transform_mouse_event"
@mouseup="transform_mouse_event"
@click="transform_mouse_event"
@dblclick="transform_mouse_event"
>
<slot name="default" />
</svg>
</template>
<script lang="ts">
import { Vue, Options } from "vue-class-component";
import { Vector2D } from "./rectangles";
function get_event_thous(event: MouseEvent): Vector2D {
if (event.currentTarget === null) {
return new Vector2D();
}
const target = event.currentTarget as Element;
return new Vector2D(
Math.round((event.offsetX / target.clientWidth) * 1000),
Math.round((event.offsetY / target.clientHeight) * 1000)
);
}
function mouse_event_validator(event: object, point: object): boolean {
if (!(event instanceof MouseEvent)) {
console.warn(event, "is not a MouseEvent!");
return false;
}
if (!(point instanceof Vector2D)) {
console.warn(point, "is not a Vector2D!");
return false;
}
return true;
}
@Options({
emits: {
mousedown: mouse_event_validator,
mouseup: mouse_event_validator,
mousemove: mouse_event_validator,
click: mouse_event_validator,
dblclick: mouse_event_validator,
},
})
export default class extends Vue {
private transform_mouse_event(event: MouseEvent) {
const point = get_event_thous(event);
this.$emit(event.type, event, point);
}
}
</script>
<style scoped>
svg {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: auto;
}
</style>

View file

@ -0,0 +1,104 @@
export class Vector2D {
public readonly x: number;
public readonly y: number;
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
public plus(other: Vector2D): Vector2D {
return new Vector2D(this.x + other.x, this.y + other.y);
}
public minus(other: Vector2D): Vector2D {
return new Vector2D(this.x - other.x, this.y - other.y);
}
public scale(other: number): Vector2D {
return new Vector2D(this.x * other, this.y * other);
}
public equals(other: Vector2D): boolean {
return this.x === other.x &&
this.y === other.y;
}
}
export class Rectangle {
private readonly corner_1: Vector2D;
private readonly corner_2: Vector2D;
constructor(corner_1 = new Vector2D(), corner_2 = new Vector2D()) {
this.corner_1 = corner_1;
this.corner_2 = corner_2;
}
public get origin(): Vector2D {
return new Vector2D(
Math.min(this.corner_1.x, this.corner_2.x),
Math.min(this.corner_1.y, this.corner_2.y),
)
}
public get left(): number {
return this.origin.x;
}
public get top(): number {
return this.origin.y;
}
public get corner(): Vector2D {
return new Vector2D(
Math.max(this.corner_1.x, this.corner_2.x),
Math.max(this.corner_1.y, this.corner_2.y),
)
}
public get size(): Vector2D {
return this.corner.minus(this.origin);
}
public get width(): number {
return this.size.x;
}
public get height(): number {
return this.size.y;
}
public get middle(): Vector2D {
return this.origin.plus(this.size.scale(0.5))
}
public get area(): number {
return this.width * this.height;
}
public equals(other: Rectangle): boolean {
return this.origin.equals(other.origin) &&
this.corner.equals(other.corner);
}
public contains(point: Vector2D): boolean {
return point.x >= this.origin.x &&
point.y >= this.origin.y &&
point.x <= this.corner.x &&
point.y <= this.corner.y;
}
public update(corner_1?: Vector2D, corner_2?: Vector2D): Rectangle {
return new Rectangle(
corner_1 || this.corner_1,
corner_2 || this.corner_2,
);
}
public move(vector: Vector2D): Rectangle {
return new Rectangle(
this.corner_1.plus(vector),
this.corner_2.plus(vector),
);
}
}

View file

@ -1,4 +1,5 @@
import { Advent22Plugin } from "@/plugins/advent22"
import { FontAwesomePlugin } from "@/plugins/fontawesome"
import { createApp } from 'vue'
import App from './App.vue'
@ -6,5 +7,6 @@ import "@/main.scss"
const app = createApp(App)
app.use(Advent22Plugin)
app.use(FontAwesomePlugin)
app.mount('#app')

View file

@ -0,0 +1,20 @@
import { App, Plugin } from 'vue';
/* import the fontawesome core */
import { library } from '@fortawesome/fontawesome-svg-core';
/* import specific icons */
import { fab } from '@fortawesome/free-brands-svg-icons';
import { fas } from '@fortawesome/free-solid-svg-icons';
/* add icons to the library */
library.add(fas, fab);
/* import font awesome icon component */
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
export const FontAwesomePlugin: Plugin = {
install(app: App) {
app.component('font-awesome-icon', FontAwesomeIcon);
}
}

View file

@ -0,0 +1,126 @@
import { Rectangle, Vector2D } from '@/components/rects/rectangles';
import { expect } from "chai";
describe("Vector2D Tests", () => {
const v = new Vector2D(1, 2);
it("should create a default vector", () => {
const v0 = new Vector2D();
expect(v0.x).to.equal(0);
expect(v0.y).to.equal(0);
});
it("should create a vector", () => {
expect(v.x).to.equal(1);
expect(v.y).to.equal(2);
});
it("should add vectors", () => {
const v2 = v.plus(new Vector2D(3, 4));
expect(v2.x).to.equal(4);
expect(v2.y).to.equal(6);
});
it("should subtract vectors", () => {
const v2 = v.minus(new Vector2D(3, 4));
expect(v2.x).to.equal(-2);
expect(v2.y).to.equal(-2);
});
it("should scale vectors", () => {
const v2 = v.scale(3);
expect(v2.x).to.equal(3);
expect(v2.y).to.equal(6);
});
it("should compare vectors", () => {
expect(v.equals(v.scale(1))).to.be.true;
expect(v.equals(v.scale(2))).to.be.false;
})
});
describe("Rectangle Tests", () => {
const v1 = new Vector2D(1, 2);
const v2 = new Vector2D(4, 6);
const r1 = new Rectangle(v1, v2);
const r2 = new Rectangle(v2, v1);
function check_rectangle(
r: Rectangle,
left: number, top: number,
width: number, height: number,
) {
expect(r.left).to.equal(left);
expect(r.top).to.equal(top);
expect(r.width).to.equal(width);
expect(r.height).to.equal(height);
expect(r.area).to.equal(width * height);
expect(r.middle.x).to.equal(left + 0.5 * width);
expect(r.middle.y).to.equal(top + 0.5 * height);
}
it("should create a default rectangle", () => {
check_rectangle(new Rectangle(), 0, 0, 0, 0);
});
it("should create a rectangle", () => {
check_rectangle(r1, 1, 2, 3, 4);
});
it("should create the same rectangle backwards", () => {
check_rectangle(r2, 1, 2, 3, 4);
});
it("should compare rectangles", () => {
expect(r1.equals(r2)).to.be.true;
expect(r1.equals(new Rectangle())).to.be.false;
})
it("should create the same rectangle transposed", () => {
const v1t = new Vector2D(v1.x, v2.y);
const v2t = new Vector2D(v2.x, v1.y);
expect(r1.equals(new Rectangle(v1t, v2t))).to.be.true;
});
it("should contain itself", () => {
expect(r1.contains(v1)).to.be.true;
expect(r1.contains(v2)).to.be.true;
expect(r1.contains(r1.origin)).to.be.true;
expect(r1.contains(r1.corner)).to.be.true;
expect(r1.contains(r1.middle)).to.be.true;
});
it("should not contain certain points", () => {
expect(r1.contains(new Vector2D(0, 0))).to.be.false;
expect(r1.contains(new Vector2D(100, 100))).to.be.false;
});
it("should update a rectangle", () => {
const v = new Vector2D(1, 1);
check_rectangle(r1.update(v1.plus(v), undefined), 2, 3, 2, 3);
check_rectangle(r1.update(v1.minus(v), undefined), 0, 1, 4, 5);
check_rectangle(r1.update(undefined, v2.plus(v)), 1, 2, 4, 5);
check_rectangle(r1.update(undefined, v2.minus(v)), 1, 2, 2, 3);
check_rectangle(r1.update(v1.plus(v), v2.plus(v)), 2, 3, 3, 4);
check_rectangle(r1.update(v1.minus(v), v2.minus(v)), 0, 1, 3, 4);
check_rectangle(r1.update(v1.minus(v), v2.plus(v)), 0, 1, 5, 6);
check_rectangle(r1.update(v1.plus(v), v2.minus(v)), 2, 3, 1, 2);
});
it("should move a rectangle", () => {
const v = new Vector2D(1, 1);
check_rectangle(r1.move(v), 2, 3, 3, 4);
});
});

View file

@ -14,7 +14,9 @@
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
"webpack-env",
"mocha",
"chai"
],
"paths": {
"@/*": [

File diff suppressed because it is too large Load diff