@ -1287,6 +1287,8 @@ HTML = """<!doctype html>
. config - scale - control input { width : 74 px ; height : 16 px ; margin : 0 ; accent - color : var ( - - ui - accent ) ; cursor : pointer ; }
. config - scale - control input : disabled { opacity : .45 ; cursor : default ; }
. config - scale - control output { min - width : 34 px ; color : var ( - - ui - text ) ; font - variant - numeric : tabular - nums ; text - align : right ; }
. config - scale - mode { min - height : 26 px ! important ; padding : 0 8 px ! important ; white - space : nowrap ; }
. config - scale - mode [ aria - pressed = " true " ] { color : #fff !important; background: var(--ui-accent-bg) !important; border-color: var(--ui-accent) !important; box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-accent) 16%, transparent); }
. config - layout - state { min - width : 0 ; color : var ( - - ui - muted ) ; font - size : 10 px ; }
. config - workspace { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) ; min - width : 0 ; min - height : 0 ; }
. config - workspace . has - structure { grid - template - columns : minmax ( 190 px , 260 px ) minmax ( 0 , 1 fr ) ; align - items : start ; }
@ -1603,6 +1605,7 @@ HTML = """<!doctype html>
< button id = " configResetLayout " type = " button " > сбросить раскладку < / button >
< button id = " configRestoreToggle " type = " button " aria - expanded = " false " > структура < / button >
< button id = " configDeleteSelected " class = " command danger " type = " button " disabled > удалить выбранные < / button >
< button id = " configScaleMode " class = " config-scale-mode " type = " button " aria - pressed = " false " disabled > масштаб колесом : выкл < / button >
< label class = " config-scale-control " title = " Масштаб элементов и текста выбранного блока: 50– 200 % " > < span id = " configScaleLabel " > масштаб блока < / span > < input id = " configContentScale " type = " range " min = " 50 " max = " 200 " step = " 5 " value = " 100 " aria - label = " Масштаб содержимого выбранного блока, от 50 до 200 процентов " disabled > < output id = " configContentScaleValue " > 100 % < / output > < / label >
< span id = " configLayoutState " class = " config-layout-state " > Ctrl / Cmd + клик — выбрать несколько · перетаскивайте группу < / span >
< / div >
@ -1829,6 +1832,7 @@ HTML = """<!doctype html>
< button id = " networkResetLayout " type = " button " > сбросить раскладку < / button >
< button id = " networkStructureToggle " type = " button " aria - expanded = " false " > структура < / button >
< button id = " networkDeleteSelected " class = " command danger " type = " button " disabled > удалить выбранные < / button >
< button id = " networkScaleMode " class = " config-scale-mode " type = " button " aria - pressed = " false " disabled > масштаб колесом : выкл < / button >
< label class = " config-scale-control " title = " Масштаб элементов и текста выбранного сетевого блока " > < span > масштаб блока < / span > < input id = " networkContentScale " type = " range " min = " 50 " max = " 200 " step = " 5 " value = " 100 " aria - label = " Масштаб содержимого выбранного сетевого блока " disabled > < output id = " networkContentScaleValue " > 100 % < / output > < / label >
< span id = " networkLayoutState " class = " config-layout-state " > Ctrl / Cmd + клик — выбрать несколько · перетаскивайте группу < / span >
< / div >
@ -2093,6 +2097,7 @@ HTML = """<!doctype html>
let deletedPanelOpen = false ;
let configZIndex = 1 ;
let configContentScale = 1 ;
let configScaleWheelEnabled = false ;
let configBlockObserver = null ;
let selectedConfigBlock = null ;
let selectedConfigBlocks = new Set ( ) ;
@ -2138,6 +2143,14 @@ HTML = """<!doctype html>
updateConfigBlockScale ( block ) ;
saveConfigLayout ( ) ;
}
function setConfigScaleMode ( enabled ) {
configScaleWheelEnabled = ! ! enabled & & ! ! selectedConfigBlock ;
const button = $ ( ' configScaleMode ' ) ;
if ( ! button ) return ;
button . disabled = ! selectedConfigBlock ;
button . setAttribute ( ' aria-pressed ' , String ( configScaleWheelEnabled ) ) ;
button . textContent = configScaleWheelEnabled ? ' масштаб колесом: вкл ' : ' масштаб колесом: выкл ' ;
}
function selectConfigBlock ( block ) {
selectedConfigBlocks = new Set ( block & & ! configBlockState ( block ) ? [ block ] : [ ] ) ;
updateConfigSelection ( block ) ;
@ -2152,6 +2165,7 @@ HTML = """<!doctype html>
if ( ! selectedConfigBlock ) {
if ( control ) control . disabled = true ;
if ( remove ) remove . disabled = true ;
setConfigScaleMode ( false ) ;
return ;
}
const entry = configLayout [ selectedConfigBlock . dataset . section ] | | { } ;
@ -2161,6 +2175,7 @@ HTML = """<!doctype html>
setText ( ' configContentScaleValue ' , ` $ { value } % ` ) ;
if ( control ) control . disabled = selectedConfigBlocks . size != = 1 ;
if ( remove ) remove . disabled = ! selectedConfigBlocks . size ;
setConfigScaleMode ( configScaleWheelEnabled ) ;
updateConfigBlockScale ( selectedConfigBlock ) ;
if ( selectedConfigBlocks . size > 1 ) setText ( ' configLayoutState ' , ` выбрано $ { selectedConfigBlocks . size } блока · перетаскивайте группу ` ) ;
}
@ -2637,6 +2652,14 @@ HTML = """<!doctype html>
block . style . cursor = edge ? ` $ { edge } - resize ` : ' ' ;
}
} ) ;
block . addEventListener ( ' wheel ' , event = > {
if ( ! configScaleWheelEnabled | | configBlockState ( block ) | | ! event . deltaY ) return ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
if ( selectedConfigBlock != = block ) selectConfigBlock ( block ) ;
const current = Number ( configLayout [ block . dataset . section ] ? . scale ) | | 100 ;
applyConfigContentScale ( current + ( event . deltaY < 0 ? 5 : - 5 ) ) ;
} , { passive : false } ) ;
block . addEventListener ( ' pointerleave ' , ( ) = > { if ( ! block . classList . contains ( ' is-resizing ' ) ) block . style . cursor = ' ' ; } ) ;
block . addEventListener ( ' pointerdown ' , event = > {
if ( event . defaultPrevented | | block . classList . contains ( ' is-resizing ' ) ) return ;
@ -2756,6 +2779,7 @@ HTML = """<!doctype html>
selectConfigBlock ( configBlocks . find ( block = > ! configBlockState ( block ) ) | | null ) ;
$ ( ' configResetLayout ' ) ? . addEventListener ( ' click ' , resetConfigLayout ) ;
$ ( ' configDeleteSelected ' ) ? . addEventListener ( ' click ' , deleteSelectedConfigBlocks ) ;
$ ( ' configScaleMode ' ) ? . addEventListener ( ' click ' , ( ) = > setConfigScaleMode ( ! configScaleWheelEnabled ) ) ;
$ ( ' configRestoreToggle ' ) ? . addEventListener ( ' click ' , ( ) = > {
restorePanelOpen = ! restorePanelOpen ;
renderConfigRestorePanel ( ) ;
@ -2811,6 +2835,7 @@ HTML = """<!doctype html>
let selectedNetworkBlocks = new Set ( ) ;
let networkStructureOpen = false ;
let networkContentScale = 1 ;
let networkScaleWheelEnabled = false ;
function readNetworkLayout ( ) {
try {
const saved = JSON . parse ( localStorage . getItem ( ' fpv-network-layout-v1 ' ) | | ' {} ' ) ;
@ -2857,6 +2882,7 @@ HTML = """<!doctype html>
if ( ! networkSelectedBlock ) {
if ( control ) control . disabled = true ;
if ( remove ) remove . disabled = true ;
setNetworkScaleMode ( false ) ;
return ;
}
const value = Math . max ( 50 , Math . min ( 200 , Math . round ( Number ( networkLayout [ networkSelectedBlock . dataset . section ] ? . scale ) | | 100 ) ) ) ;
@ -2865,6 +2891,7 @@ HTML = """<!doctype html>
setText ( ' networkContentScaleValue ' , ` $ { value } % ` ) ;
if ( control ) control . disabled = selectedNetworkBlocks . size != = 1 ;
if ( remove ) remove . disabled = ! selectedNetworkBlocks . size ;
setNetworkScaleMode ( networkScaleWheelEnabled ) ;
updateNetworkBlockScale ( networkSelectedBlock ) ;
if ( selectedNetworkBlocks . size > 1 ) setText ( ' networkLayoutState ' , ` выбрано $ { selectedNetworkBlocks . size } блока · перетаскивайте группу ` ) ;
}
@ -2887,6 +2914,14 @@ HTML = """<!doctype html>
updateNetworkBlockScale ( block ) ;
saveNetworkLayout ( ) ;
}
function setNetworkScaleMode ( enabled ) {
networkScaleWheelEnabled = ! ! enabled & & ! ! networkSelectedBlock ;
const button = $ ( ' networkScaleMode ' ) ;
if ( ! button ) return ;
button . disabled = ! networkSelectedBlock ;
button . setAttribute ( ' aria-pressed ' , String ( networkScaleWheelEnabled ) ) ;
button . textContent = networkScaleWheelEnabled ? ' масштаб колесом: вкл ' : ' масштаб колесом: выкл ' ;
}
function captureNetworkLayout ( block ) {
const key = block ? . dataset . section ;
if ( ! key ) return ;
@ -3261,6 +3296,14 @@ HTML = """<!doctype html>
block . style . cursor = edge ? ` $ { edge } - resize ` : ' ' ;
}
} ) ;
block . addEventListener ( ' wheel ' , event = > {
if ( ! networkScaleWheelEnabled | | networkState ( block ) | | ! event . deltaY ) return ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
if ( networkSelectedBlock != = block ) selectNetworkBlock ( block ) ;
const current = Number ( networkLayout [ block . dataset . section ] ? . scale ) | | 100 ;
applyNetworkContentScale ( current + ( event . deltaY < 0 ? 5 : - 5 ) ) ;
} , { passive : false } ) ;
block . addEventListener ( ' pointerdown ' , event = > {
if ( event . defaultPrevented | | block . classList . contains ( ' is-resizing ' ) ) return ;
startNetworkDrag ( block , event ) ;
@ -3314,6 +3357,7 @@ HTML = """<!doctype html>
selectNetworkBlock ( networkBlocks . find ( block = > ! networkState ( block ) ) | | null ) ;
$ ( ' networkResetLayout ' ) ? . addEventListener ( ' click ' , resetNetworkLayout ) ;
$ ( ' networkDeleteSelected ' ) ? . addEventListener ( ' click ' , deleteSelectedNetworkBlocks ) ;
$ ( ' networkScaleMode ' ) ? . addEventListener ( ' click ' , ( ) = > setNetworkScaleMode ( ! networkScaleWheelEnabled ) ) ;
$ ( ' networkStructureToggle ' ) ? . addEventListener ( ' click ' , ( ) = > {
networkStructureOpen = ! networkStructureOpen ;
renderNetworkStructure ( ) ;