diff --git a/tests/test_ui_server.py b/tests/test_ui_server.py index fcc4879..e2439da 100644 --- a/tests/test_ui_server.py +++ b/tests/test_ui_server.py @@ -70,8 +70,13 @@ class UIServerTests(unittest.TestCase): self.assertIn('initCollapsibleSections', HTML) self.assertIn('configResetLayout', HTML) self.assertIn('configRestoreToggle', HTML) + self.assertIn('configTemplateSelect', HTML) + self.assertIn('configCreateBlock', HTML) self.assertIn('config-canvas', HTML) self.assertIn('setConfigBlockState', HTML) + self.assertIn('createCustomConfigBlock', HTML) + self.assertIn('duplicateConfigBlock', HTML) + self.assertIn('positionNewConfigBlock', HTML) self.assertIn('finishConfigDrag', HTML) self.assertIn('fpv-config-layout-v1', HTML) self.assertIn('config-trash', HTML) diff --git a/ui_server.py b/ui_server.py index 2e57684..0d83984 100644 --- a/ui_server.py +++ b/ui_server.py @@ -1283,15 +1283,26 @@ HTML = """ .config-toolbar button, .config-restore-panel button { min-height: 26px; padding: 0 8px; border: 1px solid var(--ui-border); border-radius: 7px; background: var(--ui-input); color: var(--ui-muted); font: 700 10px/1 ui-sans-serif, system-ui, sans-serif; cursor: pointer; transition: border-color .18s ease, color .18s ease, background .18s ease, transform .18s ease; } .config-toolbar button:hover, .config-toolbar button:focus-visible, .config-restore-panel button:hover, .config-restore-panel button:focus-visible { outline: 0; border-color: var(--ui-accent); color: var(--ui-accent); transform: translateY(-1px); } .config-layout-state { min-width: 0; color: var(--ui-muted); font-size: 10px; } - .config-restore-panel { display: grid; gap: 6px; padding: 7px 10px; border-bottom: 1px solid var(--ui-border); background: color-mix(in srgb, var(--ui-input) 45%, transparent); } + .config-restore-panel { display: grid; gap: 8px; padding: 8px 10px; border-bottom: 1px solid var(--ui-border); background: color-mix(in srgb, var(--ui-input) 45%, transparent); } .config-restore-title { color: var(--ui-muted); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; } - .config-restore-list { display: flex; flex-wrap: wrap; gap: 5px; } - .config-restore-item { display: inline-flex; align-items: center; gap: 6px; padding: 3px 5px 3px 8px; border: 1px solid var(--ui-border); border-radius: 7px; color: var(--ui-text); font-size: 11px; } + .config-structure-create { display: flex; align-items: end; flex-wrap: wrap; gap: 6px; } + .config-structure-create label { display: grid; gap: 3px; min-width: 150px; color: var(--ui-muted); font-size: 10px; } + .config-structure-create label:first-child { min-width: 190px; } + .config-structure-create input, .config-structure-create select { height: 27px; min-width: 0; padding: 0 7px; border: 1px solid var(--ui-border); border-radius: 7px; background: var(--ui-input); color: var(--ui-text); font: inherit; } + .config-structure-create input:focus, .config-structure-create select:focus { outline: 0; border-color: var(--ui-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-accent) 10%, transparent); } + .config-structure-hint { color: var(--ui-muted); font-size: 10px; } + .config-restore-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 5px; } + .config-restore-item { display: flex; align-items: center; gap: 6px; min-width: 0; padding: 4px 5px 4px 8px; border: 1px solid var(--ui-border); border-radius: 7px; color: var(--ui-text); font-size: 11px; } + .config-restore-item.is-hidden { opacity: .72; } + .config-restore-item .config-item-title { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .config-restore-item .config-item-state { color: var(--ui-muted); font-size: 9px; text-transform: uppercase; } + .config-item-actions { display: inline-flex; gap: 3px; } .config-restore-panel[hidden], .config-trash[hidden] { display: none !important; } - .config-canvas { position: relative; display: block; min-height: 760px; padding: 8px 6px 18px; } - .config-canvas > .config-block { position: absolute; margin: 0; min-width: 240px; min-height: 110px; border: 1px solid var(--ui-border) !important; border-radius: 10px; background: var(--ui-panel) !important; box-shadow: 0 6px 18px rgba(0, 0, 0, .1); overflow: auto; transition: border-color .2s ease, box-shadow .2s ease, opacity .2s ease; } + .config-canvas { position: relative; display: block; min-height: 760px; padding: 8px 6px 18px; overflow: auto; overscroll-behavior: contain; } + .config-canvas > .config-block { position: absolute; box-sizing: border-box; margin: 0; min-width: 240px; min-height: 110px; border: 1px solid var(--ui-border) !important; border-radius: 10px; background: var(--ui-panel) !important; box-shadow: 0 6px 18px rgba(0, 0, 0, .1); overflow: auto; transition: border-color .2s ease, box-shadow .2s ease, opacity .2s ease; } .config-canvas > .config-block:hover { border-color: color-mix(in srgb, var(--ui-accent) 45%, var(--ui-border)) !important; } .config-canvas > .config-block.is-dragging, .config-canvas > .config-block.is-resizing { z-index: 5; border-color: var(--ui-accent) !important; box-shadow: 0 10px 28px color-mix(in srgb, var(--ui-accent) 16%, transparent); transition: none; } + .config-canvas > .config-custom-block { border-style: dashed !important; } .config-block > summary { position: sticky; top: 0; z-index: 2; background: var(--ui-panel); } .block-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .config-block > summary { cursor: grab; touch-action: none; } @@ -1305,8 +1316,10 @@ HTML = """ @media (max-width: 760px) { .config-toolbar { align-items: stretch; } .config-toolbar-title, .config-layout-state { flex-basis: 100%; } - .config-canvas { min-height: 0 !important; display: grid; gap: 8px; padding: 8px 0; } - .config-canvas > .config-block { position: relative !important; inset: auto !important; width: auto !important; height: auto !important; min-width: 0; max-height: none; } + .config-canvas { min-height: 900px; padding: 8px 4px 24px; } + .config-canvas > .config-block { min-width: 220px; max-width: calc(100% - 8px); } + .config-structure-create { align-items: stretch; } + .config-structure-create label, .config-structure-create label:first-child { min-width: min(100%, 220px); flex: 1 1 180px; } .config-trash { bottom: 10px; } } @keyframes form-field-in { from { opacity: .35; transform: translateY(3px); } to { opacity: 1; transform: none; } } @@ -1402,11 +1415,26 @@ HTML = """