changes
This commit is contained in:
parent
9424f831bf
commit
01fbae4fec
72
package-lock.json
generated
72
package-lock.json
generated
@ -12,6 +12,8 @@
|
|||||||
"@heroicons/vue": "^2.0.13",
|
"@heroicons/vue": "^2.0.13",
|
||||||
"@vueuse/core": "^9.10.0",
|
"@vueuse/core": "^9.10.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
|
"lodash.omit": "^4.5.0",
|
||||||
|
"lodash.pick": "^4.4.0",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.0.28",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
@ -20,6 +22,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/line-clamp": "^0.4.2",
|
"@tailwindcss/line-clamp": "^0.4.2",
|
||||||
|
"@types/lodash.omit": "^4.5.7",
|
||||||
|
"@types/lodash.pick": "^4.4.7",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
@ -469,6 +473,30 @@
|
|||||||
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
|
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.14.191",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
|
||||||
|
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash.omit": {
|
||||||
|
"version": "4.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.omit/-/lodash.omit-4.5.7.tgz",
|
||||||
|
"integrity": "sha512-6q6cNg0tQ6oTWjSM+BcYMBhan54P/gLqBldG4AuXd3nKr0oeVekWNS4VrNEu3BhCSDXtGapi7zjhnna0s03KpA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash.pick": {
|
||||||
|
"version": "4.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.pick/-/lodash.pick-4.4.7.tgz",
|
||||||
|
"integrity": "sha512-HgdyKz7/1+oeoVzbpu1XiX/Bti9AUksHtOILH38T07aKvqoirzcdOsrO2+Yg3L51Hv/8m1MetvHZEUGeABiTiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/web-bluetooth": {
|
"node_modules/@types/web-bluetooth": {
|
||||||
"version": "0.0.16",
|
"version": "0.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
||||||
@ -1220,6 +1248,16 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.omit": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.pick": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q=="
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.25.9",
|
"version": "0.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
||||||
@ -2174,6 +2212,30 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"@types/lodash": {
|
||||||
|
"version": "4.14.191",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
|
||||||
|
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/lodash.omit": {
|
||||||
|
"version": "4.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.omit/-/lodash.omit-4.5.7.tgz",
|
||||||
|
"integrity": "sha512-6q6cNg0tQ6oTWjSM+BcYMBhan54P/gLqBldG4AuXd3nKr0oeVekWNS4VrNEu3BhCSDXtGapi7zjhnna0s03KpA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/lodash.pick": {
|
||||||
|
"version": "4.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.pick/-/lodash.pick-4.4.7.tgz",
|
||||||
|
"integrity": "sha512-HgdyKz7/1+oeoVzbpu1XiX/Bti9AUksHtOILH38T07aKvqoirzcdOsrO2+Yg3L51Hv/8m1MetvHZEUGeABiTiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/web-bluetooth": {
|
"@types/web-bluetooth": {
|
||||||
"version": "0.0.16",
|
"version": "0.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
||||||
@ -2724,6 +2786,16 @@
|
|||||||
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
|
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.omit": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg=="
|
||||||
|
},
|
||||||
|
"lodash.pick": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q=="
|
||||||
|
},
|
||||||
"magic-string": {
|
"magic-string": {
|
||||||
"version": "0.25.9",
|
"version": "0.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
"@heroicons/vue": "^2.0.13",
|
"@heroicons/vue": "^2.0.13",
|
||||||
"@vueuse/core": "^9.10.0",
|
"@vueuse/core": "^9.10.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
|
"lodash.omit": "^4.5.0",
|
||||||
|
"lodash.pick": "^4.4.0",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.0.28",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
@ -21,6 +23,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/line-clamp": "^0.4.2",
|
"@tailwindcss/line-clamp": "^0.4.2",
|
||||||
|
"@types/lodash.omit": "^4.5.7",
|
||||||
|
"@types/lodash.pick": "^4.4.7",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
|
@ -131,6 +131,7 @@ const emit = defineEmits<{
|
|||||||
boundingBox?: Vec2Box
|
boundingBox?: Vec2Box
|
||||||
): void;
|
): void;
|
||||||
(e: 'edited'): void;
|
(e: 'edited'): void;
|
||||||
|
(e: 'zoom', scale: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const localFloorDocument = ref(deepUnref(props.floorDocument));
|
const localFloorDocument = ref(deepUnref(props.floorDocument));
|
||||||
@ -258,6 +259,7 @@ const events: Record<string, (e: CustomEvent) => void> = {
|
|||||||
'hpc:position': (e: CustomEvent<RepositionEvent>) => {
|
'hpc:position': (e: CustomEvent<RepositionEvent>) => {
|
||||||
canvasPos.value = e.detail.position;
|
canvasPos.value = e.detail.position;
|
||||||
canvasZoom.value = e.detail.zoom;
|
canvasZoom.value = e.detail.zoom;
|
||||||
|
emit('zoom', e.detail.zoom);
|
||||||
},
|
},
|
||||||
'hpc:save': (e: CustomEvent) => {
|
'hpc:save': (e: CustomEvent) => {
|
||||||
updateLocalDocument();
|
updateLocalDocument();
|
||||||
@ -297,6 +299,7 @@ onMounted(() => {
|
|||||||
);
|
);
|
||||||
canvasPos.value = setPos;
|
canvasPos.value = setPos;
|
||||||
canvasZoom.value = setZoom;
|
canvasZoom.value = setZoom;
|
||||||
|
emit('zoom', setZoom);
|
||||||
|
|
||||||
Object.keys(events).forEach((event) =>
|
Object.keys(events).forEach((event) =>
|
||||||
canvas.value.addEventListener(event, events[event])
|
canvas.value.addEventListener(event, events[event])
|
||||||
|
14
src/enums/item-type.enum.ts
Normal file
14
src/enums/item-type.enum.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export enum ItemType {
|
||||||
|
ITEM = 'ITEM',
|
||||||
|
OBJECT = 'OBJECT',
|
||||||
|
TECHNOLOGY = 'TECHNOLOGY',
|
||||||
|
TOOL = 'TOOL',
|
||||||
|
FOOD = 'FOOD',
|
||||||
|
MEDICINE = 'MEDICINE',
|
||||||
|
ART = 'ART',
|
||||||
|
CRAFT = 'CRAFT',
|
||||||
|
COMPOSITION = 'COMPOSITION',
|
||||||
|
CLOTHING = 'CLOTHING',
|
||||||
|
SERVICE = 'SERVICE',
|
||||||
|
OTHER = 'OTHER',
|
||||||
|
}
|
11
src/enums/storage-set-type.enum.ts
Normal file
11
src/enums/storage-set-type.enum.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export enum StorageSetType {
|
||||||
|
DRAWERS = 'DRAWERS',
|
||||||
|
SHELVES = 'SHELVES',
|
||||||
|
CUPBOARD = 'CUPBOARD',
|
||||||
|
CLOSET = 'CLOSET',
|
||||||
|
FRIDGE = 'FRIDGE',
|
||||||
|
FREEZER = 'FREEZER',
|
||||||
|
BOX = 'BOX',
|
||||||
|
BOXES = 'BOXES',
|
||||||
|
OTHER = 'OTHER',
|
||||||
|
}
|
14
src/enums/storage-type.enum.ts
Normal file
14
src/enums/storage-type.enum.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export enum StorageType {
|
||||||
|
DRAWER = 'DRAWER',
|
||||||
|
SHELF = 'SHELF',
|
||||||
|
ASSEMBLY = 'ASSEMBLY',
|
||||||
|
CUPBOARD = 'CUPBOARD',
|
||||||
|
CLOSET = 'CLOSET',
|
||||||
|
FRIDGE = 'FRIDGE',
|
||||||
|
FREEZER = 'FREEZER',
|
||||||
|
TABLE = 'TABLE',
|
||||||
|
BOX = 'BOX',
|
||||||
|
VIRTUAL = 'VIRTUAL',
|
||||||
|
PERSON = 'PERSON',
|
||||||
|
OTHER = 'OTHER',
|
||||||
|
}
|
8
src/enums/transaction-type.enum.ts
Normal file
8
src/enums/transaction-type.enum.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export enum TransactionType {
|
||||||
|
ACQUIRED = 'ACQUIRED',
|
||||||
|
SOLD = 'SOLD',
|
||||||
|
DESTROYED = 'DESTROYED',
|
||||||
|
BINNED = 'BINNED',
|
||||||
|
MOVED = 'MOVED',
|
||||||
|
TRANSFERRED = 'TRANSFERRED',
|
||||||
|
}
|
@ -1,11 +1,66 @@
|
|||||||
export interface StorageListItem {
|
import { ItemType } from '../enums/item-type.enum';
|
||||||
|
import { StorageSetType } from '../enums/storage-set-type.enum';
|
||||||
|
import { StorageType } from '../enums/storage-type.enum';
|
||||||
|
import { TransactionType } from '../enums/transaction-type.enum';
|
||||||
|
import { AddedBy } from './user.interfaces';
|
||||||
|
|
||||||
|
export interface StorageSharedType {
|
||||||
id: number;
|
id: number;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
type: string;
|
|
||||||
location: string;
|
location: string;
|
||||||
locationDescription: string;
|
locationDescription: string;
|
||||||
color: string;
|
color: string;
|
||||||
itemCount: number;
|
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StorageListItem extends StorageSharedType {
|
||||||
|
type: StorageType;
|
||||||
|
items?: StoredItem[];
|
||||||
|
itemCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageSetListItem extends StorageSharedType {
|
||||||
|
type: StorageSetType;
|
||||||
|
storages: StorageListItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageItem {
|
||||||
|
id: number;
|
||||||
|
displayName: string;
|
||||||
|
type: ItemType;
|
||||||
|
barcode?: string;
|
||||||
|
consumable?: boolean;
|
||||||
|
image?: string;
|
||||||
|
weight?: number;
|
||||||
|
url?: string;
|
||||||
|
notes?: string;
|
||||||
|
public: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoredItemTransaction {
|
||||||
|
id: number;
|
||||||
|
type: TransactionType;
|
||||||
|
price?: number;
|
||||||
|
currency?: string;
|
||||||
|
notes?: string;
|
||||||
|
actionAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
actor?: AddedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoredItem {
|
||||||
|
id: number;
|
||||||
|
notes: string;
|
||||||
|
expiresAt?: string;
|
||||||
|
acquiredAt?: string;
|
||||||
|
consumedAt?: string;
|
||||||
|
item: StorageItem;
|
||||||
|
addedBy: AddedBy;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
transactions?: StoredItemTransaction[];
|
||||||
|
}
|
||||||
|
@ -5,3 +5,10 @@ export interface User {
|
|||||||
picture?: string;
|
picture?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AddedBy {
|
||||||
|
sub: string;
|
||||||
|
name: string;
|
||||||
|
picture?: string;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
8
src/utils/is-storage-set.ts
Normal file
8
src/utils/is-storage-set.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import {
|
||||||
|
StorageSetListItem,
|
||||||
|
StorageSharedType,
|
||||||
|
} from '../interfaces/storage.interfaces';
|
||||||
|
|
||||||
|
export default function isSet(storage: StorageSharedType) {
|
||||||
|
return !!(storage as StorageSetListItem).storages;
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import deepUnref from './deep-unref';
|
||||||
|
|
||||||
export class JFetchResponse<T> {
|
export class JFetchResponse<T> {
|
||||||
constructor(
|
constructor(
|
||||||
public statusCode: number,
|
public statusCode: number,
|
||||||
@ -24,7 +26,7 @@ export default async function jfetch<T = any>(
|
|||||||
if (opts['body'] && typeof opts['body'] === 'object') {
|
if (opts['body'] && typeof opts['body'] === 'object') {
|
||||||
opts['body'] = JSON.stringify(opts['body']);
|
opts['body'] = JSON.stringify(opts['body']);
|
||||||
opts['headers'] = {
|
opts['headers'] = {
|
||||||
...(opts['headers'] || {}),
|
...deepUnref(opts['headers'] || {}),
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -53,67 +53,67 @@
|
|||||||
transparent
|
transparent
|
||||||
headless
|
headless
|
||||||
:floor-document="floorDocument"
|
:floor-document="floorDocument"
|
||||||
@click="clickOnRoom(undefined, $event)"
|
@click="clickOnRoom()"
|
||||||
|
@zoom="(newZoom) => (canvasZoom = newZoom)"
|
||||||
>
|
>
|
||||||
<RoomPolygon
|
<RoomPolygon
|
||||||
v-for="room of rooms"
|
v-for="room of rooms"
|
||||||
:room="room"
|
:room="room"
|
||||||
|
:zoom="canvasZoom"
|
||||||
:highlighted="selectedRoom?.id"
|
:highlighted="selectedRoom?.id"
|
||||||
@clicked-in="(ev) => clickOnRoom(room, ev)"
|
@clicked-in="(x, y) => clickOnRoom(room, x, y)"
|
||||||
|
@mouse-moved-in="(x, y) => mouseMovedInRoom(room, x, y)"
|
||||||
>
|
>
|
||||||
<template v-if="selectedRoom?.id === room.id">
|
<template v-if="selectedRoom?.id === room.id">
|
||||||
|
<template v-for="storage of storages">
|
||||||
<StorageBubble
|
<StorageBubble
|
||||||
|
v-if="!movingBubble || storage.id === movingBubble?.id"
|
||||||
:storage="storage"
|
:storage="storage"
|
||||||
v-for="storage of storages"
|
:class="{
|
||||||
:class="{ 'z-20': storage.id === hoveredBubble }"
|
'z-20': storage.id === hoveredBubble?.id,
|
||||||
@mouseenter="() => (hoveredBubble = storage.id)"
|
'pointer-events-none': !!movingBubble,
|
||||||
@mouseleave="() => (hoveredBubble = undefined)"
|
}"
|
||||||
|
@mouseenter="() => (hoveredBubble = storage)"
|
||||||
|
@start-moving="moveBubble(storage)"
|
||||||
|
>
|
||||||
|
<div class="flex items-center space-x-1">
|
||||||
|
<span class="text-md font-bold">{{
|
||||||
|
storage.displayName
|
||||||
|
}}</span>
|
||||||
|
<span
|
||||||
|
v-if="isSet(storage)"
|
||||||
|
class="text-[0.75rem] font-bold uppercase text-gray-400"
|
||||||
|
>(Storage Set)</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm" v-if="!isSet(storage)"
|
||||||
|
>Stored items:
|
||||||
|
{{ (storage as StorageListItem).itemCount }}</span
|
||||||
|
>
|
||||||
|
<span class="text-sm" v-else
|
||||||
|
>Storages:
|
||||||
|
{{ (storage as StorageSetListItem).storages.length }}</span
|
||||||
>
|
>
|
||||||
<span class="text-md font-bold">{{ storage.displayName }}</span>
|
|
||||||
<span class="text-sm">Stored items: {{ storage.itemCount }}</span>
|
|
||||||
</StorageBubble>
|
</StorageBubble>
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
</RoomPolygon>
|
</RoomPolygon>
|
||||||
</HousePlanner>
|
</HousePlanner>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<div class="grid grid-cols-3">
|
<ItemSelector
|
||||||
<div class="flex flex-col">
|
:rooms="rooms"
|
||||||
<button
|
:storages="storages"
|
||||||
v-for="room of rooms"
|
:selected-room="selectedRoom"
|
||||||
@click="selectRoomFromList(room)"
|
:selected-storage="selectedStorage"
|
||||||
:class="[
|
@select-room="(room) => selectRoomFromList(room)"
|
||||||
selectedRoom?.id === room.id
|
@select-storage="(storage) => selectStorage(storage)"
|
||||||
? 'bg-blue-100 hover:bg-blue-200'
|
/>
|
||||||
: 'hover:bg-blue-100',
|
|
||||||
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ room.displayName }} <ChevronRightIcon class="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col" v-if="selectedRoom?.id">
|
|
||||||
<button
|
|
||||||
v-for="storage of storages"
|
|
||||||
:class="[
|
|
||||||
'hover:bg-blue-100',
|
|
||||||
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ storage.displayName }} ({{ storage.itemCount }})
|
|
||||||
<ChevronRightIcon class="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline';
|
||||||
ChevronDownIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
ChevronUpIcon,
|
|
||||||
} from '@heroicons/vue/24/outline';
|
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import PageHead from '../../../components/PageHead.vue';
|
import PageHead from '../../../components/PageHead.vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
@ -122,26 +122,34 @@ import { computed, ref } from '@vue/reactivity';
|
|||||||
import HousePlanner from '../../../components/house-planner/HousePlanner.vue';
|
import HousePlanner from '../../../components/house-planner/HousePlanner.vue';
|
||||||
import { boundingBox } from '../../../modules/house-planner/utils';
|
import { boundingBox } from '../../../modules/house-planner/utils';
|
||||||
import { Vec2 } from '../../../modules/house-planner/interfaces';
|
import { Vec2 } from '../../../modules/house-planner/interfaces';
|
||||||
import { watch } from 'vue';
|
|
||||||
import { RoomLayoutObject } from '../../../interfaces/room.interfaces';
|
import { RoomLayoutObject } from '../../../interfaces/room.interfaces';
|
||||||
import jfetch from '../../../utils/jfetch';
|
import jfetch from '../../../utils/jfetch';
|
||||||
import { BACKEND_URL } from '../../../constants';
|
import { BACKEND_URL } from '../../../constants';
|
||||||
import { useAccessToken } from '../../../composables/useAccessToken';
|
import { useAccessToken } from '../../../composables/useAccessToken';
|
||||||
import { StorageListItem } from '../../../interfaces/storage.interfaces';
|
import {
|
||||||
|
StorageListItem,
|
||||||
|
StorageSetListItem,
|
||||||
|
StorageSharedType,
|
||||||
|
} from '../../../interfaces/storage.interfaces';
|
||||||
import StorageBubble from './StorageBubble.vue';
|
import StorageBubble from './StorageBubble.vue';
|
||||||
import RoomPolygon from './RoomPolygon.vue';
|
import RoomPolygon from './RoomPolygon.vue';
|
||||||
import { useLocalStorage } from '@vueuse/core';
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
|
import isSet from '../../../utils/is-storage-set';
|
||||||
|
import ItemSelector from './ItemSelector.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const buildingStore = useBuildingStore();
|
const buildingStore = useBuildingStore();
|
||||||
const { building } = storeToRefs(buildingStore);
|
const { building } = storeToRefs(buildingStore);
|
||||||
const { authHeader } = useAccessToken();
|
const { authHeader } = useAccessToken();
|
||||||
const canvas = ref<InstanceType<typeof HousePlanner>>();
|
const canvas = ref<InstanceType<typeof HousePlanner>>();
|
||||||
const storages = ref<StorageListItem[]>([]);
|
|
||||||
const selectedRoom = ref<RoomLayoutObject>();
|
|
||||||
const showMap = useLocalStorage('showRoomMap', true, { writeDefaults: false });
|
const showMap = useLocalStorage('showRoomMap', true, { writeDefaults: false });
|
||||||
const hoveredBubble = ref<number>();
|
const canvasZoom = ref(1);
|
||||||
const selectedStorage = ref<number>();
|
const storages = ref<StorageSharedType[]>([]);
|
||||||
|
const selectedRoom = ref<RoomLayoutObject>();
|
||||||
|
const hoveredBubble = ref<StorageSharedType>();
|
||||||
|
const movingBubble = ref<StorageSharedType>();
|
||||||
|
const selectedSet = ref<StorageSetListItem>();
|
||||||
|
const selectedStorage = ref<StorageListItem>();
|
||||||
|
|
||||||
const floor = computed(() =>
|
const floor = computed(() =>
|
||||||
building.value?.floors.find(
|
building.value?.floors.find(
|
||||||
@ -153,32 +161,75 @@ const floorDocument = computed(
|
|||||||
() => floor.value?.plan && JSON.parse(floor.value.plan)
|
() => floor.value?.plan && JSON.parse(floor.value.plan)
|
||||||
);
|
);
|
||||||
|
|
||||||
const roomCoordinate = (ev: MouseEvent) => {
|
const clickOnRoom = (room?: RoomLayoutObject, x?: number, y?: number) => {
|
||||||
const rect = (ev.target as HTMLElement).getBoundingClientRect();
|
|
||||||
const [x, y] = [ev.clientX - rect.left, ev.clientY - rect.top];
|
|
||||||
console.log(x, y);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clickOnRoom = (room: RoomLayoutObject | undefined, ev: MouseEvent) => {
|
|
||||||
canvas.value?.setViewRectangle(room?.boundingBox);
|
canvas.value?.setViewRectangle(room?.boundingBox);
|
||||||
selectedStorage.value = undefined;
|
selectedStorage.value = undefined;
|
||||||
|
selectedSet.value = undefined;
|
||||||
if (room && (!selectedRoom.value || room.id !== selectedRoom.value?.id)) {
|
if (room && (!selectedRoom.value || room.id !== selectedRoom.value?.id)) {
|
||||||
getRoomStorages(room);
|
getRoomStorages(room);
|
||||||
} else if (room?.id == selectedRoom.value?.id) {
|
} else if (room?.id == selectedRoom.value?.id) {
|
||||||
roomCoordinate(ev);
|
if (movingBubble.value && x != null && y != null) {
|
||||||
|
setBubbleLocation(x, y);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
storages.value = [];
|
storages.value = [];
|
||||||
}
|
}
|
||||||
selectedRoom.value = room;
|
selectedRoom.value = room;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mouseMovedInRoom = (room: RoomLayoutObject, x: number, y: number) => {
|
||||||
|
if (movingBubble.value) {
|
||||||
|
const storage = storages.value.find(
|
||||||
|
(item) => item.id === movingBubble.value?.id
|
||||||
|
);
|
||||||
|
if (!storage) return;
|
||||||
|
storage.location = `${x},${y}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveBubble = (bubble: StorageSharedType) => {
|
||||||
|
movingBubble.value = bubble;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBubbleLocation = async (x: number, y: number) => {
|
||||||
|
if (!movingBubble.value) return;
|
||||||
|
await jfetch(
|
||||||
|
`${BACKEND_URL}/storage/${
|
||||||
|
(movingBubble.value as StorageSetListItem).storages ? 'set' : 'storages'
|
||||||
|
}/${movingBubble.value.id}`,
|
||||||
|
{
|
||||||
|
method: 'PATCH',
|
||||||
|
body: {
|
||||||
|
location: `${x},${y}`,
|
||||||
|
},
|
||||||
|
headers: authHeader.value,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
movingBubble.value = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const selectRoomFromList = (room: RoomLayoutObject) => {
|
const selectRoomFromList = (room: RoomLayoutObject) => {
|
||||||
canvas.value?.setViewRectangle(room?.boundingBox);
|
canvas.value?.setViewRectangle(room?.boundingBox);
|
||||||
selectedStorage.value = undefined;
|
selectedStorage.value = undefined;
|
||||||
|
selectedSet.value = undefined;
|
||||||
getRoomStorages(room);
|
getRoomStorages(room);
|
||||||
selectedRoom.value = room;
|
selectedRoom.value = room;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectStorage = (storage: StorageSharedType) => {
|
||||||
|
if (isSet(storage)) {
|
||||||
|
selectedSet.value = storage as StorageSetListItem;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedStorage.value = storage as StorageListItem;
|
||||||
|
jfetch(`${BACKEND_URL}/storage/storages/${storage.id}`, {
|
||||||
|
headers: authHeader.value,
|
||||||
|
}).then(({ data: storage }) => {
|
||||||
|
selectedStorage.value = storage;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const rooms = computed<RoomLayoutObject[]>(
|
const rooms = computed<RoomLayoutObject[]>(
|
||||||
() =>
|
() =>
|
||||||
(floor.value &&
|
(floor.value &&
|
||||||
|
94
src/views/building/floors/ItemSelector.vue
Normal file
94
src/views/building/floors/ItemSelector.vue
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="['grid', selectedSet ? 'grid-cols-4' : 'grid-cols-3']">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<button
|
||||||
|
v-for="room of rooms"
|
||||||
|
@click="emit('selectRoom', room)"
|
||||||
|
:class="[
|
||||||
|
selectedRoom?.id === room.id
|
||||||
|
? 'bg-blue-100 hover:bg-blue-200'
|
||||||
|
: 'hover:bg-blue-100',
|
||||||
|
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span>{{ room.displayName }}</span> <ChevronRightIcon class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col" v-if="selectedRoom?.id">
|
||||||
|
<button
|
||||||
|
v-for="storage of storages"
|
||||||
|
@click="emit('selectStorage', storage)"
|
||||||
|
:class="[
|
||||||
|
selectedSet?.id === storage.id || selectedStorage?.id === storage.id
|
||||||
|
? 'bg-blue-100 hover:bg-blue-200'
|
||||||
|
: 'hover:bg-blue-100',
|
||||||
|
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>{{ storage.displayName }}
|
||||||
|
|
||||||
|
<span v-if="!isSet(storage)"
|
||||||
|
>({{ (storage as StorageListItem).itemCount }})</span
|
||||||
|
>
|
||||||
|
<span v-else>
|
||||||
|
(set) ({{ (storage as StorageSetListItem).storages.length }})</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<ChevronRightIcon class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col" v-if="selectedSet">
|
||||||
|
<button
|
||||||
|
v-for="storage of selectedSet.storages"
|
||||||
|
@click="emit('selectStorage', storage)"
|
||||||
|
:class="[
|
||||||
|
'hover:bg-blue-100',
|
||||||
|
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span>{{ storage.displayName }} ({{ storage.itemCount }})</span>
|
||||||
|
<ChevronRightIcon class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col" v-if="selectedStorage?.items">
|
||||||
|
<button
|
||||||
|
v-for="item of selectedStorage.items"
|
||||||
|
:class="[
|
||||||
|
'hover:bg-blue-100',
|
||||||
|
'flex items-center justify-between border-b-2 border-gray-100 py-2 px-2',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span>{{ item.item.displayName }}</span>
|
||||||
|
<ChevronRightIcon class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ChevronRightIcon } from '@heroicons/vue/24/outline';
|
||||||
|
import isSet from '../../../utils/is-storage-set';
|
||||||
|
import {
|
||||||
|
StorageListItem,
|
||||||
|
StorageSetListItem,
|
||||||
|
StorageSharedType,
|
||||||
|
} from '../../../interfaces/storage.interfaces';
|
||||||
|
import { RoomLayoutObject } from '../../../interfaces/room.interfaces';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
rooms: RoomLayoutObject[];
|
||||||
|
storages: StorageSharedType[];
|
||||||
|
selectedRoom?: RoomLayoutObject;
|
||||||
|
selectedSet?: StorageSetListItem;
|
||||||
|
selectedStorage?: StorageListItem;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'selectStorage', storage: StorageSharedType): void;
|
||||||
|
(e: 'selectRoom', room: RoomLayoutObject): void;
|
||||||
|
}>();
|
||||||
|
</script>
|
@ -13,6 +13,7 @@
|
|||||||
class="absolute"
|
class="absolute"
|
||||||
:style="roomPolygonCSS"
|
:style="roomPolygonCSS"
|
||||||
@click.stop="clickOnRoom($event)"
|
@click.stop="clickOnRoom($event)"
|
||||||
|
@mousemove="moveOverRoom($event)"
|
||||||
></div>
|
></div>
|
||||||
<span class="pointer-events-none z-10" v-if="!highlighted">{{
|
<span class="pointer-events-none z-10" v-if="!highlighted">{{
|
||||||
room.displayName
|
room.displayName
|
||||||
@ -27,14 +28,25 @@ import { RoomLayoutObject } from '../../../interfaces/room.interfaces';
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
room: RoomLayoutObject;
|
room: RoomLayoutObject;
|
||||||
|
zoom: number;
|
||||||
highlighted?: number;
|
highlighted?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'clickedIn', ev: MouseEvent): void;
|
(e: 'clickedIn', x: number, y: number): void;
|
||||||
|
(e: 'mouseMovedIn', x: number, y: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
const mouseCoordsFromEvent = (ev: MouseEvent): [number, number] => {
|
||||||
const clickOnRoom = (ev: MouseEvent) => emit('clickedIn', ev);
|
const rect = (ev.target as HTMLElement).getBoundingClientRect();
|
||||||
|
return [
|
||||||
|
(ev.clientX - rect.left) / props.zoom,
|
||||||
|
(ev.clientY - rect.top) / props.zoom,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
const clickOnRoom = (ev: MouseEvent) =>
|
||||||
|
emit('clickedIn', ...mouseCoordsFromEvent(ev));
|
||||||
|
const moveOverRoom = (ev: MouseEvent) =>
|
||||||
|
emit('mouseMovedIn', ...mouseCoordsFromEvent(ev));
|
||||||
|
|
||||||
const roomPolygonCSS = computed(() => {
|
const roomPolygonCSS = computed(() => {
|
||||||
if (!props.room) return {};
|
if (!props.room) return {};
|
||||||
|
@ -12,15 +12,26 @@
|
|||||||
<div
|
<div
|
||||||
class="absolute -bottom-2 left-1/2 h-0 w-0 -translate-x-1/2 border-x-8 border-t-[16px] border-x-transparent border-t-white"
|
class="absolute -bottom-2 left-1/2 h-0 w-0 -translate-x-1/2 border-x-8 border-t-[16px] border-x-transparent border-t-white"
|
||||||
></div>
|
></div>
|
||||||
|
<button
|
||||||
|
class="absolute left-1/2 bottom-0.5 -translate-x-1/2 rounded-full bg-gray-50 px-1 py-1 ring-1 ring-black ring-opacity-5"
|
||||||
|
@click.stop="emit('startMoving')"
|
||||||
|
>
|
||||||
|
<ArrowsPointingOutIcon class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { StorageListItem } from '../../../interfaces/storage.interfaces';
|
import { ArrowsPointingOutIcon } from '@heroicons/vue/24/outline';
|
||||||
|
import { StorageSharedType } from '../../../interfaces/storage.interfaces';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
storage: StorageListItem;
|
storage: StorageSharedType;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'startMoving'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const getStoragePosition = (storage: any) => {
|
const getStoragePosition = (storage: any) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
})
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user