Files
EpicNext-Cms/src/lib/furni/auto-interaction.ts
T
openhands dee843106f
CI / check (push) Successful in 25s
CI / release (push) Skipped
CI / deploy (push) Successful in 59s
feat: add auto furniture interaction detection
- New auto-interaction.ts: detects canSit/canLay/canStand from classname/name keywords
- Maps keywords to interaction types (bench, chair, bed, table, etc.)
- Returns clickLimit and interactionModesCount for proper click/interaction limits
- Nitro editor: added auto-detect button with preview of detected settings
- furni-import.ts: auto-sets interaction_modes_count during import
- upload-import.ts: auto-detects for direct insert and SQL migrations
- clone-import.ts: auto-detects when cloning from other hotels
2026-08-18 22:33:40 +02:00

390 lines
6.5 KiB
TypeScript

/**
* Auto-detect furniture interaction capabilities from classname/name.
* Determines: canSit, canLay, canStand, interactionType, clickLimit
*/
export interface AutoInteractionResult {
canSit: boolean;
canLay: boolean;
canStand: boolean;
interactionType: string;
clickLimit: number;
interactionModesCount: number;
reason: string;
}
const SIT_KEYWORDS = [
"bench",
"bank",
"sofa",
"couch",
"chair",
"stool",
"seat",
"armchair",
"loveseat",
"settee",
"ottoman",
"pouf",
"beanbag",
"bean_bag",
"rocking",
"swing",
"hammock",
"throne",
"wheelchair",
"toilet",
"potty",
];
const LAY_KEYWORDS = [
"bed",
"mattress",
"futon",
"bunk",
"cot",
"hammock",
"sleeping",
"bedroll",
"blanket",
"pillow",
"cushion",
"lounger",
"sunbed",
"daybed",
"divan",
"waterbed",
];
const STAND_KEYWORDS = [
"table",
"desk",
"counter",
"bar",
"shelf",
"cabinet",
"wardrobe",
"dresser",
"bookcase",
"rack",
"stand",
"pedestal",
"platform",
"stage",
"floor",
"tile",
"carpet",
"rug",
"path",
"road",
"grass",
"dirt",
"sand",
"snow",
"ice",
"water",
"pool",
"fountain",
];
const INTERACTION_TYPE_MAP: Record<string, string> = {
bench: "bench",
bank: "bench",
sofa: "sofa",
couch: "sofa",
chair: "chair",
stool: "stool",
seat: "chair",
armchair: "armchair",
loveseat: "sofa",
settee: "sofa",
ottoman: "ottoman",
pouf: "ottoman",
beanbag: "beanbag",
rocking: "rocking_chair",
swing: "swing",
hammock: "hammock",
throne: "throne",
wheelchair: "wheelchair",
toilet: "toilet",
potty: "toilet",
bed: "bed",
mattress: "bed",
futon: "bed",
bunk: "bunkbed",
cot: "bed",
sleeping: "bed",
bedroll: "bed",
blanket: "bed",
pillow: "bed",
cushion: "bed",
lounger: "lounger",
sunbed: "sunbed",
daybed: "daybed",
divan: "divan",
waterbed: "waterbed",
table: "table",
desk: "desk",
counter: "counter",
bar: "bar",
shelf: "shelf",
cabinet: "cabinet",
wardrobe: "wardrobe",
dresser: "dresser",
bookcase: "bookcase",
rack: "rack",
stand: "stand",
pedestal: "pedestal",
platform: "platform",
stage: "stage",
};
const INTERACTION_MODES: Record<string, number> = {
bench: 1,
bank: 1,
sofa: 1,
couch: 1,
chair: 1,
stool: 1,
seat: 1,
armchair: 1,
loveseat: 1,
settee: 1,
ottoman: 1,
pouf: 1,
beanbag: 1,
rocking: 1,
swing: 1,
hammock: 1,
throne: 1,
wheelchair: 1,
toilet: 1,
potty: 1,
bed: 1,
mattress: 1,
futon: 1,
bunk: 1,
cot: 1,
sleeping: 1,
bedroll: 1,
blanket: 1,
pillow: 1,
cushion: 1,
lounger: 1,
sunbed: 1,
daybed: 1,
divan: 1,
waterbed: 1,
table: 0,
desk: 0,
counter: 0,
bar: 0,
shelf: 0,
cabinet: 0,
wardrobe: 0,
dresser: 0,
bookcase: 0,
rack: 0,
stand: 0,
pedestal: 0,
platform: 0,
stage: 0,
floor: 0,
tile: 0,
carpet: 0,
rug: 0,
path: 0,
road: 0,
grass: 0,
dirt: 0,
sand: 0,
snow: 0,
ice: 0,
water: 0,
pool: 0,
fountain: 0,
};
const CLICK_LIMITS: Record<string, number> = {
bench: 1,
bank: 1,
sofa: 1,
couch: 1,
chair: 1,
stool: 1,
seat: 1,
armchair: 1,
loveseat: 1,
settee: 1,
ottoman: 1,
pouf: 1,
beanbag: 1,
rocking: 1,
swing: 1,
hammock: 1,
throne: 1,
wheelchair: 1,
toilet: 1,
potty: 1,
bed: 1,
mattress: 1,
futon: 1,
bunk: 1,
cot: 1,
sleeping: 1,
bedroll: 1,
blanket: 1,
pillow: 1,
cushion: 1,
lounger: 1,
sunbed: 1,
daybed: 1,
divan: 1,
waterbed: 1,
table: 0,
desk: 0,
counter: 0,
bar: 0,
shelf: 0,
cabinet: 0,
wardrobe: 0,
dresser: 0,
bookcase: 0,
rack: 0,
stand: 0,
pedestal: 0,
platform: 0,
stage: 0,
floor: 0,
tile: 0,
carpet: 0,
rug: 0,
path: 0,
road: 0,
grass: 0,
dirt: 0,
sand: 0,
snow: 0,
ice: 0,
water: 0,
pool: 0,
fountain: 0,
};
function normalizeString(str: string): string {
return str.toLowerCase().replace(/[^a-z0-9_]/g, "_");
}
function findMatch(normalized: string, keywords: string[]): string | null {
for (const kw of keywords) {
if (normalized.includes(kw)) {
return kw;
}
}
return null;
}
export function autoDetectInteraction(
classname: string,
name?: string,
): AutoInteractionResult {
const normalizedClassname = normalizeString(classname);
const normalizedName = name ? normalizeString(name) : "";
const searchText = `${normalizedClassname} ${normalizedName}`;
const sitMatch = findMatch(searchText, SIT_KEYWORDS);
const layMatch = findMatch(searchText, LAY_KEYWORDS);
const standMatch = findMatch(searchText, STAND_KEYWORDS);
let canSit = false;
let canLay = false;
let canStand = false;
let matchedKeyword: string | null = null;
let interactionType = "default";
let clickLimit = 0;
let interactionModesCount = 0;
if (layMatch) {
canLay = true;
canSit = true;
matchedKeyword = layMatch;
interactionType = INTERACTION_TYPE_MAP[layMatch] || "bed";
clickLimit = CLICK_LIMITS[layMatch] ?? 1;
interactionModesCount = INTERACTION_MODES[layMatch] ?? 1;
} else if (sitMatch) {
canSit = true;
matchedKeyword = sitMatch;
interactionType = INTERACTION_TYPE_MAP[sitMatch] || "chair";
clickLimit = CLICK_LIMITS[sitMatch] ?? 1;
interactionModesCount = INTERACTION_MODES[sitMatch] ?? 1;
} else if (standMatch) {
canStand = true;
matchedKeyword = standMatch;
interactionType = INTERACTION_TYPE_MAP[standMatch] || "default";
clickLimit = CLICK_LIMITS[standMatch] ?? 0;
interactionModesCount = INTERACTION_MODES[standMatch] ?? 0;
}
const reasonParts: string[] = [];
if (matchedKeyword) {
reasonParts.push(`Matched "${matchedKeyword}"`);
}
if (canSit) reasonParts.push("can sit");
if (canLay) reasonParts.push("can lay");
if (canStand) reasonParts.push("can stand");
if (!matchedKeyword) {
reasonParts.push("No keywords matched - default");
}
return {
canSit,
canLay,
canStand,
interactionType,
clickLimit,
interactionModesCount,
reason: reasonParts.join(" · "),
};
}
export function getInteractionTypeLabel(type: string): string {
const labels: Record<string, string> = {
bench: "Bench / Bank",
sofa: "Sofa / Couch",
chair: "Chair",
stool: "Stool",
armchair: "Armchair",
ottoman: "Ottoman / Pouf",
beanbag: "Bean Bag",
rocking_chair: "Rocking Chair",
swing: "Swing",
hammock: "Hammock",
throne: "Throne",
wheelchair: "Wheelchair",
toilet: "Toilet",
bed: "Bed",
bunkbed: "Bunk Bed",
lounger: "Lounger / Sunbed",
daybed: "Daybed",
divan: "Divan",
waterbed: "Waterbed",
table: "Table",
desk: "Desk",
counter: "Counter",
bar: "Bar",
shelf: "Shelf",
cabinet: "Cabinet",
wardrobe: "Wardrobe",
dresser: "Dresser",
bookcase: "Bookcase",
rack: "Rack",
stand: "Stand",
pedestal: "Pedestal",
platform: "Platform",
stage: "Stage",
default: "Default (no interaction)",
};
return labels[type] || type;
}