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>
|