css aufgeräumt
This commit is contained in:
301
app/assets/css/device-type-edit.css
Normal file
301
app/assets/css/device-type-edit.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user