openreplay/spot/entrypoints/popup/App.tsx
Delirium 1326bb2eae
feat spot: init commit for extension (#2452)
* 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>
2024-08-29 13:35:58 +02:00

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;