310 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
<?php include ("./link/session.php") ?>
 | 
						|
<!doctype html>
 | 
						|
<html lang="uft-8">
 | 
						|
<head>
 | 
						|
    <?php include ("./public/head.inc") ?>
 | 
						|
    <link href="assets/plugins/nouislider/css/nouislider.min.css" rel="stylesheet">
 | 
						|
</head>
 | 
						|
<body>
 | 
						|
<?php include ("./public/menu.inc") ?>
 | 
						|
<div data-simplebar style="padding: 0;">
 | 
						|
    <main class="page-content mix" id="app" v-cloak>
 | 
						|
        <div class="row">
 | 
						|
            <div class="col-lg-6 mx-auto">
 | 
						|
                <div class="card">
 | 
						|
                    <div class="card-body">
 | 
						|
                        <div class="row">
 | 
						|
                            <div class="d-flex align-items-center gap-3 px-2 py-1">
 | 
						|
                                <div class="flex-grow-0">
 | 
						|
                                    <label class="fw-bold">
 | 
						|
                                        <cn>频道</cn>
 | 
						|
                                        <en>Channel</en>:
 | 
						|
                                    </label>
 | 
						|
                                </div>
 | 
						|
                                <div class="flex-grow-0">
 | 
						|
                                    <select class="form-select">
 | 
						|
                                        <option v-if="defaultConf.length > 0 && mixIndex > -1" :value="defaultConf[mixIndex].id">{{defaultConf[mixIndex].name}}</option>
 | 
						|
                                    </select>
 | 
						|
                                </div>
 | 
						|
                                <div class="flex-grow-0">
 | 
						|
                                    <label class="fw-bold">
 | 
						|
                                        <cn>布局</cn>
 | 
						|
                                        <en>Layout</en>:
 | 
						|
                                    </label>
 | 
						|
                                </div>
 | 
						|
                                <div class="flex-grow-0">
 | 
						|
                                    <select class="form-select" v-model="curLayId" @change="onChangeLayout">
 | 
						|
                                        <option v-for="(item,index) in defLaysConf" :value="item.layId">{{item.layName}}</option>
 | 
						|
                                    </select>
 | 
						|
                                </div>
 | 
						|
                                <div class="flex-grow-1 d-flex justify-content-end pe-3">
 | 
						|
                                    <i class="fa-solid fa-gear fa-lg lp-cursor-pointer" @click="hrefDefLayout"></i>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
                        <div class="row">
 | 
						|
                            <div class="col-lg-12 mt-2 mb-2">
 | 
						|
                                <img :src="chnImgUrl" class="card-img" alt="...">
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
    
 | 
						|
            <div class="col-lg-6 mx-auto">
 | 
						|
                <div class="card">
 | 
						|
                    <div class="card-header bg-transparent">
 | 
						|
                        <div class="p-2 mb-0 d-flex align-items-end">
 | 
						|
                            <cn>布局设定</cn>
 | 
						|
                            <en>Layout config</en>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                    <div class="card-body pb-4" >
 | 
						|
                        <div class="lp-aspect-ratio">
 | 
						|
                            <div class="aspect-ratio-content bg-black">
 | 
						|
                                <div class="lay-border" v-for="(item,index) in handleActiveDefLayConf.layouts" :style="{position:'absolute',width:item.pos.w * 100+'%',height:item.pos.h*100+'%',left:item.pos.x*100+'%',top:item.pos.y*100+'%'}">
 | 
						|
                                    <div :style="{width:'100%',height:'100%',backgroundColor: handleLayBackColor(index)}">
 | 
						|
                                        <div class="d-flex align-items-center gap-1 border-0 px-2 py-1">
 | 
						|
                                            <div class="flex-grow-1">
 | 
						|
                                                <select class="form-select" v-model="defaultConf[mixIndex].srcV[index]" @change="updateDefaultConf('noTip')">
 | 
						|
                                                    <option value="-1" cn="空" en="none" v-language-option></option>
 | 
						|
                                                    <option v-for="(it,index) in handleLayoutChnSelect(defaultConf[mixIndex].srcV[index])" :value="it.id">{{it.name}}</option>
 | 
						|
                                                </select>
 | 
						|
                                            </div>
 | 
						|
                                            <div class="flex-grow-0">
 | 
						|
                                                <button :class="['btn',{'btn-default':handleActiveVolume(index)},{'px-2 btn-primary':!handleActiveVolume(index)}]" @click="onUpdateActiveVolume(defaultConf[mixIndex].srcV[index])">
 | 
						|
                                                    <i :class="['fa-solid',{'fa-volume-off':handleActiveVolume(index)},{'fa-volume-high':!handleActiveVolume(index)}]"></i>
 | 
						|
                                                </button>
 | 
						|
                                            </div>
 | 
						|
                                        </div>
 | 
						|
                                    </div>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
        </div>
 | 
						|
    </main>
 | 
						|
