Fixed multiple floor plan transitions between same source and target.
This commit is contained in:
parent
ee504d878f
commit
0944874510
@ -219,20 +219,66 @@ public sealed class FloorPlansController(IFloorPlanService floorPlans) : Control
|
|||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> AddBlock(int floorPlanId, FloorPlanBlockEditViewModel model)
|
public async Task<IActionResult> AddBlock(int floorPlanId, FloorPlanBlockEditViewModel model)
|
||||||
{
|
{
|
||||||
|
var isAsyncSave = string.Equals(Request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.OrdinalIgnoreCase);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
var floorPlanBlockId = await floorPlans.SaveBlockAsync(model);
|
var floorPlanBlockId = await floorPlans.SaveBlockAsync(model);
|
||||||
|
if (isAsyncSave)
|
||||||
|
{
|
||||||
|
var editor = await floorPlans.GetEditorAsync(floorPlanId);
|
||||||
|
var block = editor?.Blocks.FirstOrDefault(x => x.FloorPlanBlockID == floorPlanBlockId);
|
||||||
|
if (block is null)
|
||||||
|
{
|
||||||
|
Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||||
|
return Json(new { ok = false, message = "Block was saved but could not be loaded." });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
message = "Saved",
|
||||||
|
block = new
|
||||||
|
{
|
||||||
|
block.FloorPlanBlockID,
|
||||||
|
block.FloorPlanFloorID,
|
||||||
|
block.LocationID,
|
||||||
|
block.LocationName,
|
||||||
|
block.LocationPath,
|
||||||
|
block.DisplayLabel,
|
||||||
|
block.X,
|
||||||
|
block.Y,
|
||||||
|
block.WidthCells,
|
||||||
|
block.HeightCells,
|
||||||
|
block.BlockType,
|
||||||
|
block.LabelOverride,
|
||||||
|
block.Notes
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
TempData["FloorPlanMessage"] = "Block added.";
|
TempData["FloorPlanMessage"] = "Block added.";
|
||||||
return RedirectToAction(nameof(Edit), new { id = floorPlanId, selectedBlockId = floorPlanBlockId });
|
return RedirectToAction(nameof(Edit), new { id = floorPlanId, selectedBlockId = floorPlanBlockId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
|
if (isAsyncSave)
|
||||||
|
{
|
||||||
|
Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||||
|
return Json(new { ok = false, message = ex.Message });
|
||||||
|
}
|
||||||
|
|
||||||
TempData["FloorPlanError"] = ex.Message;
|
TempData["FloorPlanError"] = ex.Message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAsyncSave)
|
||||||
|
{
|
||||||
|
Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||||
|
return Json(new { ok = false, message = "Block could not be added." });
|
||||||
|
}
|
||||||
|
|
||||||
return RedirectToAction(nameof(Edit), new { id = floorPlanId });
|
return RedirectToAction(nameof(Edit), new { id = floorPlanId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -830,7 +830,7 @@ else
|
|||||||
const floorEditors = [...editor.querySelectorAll("[data-floor-editor]")];
|
const floorEditors = [...editor.querySelectorAll("[data-floor-editor]")];
|
||||||
const backgroundEditors = [...editor.querySelectorAll("[data-background-editor]")];
|
const backgroundEditors = [...editor.querySelectorAll("[data-background-editor]")];
|
||||||
const transitionEditors = [...editor.querySelectorAll("[data-transition-editor]")];
|
const transitionEditors = [...editor.querySelectorAll("[data-transition-editor]")];
|
||||||
const blocks = [...editor.querySelectorAll("[data-block]")];
|
let blocks = [...editor.querySelectorAll("[data-block]")];
|
||||||
const noSelection = editor.querySelector("[data-no-selection]");
|
const noSelection = editor.querySelector("[data-no-selection]");
|
||||||
const warning = editor.querySelector("[data-overlap-warning]");
|
const warning = editor.querySelector("[data-overlap-warning]");
|
||||||
const addBlockFloor = document.querySelector("[data-add-block-floor-id]");
|
const addBlockFloor = document.querySelector("[data-add-block-floor-id]");
|
||||||
@ -843,6 +843,7 @@ else
|
|||||||
const sizePreset = document.querySelector("[data-size-preset]");
|
const sizePreset = document.querySelector("[data-size-preset]");
|
||||||
const addBlockWidth = document.querySelector("[data-add-block-width]");
|
const addBlockWidth = document.querySelector("[data-add-block-width]");
|
||||||
const addBlockHeight = document.querySelector("[data-add-block-height]");
|
const addBlockHeight = document.querySelector("[data-add-block-height]");
|
||||||
|
const addBlockForm = document.querySelector("#floor-plan-add-block-form");
|
||||||
const toolboxTabs = [...editor.querySelectorAll("[data-toolbox-tab]")];
|
const toolboxTabs = [...editor.querySelectorAll("[data-toolbox-tab]")];
|
||||||
const toolboxPanels = [...editor.querySelectorAll("[data-toolbox-panel]")];
|
const toolboxPanels = [...editor.querySelectorAll("[data-toolbox-panel]")];
|
||||||
const saveStatus = editor.querySelector("[data-save-status]");
|
const saveStatus = editor.querySelector("[data-save-status]");
|
||||||
@ -958,6 +959,25 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateFloorGrid(floorId) {
|
||||||
|
const grid = editor.querySelector(`[data-floor-grid="${floorId}"]`);
|
||||||
|
const widthInput = editor.querySelector(`[data-floor-width="${floorId}"]`);
|
||||||
|
const heightInput = editor.querySelector(`[data-floor-height="${floorId}"]`);
|
||||||
|
if (!grid || !widthInput || !heightInput) return;
|
||||||
|
|
||||||
|
const width = Math.max(8, Math.min(Number(widthInput.value || 24), 80));
|
||||||
|
const height = Math.max(8, Math.min(Number(heightInput.value || 16), 80));
|
||||||
|
widthInput.value = width;
|
||||||
|
heightInput.value = height;
|
||||||
|
grid.dataset.gridWidth = width;
|
||||||
|
grid.dataset.gridHeight = height;
|
||||||
|
grid.style.setProperty("--grid-width", width);
|
||||||
|
grid.style.setProperty("--grid-height", height);
|
||||||
|
|
||||||
|
grid.querySelectorAll("[data-block]").forEach(block => writeBlock(block, readBlock(block)));
|
||||||
|
checkOverlaps();
|
||||||
|
}
|
||||||
|
|
||||||
function setActiveFloor(floorId) {
|
function setActiveFloor(floorId) {
|
||||||
tabs.forEach(tab => tab.classList.toggle("active", tab.dataset.floorTab === floorId));
|
tabs.forEach(tab => tab.classList.toggle("active", tab.dataset.floorTab === floorId));
|
||||||
panels.forEach(panel => panel.classList.toggle("active", panel.dataset.floorPanel === floorId));
|
panels.forEach(panel => panel.classList.toggle("active", panel.dataset.floorPanel === floorId));
|
||||||
@ -1069,14 +1089,239 @@ else
|
|||||||
newLocationPanel?.classList.toggle("d-none", !isCreate);
|
newLocationPanel?.classList.toggle("d-none", !isCreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectBlock(block) {
|
function attachBlockEvents(block) {
|
||||||
|
block.addEventListener("pointerdown", event => {
|
||||||
|
selectBlock(block);
|
||||||
|
const grid = gridFor(block);
|
||||||
|
if (!grid) return;
|
||||||
|
const start = cellFromPointer(grid, event);
|
||||||
|
const current = readBlock(block);
|
||||||
|
const resizing = event.target.matches("[data-resize-handle]");
|
||||||
|
pointerState = { block, grid, start, current, resizing };
|
||||||
|
block.setPointerCapture(event.pointerId);
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendHidden(panel, name, value, field) {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "hidden";
|
||||||
|
input.name = name;
|
||||||
|
input.value = value ?? "";
|
||||||
|
if (field) input.dataset.blockField = field;
|
||||||
|
panel.append(input);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBlockEditor(block) {
|
||||||
|
const selectedPanel = editor.querySelector("#floor-plan-tool-selected");
|
||||||
|
if (!selectedPanel) return null;
|
||||||
|
|
||||||
|
const index = editor.querySelectorAll("[data-block-editor]").length;
|
||||||
|
const panel = document.createElement("div");
|
||||||
|
panel.className = "floor-plan-block-editor d-none";
|
||||||
|
panel.dataset.blockEditor = block.floorPlanBlockID;
|
||||||
|
|
||||||
|
appendHidden(panel, `Blocks[${index}].FloorPlanBlockID`, block.floorPlanBlockID);
|
||||||
|
appendHidden(panel, `Blocks[${index}].FloorPlanFloorID`, block.floorPlanFloorID, "floor");
|
||||||
|
appendHidden(panel, `Blocks[${index}].LocationName`, block.locationName || "");
|
||||||
|
appendHidden(panel, `Blocks[${index}].LocationPath`, block.locationPath || "");
|
||||||
|
|
||||||
|
const locationTemplate = editor.querySelector("[data-block-field='location']");
|
||||||
|
const typeTemplate = editor.querySelector("[data-block-field='type']");
|
||||||
|
|
||||||
|
const locationWrap = document.createElement("div");
|
||||||
|
locationWrap.className = "mb-2";
|
||||||
|
locationWrap.innerHTML = `<label class="form-label">Location</label>`;
|
||||||
|
const locationSelect = locationTemplate?.cloneNode(true) || document.createElement("select");
|
||||||
|
locationSelect.name = `Blocks[${index}].LocationID`;
|
||||||
|
locationSelect.dataset.blockField = "location";
|
||||||
|
locationSelect.value = block.locationID || "";
|
||||||
|
locationSelect.addEventListener("change", () => {
|
||||||
|
if (!selectedBlock) return;
|
||||||
|
selectedBlock.dataset.locationId = locationSelect.value || "";
|
||||||
|
checkOverlaps();
|
||||||
|
markDirty();
|
||||||
|
});
|
||||||
|
locationWrap.append(locationSelect);
|
||||||
|
panel.append(locationWrap);
|
||||||
|
|
||||||
|
const typeWrap = document.createElement("div");
|
||||||
|
typeWrap.className = "mb-2";
|
||||||
|
typeWrap.innerHTML = `<label class="form-label">Type</label>`;
|
||||||
|
const typeSelect = typeTemplate?.cloneNode(true) || document.createElement("select");
|
||||||
|
typeSelect.name = `Blocks[${index}].BlockType`;
|
||||||
|
typeSelect.dataset.blockField = "type";
|
||||||
|
typeSelect.value = block.blockType || "Room";
|
||||||
|
typeSelect.addEventListener("change", () => {
|
||||||
|
if (!selectedBlock) return;
|
||||||
|
selectedBlock.className = selectedBlock.className
|
||||||
|
.split(" ")
|
||||||
|
.filter(name => !name.startsWith("floor-plan-block--"))
|
||||||
|
.concat(`floor-plan-block--${typeSelect.value.toLowerCase()}`)
|
||||||
|
.join(" ");
|
||||||
|
markDirty();
|
||||||
|
});
|
||||||
|
typeWrap.append(typeSelect);
|
||||||
|
panel.append(typeWrap);
|
||||||
|
|
||||||
|
const gridFields = document.createElement("div");
|
||||||
|
gridFields.className = "row g-2";
|
||||||
|
[
|
||||||
|
["X", "x", block.x, "X"],
|
||||||
|
["Y", "y", block.y, "Y"],
|
||||||
|
["Width", "w", block.widthCells, "WidthCells"],
|
||||||
|
["Height", "h", block.heightCells, "HeightCells"]
|
||||||
|
].forEach(([label, field, value, property]) => {
|
||||||
|
const wrap = document.createElement("div");
|
||||||
|
wrap.className = "col-6";
|
||||||
|
wrap.innerHTML = `<label class="form-label">${label}</label>`;
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.className = "form-control form-control-sm";
|
||||||
|
input.type = "number";
|
||||||
|
input.min = field === "x" || field === "y" ? "0" : "1";
|
||||||
|
input.max = field === "x" || field === "y" ? "79" : "80";
|
||||||
|
input.name = `Blocks[${index}].${property}`;
|
||||||
|
input.value = value;
|
||||||
|
input.dataset.blockField = field;
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
if (!selectedBlock) return;
|
||||||
|
writeBlock(selectedBlock, readBlock(selectedBlock));
|
||||||
|
markDirty();
|
||||||
|
});
|
||||||
|
wrap.append(input);
|
||||||
|
gridFields.append(wrap);
|
||||||
|
});
|
||||||
|
panel.append(gridFields);
|
||||||
|
|
||||||
|
const labelWrap = document.createElement("div");
|
||||||
|
labelWrap.className = "mt-2";
|
||||||
|
labelWrap.innerHTML = `<label class="form-label">Label override</label>`;
|
||||||
|
const labelInput = document.createElement("input");
|
||||||
|
labelInput.className = "form-control form-control-sm";
|
||||||
|
labelInput.name = `Blocks[${index}].LabelOverride`;
|
||||||
|
labelInput.value = block.labelOverride || "";
|
||||||
|
labelInput.addEventListener("input", markDirty);
|
||||||
|
labelWrap.append(labelInput);
|
||||||
|
panel.append(labelWrap);
|
||||||
|
|
||||||
|
const notesWrap = document.createElement("div");
|
||||||
|
notesWrap.className = "mt-2";
|
||||||
|
notesWrap.innerHTML = `<label class="form-label">Notes</label>`;
|
||||||
|
const notesInput = document.createElement("textarea");
|
||||||
|
notesInput.className = "form-control form-control-sm";
|
||||||
|
notesInput.name = `Blocks[${index}].Notes`;
|
||||||
|
notesInput.rows = 3;
|
||||||
|
notesInput.value = block.notes || "";
|
||||||
|
notesInput.addEventListener("input", markDirty);
|
||||||
|
notesWrap.append(notesInput);
|
||||||
|
panel.append(notesWrap);
|
||||||
|
|
||||||
|
selectedPanel.append(panel);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBlockToGrid(block) {
|
||||||
|
const grid = editor.querySelector(`[data-floor-grid="${block.floorPlanFloorID}"]`);
|
||||||
|
if (!grid) return null;
|
||||||
|
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = `floor-plan-block floor-plan-block--${(block.blockType || "Room").toLowerCase()}`;
|
||||||
|
button.style.setProperty("--x", block.x);
|
||||||
|
button.style.setProperty("--y", block.y);
|
||||||
|
button.style.setProperty("--w", block.widthCells);
|
||||||
|
button.style.setProperty("--h", block.heightCells);
|
||||||
|
button.dataset.block = block.floorPlanBlockID;
|
||||||
|
button.dataset.blockIndex = blocks.length;
|
||||||
|
button.dataset.floorId = block.floorPlanFloorID;
|
||||||
|
button.dataset.locationId = block.locationID || "";
|
||||||
|
button.title = block.locationPath || "";
|
||||||
|
button.innerHTML = `<span class="floor-plan-block-label"></span><span class="floor-plan-resize-handle" data-resize-handle aria-hidden="true"></span>`;
|
||||||
|
button.querySelector(".floor-plan-block-label").textContent = block.displayLabel || block.locationName || "New block";
|
||||||
|
grid.append(button);
|
||||||
|
|
||||||
|
blocks.push(button);
|
||||||
|
attachBlockEvents(button);
|
||||||
|
createBlockEditor(block);
|
||||||
|
checkOverlaps();
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addBlockAsync(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!addBlockForm) return;
|
||||||
|
if (document.activeElement instanceof HTMLElement) {
|
||||||
|
document.activeElement.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridWrap = editor.querySelector(".floor-plan-floor.active .floor-plan-grid-wrap");
|
||||||
|
const toolboxBody = editor.querySelector(".floor-plan-toolbox-body");
|
||||||
|
const scrollState = {
|
||||||
|
pageX: window.scrollX,
|
||||||
|
pageY: window.scrollY,
|
||||||
|
gridLeft: gridWrap?.scrollLeft || 0,
|
||||||
|
gridTop: gridWrap?.scrollTop || 0,
|
||||||
|
toolboxTop: toolboxBody?.scrollTop || 0
|
||||||
|
};
|
||||||
|
const restoreScroll = () => {
|
||||||
|
if (gridWrap) {
|
||||||
|
gridWrap.scrollLeft = scrollState.gridLeft;
|
||||||
|
gridWrap.scrollTop = scrollState.gridTop;
|
||||||
|
}
|
||||||
|
if (toolboxBody) {
|
||||||
|
toolboxBody.scrollTop = scrollState.toolboxTop;
|
||||||
|
}
|
||||||
|
window.scrollTo(scrollState.pageX, scrollState.pageY);
|
||||||
|
document.documentElement.scrollTop = scrollState.pageY;
|
||||||
|
document.body.scrollTop = scrollState.pageY;
|
||||||
|
};
|
||||||
|
setSaveStatus("saving");
|
||||||
|
try {
|
||||||
|
const response = await fetch(addBlockForm.action, {
|
||||||
|
method: "POST",
|
||||||
|
body: new FormData(addBlockForm),
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"X-Requested-With": "XMLHttpRequest"
|
||||||
|
},
|
||||||
|
credentials: "same-origin"
|
||||||
|
});
|
||||||
|
const result = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok || result.ok === false || !result.block) {
|
||||||
|
throw new Error(result.message || "Unable to add block.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const block = addBlockToGrid(result.block);
|
||||||
|
if (block) {
|
||||||
|
selectBlock(block, { showEditor: false });
|
||||||
|
}
|
||||||
|
restoreScroll();
|
||||||
|
window.requestAnimationFrame(restoreScroll);
|
||||||
|
window.setTimeout(restoreScroll, 50);
|
||||||
|
window.setTimeout(restoreScroll, 250);
|
||||||
|
const activeFloorId = panels.find(panel => panel.classList.contains("active"))?.dataset.floorPanel;
|
||||||
|
addBlockForm.reset();
|
||||||
|
if (addBlockFloor && activeFloorId) addBlockFloor.value = activeFloorId;
|
||||||
|
updateAddBlockMode();
|
||||||
|
updateSizePresets();
|
||||||
|
setSaveStatus("saved");
|
||||||
|
} catch (error) {
|
||||||
|
setSaveStatus("failed", error.message || "Save failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectBlock(block, options = {}) {
|
||||||
|
const showEditor = options.showEditor !== false;
|
||||||
selectedBlock = block;
|
selectedBlock = block;
|
||||||
blocks.forEach(item => item.classList.toggle("selected", item === block));
|
blocks.forEach(item => item.classList.toggle("selected", item === block));
|
||||||
editor.querySelectorAll("[data-block-editor]").forEach(panel => panel.classList.add("d-none"));
|
editor.querySelectorAll("[data-block-editor]").forEach(panel => panel.classList.add("d-none"));
|
||||||
const panel = editor.querySelector(`[data-block-editor="${block.dataset.block}"]`);
|
const panel = editor.querySelector(`[data-block-editor="${block.dataset.block}"]`);
|
||||||
if (panel) panel.classList.remove("d-none");
|
if (panel) panel.classList.remove("d-none");
|
||||||
if (noSelection) noSelection.classList.add("d-none");
|
if (noSelection) noSelection.classList.add("d-none");
|
||||||
setToolboxTab("selected");
|
if (showEditor) {
|
||||||
|
setToolboxTab("selected");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldsFor(block) {
|
function fieldsFor(block) {
|
||||||
@ -1171,6 +1416,9 @@ else
|
|||||||
if (length > 0 && (!best || length > best.length)) {
|
if (length > 0 && (!best || length > best.length)) {
|
||||||
best = {
|
best = {
|
||||||
length,
|
length,
|
||||||
|
orientation: "vertical",
|
||||||
|
start,
|
||||||
|
end,
|
||||||
x: from.x + from.w === to.x ? to.x : from.x,
|
x: from.x + from.w === to.x ? to.x : from.x,
|
||||||
y: start + length / 2
|
y: start + length / 2
|
||||||
};
|
};
|
||||||
@ -1184,6 +1432,9 @@ else
|
|||||||
if (length > 0 && (!best || length > best.length)) {
|
if (length > 0 && (!best || length > best.length)) {
|
||||||
best = {
|
best = {
|
||||||
length,
|
length,
|
||||||
|
orientation: "horizontal",
|
||||||
|
start,
|
||||||
|
end,
|
||||||
x: start + length / 2,
|
x: start + length / 2,
|
||||||
y: from.y + from.h === to.y ? to.y : from.y
|
y: from.y + from.h === to.y ? to.y : from.y
|
||||||
};
|
};
|
||||||
@ -1195,6 +1446,7 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
function positionTransitions(activePanel, visibleBlocks) {
|
function positionTransitions(activePanel, visibleBlocks) {
|
||||||
|
const markerPositions = [];
|
||||||
activePanel.querySelectorAll("[data-transition-marker]").forEach(marker => {
|
activePanel.querySelectorAll("[data-transition-marker]").forEach(marker => {
|
||||||
const fromBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.fromLocationId);
|
const fromBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.fromLocationId);
|
||||||
const toBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.toLocationId);
|
const toBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.toLocationId);
|
||||||
@ -1208,8 +1460,38 @@ else
|
|||||||
const toCenter = locationCenter(toBlocks);
|
const toCenter = locationCenter(toBlocks);
|
||||||
marker.hidden = false;
|
marker.hidden = false;
|
||||||
marker.classList.toggle("is-shared-edge", !!sharedEdge);
|
marker.classList.toggle("is-shared-edge", !!sharedEdge);
|
||||||
marker.style.setProperty("--transition-x", sharedEdge ? sharedEdge.x : (fromCenter.x + toCenter.x) / 2);
|
const pairKey = [marker.dataset.fromLocationId, marker.dataset.toLocationId].sort().join("-");
|
||||||
marker.style.setProperty("--transition-y", sharedEdge ? sharedEdge.y : (fromCenter.y + toCenter.y) / 2);
|
const key = sharedEdge
|
||||||
|
? `${pairKey}:${sharedEdge.orientation}:${sharedEdge.x}:${sharedEdge.y}:${sharedEdge.start}:${sharedEdge.end}`
|
||||||
|
: `${pairKey}:fallback`;
|
||||||
|
markerPositions.push({ marker, sharedEdge, fromCenter, toCenter, key });
|
||||||
|
});
|
||||||
|
|
||||||
|
const groups = markerPositions.reduce((map, item) => {
|
||||||
|
if (!map.has(item.key)) map.set(item.key, []);
|
||||||
|
map.get(item.key).push(item);
|
||||||
|
return map;
|
||||||
|
}, new Map());
|
||||||
|
|
||||||
|
groups.forEach(group => {
|
||||||
|
group.forEach((item, index) => {
|
||||||
|
const fraction = (index + 1) / (group.length + 1);
|
||||||
|
let x;
|
||||||
|
let y;
|
||||||
|
if (item.sharedEdge?.orientation === "vertical") {
|
||||||
|
x = item.sharedEdge.x;
|
||||||
|
y = item.sharedEdge.start + item.sharedEdge.length * fraction;
|
||||||
|
} else if (item.sharedEdge?.orientation === "horizontal") {
|
||||||
|
x = item.sharedEdge.start + item.sharedEdge.length * fraction;
|
||||||
|
y = item.sharedEdge.y;
|
||||||
|
} else {
|
||||||
|
x = item.fromCenter.x + (item.toCenter.x - item.fromCenter.x) * fraction;
|
||||||
|
y = item.fromCenter.y + (item.toCenter.y - item.fromCenter.y) * fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.marker.style.setProperty("--transition-x", x);
|
||||||
|
item.marker.style.setProperty("--transition-y", y);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1266,25 +1548,14 @@ else
|
|||||||
|
|
||||||
tabs.forEach(tab => tab.addEventListener("click", () => setActiveFloor(tab.dataset.floorTab)));
|
tabs.forEach(tab => tab.addEventListener("click", () => setActiveFloor(tab.dataset.floorTab)));
|
||||||
toolboxTabs.forEach(tab => tab.addEventListener("click", () => setToolboxTab(tab.dataset.toolboxTab)));
|
toolboxTabs.forEach(tab => tab.addEventListener("click", () => setToolboxTab(tab.dataset.toolboxTab)));
|
||||||
|
addBlockForm?.addEventListener("submit", addBlockAsync);
|
||||||
|
|
||||||
editor.addEventListener("submit", event => {
|
editor.addEventListener("submit", event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
saveLayoutAsync();
|
saveLayoutAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
blocks.forEach(block => {
|
blocks.forEach(attachBlockEvents);
|
||||||
block.addEventListener("pointerdown", event => {
|
|
||||||
selectBlock(block);
|
|
||||||
const grid = gridFor(block);
|
|
||||||
if (!grid) return;
|
|
||||||
const start = cellFromPointer(grid, event);
|
|
||||||
const current = readBlock(block);
|
|
||||||
const resizing = event.target.matches("[data-resize-handle]");
|
|
||||||
pointerState = { block, grid, start, current, resizing };
|
|
||||||
block.setPointerCapture(event.pointerId);
|
|
||||||
event.preventDefault();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.addEventListener("pointermove", event => {
|
editor.addEventListener("pointermove", event => {
|
||||||
if (!pointerState) return;
|
if (!pointerState) return;
|
||||||
@ -1344,10 +1615,24 @@ else
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
editor.querySelectorAll("[data-floor-width],[data-floor-height]").forEach(input => {
|
||||||
|
input.addEventListener("input", () => {
|
||||||
|
const floorId = input.dataset.floorWidth || input.dataset.floorHeight;
|
||||||
|
updateFloorGrid(floorId);
|
||||||
|
markDirty();
|
||||||
|
});
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
const floorId = input.dataset.floorWidth || input.dataset.floorHeight;
|
||||||
|
updateFloorGrid(floorId);
|
||||||
|
markDirty();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
editor.querySelectorAll("input, select, textarea").forEach(input => {
|
editor.querySelectorAll("input, select, textarea").forEach(input => {
|
||||||
if (input.matches("[form='floor-plan-add-block-form'], [form='floor-plan-add-floor-form']")) return;
|
if (input.matches("[form='floor-plan-add-block-form'], [form='floor-plan-add-floor-form']")) return;
|
||||||
if (input.matches("[form^='floor-plan-background-'], [form^='floor-plan-transition-add-']")) return;
|
if (input.matches("[form^='floor-plan-background-'], [form^='floor-plan-transition-add-']")) return;
|
||||||
if (input.matches("[data-background-field]")) return;
|
if (input.matches("[data-background-field]")) return;
|
||||||
|
if (input.matches("[data-floor-width],[data-floor-height]")) return;
|
||||||
if (input.matches("[data-block-field='x'],[data-block-field='y'],[data-block-field='w'],[data-block-field='h'],[data-block-field='type'],[data-block-field='location']")) return;
|
if (input.matches("[data-block-field='x'],[data-block-field='y'],[data-block-field='w'],[data-block-field='h'],[data-block-field='type'],[data-block-field='location']")) return;
|
||||||
const eventName = input.tagName === "TEXTAREA" || input.type === "text" ? "input" : "change";
|
const eventName = input.tagName === "TEXTAREA" || input.type === "text" ? "input" : "change";
|
||||||
input.addEventListener(eventName, markDirty);
|
input.addEventListener(eventName, markDirty);
|
||||||
|
|||||||
@ -4663,19 +4663,19 @@ body.dragging-location [data-drag-type="location"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.floor-plan-block.merge-east {
|
.floor-plan-block.merge-east {
|
||||||
border-right-color: transparent;
|
border-right-color: rgba(44, 84, 75, .28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.floor-plan-block.merge-west {
|
.floor-plan-block.merge-west {
|
||||||
border-left-color: transparent;
|
border-left-color: rgba(44, 84, 75, .28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.floor-plan-block.merge-north {
|
.floor-plan-block.merge-north {
|
||||||
border-top-color: transparent;
|
border-top-color: rgba(44, 84, 75, .28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.floor-plan-block.merge-south {
|
.floor-plan-block.merge-south {
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: rgba(44, 84, 75, .28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.floor-plan-block.selected.merge-east,
|
.floor-plan-block.selected.merge-east,
|
||||||
|
|||||||
2
PlotLine/wwwroot/css/site.min.css
vendored
2
PlotLine/wwwroot/css/site.min.css
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user