feat: add remote control to integration view
This commit is contained in:
parent
b0c3a48da6
commit
23245dd549
180
www/src/components/remote-control.jsx
Normal file
180
www/src/components/remote-control.jsx
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { createEffect, createMemo, mergeProps } from "solid-js";
|
||||||
|
import Remote from "../data/remote";
|
||||||
|
import Command from "../data/command";
|
||||||
|
|
||||||
|
function RemoteControl(props) {
|
||||||
|
props = mergeProps(
|
||||||
|
{
|
||||||
|
remote: new Remote(),
|
||||||
|
onCommand: () => {},
|
||||||
|
},
|
||||||
|
props
|
||||||
|
);
|
||||||
|
|
||||||
|
const BUTTON_SIZE_REGULAR = "regular";
|
||||||
|
const BUTTON_SIZE_SMALL = "small";
|
||||||
|
|
||||||
|
const BUTTON_HEIGHT = 2;
|
||||||
|
const BUTTON_WIDTH = BUTTON_HEIGHT * 1.4;
|
||||||
|
const SMALL_BUTTON_HEIGHT = 1.5;
|
||||||
|
const SMALL_BUTTON_WIDTH = SMALL_BUTTON_HEIGHT * 1.3334;
|
||||||
|
const TYPES = Command.TYPES;
|
||||||
|
|
||||||
|
const commandMap = createMemo(() =>
|
||||||
|
props.remote
|
||||||
|
.getCommands()
|
||||||
|
.reduce(
|
||||||
|
(map, command) => ({ ...map, [command.getCommandType()]: command }),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let layout = [
|
||||||
|
[TYPES.POWER, null, TYPES.INPUT],
|
||||||
|
[TYPES.ONE, TYPES.TWO, TYPES.THREE],
|
||||||
|
[TYPES.FOUR, TYPES.FIVE, TYPES.SIX],
|
||||||
|
[TYPES.SEVEN, TYPES.EIGHT, TYPES.NINE],
|
||||||
|
[TYPES.VOLUME_UP, TYPES.ZERO, TYPES.CHANNEL_UP],
|
||||||
|
[TYPES.VOLUME_DOWN, TYPES.MUTE, TYPES.CHANNEL_DOWN],
|
||||||
|
[TYPES.MENU, TYPES.HOME, TYPES.SETTINGS],
|
||||||
|
[TYPES.OPTIONS, TYPES.UP, TYPES.INFO],
|
||||||
|
[TYPES.LEFT, TYPES.ENTER, TYPES.RIGHT],
|
||||||
|
[TYPES.RETURN, TYPES.DOWN, TYPES.EXIT],
|
||||||
|
[TYPES.RED, TYPES.GREEN, TYPES.YELLOW, TYPES.BLUE],
|
||||||
|
[TYPES.PLAY, TYPES.PAUSE, TYPES.STOP],
|
||||||
|
[TYPES.REWIND, null, TYPES.FORWARD],
|
||||||
|
];
|
||||||
|
|
||||||
|
function toButtonProps(type) {
|
||||||
|
let mapping = {
|
||||||
|
[TYPES.POWER]: () => ({ icon: "bi-power", text: "" }),
|
||||||
|
[TYPES.INPUT]: () => ({ icon: "bi-box-arrow-in-right", text: "" }),
|
||||||
|
[TYPES.VOLUME_UP]: () => ({ icon: "bi-volume-up", text: "" }),
|
||||||
|
[TYPES.VOLUME_DOWN]: () => ({ icon: "bi-volume-down", text: "" }),
|
||||||
|
[TYPES.MUTE]: () => ({ icon: "bi-volume-mute", text: "" }),
|
||||||
|
[TYPES.CHANNEL_UP]: () => ({ icon: "bi-chevron-up", text: "" }),
|
||||||
|
[TYPES.CHANNEL_DOWN]: () => ({ icon: "bi-chevron-down", text: "" }),
|
||||||
|
[TYPES.HOME]: () => ({ icon: "bi-house-door", text: "" }),
|
||||||
|
[TYPES.SETTINGS]: () => ({ icon: "bi-gear", text: "" }),
|
||||||
|
[TYPES.INFO]: () => ({ icon: "bi-info-circle", text: "" }),
|
||||||
|
[TYPES.UP]: () => ({ icon: "bi-arrow-up", text: "" }),
|
||||||
|
[TYPES.DOWN]: () => ({ icon: "bi-arrow-down", text: "" }),
|
||||||
|
[TYPES.LEFT]: () => ({ icon: "bi-arrow-left", text: "" }),
|
||||||
|
[TYPES.RIGHT]: () => ({ icon: "bi-arrow-right", text: "" }),
|
||||||
|
[TYPES.ENTER]: () => ({ icon: "", text: "OK" }),
|
||||||
|
[TYPES.EXIT]: () => ({ icon: "", text: "EXIT" }),
|
||||||
|
[TYPES.OPTIONS]: () => ({ icon: "bi-list-task", text: "" }),
|
||||||
|
[TYPES.RETURN]: () => ({ icon: "bi-arrow-return-left", text: "" }),
|
||||||
|
[TYPES.ONE]: () => ({ icon: "", text: "1" }),
|
||||||
|
[TYPES.TWO]: () => ({ icon: "", text: "2" }),
|
||||||
|
[TYPES.THREE]: () => ({ icon: "", text: "3" }),
|
||||||
|
[TYPES.FOUR]: () => ({ icon: "", text: "4" }),
|
||||||
|
[TYPES.FIVE]: () => ({ icon: "", text: "5" }),
|
||||||
|
[TYPES.SIX]: () => ({ icon: "", text: "6" }),
|
||||||
|
[TYPES.SEVEN]: () => ({ icon: "", text: "7" }),
|
||||||
|
[TYPES.EIGHT]: () => ({ icon: "", text: "8" }),
|
||||||
|
[TYPES.NINE]: () => ({ icon: "", text: "9" }),
|
||||||
|
[TYPES.ZERO]: () => ({ icon: "", text: "0" }),
|
||||||
|
[TYPES.MENU]: () => ({ icon: "", text: "MENU" }),
|
||||||
|
[TYPES.PLAY]: () => ({ icon: "bi-play", text: "" }),
|
||||||
|
[TYPES.PAUSE]: () => ({ icon: "bi-pause", text: "" }),
|
||||||
|
[TYPES.STOP]: () => ({ icon: "bi-stop", text: "" }),
|
||||||
|
[TYPES.REWIND]: () => ({ icon: "bi-rewind", text: "" }),
|
||||||
|
[TYPES.FORWARD]: () => ({ icon: "bi-fast-forward", text: "" }),
|
||||||
|
[TYPES.RED]: () => ({
|
||||||
|
class: "bg-danger",
|
||||||
|
buttonSize: BUTTON_SIZE_SMALL,
|
||||||
|
}),
|
||||||
|
[TYPES.GREEN]: () => ({
|
||||||
|
class: "bg-success",
|
||||||
|
buttonSize: BUTTON_SIZE_SMALL,
|
||||||
|
}),
|
||||||
|
[TYPES.YELLOW]: () => ({
|
||||||
|
class: "bg-warning",
|
||||||
|
buttonSize: BUTTON_SIZE_SMALL,
|
||||||
|
}),
|
||||||
|
[TYPES.BLUE]: () => ({
|
||||||
|
class: "bg-primary",
|
||||||
|
buttonSize: BUTTON_SIZE_SMALL,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!(type in mapping)) return {};
|
||||||
|
let props = mapping[type]();
|
||||||
|
props.type = type;
|
||||||
|
props.text = props.text || "";
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PlaceholderButton() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={`width: ${BUTTON_WIDTH}em; height: ${BUTTON_HEIGHT}em; margin: 0.2em;`}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RemoteButton(props) {
|
||||||
|
props = mergeProps(
|
||||||
|
{
|
||||||
|
onClick: () => {},
|
||||||
|
disabled: false,
|
||||||
|
class: "",
|
||||||
|
buttonSize: BUTTON_SIZE_REGULAR,
|
||||||
|
icon: "",
|
||||||
|
text: "",
|
||||||
|
},
|
||||||
|
props
|
||||||
|
);
|
||||||
|
|
||||||
|
let buttonWidth = BUTTON_WIDTH;
|
||||||
|
let buttonHeight = BUTTON_HEIGHT;
|
||||||
|
if (props.buttonSize === BUTTON_SIZE_SMALL) {
|
||||||
|
buttonWidth = SMALL_BUTTON_WIDTH;
|
||||||
|
buttonHeight = SMALL_BUTTON_HEIGHT;
|
||||||
|
}
|
||||||
|
let buttonFontSize = buttonHeight * 0.4;
|
||||||
|
let iconSize = buttonHeight * 0.5;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
class={
|
||||||
|
"btn btn-dark d-flex justify-content-center align-items-center " +
|
||||||
|
props.class
|
||||||
|
}
|
||||||
|
style={`width: ${buttonWidth}em; height: ${buttonHeight}em; margin: 0.2em;`}
|
||||||
|
onClick={props.onClick}
|
||||||
|
disabled={props.disabled}
|
||||||
|
>
|
||||||
|
<i style={`font-size: ${iconSize}em;`} class={"bi " + props.icon} />
|
||||||
|
<span style={`font-size: ${buttonFontSize}em;`}>{props.text}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="d-flex flex-column justify-content-center align-items-center p-1 bg-secondary rounded">
|
||||||
|
{layout.map((row) => (
|
||||||
|
<div class="d-flex flex-row justify-content-center align-items-center">
|
||||||
|
{row
|
||||||
|
.map(toButtonProps)
|
||||||
|
.map(({ type, class: className, buttonSize, icon, text }) =>
|
||||||
|
!type ? (
|
||||||
|
<PlaceholderButton />
|
||||||
|
) : (
|
||||||
|
<RemoteButton
|
||||||
|
class={className}
|
||||||
|
buttonSize={buttonSize}
|
||||||
|
onClick={() => props.onCommand(commandMap()[type])}
|
||||||
|
disabled={!commandMap()[type]}
|
||||||
|
icon={icon}
|
||||||
|
text={text}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RemoteControl;
|
||||||
@ -1,6 +1,9 @@
|
|||||||
import Command from "../data/command";
|
import Command from "../data/command";
|
||||||
import Serializer from "../data/serializer";
|
import Serializer from "../data/serializer";
|
||||||
import Net from "../tools/net";
|
import Net from "../tools/net";
|
||||||
|
import WebRTCService from "./webrtc-service";
|
||||||
|
|
||||||
|
const MESSAGE_TYPE_COMMAND = "command";
|
||||||
|
|
||||||
function RemoteService() {
|
function RemoteService() {
|
||||||
async function getRemote(remoteId) {
|
async function getRemote(remoteId) {
|
||||||
@ -104,6 +107,11 @@ function RemoteService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendCommand(command) {
|
||||||
|
let commandObject = Serializer.serializeCommand(command);
|
||||||
|
WebRTCService.sendDataJson({type: MESSAGE_TYPE_COMMAND, data: commandObject});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getRemote,
|
getRemote,
|
||||||
getRemotes,
|
getRemotes,
|
||||||
@ -113,6 +121,7 @@ function RemoteService() {
|
|||||||
createCommand,
|
createCommand,
|
||||||
createCommands,
|
createCommands,
|
||||||
deleteCommand,
|
deleteCommand,
|
||||||
|
sendCommand,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
|
import {
|
||||||
|
createEffect,
|
||||||
|
createMemo,
|
||||||
|
createResource,
|
||||||
|
createSignal,
|
||||||
|
onCleanup,
|
||||||
|
} from "solid-js";
|
||||||
import WebRTCService from "../../services/webrtc-service";
|
import WebRTCService from "../../services/webrtc-service";
|
||||||
import Integration from "../../data/integration";
|
import Integration from "../../data/integration";
|
||||||
import DeviceService from "../../services/device-service";
|
import DeviceService from "../../services/device-service";
|
||||||
import UrlUtils from "../../tools/url-utils";
|
import UrlUtils from "../../tools/url-utils";
|
||||||
|
import RemoteControl from "../../components/remote-control";
|
||||||
|
import RemoteService from "../../services/remotes-service";
|
||||||
|
|
||||||
const [integration, setIntegration] = createSignal(new Integration());
|
const [integration, setIntegration] = createSignal(new Integration());
|
||||||
|
|
||||||
@ -25,6 +33,13 @@ function IntegrationView(props) {
|
|||||||
WebRTCService.onDataChannelOpen(handleDataChannelOpen);
|
WebRTCService.onDataChannelOpen(handleDataChannelOpen);
|
||||||
let videoElement = null;
|
let videoElement = null;
|
||||||
|
|
||||||
|
const [availableRemotes] = createResource(RemoteService.getRemotes, {
|
||||||
|
initialValue: [],
|
||||||
|
});
|
||||||
|
const [selectedRemote, setSelectedRemote] = createSignal();
|
||||||
|
|
||||||
|
createEffect(() => handleRemoteSelected(availableRemotes().shift()?.getId()));
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
let url = UrlUtils.getUrl();
|
let url = UrlUtils.getUrl();
|
||||||
url = UrlUtils.addQueryParameter(url, "id", integration()?.getId());
|
url = UrlUtils.addQueryParameter(url, "id", integration()?.getId());
|
||||||
@ -65,6 +80,16 @@ function IntegrationView(props) {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRemoteSelected(remoteId) {
|
||||||
|
if (!remoteId) return;
|
||||||
|
let remote = await RemoteService.getRemote(remoteId);
|
||||||
|
setSelectedRemote(remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemoteButtonPressed(command) {
|
||||||
|
RemoteService.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={
|
class={
|
||||||
@ -99,12 +124,30 @@ function IntegrationView(props) {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-fill d-flex flex-column justify-content-center align-items-center rounded overflow-hidden">
|
<div class="flex-fill d-flex flex-row overflow-hidden">
|
||||||
<video
|
<div class="flex-fill rounded overflow-hidden">
|
||||||
ref={videoElement}
|
<video
|
||||||
class="w-100 h-100"
|
ref={videoElement}
|
||||||
style="background-color: #000"
|
class="w-100 h-100"
|
||||||
></video>
|
style="background-color: #000"
|
||||||
|
></video>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column ps-3">
|
||||||
|
<select
|
||||||
|
class="form-select mb-3"
|
||||||
|
onChange={(e) => handleRemoteSelected(e.target.value)}
|
||||||
|
>
|
||||||
|
{availableRemotes().map((remote, index) => (
|
||||||
|
<option value={remote.getId()} selected={index === 0}>
|
||||||
|
{remote.getTitle()}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<RemoteControl
|
||||||
|
onCommand={handleRemoteButtonPressed}
|
||||||
|
remote={selectedRemote()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user