css aufgeräumt

This commit is contained in:
2026-02-12 08:35:53 +01:00
parent fb4ee93b17
commit b469a7ab33
5 changed files with 507 additions and 265 deletions

View File

@@ -0,0 +1,301 @@
.device-type-edit {
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
.device-type-edit .edit-form {
background: white;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.device-type-edit .edit-form fieldset {
margin: 20px 0;
padding: 15px;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
.device-type-edit .edit-form legend {
padding: 0 10px;
font-weight: bold;
font-size: 1.1em;
}
.device-type-edit .form-group {
margin: 15px 0;
}
.device-type-edit .form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.device-type-edit .form-group input[type="text"],
.device-type-edit .form-group input[type="file"],
.device-type-edit .form-group select,
.device-type-edit .form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: inherit;
}
.device-type-edit .form-group textarea {
resize: vertical;
}
.device-type-edit .form-group small {
display: block;
margin-top: 5px;
color: #666;
}
.device-type-edit .required {
color: red;
}
.device-type-edit .form-file-preview {
margin-top: 10px;
}
.device-type-edit .device-type-current-image {
max-width: 300px;
border: 1px solid #ddd;
padding: 10px;
display: block;
}
.device-type-edit .port-definition-table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
.device-type-edit .port-definition-table th,
.device-type-edit .port-definition-table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
vertical-align: middle;
}
.device-type-edit .port-definition-table th {
background: #f5f5f5;
}
.device-type-edit .port-definition-table input[type="text"],
.device-type-edit .port-definition-table select {
width: 100%;
padding: 6px 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: inherit;
}
.device-type-edit .form-actions {
display: flex;
gap: 10px;
margin-top: 30px;
}
.device-type-edit .shape-editor {
display: grid;
grid-template-columns: 180px 1fr 320px;
gap: 16px;
margin-top: 16px;
}
.device-type-edit .shape-meta-controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.device-type-edit .shape-meta-column {
display: flex;
flex-direction: column;
gap: 4px;
}
.device-type-edit .shape-meta-column label {
font-weight: 600;
font-size: 0.9em;
}
.device-type-edit .shape-meta-column input,
.device-type-edit .shape-meta-column select {
padding: 6px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: inherit;
font-size: 0.95em;
}
.device-type-edit .port-actions {
margin-top: 15px;
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.device-type-edit .shape-editor-canvas {
border: 1px solid #ddd;
border-radius: 6px;
background: white;
padding: 12px;
}
.device-type-edit .shape-editor-canvas svg {
width: 100%;
min-height: 320px;
display: block;
font-family: inherit;
cursor: crosshair;
}
.device-type-edit .shape-toolbox,
.device-type-edit .shape-overlay {
border: 1px solid #ddd;
border-radius: 6px;
padding: 12px;
background: #fff;
}
.device-type-edit .shape-toolbox h4,
.device-type-edit .shape-overlay h4 {
margin: 0 0 8px;
}
.device-type-edit .shape-tool-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.device-type-edit .shape-tool {
text-align: left;
border: 1px solid #bbb;
background: #f7f7f7;
color: #222;
padding: 10px;
border-radius: 4px;
cursor: grab;
}
.device-type-edit .shape-tool:active {
cursor: grabbing;
}
.device-type-edit .shape-tool.is-active {
background: #007bff;
color: #fff;
border-color: #0056b3;
}
.device-type-edit .shape-editor-canvas.drag-over {
outline: 2px dashed #007bff;
outline-offset: 2px;
}
.device-type-edit .shape-object {
cursor: move;
}
.device-type-edit #shape-canvas.shape-tool-active {
cursor: crosshair;
}
.device-type-edit .shape-object.is-selected {
filter: drop-shadow(0 0 5px rgba(0, 123, 255, 0.7));
}
.device-type-edit .shape-object.is-port {
stroke-dasharray: 4 2;
}
.device-type-edit .shape-overlay-form .shape-control-grid {
display: grid;
grid-template-columns: repeat(2, minmax(120px, 1fr));
gap: 8px;
}
.device-type-edit .shape-overlay-form label {
font-size: 0.82rem;
display: flex;
flex-direction: column;
gap: 4px;
}
.device-type-edit .shape-overlay-form input[type="number"],
.device-type-edit .shape-overlay-form input[type="text"],
.device-type-edit .shape-overlay-form input[type="color"] {
width: 100%;
padding: 6px 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: inherit;
}
.device-type-edit .shape-port-settings {
margin-top: 10px;
}
.device-type-edit .inline-checkbox {
flex-direction: row !important;
align-items: center;
gap: 6px !important;
}
.device-type-edit .shape-overlay-actions {
margin-top: 12px;
display: flex;
justify-content: flex-end;
}
.device-type-edit .shape-overlay-empty {
margin: 6px 0 0;
color: #666;
font-size: 0.9rem;
}
.device-type-edit .hint {
font-size: 0.8rem;
color: #666;
margin: 8px 0 0;
}
.device-type-edit .button {
padding: 10px 15px;
background: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 0.95em;
}
.device-type-edit .button-primary {
background: #28a745;
}
.device-type-edit .button-danger {
background: #dc3545;
}
.device-type-edit .button:hover {
opacity: 0.8;
}
@media (max-width: 1100px) {
.device-type-edit .shape-editor {
grid-template-columns: 1fr;
}
}

View File

@@ -1,6 +1,12 @@
(() => {
const SVG_NS = 'http://www.w3.org/2000/svg';
const MIN_DRAW_SIZE = 4;
const MIN_CANVAS_WIDTH = 200;
const MIN_CANVAS_HEIGHT = 120;
const FORM_FACTOR_PRESETS = {
'19': { width: 760, height: 80 },
'10': { width: 420, height: 80 }
};
function initEditor() {
const editor = document.getElementById('device-type-shape-editor');
@@ -31,6 +37,13 @@
deleteButton: document.getElementById('shape-delete')
};
const metaInputs = {
formFactor: document.getElementById('shape-meta-form-factor'),
rackHeight: document.getElementById('shape-meta-rack-height'),
canvasWidth: document.getElementById('shape-meta-canvas-width'),
canvasHeight: document.getElementById('shape-meta-canvas-height')
};
const fieldVisibility = {
width: editor.querySelector('[data-field="width"]'),
height: editor.querySelector('[data-field="height"]'),
@@ -53,11 +66,20 @@
offsetY: 0
};
let selectedShapeId = null;
let shapes = normalizeShapeList(readJson(hiddenInput.value));
let shapes = [];
let meta = getDefaultMeta();
const definition = readDefinition(hiddenInput.value);
shapes = normalizeShapeList(definition.shapes);
meta = definition.meta;
bindToolbarEvents(editor);
bindCanvasPointerEvents(svg);
bindOverlayEvents(overlay);
bindMetaEvents();
applyFormFactorPreset();
applyMetaToInputs();
persist();
render();
function bindToolbarEvents(root) {
@@ -218,6 +240,63 @@
});
}
function bindMetaEvents() {
Object.values(metaInputs).forEach((input) => {
if (!input) {
return;
}
const eventName = input.tagName === 'SELECT' ? 'change' : 'input';
input.addEventListener(eventName, handleMetaInputChange);
});
}
function handleMetaInputChange(event) {
const targetId = event?.target?.id;
applyMetaFromInputs();
applyFormFactorPreset(targetId);
applyMetaToInputs();
persist();
render();
}
function applyMetaFromInputs() {
if (!metaInputs.formFactor) {
return;
}
meta.formFactor = normalizeFormFactor(metaInputs.formFactor.value);
meta.heightHe = Math.max(1, toNumberOrDefault(metaInputs.rackHeight.value, meta.heightHe));
meta.canvasWidth = Math.max(MIN_CANVAS_WIDTH, toNumberOrDefault(metaInputs.canvasWidth.value, meta.canvasWidth));
meta.canvasHeight = Math.max(MIN_CANVAS_HEIGHT, toNumberOrDefault(metaInputs.canvasHeight.value, meta.canvasHeight));
}
function applyMetaToInputs() {
if (!metaInputs.formFactor) {
return;
}
metaInputs.formFactor.value = meta.formFactor;
metaInputs.rackHeight.value = meta.heightHe;
metaInputs.canvasWidth.value = meta.canvasWidth;
metaInputs.canvasHeight.value = meta.canvasHeight;
}
function applyFormFactorPreset(triggerId) {
if (!['shape-meta-form-factor', 'shape-meta-rack-height'].includes(triggerId) && triggerId !== undefined) {
return;
}
if (meta.heightHe !== 1) {
return;
}
const preset = FORM_FACTOR_PRESETS[meta.formFactor];
if (!preset) {
return;
}
meta.canvasWidth = Math.max(MIN_CANVAS_WIDTH, preset.width);
meta.canvasHeight = Math.max(MIN_CANVAS_HEIGHT, preset.height);
}
function applyOverlayToSelectedShape() {
const shape = findShape(selectedShapeId);
if (!shape) {
@@ -248,6 +327,7 @@
function render() {
renderToolState();
updateCanvasDimensions();
renderCanvas();
renderOverlay();
}
@@ -261,7 +341,8 @@
}
function renderCanvas() {
svg.querySelectorAll('.shape-object').forEach((el) => el.remove());
svg.querySelectorAll('.shape-object, .shape-auto-frame').forEach((el) => el.remove());
renderAutoFrame();
shapes.forEach((shape) => {
const element = createSvgShapeElement(shape);
@@ -280,6 +361,39 @@
});
}
function renderAutoFrame() {
if (!['19', '10'].includes(meta.formFactor)) {
return;
}
const padding = 4;
const frameWidth = Math.max(0, meta.canvasWidth - padding * 2);
const frameHeight = Math.max(0, meta.canvasHeight - padding * 2);
if (frameWidth <= 0 || frameHeight <= 0) {
return;
}
const frame = document.createElementNS(SVG_NS, 'rect');
frame.setAttribute('x', String(padding));
frame.setAttribute('y', String(padding));
frame.setAttribute('width', String(frameWidth));
frame.setAttribute('height', String(frameHeight));
frame.setAttribute('fill', '#ffffff');
frame.setAttribute('stroke', '#000000');
frame.setAttribute('stroke-width', '1');
frame.setAttribute('class', 'shape-auto-frame');
frame.setAttribute('pointer-events', 'none');
svg.appendChild(frame);
}
function updateCanvasDimensions() {
const width = Math.max(MIN_CANVAS_WIDTH, meta.canvasWidth);
const height = Math.max(MIN_CANVAS_HEIGHT, meta.canvasHeight);
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
svg.style.height = `${height}px`;
svg.style.minHeight = `${height}px`;
}
function renderOverlay() {
const selected = findShape(selectedShapeId);
const hasSelection = !!selected;
@@ -319,7 +433,16 @@
}
function persist() {
hiddenInput.value = JSON.stringify(shapes);
const payload = {
shapes,
meta: {
formFactor: meta.formFactor,
heightHe: meta.heightHe,
canvasWidth: meta.canvasWidth,
canvasHeight: meta.canvasHeight
}
};
hiddenInput.value = JSON.stringify(payload);
}
function findShape(id) {
@@ -542,6 +665,56 @@
}
}
function readDefinition(raw) {
const parsed = readJson(raw);
if (Array.isArray(parsed)) {
return {
shapes: parsed,
meta: getDefaultMeta()
};
}
if (parsed && typeof parsed === 'object') {
return {
shapes: Array.isArray(parsed.shapes) ? parsed.shapes : [],
meta: normalizeMeta(parsed.meta ?? parsed)
};
}
return {
shapes: [],
meta: getDefaultMeta()
};
}
function normalizeMeta(source) {
const base = getDefaultMeta();
if (!source || typeof source !== 'object') {
return base;
}
return {
formFactor: normalizeFormFactor(source.formFactor ?? source.rackFormFactor ?? base.formFactor),
heightHe: Math.max(1, toNumberOrDefault(source.heightHe ?? source.rackHeight ?? base.heightHe, base.heightHe)),
canvasWidth: Math.max(MIN_CANVAS_WIDTH, toNumberOrDefault(source.canvasWidth ?? source.width ?? base.canvasWidth, base.canvasWidth)),
canvasHeight: Math.max(MIN_CANVAS_HEIGHT, toNumberOrDefault(source.canvasHeight ?? source.height ?? base.canvasHeight, base.canvasHeight))
};
}
function getDefaultMeta() {
return {
formFactor: 'other',
heightHe: 1,
canvasWidth: 800,
canvasHeight: 360
};
}
function normalizeFormFactor(value) {
const normalized = String(value || '').trim();
return ['10', '19'].includes(normalized) ? normalized : 'other';
}
function normalizeColor(value, fallback) {
const v = String(value || '').trim();
if (/^#[0-9a-fA-F]{6}$/.test(v)) {