@ -1282,6 +1282,9 @@ HTML = """<!doctype html>
. config - toolbar - title { margin - right : auto ; color : var ( - - ui - muted ) ; font - size : 10 px ; font - weight : 700 ; letter - spacing : .08 em ; text - transform : uppercase ; }
. config - toolbar - title { margin - right : auto ; color : var ( - - ui - muted ) ; font - size : 10 px ; font - weight : 700 ; letter - spacing : .08 em ; text - transform : uppercase ; }
. config - toolbar button , . config - restore - panel button { min - height : 26 px ; padding : 0 8 px ; border : 1 px solid var ( - - ui - border ) ; border - radius : 7 px ; background : var ( - - ui - input ) ; color : var ( - - ui - muted ) ; font : 700 10 px / 1 ui - sans - serif , system - ui , sans - serif ; cursor : pointer ; transition : border - color .18 s ease , color .18 s ease , background .18 s ease , transform .18 s ease ; }
. config - toolbar button , . config - restore - panel button { min - height : 26 px ; padding : 0 8 px ; border : 1 px solid var ( - - ui - border ) ; border - radius : 7 px ; background : var ( - - ui - input ) ; color : var ( - - ui - muted ) ; font : 700 10 px / 1 ui - sans - serif , system - ui , sans - serif ; cursor : pointer ; transition : border - color .18 s ease , color .18 s ease , background .18 s ease , transform .18 s 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 ( - 1 px ) ; }
. 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 ( - 1 px ) ; }
. config - scale - control { display : inline - flex ! important ; align - items : center ; gap : 6 px ! important ; min - width : 148 px ; color : var ( - - ui - muted ) ! important ; font - size : 10 px ! important ; white - space : nowrap ; }
. config - scale - control input { width : 74 px ; height : 16 px ; margin : 0 ; accent - color : var ( - - ui - accent ) ; cursor : pointer ; }
. config - scale - control output { min - width : 34 px ; color : var ( - - ui - text ) ; font - variant - numeric : tabular - nums ; text - align : right ; }
. config - layout - state { min - width : 0 ; color : var ( - - ui - muted ) ; font - size : 10 px ; }
. 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 { 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 ; }
. config - workspace . has - structure { grid - template - columns : minmax ( 190 px , 260 px ) minmax ( 0 , 1 fr ) ; align - items : start ; }
@ -1443,6 +1446,11 @@ HTML = """<!doctype html>
. config - canvas > . config - block . settings - group > summary , . config - canvas > . config - block . settings - group > label , . config - canvas > . config - block . settings - group > . control - fields , . config - canvas > . config - block . settings - group > . packet - builder , . config - canvas > . config - block . settings - group > . settings - advanced { width : 100 % ; flex : 0 0 auto ; }
. config - canvas > . config - block . settings - group > summary , . config - canvas > . config - block . settings - group > label , . config - canvas > . config - block . settings - group > . control - fields , . config - canvas > . config - block . settings - group > . packet - builder , . config - canvas > . config - block . settings - group > . settings - advanced { width : 100 % ; flex : 0 0 auto ; }
. config - canvas > . config - block . settings - group > label { display : grid ; }
. config - canvas > . config - block . settings - group > label { display : grid ; }
. config - canvas > . config - block . settings - group > . control - fields { display : grid ! important ; }
. config - canvas > . config - block . settings - group > . control - fields { display : grid ! important ; }
. config - canvas > . config - block . settings - group { - - block - scale : 1 ; padding : calc ( 9 px * var ( - - block - scale ) ) calc ( 10 px * var ( - - block - scale ) ) ; gap : calc ( 8 px * var ( - - block - scale ) ) ; }
. config - canvas > . config - block . settings - group > summary { padding - bottom : calc ( 4 px * var ( - - block - scale ) ) ; font - size : clamp ( 10 px , calc ( 11 px * var ( - - block - scale ) ) , 16 px ) ; }
. config - block . settings - group label , . config - block . settings - group . file - picked { gap : calc ( 2 px * var ( - - block - scale ) ) ; font - size : clamp ( 9 px , calc ( 10 px * var ( - - block - scale ) ) , 15 px ) ; }
. config - block . settings - group input : not ( [ type = " checkbox " ] ) : not ( [ type = " file " ] ) , . config - block . settings - group select , . config - block . settings - group button { min - height : calc ( 27 px * var ( - - block - scale ) ) ; height : calc ( 27 px * var ( - - block - scale ) ) ; padding - left : calc ( 7 px * var ( - - block - scale ) ) ; padding - right : calc ( 7 px * var ( - - block - scale ) ) ; font - size : clamp ( 10 px , calc ( 12 px * var ( - - block - scale ) ) , 17 px ) ; }
. config - block . settings - group . control - fields , . config - block . settings - group . advanced - grid , . config - block . settings - group . packet - grid { gap : calc ( 6 px * var ( - - block - scale ) ) calc ( 8 px * var ( - - block - scale ) ) ; }
@keyframes appearance - in { from { opacity : 0 ; transform : translateY ( - 5 px ) scale ( .98 ) ; } to { opacity : 1 ; transform : none ; } }
@keyframes appearance - in { from { opacity : 0 ; transform : translateY ( - 5 px ) scale ( .98 ) ; } to { opacity : 1 ; transform : none ; } }
@media ( max - width : 760 px ) {
@media ( max - width : 760 px ) {
. appearance - trigger { min - width : 38 px ; width : 38 px ; padding : 0 ; justify - content : center ; }
. appearance - trigger { min - width : 38 px ; width : 38 px ; padding : 0 ; justify - content : center ; }
@ -1531,6 +1539,7 @@ HTML = """<!doctype html>
< span class = " config-toolbar-title " > Блоки конфигурации < / span >
< span class = " config-toolbar-title " > Блоки конфигурации < / span >
< button id = " configResetLayout " type = " button " > сбросить раскладку < / button >
< button id = " configResetLayout " type = " button " > сбросить раскладку < / button >
< button id = " configRestoreToggle " type = " button " aria - expanded = " false " > структура < / button >
< button id = " configRestoreToggle " type = " button " aria - expanded = " false " > структура < / button >
< label class = " config-scale-control " title = " Масштаб элементов и текста внутри блоков " > < span > масштаб < / span > < input id = " configContentScale " type = " range " min = " 80 " max = " 125 " step = " 5 " value = " 100 " aria - label = " Масштаб содержимого блоков " > < output id = " configContentScaleValue " > 100 % < / output > < / label >
< span id = " configLayoutState " class = " config-layout-state " > перетаскивайте за заголовок или свободное место < / span >
< span id = " configLayoutState " class = " config-layout-state " > перетаскивайте за заголовок или свободное место < / span >
< / div >
< / div >
< div id = " configWorkspace " class = " config-workspace " >
< div id = " configWorkspace " class = " config-workspace " >
@ -1997,6 +2006,8 @@ HTML = """<!doctype html>
let restorePanelOpen = false ;
let restorePanelOpen = false ;
let deletedPanelOpen = false ;
let deletedPanelOpen = false ;
let configZIndex = 1 ;
let configZIndex = 1 ;
let configContentScale = 1 ;
let configBlockObserver = null ;
function readConfigLayout ( ) {
function readConfigLayout ( ) {
try {
try {
const saved = JSON . parse ( localStorage . getItem ( ' fpv-config-layout-v1 ' ) | | ' {} ' ) ;
const saved = JSON . parse ( localStorage . getItem ( ' fpv-config-layout-v1 ' ) | | ' {} ' ) ;
@ -2015,6 +2026,21 @@ HTML = """<!doctype html>
function configCanvasWidth ( ) {
function configCanvasWidth ( ) {
return Math . max ( configMinWidth ( ) , ( configCanvas ? . clientWidth | | 0 ) - 12 ) ;
return Math . max ( configMinWidth ( ) , ( configCanvas ? . clientWidth | | 0 ) - 12 ) ;
}
}
function updateConfigBlockScale ( block ) {
if ( ! block ) return ;
const width = Math . max ( configMinWidth ( ) , block . clientWidth | | block . offsetWidth | | configMinWidth ( ) ) ;
const height = Math . max ( 110 , block . clientHeight | | block . offsetHeight | | 110 ) ;
const area = Math . max ( .56 , Math . min ( 1.85 , ( width / 720 ) * ( height / 320 ) ) ) ;
const scale = Math . max ( .74 , Math . min ( 1.35 , Math . sqrt ( area ) * configContentScale ) ) ;
block . style . setProperty ( ' --block-scale ' , scale . toFixed ( 3 ) ) ;
}
function applyConfigContentScale ( value ) {
configContentScale = Math . max ( .8 , Math . min ( 1.25 , ( Number ( value ) | | 100 ) / 100 ) ) ;
setValue ( ' configContentScale ' , Math . round ( configContentScale * 100 ) ) ;
setText ( ' configContentScaleValue ' , ` $ { Math . round ( configContentScale * 100 ) } % ` ) ;
configBlocks . forEach ( updateConfigBlockScale ) ;
try { localStorage . setItem ( ' fpv-config-content-scale ' , String ( Math . round ( configContentScale * 100 ) ) ) ; } catch ( _error ) { }
}
function configTitle ( block ) {
function configTitle ( block ) {
return block . querySelector ( ' :scope > summary .block-title ' ) ? . textContent | | block . dataset . section | | ' Блок ' ;
return block . querySelector ( ' :scope > summary .block-title ' ) ? . textContent | | block . dataset . section | | ' Блок ' ;
}
}
@ -2139,7 +2165,7 @@ HTML = """<!doctype html>
if ( Number . isFinite ( Number ( entry . z ) ) ) { block . style . zIndex = String ( Math . max ( 1 , Number ( entry . z ) ) ) ; configZIndex = Math . max ( configZIndex , Number ( entry . z ) ) ; }
if ( Number . isFinite ( Number ( entry . z ) ) ) { block . style . zIndex = String ( Math . max ( 1 , Number ( entry . z ) ) ) ; configZIndex = Math . max ( configZIndex , Number ( entry . z ) ) ; }
} ) ;
} ) ;
let fitted = false ;
let fitted = false ;
configBlocks . forEach ( block = > { fitted = fitConfigBlockContent ( block ) | | fitted ; } ) ;
configBlocks . forEach ( block = > { fitted = fitConfigBlockContent ( block ) | | fitted ; updateConfigBlockScale ( block ) ; } ) ;
if ( fitted ) saveConfigLayout ( ) ;
if ( fitted ) saveConfigLayout ( ) ;
updateConfigCanvasHeight ( ) ;
updateConfigCanvasHeight ( ) ;
}
}
@ -2284,6 +2310,7 @@ HTML = """<!doctype html>
}
}
configCanvas . appendChild ( clone ) ;
configCanvas . appendChild ( clone ) ;
configBlocks . push ( clone ) ;
configBlocks . push ( clone ) ;
configBlockObserver ? . observe ( clone ) ;
const entry = configLayout [ key ] | | { } ;
const entry = configLayout [ key ] | | { } ;
configLayout [ key ] = { . . . entry , custom : true , template : templateKey , title : titleNodeText ( clone ) } ;
configLayout [ key ] = { . . . entry , custom : true , template : templateKey , title : titleNodeText ( clone ) } ;
return clone ;
return clone ;
@ -2297,6 +2324,7 @@ HTML = """<!doctype html>
block . style . left = ' 12px ' ;
block . style . left = ' 12px ' ;
block . style . top = ` $ { Math . max ( 12 , bottom + 18 ) } px ` ;
block . style . top = ` $ { Math . max ( 12 , bottom + 18 ) } px ` ;
block . style . width = ` $ { Math . min ( width , Math . max ( configMinWidth ( ) , Math . round ( width * .46 ) ) ) } px ` ;
block . style . width = ` $ { Math . min ( width , Math . max ( configMinWidth ( ) , Math . round ( width * .46 ) ) ) } px ` ;
updateConfigBlockScale ( block ) ;
}
}
function duplicateConfigBlock ( block ) {
function duplicateConfigBlock ( block ) {
const clone = createCustomConfigBlock ( configTemplateKey ( block ) , ` $ { configTitle ( block ) } · копия ` ) ;
const clone = createCustomConfigBlock ( configTemplateKey ( block ) , ` $ { configTitle ( block ) } · копия ` ) ;
@ -2404,6 +2432,7 @@ HTML = """<!doctype html>
block . style . top = ` $ { Math . round ( top ) } px ` ;
block . style . top = ` $ { Math . round ( top ) } px ` ;
block . style . width = ` $ { Math . round ( width ) } px ` ;
block . style . width = ` $ { Math . round ( width ) } px ` ;
block . style . height = ` $ { Math . round ( height ) } px ` ;
block . style . height = ` $ { Math . round ( height ) } px ` ;
updateConfigBlockScale ( block ) ;
updateConfigCanvasHeight ( ) ;
updateConfigCanvasHeight ( ) ;
} ;
} ;
const end = ( ) = > {
const end = ( ) = > {
@ -2551,9 +2580,16 @@ HTML = """<!doctype html>
} ) ;
} ) ;
window . addEventListener ( ' resize ' , applyStoredConfigLayout ) ;
window . addEventListener ( ' resize ' , applyStoredConfigLayout ) ;
if ( window . ResizeObserver ) {
if ( window . ResizeObserver ) {
const observer = new ResizeObserver ( updateConfigCanvasHeight ) ;
configBlockObserver = new ResizeObserver ( entries = > {
configBlocks . forEach ( block = > observer . observe ( block ) ) ;
entries . forEach ( entry = > updateConfigBlockScale ( entry . target ) ) ;
updateConfigCanvasHeight ( ) ;
} ) ;
configBlocks . forEach ( block = > configBlockObserver . observe ( block ) ) ;
}
}
let savedScale = 100 ;
try { savedScale = Number ( localStorage . getItem ( ' fpv-config-content-scale ' ) | | 100 ) ; } catch ( _error ) { }
applyConfigContentScale ( savedScale ) ;
$ ( ' configContentScale ' ) ? . addEventListener ( ' input ' , event = > applyConfigContentScale ( event . target . value ) ) ;
}
}
function parseFrameSize ( value ) {
function parseFrameSize ( value ) {
const match = / ^ ( \\d { 1 , 5 } ) x ( \\d { 1 , 5 } ) $ / i . exec ( String ( value | | ' ' ) . trim ( ) ) ;
const match = / ^ ( \\d { 1 , 5 } ) x ( \\d { 1 , 5 } ) $ / i . exec ( String ( value | | ' ' ) . trim ( ) ) ;
@ -2816,7 +2852,7 @@ HTML = """<!doctype html>
setText ( ' uploadRunVideo ' , dumpFile ? ' загрузить выбранный UDP-дамп ' : ' загрузить выбранное видео ' ) ;
setText ( ' uploadRunVideo ' , dumpFile ? ' загрузить выбранный UDP-дамп ' : ' загрузить выбранное видео ' ) ;
if ( configCanvas ) window . requestAnimationFrame ( ( ) = > {
if ( configCanvas ) window . requestAnimationFrame ( ( ) = > {
let fitted = false ;
let fitted = false ;
configBlocks . forEach ( block = > { fitted = fitConfigBlockContent ( block ) | | fitted ; } ) ;
configBlocks . forEach ( block = > { fitted = fitConfigBlockContent ( block ) | | fitted ; updateConfigBlockScale ( block ) ; } ) ;
if ( fitted ) saveConfigLayout ( ) ;
if ( fitted ) saveConfigLayout ( ) ;
updateConfigCanvasHeight ( ) ;
updateConfigCanvasHeight ( ) ;
} ) ;
} ) ;