</div>
 | 
						|
<?php include ("./public/foot.inc") ?>
 | 
						|
<script type="module">
 | 
						|
    
 | 
						|
    import { rpc,confirm } from "./assets/js/lp.utils.js";
 | 
						|
    import { useDefaultConf,useDefLaysConf,useHardwareConf } from "./assets/js/vue.hooks.js";
 | 
						|
    import { ignoreCustomElementPlugin,filterKeywordPlugin,bootstrapSwitchComponent,nouiSliderComponent,languageOptionDirective } from "./assets/js/vue.helper.js"
 | 
						|
    import mutationObserver from './assets/plugins/polyfill/mutationobserver.esm.js';
 | 
						|
    import vue from "./assets/js/vue.build.js";
 | 
						|
 | 
						|
    const {createApp,ref,reactive,watchEffect,computed,onMounted} = vue;
 | 
						|
    const app = createApp({
 | 
						|
        directives:{
 | 
						|
            "language-option": languageOptionDirective
 | 
						|
        },
 | 
						|
        components:{
 | 
						|
            "bs-switch" : bootstrapSwitchComponent,
 | 
						|
            "noui-slider": nouiSliderComponent
 | 
						|
        },
 | 
						|
        setup(props,context) {
 | 
						|
            
 | 
						|
            const { defaultConf,updateDefaultConf } = useDefaultConf();
 | 
						|
            const { defLaysConf } = useDefLaysConf();
 | 
						|
            const { hardwareConf } = useHardwareConf();
 | 
						|
 | 
						|
            const state = {
 | 
						|
                chnImgUrl: ref(""),
 | 
						|
                curLayId: ref(-1),
 | 
						|
                mixIndex: ref(-1),
 | 
						|
                curTheme: ref("default")
 | 
						|
            }
 | 
						|
            
 | 
						|
            const updateChnImage = () => {
 | 
						|
                if(defaultConf[state.mixIndex.value].enable)
 | 
						|
                    state.chnImgUrl.value = "snap/snap" + defaultConf[state.mixIndex.value].id + ".jpg?rnd=" + Math.random();
 | 
						|
                else
 | 
						|
                    state.chnImgUrl.value = "assets/img/nosignal.jpg";
 | 
						|
                setTimeout(() => { rpc( "enc.snap" ) },200)
 | 
						|
                setTimeout(updateChnImage,500);
 | 
						|
            }
 | 
						|
    
 | 
						|
            const handleEnableConf = computed(()=>{
 | 
						|
                return defaultConf.filter((item,index)=>{
 | 
						|
                    return !!item.enable;
 | 
						|
                })
 | 
						|
            })
 | 
						|
            
 | 
						|
            const handleActiveDefLayConf = computed(()=>{
 | 
						|
                return defLaysConf.find((item) => item.layId === state.curLayId.value) || {};
 | 
						|
            });
 | 
						|
            
 | 
						|
            const handleLayoutChnSelect = computed(() => {
 | 
						|
                return (chnId) => {
 | 
						|
                    let srcV = defaultConf[state.mixIndex.value].srcV;
 | 
						|
                    return defaultConf.filter((item,index)=>{
 | 
						|
                        return !(srcV.indexOf(item.id) > -1 && item.id !== chnId);
 | 
						|
                    });
 | 
						|
                };
 | 
						|
            });
 | 
						|
    
 | 
						|
            const handleActiveVolume = index => {
 | 
						|
                if(index < defaultConf[state.mixIndex.value].srcV.length) {
 | 
						|
                    const idx = defaultConf[state.mixIndex.value].srcV[index].toString();
 | 
						|
                    return !defaultConf[state.mixIndex.value].srcA.includes(idx) && !defaultConf[state.mixIndex.value].srcA.includes(Number(idx));
 | 
						|
                }
 | 
						|
            };
 | 
						|
            
 | 
						|
            const onUpdateActiveVolume = chnId => {
 | 
						|
                if(chnId === "-1")
 | 
						|
                    return;
 | 
						|
                chnId = chnId.toString();
 | 
						|
                defaultConf[state.mixIndex.value].srcA = defaultConf[state.mixIndex.value].srcA.map(item => item = item.toString());
 | 
						|
                defaultConf[state.mixIndex.value].srcV = defaultConf[state.mixIndex.value].srcV.map(item => item = item.toString());
 | 
						|
                let idx = defaultConf[state.mixIndex.value].srcA.indexOf(chnId);
 | 
						|
                if(idx === -1)
 | 
						|
                    defaultConf[state.mixIndex.value].srcA.push(chnId);
 | 
						|
                else
 | 
						|
                    defaultConf[state.mixIndex.value].srcA.splice(idx, 1);
 | 
						|
                updateDefaultConf("noTip");
 | 
						|
            };
 | 
						|
    
 | 
						|
            const unwatch = watchEffect(()=>{
 | 
						|
                if(defaultConf.length > 0 && Object.keys(defLaysConf).length > 0) {
 | 
						|
                    for(let i=0;i<defaultConf.length;i++) {
 | 
						|
                        if(defaultConf[i].type !== "mix")
 | 
						|
                            continue;
 | 
						|
    
 | 
						|
                        var layList = [];
 | 
						|
                        let mixChn = defaultConf[i];
 | 
						|
                        for (let j = 0; j < mixChn.layout.length; j++) {
 | 
						|
                            let layout = mixChn.layout[j];
 | 
						|
                            let layObj = {
 | 
						|
                                "a": layout.a,
 | 
						|
                                "x": layout.x,
 | 
						|
                                "y": layout.y,
 | 
						|
                                "w": layout.w,
 | 
						|
                                "h": layout.h,
 | 
						|
                                "index": layout.index
 | 
						|
                            }
 | 
						|
                            layList.push(layObj);
 | 
						|
                        }
 | 
						|
                        let curLayStr = JSON.stringify(layList);
 | 
						|
                        
 | 
						|
                        for (let j = 0; j < defLaysConf.length; j++) {
 | 
						|
                            let las = defLaysConf[j].layouts;
 | 
						|
                            let layout = [];
 | 
						|
                            for (let k = 0; k < las.length; k++) {
 | 
						|
                                layout.push(las[k].pos);
 | 
						|
                            }
 | 
						|
                            if(curLayStr === JSON.stringify(layout)) {
 | 
						|
                                state.curLayId.value = defLaysConf[j].layId;
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        state.mixIndex.value = i;
 | 
						|
                    }
 | 
						|
                    updateChnImage();
 | 
						|
                    unwatch();
 | 
						|
                }
 | 
						|
            })
 | 
						|
 | 
						|
            const hrefDefLayout = () => {
 | 
						|
                confirm({
 | 
						|
                    title: '<cn>布局</cn><en>Layout</en>',
 | 
						|
                    content: '<cn>是否打开布局管理器?</cn><en>Jump to Layout Manager?</en>',
 | 
						|
                    buttons: {
 | 
						|
                        ok: {
 | 
						|
                            text: "<cn>打开</cn><en>Confirm</en>",
 | 
						|
                            btnClass: 'btn-primary',
 | 
						|
                            keys: ['enter'],
 | 
						|
                            action: () => window.location.href = "defLayout.php"
 | 
						|
                        },
 | 
						|
                        cancel: {
 | 
						|
                            text: "<cn>取消</cn><en>Cancel</en>",
 | 
						|
                            action: () => {}
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                });
 | 
						|
            }
 | 
						|
            
 | 
						|
            const onChangeLayout = () => {
 | 
						|
                let layout = [];
 | 
						|
                let srcV = [];
 | 
						|
                let srcA = [];
 | 
						|
                let markV = false;
 | 
						|
                let markA = false;
 | 
						|
                for(let i=0;i<defLaysConf.length;i++) {
 | 
						|
                    if(state.curLayId.value === defLaysConf[i].layId) {
 | 
						|
                        let las = defLaysConf[i].layouts;
 | 
						|
                        for (let j = 0; j < las.length; j++) {
 | 
						|
                            layout.push(las[j].pos);
 | 
						|
                            if(las[j].id < 0) {
 | 
						|
                                srcV.push("-1");
 | 
						|
                            } else {
 | 
						|
                                srcV.push(las[j].id + "");
 | 
						|
                                markV = true;
 | 
						|
                                if(las[j].ado) {
 | 
						|
                                    srcA.push(las[j].id + "")
 | 
						|
                                    markA = true;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
                if(!markV) {
 | 
						|
                    if (srcV.length >= defaultConf[state.mixIndex.value].srcV.length)
 | 
						|
                        srcV.splice(0, defaultConf[state.mixIndex.value].srcV.length, ...defaultConf[state.mixIndex.value].srcV);
 | 
						|
                    else
 | 
						|
                        srcV = defaultConf[state.mixIndex.value].srcV.slice(0, srcV.length);
 | 
						|
                }
 | 
						|
 | 
						|
                defaultConf[state.mixIndex.value].srcV.splice(0, defaultConf[state.mixIndex.value].srcV.length, ...srcV);
 | 
						|
                defaultConf[state.mixIndex.value].layout.splice(0, defaultConf[state.mixIndex.value].layout.length, ...layout);
 | 
						|
                if(markA)
 | 
						|
                    defaultConf[state.mixIndex.value].srcA.splice(0, defaultConf[state.mixIndex.value].srcA.length, ...srcA);
 | 
						|
                updateDefaultConf("noTip");
 | 
						|
                const options = document.querySelectorAll(`option[cn]`);
 | 
						|
                options.forEach(option => {
 | 
						|
                    option.textContent = option.getAttribute('cn');
 | 
						|
                });
 | 
						|
            }
 | 
						|
            
 | 
						|
            const handleLayBackColor = (idx) => {
 | 
						|
                let color = 0;
 | 
						|
                if(state.curTheme.value !== "dark") {
 | 
						|
                    if(idx % 2 === 0)
 | 
						|
                        color = 128 + 25 * (idx / 2);
 | 
						|
                    else
 | 
						|
                        color = 128 - 25 * (idx / 2 + 1);
 | 
						|
                } else
 | 
						|
                    color = 85 - 15 * (idx / 2 + 1);
 | 
						|
                return "rgb(" + color + "," + color + "," + color + ")";
 | 
						|
            }
 | 
						|
 | 
						|
            onMounted(()=>{
 | 
						|
                const html = document.querySelector('html');
 | 
						|
                const observer = new mutationObserver(mutations => {
 | 
						|
                    mutations.forEach(mutation => {
 | 
						|
                        if (mutation.type === 'attributes' && mutation.attributeName === "data-bs-theme")
 | 
						|
                            state.curTheme.value = mutation.target.getAttribute("data-bs-theme");
 | 
						|
                    });
 | 
						|
                });
 | 
						|
                const config = {
 | 
						|
                    attributes: true,
 | 
						|
                    attributeFilter: ["data-bs-theme"],
 | 
						|
                    subtree: false
 | 
						|
                };
 | 
						|
                observer.observe(html, config);
 | 
						|
            });
 | 
						|
 | 
						|
            return {...state,defaultConf,defLaysConf,hardwareConf,handleEnableConf,handleActiveDefLayConf,
 | 
						|
                hrefDefLayout,onChangeLayout,handleLayBackColor,handleActiveVolume,onUpdateActiveVolume,
 | 
						|
                handleLayoutChnSelect,updateDefaultConf}
 | 
						|
        }
 | 
						|
    });
 | 
						|
    app.use(ignoreCustomElementPlugin);
 | 
						|
    app.use(filterKeywordPlugin);
 | 
						|
    app.mount('#app');
 | 
						|
</script>
 | 
						|
</body>
 | 
						|
</html>
 |