Skip to content

Commit

Permalink
feat: implement recordings (#155)
Browse files Browse the repository at this point in the history
  • Loading branch information
sverben authored Dec 24, 2024
1 parent de070fe commit b79972f
Show file tree
Hide file tree
Showing 14 changed files with 474 additions and 68 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
VITE_MATOMO_URL="https://leaphyeasybloqs.com/matomo/"
VITE_MATOMO_SITE_ID="1"
VITE_BACKEND_URL="https://leaphyeasybloqs.com"
VITE_RECORDINGS_API="https://recordings.leaphyeasybloqs.com"
VITE_RECORDINGS_ADDRESS=".leaphyeasybloqs.com"
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
VITE_BACKEND_URL="https://testleaphyeasybloqs.com"
VITE_RECORDINGS_API="http://localhost:5173"
VITE_RECORDINGS_ADDRESS=".localhost"
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"date-fns": "^4.1.0",
"jszip": "^3.10.1",
"monaco-editor": "^0.52.0",
"rrweb": "^2.0.0-alpha.4",
"socket.io-client": "^4.8.1",
"svelte-fa": "^4.0.2",
"svelte-i18n": "^4.0.0",
"svelte-markdown": "^0.4.1",
Expand Down
10 changes: 9 additions & 1 deletion src/assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,13 @@
"UNDEFINED_ROBOT": "Project name invalid",
"UNDEFINED_ROBOT_MESSAGE": "This does seem like a valid Leaphy Project, but the robot type was not included in the name, we've kept your previous robot selection for now.",
"ROBOT_RESERVED": "Unable to connect to robot",
"ROBOT_RESERVED_MESSAGE": "It appears that we're unable to connect to the robot, the port might be in use by another program or another tab"
"ROBOT_RESERVED_MESSAGE": "It appears that we're unable to connect to the robot, the port might be in use by another program or another tab",
"RECORDING": "Recording is on!",
"RECORDING_DESCRIPTION": "This version of Leaphy EasyBloqs will record your screen for use by an external party for the project {project}, by continuing you are automatically agreeing to this.\n\nTo disable recordings, you may visit our regular site.\n\nPlease enter your name to continue",
"SUBMISSIONS_CLOSED_DESCRIPTION": "The submissions for this project are closed, please use the regular site for Leaphy EasyBloqs to continue building",
"ERROR": "Something went wrong!",
"INVALID_PROJECT": "Invalid project",
"PROJECT_SUBMISSIONS_CLOSED": "Submissions closed",
"PROJECT_NO_OTHER_PARTICIPANTS": "Invalid name",
"NO_PARTICIPANTS": "No users found, please ask your administrator to create them"
}
10 changes: 9 additions & 1 deletion src/assets/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,13 @@
"UNDEFINED_ROBOT": "Ongeldige project naam",
"UNDEFINED_ROBOT_MESSAGE": "Dit project ziet er goed uit, maar het type robot staat niet in de naam, je vorige robot selectie is behouden",
"ROBOT_RESERVED": "Verbinding onmogelijk",
"ROBOT_RESERVED_MESSAGE": "Het lijkt erop dat we niet kunnen verbinden met de robot, de poort zou in gebruik kunnen zijn door een ander programma of een andere app"
"ROBOT_RESERVED_MESSAGE": "Het lijkt erop dat we niet kunnen verbinden met de robot, de poort zou in gebruik kunnen zijn door een ander programma of een andere app",
"RECORDING": "Schermopnames staan aan!",
"RECORDING_DESCRIPTION": "Deze versie van Leaphy EasyBloqs neemt automatisch schermopnames op, deze opnames worden gebruikt door externe partijen voor het project {project}, door door te gaan ga je hier automatisch mee akkoord.\n\nOm schermopnames uit te zetten, kan je altijd gebruik maken van de normale site.\n\nVul je naam in om door te gaan",
"SUBMISSIONS_CLOSED_DESCRIPTION": "De inzendingen voor dit project zijn gesloten, gebruik de normale site voor Leaphy EasyBloqs om verder te gaan",
"ERROR": "Er ging iets mis!",
"INVALID_PROJECT": "Ongeldig project",
"PROJECT_SUBMISSIONS_CLOSED": "Inzendingen gesloten",
"PROJECT_NO_OTHER_PARTICIPANTS": "Ongeldige naam",
"NO_PARTICIPANTS": "Geen deelnemers, vraag je docent om deze aan te maken"
}
1 change: 0 additions & 1 deletion src/lib/components/core/popups/Popup.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ setContext("state", state);
position: absolute;
background: var(--background);
border-radius: 6px;
overflow: hidden;
box-shadow: var(--shadow-el2);
}
Expand Down
118 changes: 118 additions & 0 deletions src/lib/components/core/popups/popups/Recording.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<script lang="ts">
import Button from "$components/ui/Button.svelte";
import Select from "$components/ui/Select.svelte";
import TextInput from "$components/ui/TextInput.svelte";
import type { PopupState } from "$state/popup.svelte";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { getContext } from "svelte";
import Fa from "svelte-fa";
import { _ } from "svelte-i18n";
interface Props {
id: string;
name: string;
acceptsSubmissions: boolean;
suggestNames: boolean;
acceptsNewParticipants: boolean;
showSubmit: boolean;
names: { id: string; name: string }[];
}
const config: Props = $props();
const popupState = getContext<PopupState>("state");
let name = $state(
config.acceptsNewParticipants
? localStorage.getItem("name") || ""
: config.names[0]?.name,
);
async function done() {
localStorage.setItem("name", name);
popupState.close(name);
}
function onsubmit(event: SubmitEvent) {
event.preventDefault();
done();
}
</script>

