main
reng 3 months ago
parent 4f808506c7
commit e162d1138f
  1. 7
      package-lock.json
  2. 1
      package.json
  3. 27
      public/cuelist.json
  4. 2
      src-tauri/tauri.conf.json
  5. 6
      src/App.css
  6. 152
      src/App.jsx
  7. 3
      src/utils/constant.js
  8. 103
      src/utils/light.jsx

7
package-lock.json generated

@ -12,6 +12,7 @@
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-log": "^2.6.0",
"@tauri-apps/plugin-opener": "^2",
"gsap": "^3.13.0",
"howler": "^2.2.4",
"react": "^19.1.0",
"react-dom": "^19.1.0",
@ -1853,6 +1854,12 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/gsap": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz",
"integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==",
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
},
"node_modules/howler": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz",

@ -14,6 +14,7 @@
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-log": "^2.6.0",
"@tauri-apps/plugin-opener": "^2",
"gsap": "^3.13.0",
"howler": "^2.2.4",
"react": "^19.1.0",
"react-dom": "^19.1.0",

@ -13,30 +13,33 @@
"name": "Q2",
"type": "announce",
"description": "announce",
"audioCue": "Q2",
"status":"reset"
"audioCue": "Q2"
},
{
"id": 3,
"name": "Q3",
"type": "bg",
"description": "during bg",
"description": "(START) CQ2-CQ4.1",
"audioCue": "Q3",
"clientCue":"Q2"
"clientCue":"Q2",
"duration":563,
"auto": true
},
{
"id": 3.1,
"name": "Q3.1",
"id": 4,
"name": "Q4",
"type": "light",
"description": "fade out light",
"lightCue": "fade_out_light"
"description": "(AI) CQ4.11-CQ5.6",
"lightCue":"fade_out_light",
"duration":253,
"clientCue":"Q4.11",
"auto": true
},
{
"id": 4,
"name": "Q4",
"id": 5,
"name": "Q5",
"type": "bg",
"description": "Ending",
"description": "(END)",
"audioCue": "Q6",
"status":"end",
"lightCue":"fade_in_light",

@ -13,7 +13,7 @@
"windows": [
{
"title": "control-panel",
"width": 800,
"width": 1200,
"height": 600
}
],

@ -5,12 +5,14 @@ html{
}
button{
@apply cursor-pointer min-w-[5rem] rounded-full bg-yellow-200 p-2 text-black font-bold;
@apply cursor-pointer min-w-[3rem] rounded-full bg-yellow-200 p-2 text-black font-bold;
}
tr{
@apply border-b border-gray-400;
}
td{
@apply p-1;
}
input[type='range'] {
overflow: hidden;
/* width: 80px; */

@ -4,6 +4,7 @@ import { invoke } from "@tauri-apps/api/core";
import { listen } from '@tauri-apps/api/event';
import "./App.css";
import { OSC_ADDRESS } from "./utils/constant";
import { Light } from "./utils/light";
const DefaultFadeDuration = 3; // 1 second
@ -29,7 +30,10 @@ function App() {
const [clientStatus, setClientStatus] = useState({});
const refCue = useRef(null);
const refNextCue = useRef(null);
// const refNextCue = useRef(null);
const refLight= useRef();
const refDuration= useRef();
const refTimer= useRef();
function sendOsc(addr, message, id){
@ -57,32 +61,42 @@ function App() {
function onOsc(message){
const addr= message.addr;
const [id, status, name]= message.args[0]?.split('#');
if(clientStatus[id]){
setClientStatus(prev=>({
...prev,
[id]:[
{
status,
name,
timestamp: new Date().toLocaleTimeString(),
},
...prev[id],
]
}));
}else{
setClientStatus(prev=>({
...prev,
[id]:[{
status,
name,
timestamp: new Date().toLocaleTimeString(),
}]
}));
}
switch(addr){
case OSC_ADDRESS.CLIENT_STATUS:
setClientStatus(prev=>({
...prev,
[id]:{
status,
name,
timestamp: new Date().toLocaleTimeString(),
}
}));
break;
case OSC_ADDRESS.CLIENT_DURATION:
setClientStatus(prev=>({
...prev,
[id]:{
...prev[id],
duration: status,
timestamp: new Date().toLocaleTimeString(),
}
}));
break;
case OSC_ADDRESS.CLIENT_INPUT:
setClientStatus(prev=>({
...prev,
[id]:{
...prev[id],
input: status,
timestamp: new Date().toLocaleTimeString(),
}
}));
break;
}
}
@ -91,6 +105,7 @@ function App() {
function playCue({id, name, description, type, auto, audioFile, ...props}) {
console.log('Playing cue:', {id, name, description, type, auto, audioFile, ...props});
setCurrentCue({id, name, description, type, auto, audioFile, ...props});
// Handle other cue types and properties here
if(props.audioCue){
@ -103,6 +118,31 @@ function App() {
if(props.lightCue){
// sendOsc(OSC_ADDRESS.CLIENT_INPUT, props.lightCue);
if(props.lightCue=='fade_in_light') refLight.current.fadeIn(); // Fade in light for conversation start
if(props.lightCue=='fade_out_light') refLight.current.fadeOut(); // Fade out light for conversation end
}
if(props.duration){
if(refTimer.current) clearInterval(refTimer.current);
let due= props.duration;
console.log('Start timer:', due);
const next=cuelist.find(c=>c.id==id+1);
refTimer.current= setInterval(()=>{
due--;
if(due<=0){
clearInterval(refTimer.current);
refTimer.current=null;
if(auto){
console.log('Auto play next cue:', next);
playCue(next);
}
}
refDuration.current.innerText= secondToTime(due);
},1000);
}
}
@ -112,6 +152,14 @@ function App() {
sendOsc(OSC_ADDRESS.STOPCUE,'','all');
sendOscToSound(OSC_ADDRESS.SCS_STOP_ALL, '');
}
function reset() {
console.log('Reset all');
sendOsc(OSC_ADDRESS.RESETCUE,'','all');
sendOscToSound(OSC_ADDRESS.SCS_STOP_ALL, '');
refLight.current.set(1);
}
function secondToTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
@ -155,7 +203,7 @@ function App() {
listen('osc_message', (event) => {
console.log(`Received OSC message: ${event.payload}`);
// console.log(`Received OSC message: ${JSON.stringify(event.payload)}`);
onOsc(event.payload);
});
@ -165,14 +213,19 @@ function App() {
return (
<main className="overflow-y-auto flex flex-row gap-8 p-4 min-h-screen">
<section className="flex flex-col gap-4">
<section className="flex-1 flex flex-col gap-4">
<section className="flex flex-row justify-between items-center">
<div className="text-6xl p-4 bg-pink-300">{currentCue ? `${currentCue.name}` : 'None'}</div>
<div className="flex flex-row font-bold items-stretch justify-center w-1/3 bg-pink-300 gap-4 p-2">
<div className="text-4xl">{currentCue ? `${currentCue.name}` : 'None'}</div>
<div className="text-4xl" ref={refDuration}></div>
</div>
<p>
{timestamp}
</p>
<button onClick={stop}>stop all</button>
<span className="flex flex-col gap-2 items-stretch">
<button className="text-4xl" onClick={stop}>stop all</button>
<button className="text-4xl" onClick={reset}>reset all</button>
<Light ref={refLight} />
<span className="flex flex-col gap-2 items-stretch hidden">
<label htmlFor="fade_duration">Fade Duration</label>
<input type="range" id="fade_duration" min="0" max="30" step="0.1" defaultValue={DefaultFadeDuration} className="slider"
onChange={(e) => {
@ -191,8 +244,10 @@ function App() {
<th>Description</th>
<th>Type</th>
<th>Auto</th>
<th>Due | Light | Audio</th>
<th>clientCue</th>
<th>Due</th>
<th>Light</th>
<th>Audio</th>
<th>Client</th>
</tr>
</thead>
<tbody>
@ -214,37 +269,48 @@ function App() {
<td>{description}</td>
<td>{EmojiType[type]}</td>
<td>{auto ? '⤵' : ''}</td>
<td>{props.duration} {props.lightCue && `L${props.lightCue}`} {props.audioCue && `S${props.audioCue}`}</td>
<td>{props.duration}</td>
<td>{props.lightCue && `L${props.lightCue}`}</td>
<td>{props.audioCue && `S${props.audioCue}`}</td>
<td className={`${props.clientCue&& 'bg-green-300 rounded-full text-center font-[900]'}`}>{props.clientCue || ''}</td>
</tr>
))}
</tbody>
</table>
</section>
<table>
<table className="flex-1">
<thead className="bg-blue-500">
<tr className="text-left lowercase font-[900]">
<tr className="text-center lowercase font-[900]">
<th>id</th>
<th>status</th>
<th>cue</th>
<th>timestamp</th>
<th>control</th>
<th>input</th>
<th>due</th>
<th>timestamp</th>
<th></th>
</tr>
</thead>
<tbody>
{Array.from(Array(CLIENT_COUNT).keys()).map((i) => {
const id=i+1;
const log = clientStatus[id];
const log = clientStatus[id.toString()];
return (
<tr key={id} className="text-left lowercase font-[900]">
<tr key={id} className="text-left lowercase">
<td className="font-[900]">{id}</td>
{<>
<td>{log?.length>0 &&log[0]?.status}</td>
<td>{log?.length>0 && log[0]?.name}</td>
<td>{log?.length>0 &&log[0]?.timestamp}</td>
<td>{log?.status}</td>
<td>
<span className="bg-green-200 rounded-full p-2">{log?.name}</span>
</td>
<td className="flex-1 text-[9px] min-w-[80px]">
{log?.input?.substr(log.input.length - 20)?.split('').map((ch,i)=>i%3<1?ch:'*').join('')}
</td>
<td>{log?.duration}</td>
<td className="text-[12px]">{log?.timestamp}</td>
</>}
<td className="flex flex-row gap-1 p-1">
<td className="flex flex-row justify-end gap-1 p-1">
<button onClick={()=>sendOsc(OSC_ADDRESS.STOPCUE,'',id)}>stop</button>
<button onClick={()=>sendOsc(OSC_ADDRESS.RESETCUE,'',id)}>reset</button>
{cuelist?.filter(c=>c.clientCue).map(c=>(
<button key={c.clientCue} onClick={()=>{
sendOsc(OSC_ADDRESS.PLAYCUE, c.clientCue, id);

@ -1,8 +1,11 @@
export const OSC_ADDRESS={
PLAYCUE: '/playcue',
STOPCUE: '/stopcue',
RESETCUE:'/resetcue',
CLIENT_STATUS: '/client',
CLIENT_INPUT: '/client_input',
CLIENT_DURATION:'/client_duration',
SCS_STOP_ALL:'/ctrl/stop_all',

@ -0,0 +1,103 @@
import gsap from "gsap"
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"
import { invoke } from '@tauri-apps/api/core';
const FADE_TIME=5;
export const Light=forwardRef((props, ref)=>{
const refVal=useRef({val: 0});
const refInput=useRef();
const refContainer=useRef();
const refGsap=useRef();
function fade(from, to){
const time= parseFloat(refInput.current.value) || FADE_TIME;
gsap.killTweensOf(refVal.current); // Kill all tweens of refVal
gsap.current=gsap.fromTo(refVal.current,{val: from}, {
val: to,
duration: time,
onUpdate: () => {
// console.log(refVal.current.val);
// sendOsc(OSC_ADDRESS.LIGHT, refVal.current.val.toString());
// invoke('send_dmx_message', {
// message: Math.floor(refVal.current.val * 255).toString(),
// }).then(() => {
// console.log(`dmx message sent: ${Math.floor(refVal.current.val * 255)}`);
// }).catch((error) => {
// console.error('Error sending DMX message:', error);
// });
invoke('send_osc_message', {
key: '/light',
message: refVal.current.val.toString(),
host:`0.0.0.0:0`,
target: '127.0.0.1:8888',
});
if(refContainer.current)
refContainer.current.style.background= `rgba(0, 255, 0, ${refVal.current.val})`; // Update background color based on value
},
});
}
useImperativeHandle(ref, () => ({
fadeIn: ()=>{
console.log('fadeIn');
fade(0, 1);
},
fadeOut: ()=>{
console.log('fadeOut');
fade(1, 0);
},
reset:()=>{
console.log('reset');
refVal.current.val=0;
refContainer.current.style.background= `rgba(0, 255, 0, 0)`; // Reset background color
refInput.current.value=FADE_TIME; // Reset input value
gsap.killTweensOf(refVal.current); // Kill all tweens of refVal
invoke('send_osc_message', {
key: '/light',
message: '0',
host:`0.0.0.0:0`,
target: '127.0.0.1:8888',
});
},
set: (value)=>{
console.log('set', value);
refVal.current.val=value;
refContainer.current.style.background= `rgba(0, 255, 0, ${value})`; // Update background color based on value
invoke('send_osc_message', {
key: '/light',
message: value.toString(),
host:`0.0.0.0:0`,
target: '127.0.0.1:8888',
});
},
}));
useEffect(()=>{
refInput.current.value=FADE_TIME;
},[]);
return (
<div ref={refContainer} className="grid grid-cols-2 justify-between p-1 border *:overflow-hidden">
{/* <span className="flex flex-col justify-between"> */}
<label className="text-center">Light</label>
<input ref={refInput} type="text" className="border w-[5vw]" defaultValue={5}/>
{/* </span>
<span className="flex flex-col justify-between"> */}
<button onClick={()=>fade(0,1)}>fadeIn</button>
<button onClick={()=>fade(1,0)}>fadeOut</button>
<button onClick={()=>ref.current.set(1)}>on</button>
<button onClick={()=>ref.current.set(0)}>off</button>
{/* </span> */}
</div>
)
});
Loading…
Cancel
Save