Files
vrcnext-example-plugins/dist/kitchen-sink.js
T

42 lines
8.4 KiB
JavaScript

var a=`
.ks-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 10px;
padding: 10px 0;
}
.ks-stat {
background: var(--bg-input);
border-radius: 10px;
padding: 12px 14px;
}
.ks-stat-label {
font-size: calc(11px + var(--fs-off, 0px));
color: var(--tx3);
}
.ks-stat-value {
font-size: calc(20px + var(--fs-off, 0px));
font-weight: 600;
color: var(--tx0);
font-variant-numeric: tabular-nums;
}
.ks-log {
max-height: 220px;
overflow-y: auto;
font-family: ui-monospace, monospace;
font-size: calc(11px + var(--fs-off, 0px));
color: var(--tx2);
background: var(--bg-input);
border-radius: 8px;
padding: 8px 10px;
}
.ks-log-line { white-space: pre-wrap; word-break: break-word; }
.ks-log-line + .ks-log-line { margin-top: 3px; }
`;function d(e){let t=document.createElement("div");return t.className="ks-log",e.appendChild(t),{append(n){let i=document.createElement("div");i.className="ks-log-line";let o=new Date().toLocaleTimeString();for(i.textContent=`${o} ${n}`,t.appendChild(i);t.childElementCount>200;)t.firstElementChild?.remove();t.scrollHeight-t.scrollTop-t.clientHeight<40&&(t.scrollTop=t.scrollHeight)},clear(){t.replaceChildren()}}}var u={watchGameLog:{kind:"boolean",label:"Follow the VRChat game log",description:"Streams player joins and world changes into the panel below.",default:!0},oscParameter:{kind:"string",label:"OSC parameter to pulse",description:"Sent to /avatar/parameters/<name> by the dashboard button.",default:"VRCEmote",placeholder:"VRCEmote"},oscValue:{kind:"number",label:"OSC value",default:1,min:0,max:255,step:1},verbosity:{kind:"select",label:"Log verbosity",default:"normal",options:[{value:"quiet",label:"Quiet"},{value:"normal",label:"Normal"},{value:"loud",label:"Everything"}]}},pe={id:"kitchen-sink",settings:u,activate(e){let t={gameLogCount:0,oscParamCount:0,panel:void 0,log:n=>{e.settings.get("verbosity")!=="quiet"&&(e.logger.info(n),t.panel?.append(n))}};e.ui.injectCss(a),p(e),h(e,t),v(e,t),b(e,t),y(e,t),S(e,t),C(e,t),w(e,t),k(e,t),N(e,t),e.logger.info(`Kitchen Sink v${e.version} ready.`)},deactivate(){}};function p(e){e.disposables.add(e.settings.onChange(t=>{e.logger.debug(`Settings changed; verbosity is now ${t.verbosity}.`)}))}function h(e,t){e.events.on("friendTimelineEvent",n=>{t.log(`[friend] ${n.friendName} \u2192 ${n.type}`)}),e.events.on("vrcCurrentInstance",n=>{if(typeof n!="object"||n===null)return;let i=n.worldName;typeof i=="string"&&t.log(`[instance] now in ${i}`)}),e.events.onAny(n=>{e.settings.get("verbosity")==="loud"&&e.logger.debug(`event ${n.type}`)})}function v(e,t){e.gameLog.on(n=>{e.settings.get("watchGameLog")&&(t.gameLogCount+=1,t.log(`[gamelog] ${n.type}: ${n.message}`))}),e.gameLog.onType("OnPlayerJoined",n=>{t.log(`[join] ${n.detail||n.message}`)}),e.gameLog.history(e.signal).then(n=>{e.logger.info(`Game log backlog: ${String(n.length)} entries.`)}).catch(n=>{e.signal.aborted||e.logger.warn(`Could not read the game log: ${String(n)}`)})}function b(e,t){if(!e.osc.available){t.log("[osc] OSC is Windows-only in VRCNext; skipping.");return}e.osc.connect(),e.osc.onParam(n=>{t.oscParamCount+=1,e.settings.get("verbosity")==="loud"&&t.log(`[osc] ${n.name} = ${String(n.value)}`)}),e.osc.onAvatarChange(n=>{t.log(`[osc] avatar ${n.avatarId} with ${String(n.parameters.length)} parameters`)})}function c(e){if(!e.osc.available)return"OSC unavailable (Windows only)";let t=e.settings.get("oscParameter"),n=e.settings.get("oscValue");return e.osc.send(t,"int",n),`${t} = ${String(n)}`}function y(e,t){e.deepLinks.onPrefix("wrld",n=>{t.log(`[link] world ${n.id}`)}),e.deepLinks.on(n=>{t.log(`[link] ${n.prefix}:${n.id}${n.action?` (${n.action})`:""}`)})}function S(e,t){e.router.get("stats",()=>Response.json({gameLogEvents:t.gameLogCount,oscParams:t.oscParamCount,verbosity:e.settings.get("verbosity")})),e.router.get("greet/:name",n=>Response.json({hello:n.params.name??"world"})),e.router.post("osc/pulse",()=>Response.json({sent:c(e)})),e.logger.info(`Routes mounted at ${e.router.base.pathname}`)}function C(e,t){e.contextMenu.contribute(n=>{let i=[{kind:"divider"},{kind:"item",icon:"auto_awesome",label:"Kitchen Sink: log this element",onSelect:()=>{t.log(`[ctx] ${n.element.tagName.toLowerCase()}`)}},{kind:"submenu",icon:"tune",label:"Kitchen Sink: verbosity",items:()=>u.verbosity.options.map(o=>({kind:"item",icon:"adjust",label:o.label,checked:e.settings.get("verbosity")===o.value,onSelect:()=>{e.settings.set("verbosity",o.value)}}))}];if(n.entity!==void 0){let o=n.entity;i.push({kind:"item",icon:"content_copy",label:`Copy ${o.type} id`,onSelect:()=>{navigator.clipboard.writeText(o.id)}})}return i})}function w(e,t){e.gameLog.onType("OnPlayerJoined",n=>{let i=n.detail||n.message;e.notifications.toast({message:`${i} joined.`}),e.notifications.desktopAvailable?e.notifications.desktop({title:"Player joined",subtitle:i,accent:"info"}):t.log("[notify] desktop/VR notifications are Windows-only.")}),e.events.on("friendTimelineEvent",n=>{n.type==="online"&&e.notifications.notifToast({kind:"notification",sender:n.friendName,message:"came online"})})}async function k(e,t){if(!await e.native.ready){t.log(`[native] no companion at ${e.native.endpoint}; VR targets unavailable.`);return}let n=await e.native.targets();t.log(`[native] targets: ${n.map(o=>o.name).join(", ")||"none"}`),e.gameLog.onType("OnPlayerJoined",o=>{let s=o.detail||o.message;e.native.notify({title:"Player joined",content:s,timeoutSecs:4,icon:"user-available",overrides:{wayvr:{content:`${s} joined the instance`,height:200,opacity:.85,alwaysShow:!0}}})}),e.events.on("friendTimelineEvent",o=>{o.type==="online"&&e.native.notify({title:o.friendName,content:"came online",sinks:["wayvr"],timeoutSecs:3,opacity:.7})});let i=await e.native.describe();i!==void 0&&t.log(`[native] companion ${i.version}, services: ${Object.keys(i.services).join(", ")}`)}function N(e,t){e.ui.addDashboardCard({title:"Kitchen Sink",icon:"auto_awesome",order:10,render:n=>{let i=document.createElement("div");i.className="ks-grid",i.append(l("Game log events",()=>t.gameLogCount),l("OSC parameters",()=>t.oscParamCount)),n.appendChild(i);let o=document.createElement("button");o.textContent="Send OSC pulse",o.addEventListener("click",()=>{e.ui.toast({message:`Sent ${c(e)}.`})}),n.appendChild(o)}}),L(e,t),e.ui.addSettingsCard({title:"Kitchen Sink",icon:"auto_awesome",render:n=>{n.appendChild(e.ui.createToggleRow("Follow the VRChat game log",e.settings.get("watchGameLog"),o=>{e.settings.set("watchGameLog",o)}));let i=document.createElement("button");i.textContent="Reset settings",i.addEventListener("click",()=>{e.notifications.confirm({title:"Reset Kitchen Sink",message:"Restore every Kitchen Sink setting to its default?",confirmLabel:"Reset",icon:"restart_alt"}).then(async o=>{o&&(await e.settings.reset(),e.notifications.toast({message:"Kitchen Sink settings reset."}))})}),n.appendChild(i)}})}function L(e,t){e.ui.addNavTab({label:"Kitchen Sink",icon:"auto_awesome",render:n=>{let i=e.ui.kit,o=i.card({title:"Live activity",icon:"radar"});t.panel=d(o);let s=i.emptyState("No request yet.");n.append(i.layout(i.grid([i.card({title:"Counters",icon:"bolt",children:[i.stat({label:"Game log events",value:String(t.gameLogCount)}),i.stat({label:"OSC parameters",value:String(t.oscParamCount)})]}),i.card({title:"Capabilities",icon:"tune",children:[i.row({label:"OSC",value:e.osc.available?i.badge("ok","Ready"):i.badge("warn","Windows only"),detail:"Sent through VRCNext\u2019s own sockets."}),i.row({label:"Native companion",value:e.native.available?i.badge("ok","Connected"):i.badge("neutral","Not running"),detail:e.native.endpoint}),i.toggleRow({label:"Follow the game log",detail:"Mirrors VRChat log lines into the panel below.",value:e.settings.get("watchGameLog"),onChange:r=>{e.settings.set("watchGameLog",r)}})]}),i.card({title:"In-page routes",icon:"account_tree",children:[i.description("Plugin routes are reachable from this page only, never from curl."),i.buttonRow(i.button({label:"GET stats",icon:"download",onClick:()=>{e.router.fetch("stats").then(async r=>{s.textContent=await r.text()}).catch(r=>{s.textContent=String(r)})}})),s]})]),o))}})}function l(e,t){let n=document.createElement("div");n.className="ks-stat";let i=document.createElement("div");i.className="ks-stat-label",i.textContent=e;let o=document.createElement("div");return o.className="ks-stat-value",o.textContent=String(t()),new IntersectionObserver(r=>{for(let g of r)g.isIntersecting&&(o.textContent=String(t()))}).observe(n),n.append(i,o),n}export{pe as default};
//# sourceMappingURL=kitchen-sink.js.map