{#if config.acceptsSubmissions}
<form class="content" {onsubmit}>
<h2>
<span class="icon">
<Fa icon={faCircle} />
</span>
{$_("RECORDING")}
</h2>

<p class="description">{$_("RECORDING_DESCRIPTION", { values: {
project: config.name
}})}</p>

<div class="input">
{#if config.acceptsNewParticipants}
<TextInput
placeholder={$_("NAME")}
bind:value={name}
suggestions={config.names?.map(name => name.name)}
mode="secondary"
required
focus
rounded={true}>
</TextInput>
{:else}
{#if config.names.length > 0}
<Select
options={config.names.map(name => ([name.name, name.name]))}
bind:value={name}
mode="secondary" full
/>
{:else}
<p>{$_("NO_PARTICIPANTS")}</p>
{/if}

{/if}
</div>

<div class="actions">
<Button type="submit" mode={"primary"} name={$_("CONTINUE")}/>
</div>
</form>
{:else}
<form action={import.meta.env.VITE_BACKEND_URL} class="content">
<h2>{$_("PROJECT_SUBMISSIONS_CLOSED")}</h2>
<p class="description">{$_("SUBMISSIONS_CLOSED_DESCRIPTION")}</p>
<div class="actions">
<Button type="submit" mode="primary" name={$_("CONTINUE")} />
</div>
</form>
{/if}

<style>
.input {
padding-bottom: 10px;
}
.content {
padding: 20px;
display: flex;
flex-direction: column;
min-width: 400px;
text-align: center;
}
.actions {
display: flex;
justify-content: center;
margin-top: 20px;
}
.icon {
color: red;
}
.description {
white-space: pre-line;
}
</style>
77 changes: 25 additions & 52 deletions src/lib/components/ui/Select.svelte
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
<script lang="ts">
import { computePosition } from "@floating-ui/dom";
import SelectContext from "$components/ui/SelectContext.svelte";
import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
import { tick } from "svelte";
import Fa from "svelte-fa";
interface Props {
options: [string, any][];
full?: boolean;
mode?: "primary" | "secondary";
value: any;
}
let { options, value = $bindable() }: Props = $props();
let preview: HTMLButtonElement = $state();
let content: HTMLDivElement = $state();
let {
options,
value = $bindable(),
full = false,
mode = "primary",
}: Props = $props();
let open = $state(false);
let position = $state<{ x: number; y: number }>({ x: 0, y: 0 });
function getName(value: any) {
return options.find(([_, data]) => data === value)[0];
}
async function onclick() {
open = !open;
if (!open) return;
await tick();
position = await computePosition(preview, content);
}
function select(newValue: string) {
Expand All @@ -34,37 +32,37 @@ function select(newValue: string) {
}
</script>

<div class="select">
<button {onclick} class:open bind:this={preview} class="preview">
<div class="select" class:full class:secondary={mode === 'secondary'}>
<button type="button" {onclick} class:open class="preview">
<div class="name">{getName(value)}</div>
<div class="icon"><Fa icon={faCaretDown} /></div>
</button>
{#if open}
<div bind:this={content} class="popup">
<div class="container">
{#each options as option (option[1])}
<button onclick={() => select(option[1])} class="option"
>{option[0]}</button
>
{/each}
</div>
</div>
{/if}
<SelectContext {mode} {open} {options} onselect={select} />
</div>

<style>
.select {
position: relative;
width: 150px;
}
.full {
width: 100%;
}
.preview {
width: 150px;
width: 100%;
}
.preview,
.option {
.preview {
position: relative;
background: var(--primary-dark-tint);
color: var(--on-primary);
border: none;
padding: 10px 15px;
border-radius: 20px;
}
.secondary .preview {
background: var(--secondary);
color: var(--on-secondary);
}
.icon {
position: absolute;
right: 15px;
Expand All @@ -75,29 +73,4 @@ function select(newValue: string) {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.popup {
width: 150px;
position: fixed;
z-index: 99;
border-radius: 20px;
border-top-left-radius: 0;
border-top-right-radius: 0;
overflow: hidden;
box-shadow: var(--shadow-el1);
}
.container {
overflow-y: auto;
max-height: 200px;
display: flex;
flex-direction: column;
}
.option {
background: var(--primary);
border-radius: 0;
text-overflow: ellipsis;
}
</style>
68 changes: 68 additions & 0 deletions src/lib/components/ui/SelectContext.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts">
interface Props {
open: boolean;
options: [string, any][];
mode: "primary" | "secondary";
onselect?: (value: string) => void;
}
const { open, options, mode, onselect }: Props = $props();
</script>

{#if open}
<div class="popup" class:secondary={mode === 'secondary'}>
<div class="container">
{#each options as option (option[1])}
<button type="button" onclick={() => onselect(option[1])} class="option"
>{option[0]}</button>
{/each}
</div>
</div>
{/if}

<style>
.option {
position: relative;
background: var(--primary-dark-tint);
color: var(--on-primary);
border: none;
padding: 10px 15px;
border-radius: 20px;
}
.secondary .option {
background: var(--secondary);
color: var(--on-secondary);
}
.popup {
width: 100%;
position: absolute;
max-height: 400px;
overflow-y: auto;
z-index: 99;
border-radius: 20px;
border-top-left-radius: 0;
border-top-right-radius: 0;
overflow: hidden;
box-shadow: var(--shadow-el1);
}
.container {
overflow-y: auto;
max-height: 200px;
display: flex;
flex-direction: column;
}
.option {
background: var(--primary);
border-radius: 0;
text-overflow: ellipsis;
width: 100%;
}
.secondary .option {
background: var(--robot);
}
</style>
Loading

0 comments on commit b79972f

Please sign in to comment.