-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add VideoSectionAPI; Reutilize ts utilities * Add slidingWindow and Time * Add watchers * Update highlight search * Add back follow sections * Remove old code * Update css * Update mobile css * Small css changes * Uncomment chat
- Loading branch information
1 parent
96fb947
commit e43f18a
Showing
12 changed files
with
274 additions
and
351 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,127 +1,43 @@ | ||
{{define "videosections"}} | ||
<div> | ||
<div class="hidden md:block"> | ||
{{template "videosections-desktop" .ID}} | ||
</div> | ||
<div class="md:hidden"> | ||
{{template "videosections-mobile" .ID}} | ||
</div> | ||
</div> | ||
{{end}} | ||
|
||
{{define "videosections-desktop"}} | ||
<div x-data="{vs: new watch.VideoSectionsDesktop({{.}})}" | ||
x-init="watch.attachCurrentTimeEvent(vs);"> | ||
<div class="group relative"> | ||
<div class="grid gap-1 px-2"> | ||
<div class="flex justify-between items-center border-b mb-3 dark:border-gray-800 lg:justify-start"> | ||
<h3 class="text-4 font-semibold">Sections</h3> | ||
<button @click="vs.followSections = !vs.followSections;" | ||
class="text-xs rounded h-fit w-fit font-semibold uppercase lg:ml-3 px-1" | ||
:class="vs.followSections ? 'text-7' : 'hover:bg-gray-200 dark:hover:bg-gray-600 text-5 hover:text-1'" | ||
:disabled="vs.followSections"> | ||
Follow sections | ||
<article x-data="watch.videoSectionContext({{.ID}})" class = "p-3 lg:p-0 lg:h-48 overflow-y-clip"> | ||
<header class="flex space-x-2 mb-3 text-3 justify-between lg:justify-start"> | ||
<h3 class="font-bold">Sections</h3> | ||
<button @click="autoScroll.toggle()" | ||
class="tum-live-button tum-live-button-tertiary" | ||
:class="{'active' : autoScroll.value}"> | ||
Auto-Scroll | ||
</button> | ||
</header> | ||
<article class="relative flex flex-col space-y-2 lg:space-y-0 lg:space-x-2 lg:flex-row lg:items-stretch"> | ||
<template x-if="sections.hasNext()"> | ||
<section class = "flex items-end absolute -left-2 z-40 h-full lg:items-baseline lg:pt-8 lg:left-auto lg:-right-4"> | ||
<button type="button" @click="nextSection()" | ||
class="tum-live-icon-button tum-live-border tum-live-bg text-3 border rounded-full h-8 w-8"> | ||
<i class="fa-solid fa-chevron-right rotate-90 lg:rotate-0"></i> | ||
</button> | ||
</div> | ||
<div class="relative flex w-fit"> | ||
<template x-if="vs.showPrev()"> | ||
<button @click="vs.prev();vs.followSections = false;" | ||
class="group-hover:block hidden absolute -left-4 z-50 bg-white border shadow text-sm py-2 px-3 my-auto h-fit top-0 bottom-0 rounded-lg hover:bg-gray-50 hover:dark:bg-gray-600 dark:border-gray-800 dark:bg-secondary"> | ||
<i class="fa fa-chevron-left text-3"></i> | ||
</button> | ||
</template> | ||
<template x-if="vs.showNext()"> | ||
<button @click="vs.next();vs.followSections = false;" | ||
class="group-hover:block hidden absolute -right-2 z-50 bg-white border shadow text-sm py-2 px-3 my-auto h-fit top-0 bottom-0 rounded-lg hover:bg-gray-50 hover:dark:bg-gray-600 dark:border-gray-800 dark:bg-secondary"> | ||
<i class="fa fa-chevron-right text-3"></i> | ||
</button> | ||
</template> | ||
<template x-for="(s, i) in vs.getList()" :key="s.ID"> | ||
<button x-cloak | ||
class="relative flex h-40 w-32 mb-1 mr-2 bg-transparent outline-none border-0 rounded-lg" | ||
x-data="{previewImgLoaded: false}" | ||
@click="watch.jumpTo({ timeParts: {hours: s.startHours, minutes: s.startMinutes, seconds: s.startSeconds }}); vs.followSections = false;"> | ||
<span class="flex flex-col h-full p-1 bg-white dark:bg-secondary justify-between w-full border hover:dark:bg-gray-600 hover:bg-gray-200 rounded-lg" | ||
:class="vs.isCurrent(i) ? 'border-2 border-blue-500/50 dark:border-indigo-600/50' : 'dark:border-gray-800'"> | ||
<template x-if="s.fileID !== 0"> | ||
<img x-cloak x-show="previewImgLoaded" | ||
src="" | ||
:src="`/api/download/${s.fileID}?type=serve`" | ||
width="128" height="32" | ||
@load="previewImgLoaded=true" | ||
alt="preview" | ||
class="w-full h-16 rounded object-cover z-10"> | ||
</template> | ||
<span x-show="!previewImgLoaded" | ||
class="block w-full h-16 bg-gray-50 dark:bg-gray-700 rounded"></span> | ||
<span x-text="s.description" | ||
class="block text-left text-xs text-3 mt-2 mb-auto px-1"></span> | ||
<span class="absolute bottom-1 right-1 px-1 py-1"> | ||
<span x-text="s.friendlyTimestamp" | ||
class="block text-sky-800 ml-auto w-fit bg-sky-200 text-xs dark:text-indigo-200 dark:bg-indigo-800 p-1 rounded"> | ||
</span> | ||
</span> | ||
</span> | ||
</button> | ||
</template> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
{{end}} | ||
|
||
{{define "videosections-mobile"}} | ||
<div x-data="{vs: new watch.VideoSectionsMobile({{.}})}" | ||
x-init="watch.attachCurrentTimeEvent(vs);" class="pt-3"> | ||
<div class="flex justify-between items-center border-b dark:border-gray-800 mb-3"> | ||
<h3 class="text-4 font-semibold">Sections</h3> | ||
<template x-if="!vs.minimize"> | ||
<button @click="vs.minimize = true;" | ||
class="text-5 text-xs rounded h-fit w-fit font-semibold px-2 py-1 uppercase"> | ||
Minimize | ||
</button> | ||
</section> | ||
</template> | ||
<template x-if="vs.minimize"> | ||
<button @click="vs.minimize = false;" | ||
class="text-5 text-xs rounded h-fit w-fit font-semibold px-2 py-1 uppercase"> | ||
Show all | ||
</button> | ||
<template x-if="sections.hasPrev()"> | ||
<section class = "flex items-baseline absolute -left-2 z-40 h-full lg:items-baseline lg:pt-8 lg:-left-4"> | ||
<button type="button" @click="prevSection()" | ||
class="tum-live-icon-button tum-live-border tum-live-bg text-3 border rounded-full h-8 w-8"> | ||
<i class="fa-solid fa-chevron-left rotate-90 lg:rotate-0"></i> | ||
</button> | ||
</section> | ||
</template> | ||
</div> | ||
<div class="p-1 border bg-gray-100 rounded dark:bg-gray-800 dark:border-gray-800"> | ||
<div class="grid gap-1 overflow-y-scroll" :class="vs.minimize ? 'h-fit' : 'h-56'"> | ||
<template x-for="(s, i) in vs.getList()" :key="s.ID"> | ||
<button x-cloak class="flex w-full bg-transparent outline-none border-0 rounded-lg" | ||
x-data="{previewImgLoaded: false}" | ||
@click="watch.jumpTo({ timeParts: {hours: s.startHours, minutes: s.startMinutes, seconds: s.startSeconds }})"> | ||
<span class="flex justify-start p-1 bg-white dark:bg-secondary w-full border hover:dark:bg-gray-600 hover:bg-gray-200 rounded-lg" | ||
:class="vs.isCurrent(i) ? 'border-2 border-blue-500/50 dark:border-indigo-600/50' : 'dark:border-gray-800'"> | ||
<span class="block"> | ||
<template x-if="s.fileID !== 0"> | ||
<img x-cloak x-show="previewImgLoaded" | ||
src="" | ||
:src="`/api/download/${s.fileID}?type=serve`" | ||
width="128" height="32" | ||
@load="previewImgLoaded=true" | ||
alt="preview" | ||
class="w-full h-16 rounded object-cover z-10"> | ||
</template> | ||
<span x-show="!previewImgLoaded" | ||
class="block rounded w-36 h-16 bg-gray-50 dark:bg-gray-600"></span> | ||
<template x-for="s in sections.get()" :key="s.ID"> | ||
<button type="button" | ||
@click="watch.jumpTo({ timeParts: {hours: s.startHours, minutes: s.startMinutes, seconds: s.startSeconds }});" | ||
class="flex flex-row h-16 group rounded-lg lg:flex-col lg:h-auto lg:w-36"> | ||
<span :style="`background-image:url('/api/download/${s.fileID}?type=serve')`" | ||
class="relative block shrink-0 h-full aspect-video bg-gray-100 border-2 rounded-lg dark:bg-gray-800 dark:shadow-gray-900/75 lg:group-hover:shadow-lg lg:h-auto lg:w-full" | ||
:class="s.isCurrent ? 'border-blue-500/50 dark:border-indigo-600/50 shadow-lg' : 'border-transparent'"> | ||
<span class="tum-live-badge text-xs text-sky-800 bg-sky-200 dark:text-indigo-200 dark:bg-indigo-800 absolute bottom-2 right-2 px-1 py-1px" | ||
x-text="s.friendlyTimestamp"></span> | ||
</span> | ||
<span class="block ml-2"> | ||
<span x-text="s.description" | ||
class="block text-left text-xs text-3 my-2 px-1"></span> | ||
<span class="flex flex-grow items-end flex-1 px-1 py-1"> | ||
<span x-text="s.friendlyTimestamp" | ||
class="block text-sky-800 w-fit bg-sky-200 text-xs dark:text-indigo-200 dark:bg-indigo-800 p-1 rounded"> | ||
</span> | ||
</span> | ||
</span> | ||
</span> | ||
</button> | ||
</template> | ||
</div> | ||
</div> | ||
</div> | ||
<span x-text="s.description" class="block font-semibold overflow-ellipsis text-xs text-3 text-left p-1 my-auto pl-3 lg:my-0 lg:pl-1"></span> | ||
</button> | ||
</template> | ||
</article> | ||
</article> | ||
{{end}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { del, get, post, put } from "../utilities/fetch-wrappers"; | ||
|
||
export class UpdateVideoSectionRequest { | ||
Description: string; | ||
StartHours: number; | ||
StartMinutes: number; | ||
StartSeconds: number; | ||
} | ||
|
||
export type Section = { | ||
ID?: number; | ||
description: string; | ||
|
||
startHours: number; | ||
startMinutes: number; | ||
startSeconds: number; | ||
|
||
streamID: number; | ||
friendlyTimestamp?: string; | ||
fileID?: number; | ||
|
||
isCurrent: boolean; | ||
}; | ||
|
||
/** | ||
* REST API Wrapper for /api/stream/:id/sections | ||
*/ | ||
export const VideoSectionAPI = { | ||
get: async function (streamId: number): Promise<Section[]> { | ||
return get(`/api/stream/${streamId}/sections`); | ||
}, | ||
|
||
add: async function (streamId: number, request: object) { | ||
return post(`/api/stream/${streamId}/sections`, request); | ||
}, | ||
|
||
update: function (streamId: number, id: number, request: UpdateVideoSectionRequest) { | ||
return put(`/api/stream/${streamId}/sections/${id}`, request); | ||
}, | ||
|
||
delete: async function (streamId: number, id: number): Promise<Response> { | ||
return del(`/api/stream/${streamId}/sections/${id}`); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { AlpineComponent } from "./alpine-component"; | ||
import { Section } from "../api/video-sections"; | ||
import { DataStore } from "../data-store/data-store"; | ||
import { SlidingWindow } from "../utilities/sliding-window"; | ||
import { registerTimeWatcher } from "../video/watchers"; | ||
import { getPlayers } from "../TUMLiveVjs"; | ||
import { Time } from "../utilities/time"; | ||
import { ToggleableElement } from "../utilities/ToggleableElement"; | ||
|
||
export function videoSectionContext(streamId: number): AlpineComponent { | ||
return { | ||
streamId: streamId, | ||
autoScroll: new ToggleableElement(), | ||
sections: new SlidingWindow([], 6), | ||
|
||
init() { | ||
DataStore.videoSections.subscribe(this.streamId, this.updateSection.bind(this)); | ||
registerTimeWatcher(getPlayers()[0], this.setCurrent.bind(this)); | ||
}, | ||
|
||
nextSection() { | ||
this.autoScroll.toggle(false); | ||
this.sections.next(); | ||
}, | ||
|
||
prevSection() { | ||
this.autoScroll.toggle(false); | ||
this.sections.prev(); | ||
}, | ||
|
||
setCurrent(t: number) { | ||
this.sections.forEach((s, _) => (s.isCurrent = false)); | ||
const section = this.sections.find((s, i, arr) => { | ||
const next = arr[i + 1]; | ||
const sectionSeconds = new Time(s.startHours, s.startMinutes, s.startSeconds).toSeconds(); | ||
return next === undefined || next === null // if last element and no next exists | ||
? sectionSeconds <= t | ||
: sectionSeconds <= t && | ||
t <= new Time(next.startHours, next.startMinutes, next.startSeconds).toSeconds() - 1; | ||
}); | ||
|
||
if (section) { | ||
section.isCurrent = true; | ||
if (!this.sections.isInWindow(section) && this.autoScroll.value) this.sections.show(section); | ||
} | ||
}, | ||
|
||
isCurrent(i: number) { | ||
return i == 0; | ||
}, | ||
|
||
updateSection(sections: Section[]) { | ||
this.sections.set(sections); | ||
this.sections.reset(); | ||
}, | ||
} as AlpineComponent; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.