* feat spot: init commit for extension * nvmrc * fix login flow * Spots Gridview Updates (#2422) * feat ui: login flow for spot extension * spot list, store and service created * some fixing for header * start work on single spot * spot player start * header for player, comments, icons, etc * split stuff into compoennts, create player state manager * player controls, activity panel etc etc * comments, empty page, rename and stuff * interval buttons etc * access modal * pubkey support * fix tooltip * limit 10 -> 9 * hls lib instead of videojs * some warnings * fix date display for exp * change public links * display more client data * fix cleaning, init comment * map network to replay player network ev * stream support, console panel, close panels on X * fixing streaming, destroy on leave * fix autoplay * show notification on spot login * fix spot login * backup player added, fix audio issue * show thumbnail when no video, add spot roles * add poster thumbnail * some fixes to video check * fix events jump * fix play btn * try catch over pubkey * feat ui: login flow for spot extension * spot list, store and service created * some fixing for header * start work on single spot * spot player start * header for player, comments, icons, etc * split stuff into compoennts, create player state manager * player controls, activity panel etc etc * comments, empty page, rename and stuff * interval buttons etc * access modal * pubkey support * fix tooltip * limit 10 -> 9 * hls lib instead of videojs * some warnings * fix date display for exp * change public links * display more client data * fix cleaning, init comment * map network to replay player network ev * stream support, console panel, close panels on X * fixing streaming, destroy on leave * fix autoplay * show notification on spot login * fix spot login * backup player added, fix audio issue * show thumbnail when no video, add spot roles * add poster thumbnail * some fixes to video check * fix events jump * fix play btn * try catch over pubkey * icons * Various updates * Update SVG.tsx * Update SideMenu.tsx * SpotList & Menu updates * feat ui: login flow for spot extension * spot list, store and service created * some fixing for header * start work on single spot * spot player start * header for player, comments, icons, etc * split stuff into compoennts, create player state manager * player controls, activity panel etc etc * comments, empty page, rename and stuff * interval buttons etc * access modal * pubkey support * fix tooltip * limit 10 -> 9 * hls lib instead of videojs * some warnings * fix date display for exp * change public links * display more client data * fix cleaning, init comment * map network to replay player network ev * stream support, console panel, close panels on X * fixing streaming, destroy on leave * fix autoplay * show notification on spot login * fix spot login * backup player added, fix audio issue * show thumbnail when no video, add spot roles * add poster thumbnail * some fixes to video check * fix events jump * fix play btn * try catch over pubkey * icons * spot login pinging * Spot List & Player Updates * move spot login flow to login comp, use separate spot login path for unique jwt * invalidate spot jwt on logout * add visual data on page load event * typo fix * Spot Listing improvements post review. * Update SpotListItem.tsx * Improved Spot List and Item Details * Minor improvements * More improvements * Public player header improvements * Moved formatExpirationTime to utils * fixes after merge --------- Co-authored-by: nick-delirium <nikita@openreplay.com> * set sso link to <a>? * some small perf fixes * login duck reformat... * Update frontend.yaml * add observer to spot list header * split list header * update spotjwt param in router * fix toast in router * fix async fetch, move ctx * capture space btn ev * fix header link * public sharing error msg * fix err msg for unsuccessful rec start * fix list alignment * Caching assets. Finally!!! * fix typing in comment field * add pubkey to comments, fix console jump btn * no content comp * change refresh token logic * move thumbnail ts * move thumbnail ts * fix tab change * switch up toggler * early exit if no jwt present * regenerate icons * fix location str * fix ctx * change thumnail res, return autoplay for video player * parse links in console rows, fix injected method parse? * remove ts from js * fix console parsing order? * fixes for autoplay * xray for spot player * move to spot list after login; esc to cancel; fix signup link; move ux commit * kb sc for skipping; xray for spot ext * track aborted requests * tooltip for readability * fixing empty state * New blank state + various minor improvements (#2471) * New blank state + various minor improvements * apres merge --------- Co-authored-by: nick-delirium <nikita@openreplay.com> * rm temp v * init or card * empty state debug * empty state debug * empty state debug * fix initor img * spotonly scope support * Improved Spot dead-end pages (#2475) * Improved Spot dead-end pages * Initiate OpenReplay Setup and some more * get scope changes * fix crash * scope upgrade/downgrade * scope setup flow * ping for backend * upgrade wxt deps * cancel ping int on expiration * check rec status * fix ping * check video processing state * check video processing state * fix xray close, network highlight, fcp rounding * update wxt, move open spot stuff to settings * fix some history issues * fix spot login flow * fix spot login again * fix spot login again * don't send two requests * limit messages for logged users * limit messages for logged users * fix public ignore * microphone stuff * microphone stuff * Various improvements (#2509) * Various improvements - Updated icons in mic settings - Included prefix in Spot title - Save recording notification has been updated - Other minor UI improvements * Inline declaration of spot name field, and settings UI * str f --------- Co-authored-by: nick-delirium <nikita@openreplay.com> * UI changes in player header, spot list (#2510) * Added UI elements in player page - Badge with counts for comments - Download and Delete dropdown in player - Spot selection -- UI improvement * Minor copy updates * completing changes --------- Co-authored-by: nick-delirium <nikita@openreplay.com> * rm cmt * fix cellmeasurer * thumbnail dur * fix download * Minor fixes (#2512) - Spot delete confirmation - Spot comments UI update - Minor copy updates * limit number of notif messages * add spot title to doc title, add cache groups for webpack * drop mic controls from recording popup view * fix for webpack compress * fix for auto mic pickup * change status banners * move svgs around, remove undefined check * refactor svgs * fix timetable scaling * fix error popup * self contain css * pre-select spot on spot onboarding --------- Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com> Co-authored-by: Rajesh Rajendran <rjshrjndrn@users.noreply.github.com>
339 lines
9.8 KiB
TypeScript
339 lines
9.8 KiB
TypeScript
import orLogo from "@/assets/orSpot.svg";
|
|
import micOff from "@/assets/mic-off-red.svg";
|
|
import micOn from "@/assets/mic-on-dark.svg";
|
|
import Login from "@/entrypoints/popup/Login";
|
|
import Settings from "@/entrypoints/popup/Settings";
|
|
import { createSignal, createEffect, onMount } from "solid-js";
|
|
import Dropdown from "@/entrypoints/popup/Dropdown";
|
|
import Button from "@/entrypoints/popup/Button";
|
|
import {
|
|
ChevronSvg,
|
|
RecordDesktopSvg,
|
|
RecordTabSvg,
|
|
HomePageSvg,
|
|
SlackSvg,
|
|
SettingsSvg,
|
|
} from "./Icons";
|
|
|
|
async function getAudioDevices() {
|
|
try {
|
|
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
const audioDevices = devices
|
|
.filter((device) => device.kind === "audioinput")
|
|
.map((device) => ({ label: device.label, id: device.deviceId }));
|
|
|
|
return { granted: true, audioDevices };
|
|
} catch (error) {
|
|
console.error("Error accessing audio devices:", error);
|
|
const msg = error.message ?? "";
|
|
return {
|
|
granted: false,
|
|
denied: msg.includes("denied"),
|
|
audioDevices: [],
|
|
};
|
|
}
|
|
}
|
|
|
|
const orSite = () => {
|
|
window.open("https://openreplay.com", "_blank");
|
|
};
|
|
|
|
function Header({ openSettings }: { openSettings: () => void }) {
|
|
const openHomePage = async () => {
|
|
const { settings } = await chrome.storage.local.get("settings");
|
|
return window.open(`${settings.ingestPoint}/spots`, "_blank");
|
|
};
|
|
return (
|
|
<div class={"flex items-center gap-1"}>
|
|
<div
|
|
class="flex items-center gap-1 cursor-pointer hover:opacity-50"
|
|
onClick={orSite}
|
|
>
|
|
<img src={orLogo} class="w-5" alt={"OpenReplay Spot"} />
|
|
<div class={"text-neutral-600"}>
|
|
<span class={"text-lg font-semibold text-black"}>Spot</span>
|
|
<span class={"text-xs ml-2"}>by OpenReplay</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class={"ml-auto flex items-center gap-2"}>
|
|
<div class="text-sm tooltip tooltip-bottom" data-tip="My Spots">
|
|
<div onClick={openHomePage}>
|
|
<div class={"cursor-pointer p-2 hover:bg-indigo-50 rounded-full"}>
|
|
<HomePageSvg />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="text-sm tooltip tooltip-bottom"
|
|
data-tip="Get help on Slack"
|
|
>
|
|
<a
|
|
href={
|
|
"https://join.slack.com/t/openreplay/shared_invite/zt-2brqlwcis-k7OtqHkW53EAoTRqPjCmyg"
|
|
}
|
|
target={"_blank"}
|
|
>
|
|
<div class={"cursor-pointer p-2 hover:bg-indigo-50 rounded-full"}>
|
|
<SlackSvg />
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div
|
|
class="text-sm tooltip tooltip-bottom"
|
|
data-tip="Settings"
|
|
onClick={openSettings}
|
|
>
|
|
<div class={"cursor-pointer p-2 hover:bg-indigo-50 rounded-full"}>
|
|
<SettingsSvg />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const STATE = {
|
|
empty: "empty",
|
|
login: "login",
|
|
ready: "ready",
|
|
starting: "starting",
|
|
recording: "recording",
|
|
};
|
|
|
|
function App() {
|
|
const [state, setState] = createSignal(STATE.empty);
|
|
const [isSettingsOpen, setIsSettingsOpen] = createSignal(false);
|
|
const [mic, setMic] = createSignal(false);
|
|
const [selectedAudioDevice, setSelectedAudioDevice] = createSignal("");
|
|
const [hasPermissions, setHasPermissions] = createSignal(false);
|
|
|
|
onMount(() => {
|
|
browser.runtime.onMessage.addListener((message) => {
|
|
if (message.type === "popup:no-login") {
|
|
setState(STATE.login);
|
|
}
|
|
if (message.type === "popup:login") {
|
|
setState(STATE.ready);
|
|
}
|
|
if (message.type === "popup:stopped") {
|
|
setState(STATE.ready);
|
|
}
|
|
if (message.type === "popup:started") {
|
|
setState(STATE.recording);
|
|
}
|
|
if (message.type === "popup:mic-status") {
|
|
setMic(message.status);
|
|
}
|
|
});
|
|
void browser.runtime.sendMessage({ type: "popup:check-status" });
|
|
});
|
|
|
|
const startRecording = async (reqTab: "tab" | "desktop") => {
|
|
setState(STATE.starting);
|
|
await browser.runtime.sendMessage({
|
|
type: "popup:start",
|
|
area: reqTab,
|
|
mic: mic(),
|
|
audioId: selectedAudioDevice(),
|
|
permissions: hasPermissions(),
|
|
});
|
|
window.close();
|
|
};
|
|
|
|
const stopRecording = () => {
|
|
void browser.runtime.sendMessage({
|
|
type: "popup:stop",
|
|
mic: mic(),
|
|
audioId: selectedAudioDevice(),
|
|
});
|
|
};
|
|
|
|
const toggleMic = async () => {
|
|
setMic(!mic());
|
|
};
|
|
|
|
const openSettings = () => {
|
|
setIsSettingsOpen(true);
|
|
};
|
|
const closeSettings = () => {
|
|
setIsSettingsOpen(false);
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
{isSettingsOpen() ? (
|
|
<Settings goBack={closeSettings} />
|
|
) : (
|
|
<div class={"flex flex-col gap-4 p-5"}>
|
|
<Header openSettings={openSettings} />
|
|
|
|
{state() === STATE.login ? (
|
|
<Login />
|
|
) : (
|
|
<>
|
|
{state() === STATE.recording ? (
|
|
<Button
|
|
name={"End Recording"}
|
|
onClick={() => stopRecording()}
|
|
/>
|
|
) : null}
|
|
{state() === STATE.starting ? (
|
|
<div
|
|
class={
|
|
"flex flex-row items-center gap-2 w-full justify-center"
|
|
}
|
|
>
|
|
<div class="py-4">Your recording is starting</div>
|
|
</div>
|
|
) : null}
|
|
{state() === STATE.ready ? (
|
|
<>
|
|
<div class="flex flex-row items-center gap-2 w-full justify-center">
|
|
<button
|
|
class="btn bg-indigo-100 text-base hover:bg-primary hover:text-white w-6/12"
|
|
name="Record Tab"
|
|
onClick={() => startRecording("tab")}
|
|
>
|
|
<RecordTabSvg />
|
|
Record Tab
|
|
</button>
|
|
|
|
<button
|
|
class="btn bg-teal-50 text-base hover:bg-primary hover:text-white"
|
|
name={"Record Desktop"}
|
|
onClick={() => startRecording("desktop")}
|
|
>
|
|
<RecordDesktopSvg />
|
|
Record Desktop
|
|
</button>
|
|
</div>
|
|
<AudioPicker
|
|
mic={mic}
|
|
toggleMic={toggleMic}
|
|
selectedAudioDevice={selectedAudioDevice}
|
|
setSelectedAudioDevice={setSelectedAudioDevice}
|
|
setHasPermissions={setHasPermissions}
|
|
/>
|
|
</>
|
|
) : null}
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface IAudioPicker {
|
|
mic: () => boolean;
|
|
toggleMic: () => void;
|
|
selectedAudioDevice: () => string;
|
|
setSelectedAudioDevice: (value: string) => void;
|
|
setHasPermissions: (value: boolean) => void;
|
|
}
|
|
function AudioPicker(props: IAudioPicker) {
|
|
const [audioDevices, setAudioDevices] = createSignal(
|
|
[] as { label: string; id: string }[],
|
|
);
|
|
const [checkedAudioDevices, setCheckedAudioDevices] = createSignal(0);
|
|
|
|
createEffect(() => {
|
|
chrome.storage.local.get("audioPerm", (data) => {
|
|
console.log("audioPerm", data.audioPerm);
|
|
if (data.audioPerm && audioDevices().length === 0) {
|
|
props.setHasPermissions(true);
|
|
void checkAudioDevices();
|
|
}
|
|
});
|
|
});
|
|
|
|
const checkAudioDevices = async () => {
|
|
const { granted, audioDevices, denied } = await getAudioDevices();
|
|
if (!granted && !denied) {
|
|
void browser.runtime.sendMessage({
|
|
type: "popup:get-audio-perm",
|
|
});
|
|
browser.runtime.onMessage.addListener((message) => {
|
|
if (message.type === "popup:audio-perm") {
|
|
void checkAudioDevices();
|
|
}
|
|
});
|
|
} else if (audioDevices.length > 0) {
|
|
chrome.storage.local.set({ audioPerm: granted });
|
|
setAudioDevices(audioDevices);
|
|
props.setSelectedAudioDevice(audioDevices[0]?.id || "");
|
|
}
|
|
};
|
|
|
|
const checkAudio = async () => {
|
|
if (checkedAudioDevices() > 0) {
|
|
return;
|
|
}
|
|
setCheckedAudioDevices(1);
|
|
await checkAudioDevices();
|
|
setCheckedAudioDevices(2);
|
|
};
|
|
const onSelect = (value) => {
|
|
props.setSelectedAudioDevice(value);
|
|
if (!props.mic()) {
|
|
props.toggleMic();
|
|
}
|
|
};
|
|
|
|
const onMicToggle = async () => {
|
|
if (!audioDevices().length) {
|
|
return await checkAudioDevices();
|
|
}
|
|
if (!props.selectedAudioDevice() && audioDevices().length) {
|
|
onSelect(audioDevices()[0].id);
|
|
} else {
|
|
props.toggleMic();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div class={"inline-flex items-center gap-1 text-xs"}>
|
|
<div
|
|
class={
|
|
"p-1 cursor-pointer btn btn-xs bg-white hover:bg-indigo-50 pointer-events-auto tooltip tooltip-right text-sm font-normal"
|
|
}
|
|
data-tip={props.mic() ? "Switch Off Mic" : "Switch On Mic"}
|
|
onClick={onMicToggle}
|
|
>
|
|
<img
|
|
src={props.mic() ? micOn : micOff}
|
|
alt={props.mic() ? "microphone on" : "microphone off"}
|
|
width={16}
|
|
height={16}
|
|
/>
|
|
</div>
|
|
<div
|
|
class={
|
|
"flex items-center gap-1 btn btn-xs btn-ghost hover:bg-neutral/20 rounded-lg pointer-events-auto"
|
|
}
|
|
onClick={checkAudio}
|
|
>
|
|
{audioDevices().length === 0 ? (
|
|
<div class="max-w-64 block leading-tight cursor-pointer whitespace-nowrap overflow-hidden font-normal">
|
|
{checkedAudioDevices() === 1
|
|
? "Loading audio devices"
|
|
: "Grant microphone access"}
|
|
</div>
|
|
) : (
|
|
<Dropdown
|
|
options={audioDevices()}
|
|
selected={props.selectedAudioDevice()}
|
|
onChange={onSelect}
|
|
/>
|
|
)}
|
|
<ChevronSvg />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|