diff --git a/.github/screenshots/screenshot1.png b/.github/screenshots/screenshot1.png index 0188da6..e94cc11 100644 Binary files a/.github/screenshots/screenshot1.png and b/.github/screenshots/screenshot1.png differ diff --git a/.github/screenshots/screenshot2.png b/.github/screenshots/screenshot2.png index 4c8ef5a..8b4e41f 100644 Binary files a/.github/screenshots/screenshot2.png and b/.github/screenshots/screenshot2.png differ diff --git a/README.md b/README.md index b49085f..d319a48 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,24 @@ ## Background -This app uses the official media.ccc.de/graphql endpoint. It is built using React, Swiper, Mantine, React TV Player and ❤️ from Leipzig. +This app uses the official media.ccc.de/graphql endpoint. It is built using React, Embla, Mantine, React TV Player and ❤️ from Leipzig. ## Features - Support for conferences lazy loading. +- Preview of the events on scroll. - Support for language change. ## Roadmap -- ☐ Switch to Swiper virtual slides to optimise the DOM -- ☐ Add Search functionnality +- ☐ Add Search functionnality. +- ☐ Add Watchlist functionnality. +- ☐ Add configuration page. +- ☐ Add substitles functionnality. ## Contributions -Either you found a bug, optimisation strategy or want something implemented, go ahead and hack your way into the code, PRs are welcome. +Either you found a bug, optimisation strategy or want something implemented, go ahead and hack your way into the code, PRs are welcome 🌱. ## Development @@ -31,7 +34,7 @@ Either you found a bug, optimisation strategy or want something implemented, go ```sh yarm install -# Serve development build on http://127.0.0.1:3000 +# Serve development build on http://127.0.0.1:3333 yarn start # Production build (dumped into dist/) diff --git a/assets/appinfo.json b/assets/appinfo.json index 255d74a..da32533 100644 --- a/assets/appinfo.json +++ b/assets/appinfo.json @@ -1,6 +1,6 @@ { "id": "org.webosbrew.cccbib", - "version": "0.1.0", + "version": "1.0.0", "vendor": "volkovmqx", "type": "web", "main": "index.html", @@ -9,5 +9,6 @@ "appDescription": "media.ccc.de library application (EXPERIMENTAL)", "icon": "icon80.png", "splashBackground": "splash.png", - "largeIcon": "icon130.png" + "largeIcon": "icon130.png", + "disableBackHistoryAPI": true } diff --git a/package.json b/package.json index f6ab999..6ef21de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "org.webosbrew.cccbib", - "version": "0.1.0", + "version": "1.0.0", "description": "media.ccc.de library application (EXPERIMENTAL)", "main": "index.js", "scripts": { @@ -37,12 +37,12 @@ "@mantine/hooks": "^7.4.2", "@tabler/icons-react": "^2.46.0", "core-js": "^3.20.1", + "embla-carousel-react": "^8.0.0-rc20", "graphql": "^16.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.2.1", "react-tv-player": "^1.1.4", - "regenerator-runtime": "^0.14.1", - "swiper": "^11.0.5" + "regenerator-runtime": "^0.14.1" } } diff --git a/src/App.js b/src/App.js index ec6f172..31290c7 100644 --- a/src/App.js +++ b/src/App.js @@ -6,13 +6,11 @@ import { } from 'react-router-dom'; import MainView from './views/Main'; -import EventView from './views/Event'; const App = () => ( } /> - } /> ); diff --git a/src/components/EventCarousel.js b/src/components/EventCarousel.js new file mode 100644 index 0000000..2732f87 --- /dev/null +++ b/src/components/EventCarousel.js @@ -0,0 +1,35 @@ +import React, { useEffect, useState } from 'react'; +import useEmblaCarousel from 'embla-carousel-react' +import { Title } from '@mantine/core'; + +export function EventCarousel({ eventApis, eventRefs, events, activeEvent, ci, conferenceTitle }) { + const [emblaRef, emblaApi] = useEmblaCarousel({align: 'start', startIndex: activeEvent || 0}); + useEffect(() => { + if (emblaRef && emblaApi) { + eventRefs.current[ci] = emblaRef; + eventApis.current[ci] = emblaApi; + } + }, [emblaRef, emblaApi]); + + return ( + <> + {conferenceTitle} +
+
+
+ {events.lectures.nodes.map((e, ei) => ( +
+ {e.title} +
+ ))} +
+
+
+ + ) + } \ No newline at end of file diff --git a/src/components/Home.js b/src/components/Home.js new file mode 100644 index 0000000..8bc47d3 --- /dev/null +++ b/src/components/Home.js @@ -0,0 +1,103 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { GET_RECENT_CONFERENCES } from '../data'; +import { Center, Loader, Container, Box } from '@mantine/core'; +import { useWindowEvent, useListState } from '@mantine/hooks'; +import { useQuery } from '@apollo/client'; +import { Preview } from '../components/Preview'; +import { Player } from '../components/Player'; +import { handleArrowDown, handleArrowUp, handleArrowLeft, handleArrowRight } from '../helpers/helpers'; +import { EventCarousel } from '../components/EventCarousel'; + +import '../styles.css'; + +export const Home = () => { + const [playerIsOpen, setPlayerIsOpen] = useState(false) + const [activeSlice, setActiveSlice] = useState(0) + const [dataSlice, dataSliceHandlers] = useListState([]); + const [isLoading, setIsLoading] = useState(false) + const [activeEvents, setActiveEvents] = useState({ 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }); + + const { loading, error, data, fetchMore } = useQuery(GET_RECENT_CONFERENCES, { + variables: { offset: 0 }, + }); + + useEffect(() => { + if (!data) return + if (data.conferencesRecent) { + if (activeSlice == 0) { + dataSliceHandlers.setState(data.conferencesRecent) + } else { + dataSliceHandlers.setState(data.conferencesRecent.slice(activeSlice, activeSlice + 6)) + } + setIsLoading(false) + //setActiveSlice(activeSlice + 1) + } + }, [data]) + + const eventRefs = useRef([]); + const eventApis = useRef([]); + + + useWindowEvent('keydown', (e) => { + if (!data) return + if ((e.key === 'Escape' || e.key === 'Backspace' || e.keyCode == '461') && playerIsOpen) { + // close the Player + e.preventDefault() + setPlayerIsOpen(false) + + } else if (e.key === 'Enter' && !playerIsOpen) { + // go to the Event page + setPlayerIsOpen(true) + } else if (e.key === 'ArrowRight' && !playerIsOpen) { + handleArrowRight(eventApis, activeEvents, activeSlice, setActiveEvents) + } else if (e.key === 'ArrowLeft' && !playerIsOpen) { + handleArrowLeft(eventApis, activeEvents, activeSlice, setActiveEvents) + } else if (e.key === 'ArrowDown' && !playerIsOpen) { + + handleArrowDown(setActiveSlice, dataSlice, dataSliceHandlers, data, setIsLoading, fetchMore) + } else if (e.key === 'ArrowUp' && !playerIsOpen) { + handleArrowUp(setActiveSlice, dataSliceHandlers, data) + } + }) + if (loading || dataSlice.length == 0) return
; + if (error) return

Error : {error.message}

; + if (playerIsOpen) return + return ( + + +
+
+
+
+ {dataSlice.map((c, ci) => ( +
+ +
+ ))} + {isLoading && ( +
+ +
+ +
+
+
+ )} +
+
+
+
+
+ ); +} diff --git a/src/components/ItemCard.js b/src/components/ItemCard.js deleted file mode 100644 index a45f745..0000000 --- a/src/components/ItemCard.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import { IconEye, IconPlayerPlay, IconTimeDuration0 } from '@tabler/icons-react'; -import { Card, Text, Group, Center, rem, useMantineTheme } from '@mantine/core'; -import classes from './ItemCard.module.css'; -import { Link } from 'react-router-dom'; - -const formatSeconds = s => [parseInt(s / 60 / 60), parseInt(s / 60 % 60), parseInt(s % 60)].join(':').replace(/\b(\d)\b/g, '0$1'); - -export function ItemCard({title, image, persons, duration, viewCount, active, guid}) { - const theme = useMantineTheme(); - return ( - - -
- {active && ( -
- -
- )} -
-
- - {title} - - - - - {persons.map((p) => p + " ")} - - - -
- - - {viewCount} - -
-
- - - {formatSeconds(duration)} - -
-
-
-
-
- - ); -} \ No newline at end of file diff --git a/src/components/ItemCard.module.css b/src/components/ItemCard.module.css deleted file mode 100644 index b03d6d6..0000000 --- a/src/components/ItemCard.module.css +++ /dev/null @@ -1,55 +0,0 @@ -.card { - height: 400px; - width: 300px; - margin: 20px 5px 20px 5px; - background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6)); -} -.active { - box-shadow: rgb(255, 255, 255) 0px 0px 0px 5px ; -} - -.image { - background-position: center; - background-size: cover; -} -.activeplay { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: var(--mantine-color-white); - font-size: 50px; - z-index: 1; -} - -.overlay { - position: absolute; - top: 20%; - left: 0; - right: 0; - bottom: 0; - background-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 90%); -} - -.content { - height: 100%; - position: relative; - display: flex; - flex-direction: column; - justify-content: flex-end; - z-index: 1; -} - -.title { - color: var(--mantine-color-white); - margin-bottom: 5px; -} - -.bodyText { - color: var(--mantine-color-dark-2); - margin-left: 7px; -} - -.author { - color: var(--mantine-color-dark-2); -} diff --git a/src/components/Logo.js b/src/components/Logo.js new file mode 100644 index 0000000..87754c4 --- /dev/null +++ b/src/components/Logo.js @@ -0,0 +1,10 @@ +import React from 'react'; + +export const Logo = () => { + return ( + + + + + ) +} \ No newline at end of file diff --git a/src/components/Player.js b/src/components/Player.js new file mode 100644 index 0000000..669877f --- /dev/null +++ b/src/components/Player.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { TVPlayer } from "react-tv-player"; +import { faGlobe, faClockRotateLeft } from "@fortawesome/free-solid-svg-icons"; +import { useLocalStorage } from "@mantine/hooks"; +import { getVideo } from '../helpers/helpers'; + +export function Player({ event }) { + const [language, setLanguage] = useLocalStorage({ + key: 'language', + defaultValue: 'deu', + }); + const [recording, foundLanguage] = getVideo(event.videos) + + const customButtons = [ + { action: "skipback", align: "center", faIcon: faClockRotateLeft }, + { action: "playpause", align: "center" }, + { action: "skipforward", align: "center", faIcon: faClockRotateLeft }, + { action: "mute", align: "right" }, + { + action: "custom", + align: "left", + label: "Switch to" + (language === "deu" ? " English" : " German"), + faIcon: faGlobe, + onPress: () => { + setLanguage(language === "deu" ? "eng" : "deu"); + }, + }, + ]; + + if (!foundLanguage) { + // deactivate the language switch button + customButtons[4].disabled = true; + } + if (recording) { + return ( + + ); + } else { + return
404
; + } +} diff --git a/src/components/Preview.js b/src/components/Preview.js new file mode 100644 index 0000000..69d4667 --- /dev/null +++ b/src/components/Preview.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { getVideo } from '../helpers/helpers'; +import { Box, Title, Group, Center, Text, Space } from '@mantine/core'; +import { IconEye, IconClock, IconUser } from '@tabler/icons-react'; +import { formatSeconds } from '../helpers/helpers'; + +import { Logo } from './Logo' +export function Preview({ event, conferenceTitle }) { + const [recording] = getVideo(event.videos) + + return ( + + +
+ + {event.title} + +
+ + + {event.viewCount} + +
+
+ + + {formatSeconds(event.duration)} + +
+
+ + + {event.persons.join(', ')} + +
+
+ {event.description} + +
+
+
+ {recording && recording.url && ( +
+ + ) +} \ No newline at end of file diff --git a/src/data.js b/src/data.js index edee669..8269449 100644 --- a/src/data.js +++ b/src/data.js @@ -3,7 +3,7 @@ import { gql } from '@apollo/client'; export const GET_RECENT_CONFERENCES = gql` query conferencesRecent($offset: Int) { - conferencesRecent(offset: $offset, first: 3) { + conferencesRecent(offset: $offset, first: 6) { id title slug @@ -12,14 +12,16 @@ export const GET_RECENT_CONFERENCES = gql` scheduleUrl updatedAt eventLastReleasedAt - lectures { + lectures { nodes { guid title persons duration + description viewCount images { + thumbUrl posterUrl } videos { diff --git a/src/helpers/helpers.js b/src/helpers/helpers.js new file mode 100644 index 0000000..f41efc5 --- /dev/null +++ b/src/helpers/helpers.js @@ -0,0 +1,88 @@ +import { useLocalStorage } from "@mantine/hooks"; + + +export function getVideo(videos) { + const [language] = useLocalStorage({ + key: 'language', + defaultValue: 'deu', + }); + let recording = videos.find( + (s) => (s.language === language && s.mimeType === "video/mp4" && s.url) + ); + let foundLanguage = true; + // check if another language is available + let otherLanguage = language === "deu" ? "eng" : "deu"; + const otherLanguageAvailable = videos.find( + (s) => (s.language === otherLanguage && s.mimeType === "video/mp4" && s.url) + ); + // video in language not found, try to find any video + if (!recording) { + recording = videos.find( + (s) => s.mimeType === "video/mp4" && s.url + ); + foundLanguage = false; + } + return [recording, foundLanguage]; +} +export const formatSeconds = s => [parseInt(s / 60 / 60), parseInt(s / 60 % 60), parseInt(s % 60)].join(':').replace(/\b(\d)\b/g, '0$1'); + +export const handleArrowRight = (eventApis, activeEvents, activeSlice, setActiveEvents) => { + eventApis.current[0].scrollNext() + if (eventApis.current[0].canScrollNext()) { + const newActiveEvents = {...activeEvents, [activeSlice]: eventApis.current[0].selectedScrollSnap()} + setActiveEvents(newActiveEvents) + } else { + const eventSlides = eventApis.current[0].slidesInView() + const newActiveEvents = {...activeEvents, [activeSlice]: ((activeEvents[activeSlice] || 0) + 1) % eventSlides.length} + setActiveEvents(newActiveEvents) + } +} + +export const handleArrowLeft = (eventApis, activeEvents, activeSlice, setActiveEvents) => { + eventApis.current[0].scrollPrev() + if (eventApis.current[0].canScrollPrev()) { + const newActiveEvents = {...activeEvents, [activeSlice]: eventApis.current[0].selectedScrollSnap()} + setActiveEvents(newActiveEvents) + } else { + if((activeEvents[activeSlice] || 0) == 0) { + console.log("cannot scroll prev -- open the sidebar") + } else { + const newActiveEvent = activeEvents[activeSlice] - 1 + const newActiveEvents = {...activeEvents, [activeSlice]: newActiveEvent} + setActiveEvents(newActiveEvents) + } + + } +} +export const handleArrowDown = (setActiveSlice, dataSlice, dataSliceHandlers, data, setIsLoading, fetchMore) => { + setActiveSlice((activeSlice) => { + if (dataSlice.length >= 2) { + dataSliceHandlers.shift() + if (dataSlice.length - 2 == 1) { + setIsLoading(true) + fetchMore({ + variables: { + offset: data.conferencesRecent.length, + }, + }) + } + + return activeSlice + 1 + } + return activeSlice + }) + +} + +export const handleArrowUp = (setActiveSlice, dataSliceHandlers, data) => { + setActiveSlice((activeSlice) => { + if (activeSlice - 1 >= 0) { + dataSliceHandlers.prepend(data.conferencesRecent[activeSlice - 1]) + return activeSlice - 1 + + } + return activeSlice + }) + +} + diff --git a/src/index.js b/src/index.js index cc0928d..e2bec0d 100644 --- a/src/index.js +++ b/src/index.js @@ -3,10 +3,13 @@ import 'core-js/stable'; import React from 'react'; import '@mantine/core/styles.css'; -import { createTheme, MantineProvider } from '@mantine/core'; + +import { MantineProvider, createTheme } from '@mantine/core'; import { createRoot } from 'react-dom/client'; import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'; +const theme = createTheme(); + const cache = new InMemoryCache({ typePolicies: { Query: { @@ -43,14 +46,11 @@ const client = new ApolloClient({ import App from './App'; -const theme = createTheme({ - /** Put your mantine theme override here */ -}); const container = document.getElementById('app'); const root = createRoot(container); // createRoot(container!) if you use TypeScript root.render( - + diff --git a/src/styles.css b/src/styles.css index eff658e..f60652f 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,19 +1,363 @@ -#app { - height: 100%; +.theme-dark { + --brand-primary: rgb(138, 180, 248); + --brand-secondary: rgb(193, 168, 226); + --brand-alternative: rgb(136, 186, 191); + --background-site: rgb(0, 0, 0); + --background-code: rgb(12, 12, 12); + --text-body: rgb(222, 222, 222); + --text-comment: rgb(170, 170, 170); + --text-high-contrast: rgb(230, 230, 230); + --text-medium-contrast: rgb(202, 202, 202); + --text-low-contrast: rgb(170, 170, 170); + --detail-high-contrast: rgb(101, 101, 101); + --detail-medium-contrast: rgb(25, 25, 25); + --detail-low-contrast: rgb(21, 21, 21); + --admonition-note: rgb(138, 180, 248); + --admonition-warning: rgb(253, 186, 116); + --admonition-danger: rgb(220, 38, 38); + --brand-primary-rgb-value: 138, 180, 248; + --brand-secondary-rgb-value: 193, 168, 226; + --brand-alternative-rgb-value: 136, 186, 191; + --background-site-rgb-value: 0, 0, 0; + --background-code-rgb-value: 12, 12, 12; + --text-body-rgb-value: 222, 222, 222; + --text-comment-rgb-value: 170, 170, 170; + --text-high-contrast-rgb-value: 230, 230, 230; + --text-medium-contrast-rgb-value: 202, 202, 202; + --text-low-contrast-rgb-value: 170, 170, 170; + --detail-high-contrast-rgb-value: 101, 101, 101; + --detail-medium-contrast-rgb-value: 25, 25, 25; + --detail-low-contrast-rgb-value: 21, 21, 21; + --admonition-note-rgb-value: 138, 180, 248; + --admonition-warning-rgb-value: 253, 186, 116; + --admonition-danger-rgb-value: 220, 38, 38; +} +html { + box-sizing: border-box; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +*, +*::before, +*::after { + box-sizing: inherit; +} +html, +body, +p, +ol, +ul, +li, +dl, +dt, +dd, +blockquote, +figure, +fieldset, +legend, +textarea, +pre, +iframe, +hr, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + padding: 0; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: 100%; + font-weight: 400; +} +ul { + list-style: none; +} +:root { + -moz-tab-size: 4; + tab-size: 4; +} +hr { + height: 0; +} +abbr[title] { + text-decoration: underline dotted; +} +b, +strong { + font-weight: bolder; +} +code, +kbd, +samp, +pre { + font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, + monospace; + font-size: 1em; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; + top: -0.5em; +} +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; +} +button, +select { + text-transform: none; +} +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: none; + appearance: none; +} +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + border-style: none; + padding: 0; +} +button:-moz-focusring, +[type='button']:-moz-focusring, +[type='reset']:-moz-focusring, +[type='submit']:-moz-focusring { + outline: 1px dotted ButtonText; +} +img, +embed, +iframe, +object, +audio, +video { + height: auto; + max-width: 100%; +} +html { + background-color: #000; + font-size: 62.5%; +} +body { + background-color: #000; + color: #fff; + font-size: 1.6rem; + line-height: 1.65; + padding: 0!important; + margin: 0!important; +} +html { + font-family: 'system-ui', -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol'; + letter-spacing: -0.02rem; +} +@supports (font-variation-settings: normal) { + html { + font-family: 'Inter var', 'system-ui', -apple-system, BlinkMacSystemFont, + 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol'; } - - .swiper { - width: 100%; - height: 100%; - } - - .swiper-slide { - height: auto; - } - .swiper-event-slide { - width: 310px; - } - .conference { - margin-top: 10px; - margin-bottom: 10px; - } \ No newline at end of file +} + +.loaderContainer { + transform: translate(-50%, -50%); + position: absolute; + top: 50%; + left: 50%; +} +.skeleton { + width: 400px; + height: 225px; + border-radius: 1rem; + position: relative; + background-color: #525252; +} +.embla { + --slide-spacing: 1rem; + --slide-size: 400px; + --slide-height: 225px; + padding: 0; + margin-bottom: 20px; +} +.embla_vertical { + --slide-spacing: 0; + --slide-size: 225px; + --slide-height: 225px; + padding: 0; +} +.embla__viewport { + overflow: hidden; +} +.embla__container { + backface-visibility: hidden; + display: flex; +} + +.embla__container_vertical { + flex-direction: column; + height: calc(100vh - 450px); +} +.embla__slide { + flex: 0 0 var(--slide-size); + min-width: 0; + padding-left: var(--slide-spacing); + position: relative; + + +} +.embla__slide__img { + display: block; + height: var(--slide-height); + width: 100%; + object-fit: cover; + opacity: 0.5; + border-radius: 1rem; + border: 3px solid #fff; +} +.embla__slide__number { + width: 4.6rem; + height: 4.6rem; + z-index: 1; + position: absolute; + top: 0.6rem; + right: 0.6rem; + border-radius: 50%; + background-color: rgba(var(--background-site-rgb-value), 0.85); + line-height: 4.6rem; + font-weight: 900; + text-align: center; + pointer-events: none; +} +.embla__slide__number > span { + color: #fff; + background-image: linear-gradient( + 45deg, + var(--brand-primary), + var(--brand-secondary) + ); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-size: 1.6rem; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} +.previewContainer { + position: relative; +} +.active .embla__slide__img { + opacity: 1; + border: 10px solid #AAF40D; + +} + +.preview { + width: 880px; + height: 495px; + z-index: 999; + position: absolute; + right: 0; +} + +.container { + background-color: #000; + width: 100%; + position: fixed; + z-index: 99; + top: 450px; +} + +.previewOverlay { + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 90%), + linear-gradient(0deg, rgba(0, 0, 0, 1) 1%, rgba(0, 0, 0, 0) 40%); + z-index: 9999; +} +.title { + display: block; + position: absolute; + top: 40px; + left: 20px; + z-index: 99999; + width: 1200px; +} +.preview .player { + width: 100%; + height: 100%; + object-fit: contain; +} +.conferenceTitle { + position: absolute; + top: 360px; +} +.noRecording { + position: absolute; + top: 150px; + left: 260px; + font-size: 48px; + z-index: 99999; +} + +.actionButton { + width: 50px; + height: 50px; + margin: 20px; + display: flex; + align-items: center; + justify-content: center; + color: #fff; +} +.tv-player>* { + z-index: 9999; +} +.tv-player { + position: initial; +} +.mantine-AppShell-navbar { + width: 110px; + height: 100vh; + padding: 20px; + display: flex; + flex-direction: row; + background-color: #000; +} +[data-testid="skipforward"] svg { + transform: scaleX(-1) +} \ No newline at end of file diff --git a/src/views/Event.js b/src/views/Event.js deleted file mode 100644 index befdca3..0000000 --- a/src/views/Event.js +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useEffect } from 'react'; -import { useParams } from 'react-router'; -import { useQuery } from '@apollo/client'; -import { GET_LECTURE } from '../data'; -import { TVPlayer } from "react-tv-player"; -import { faGlobe } from "@fortawesome/free-solid-svg-icons"; -import { useLocalStorage } from "@mantine/hooks"; -import { Center, Loader } from "@mantine/core"; - -export default function Event({}) { - let params = useParams(); - const [language, setLanguage] = useLocalStorage({ - key: 'language', - defaultValue: 'deu', - }); - - const { loading, error, data } = useQuery(GET_LECTURE, { - variables: { id: params.id }, - }); - const customButtons = [ - { action: "skipback", align: "center" }, - { action: "playpause", align: "center" }, - { action: "skipforward", align: "center" }, - { action: "mute", align: "right" }, - { - action: "custom", - align: "left", - label: "Switch to" + (language === "deu" ? " English" : " German"), - faIcon: faGlobe, - onPress: () => { - setLanguage(language === "deu" ? "eng" : "deu"); - }, - }, - ]; - - - if (loading) return
; - - if (error) return

Error : {error.message}

; - - - let recording = data.lecture.videos.find( - (s) => (s.language === language && s.mimeType === "video/mp4" && s.url) - ); - // check if another language is available - let otherLanguage = language === "deu" ? "eng" : "deu"; - const otherLanguageAvailable = data.lecture.videos.find( - (s) => (s.language === otherLanguage && s.mimeType === "video/mp4" && s.url) - ); - if( !otherLanguageAvailable ) { - // deactivate the language switch button - customButtons[4].disabled = true; - } - // video in language not found, try to find any video - if (!recording) { - recording = data.lecture.videos.find( - (s) => s.mimeType === "video/mp4" && s.url - ); - // deactivate the language switch button - customButtons[4].disabled = true; - } - if (recording) { - return ( - - ); - } else { - return
404
; - } -} diff --git a/src/views/Main.js b/src/views/Main.js index 7d28d06..59a9e9e 100644 --- a/src/views/Main.js +++ b/src/views/Main.js @@ -1,139 +1,33 @@ -import React, { useState } from 'react'; -// Import Swiper React components -import { Swiper, SwiperSlide } from 'swiper/react'; -import { GET_RECENT_CONFERENCES } from '../data'; -import { Container, Group, Image, Box, Center, Loader, Text } from '@mantine/core'; -import { useWindowEvent, useListState, } from '@mantine/hooks'; -import { ItemCard } from '../components/ItemCard'; -import { useQuery } from '@apollo/client'; -import { useNavigate } from "react-router-dom"; - - -// Import Swiper styles -import 'swiper/css'; - - -import '../styles.css'; +import React from 'react'; +import { Home } from '../components/Home'; +import { AppShell, Stack, ActionIcon } from '@mantine/core'; +import { IconHeart, IconInfoCircle, IconSearch, IconSettings } from '@tabler/icons-react'; export default function App() { - const [isLoadingMore, setIsLoadingMore] = useState(false); - const [initialLoaded, setInitialLoaded] = useState(false); - const navigate = useNavigate(); - const [activeLecture, setActiveLecture] = useState(0); - const [activeConference, setActiveConference] = useState(0); - const { loading, error, data, fetchMore } = useQuery(GET_RECENT_CONFERENCES, { - variables: { offset: 0, first: 3 }, - }); - - const [conferencesSliderRef, setConferencesSliderRef] = useState(null); - const [events, eventHandlers] = useListState([]); - - - useWindowEvent('keydown', (e) => { - if (!data) return - if (e.key === 'Enter') { - // go to the Event page - navigate(`/event/${data.conferencesRecent[activeConference].lectures.nodes[activeLecture].guid}`) - - } else if (e.key === 'ArrowRight') { - if (data.conferencesRecent[activeConference].lectures.nodes[activeLecture + 1]) { - setActiveLecture(activeLecture + 1) - events[activeConference].slideNext() - } - } else if (e.key === 'ArrowLeft') { - if (data.conferencesRecent[activeConference].lectures.nodes[activeLecture - 1]) { - setActiveLecture(activeLecture - 1) - events[activeConference].slidePrev() - } - } else if (e.key === 'ArrowDown') { - e.preventDefault() - // load more if there is no more data - if (!data.conferencesRecent[activeConference + 2]) { - setIsLoadingMore(true) - fetchMore({ - variables: { - offset: data.conferencesRecent.length, - } - }).then(() => { - setIsLoadingMore(false) - }); - } - else if (data.conferencesRecent[activeConference + 1] && events[activeConference + 1]) { - setActiveLecture(0) - setActiveConference(activeConference + 1) - events[activeConference].slideTo(0, 0) - conferencesSliderRef.slideNext() - } - } else if (e.key === 'ArrowUp') { - e.preventDefault() - if (data.conferencesRecent[activeConference - 1] && events[activeConference - 1]) { - setActiveLecture(0) - setActiveConference(activeConference - 1) - events[activeConference].slideTo(0, 0) - conferencesSliderRef.slidePrev() - } - } - }) - console.log(loading) - if (loading) return
; - if (error) return

Error : {error.message}

; return ( - -

CCCBIB {isLoadingMore && }

- { - if(initialLoaded) { - setActiveLecture(0) - setActiveConference(activeConference + 1) - events[activeConference].slideTo(0, 0) - conferencesSliderRef.slideNext() - } - else { - setInitialLoaded(true) - } - } - } - > - {data.conferencesRecent.map((c, ci) => ( - - - - - {c.title} - - eventHandlers.insert(ci, el)} - - > - {c.lectures.nodes.map((l, li) => ( - - - - ))} - - - - ))} - -
+ + + + + + + + + + + + + + + + + + + + ); -} +} \ No newline at end of file diff --git a/tools/gen-manifest.js b/tools/gen-manifest.js index c0fb7b4..09cf578 100644 --- a/tools/gen-manifest.js +++ b/tools/gen-manifest.js @@ -22,7 +22,6 @@ fs.writeFileSync( iconUri: 'https://raw.githubusercontent.com/volkovmqx/cccbib/main/assets/icon130.png', sourceUrl: 'https://github.com/volkovmqx/cccbib', - rootRequired: true, ipkUrl: ipkfile, ipkHash: { sha256: ipkhash, diff --git a/webpack.config.js b/webpack.config.js index 543a19a..c6447e5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -47,6 +47,6 @@ module.exports = { ], devServer: { static: path.resolve(__dirname, './dist'), - port: 3000, + port: 3333, }, }; diff --git a/yarn.lock b/yarn.lock index 2acc6ce..520ea7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2081,7 +2081,7 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -classnames@^2.3.2: +classnames@^2.2.5, classnames@^2.3.2: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== @@ -2456,6 +2456,11 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +desandro-matches-selector@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz#717beed4dc13e7d8f3762f707a6d58a6774218e1" + integrity sha512-+1q0nXhdzg1IpIJdMKalUwvvskeKnYyEe3shPRwedNcWtnhEKT3ZxvFjzywHDeGcKViIxTCAoOYQWP1qD7VNyg== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -2524,6 +2529,29 @@ elfy@1.0.0: dependencies: endian-reader "^0.3.0" +embla-carousel-class-names@^8.0.0-rc20: + version "8.0.0-rc20" + resolved "https://registry.yarnpkg.com/embla-carousel-class-names/-/embla-carousel-class-names-8.0.0-rc20.tgz#d9ee764405e79df1885590617135042ba84e9cd6" + integrity sha512-UfhlpyeSgecbLp0tsBIKmjRmv56kpkN/B1e9OtzP3ReH41fDDbvPK7L0L9ETPpJslIAZoTaSKSVU016/ajaxrg== + +embla-carousel-react@^8.0.0-rc20: + version "8.0.0-rc20" + resolved "https://registry.yarnpkg.com/embla-carousel-react/-/embla-carousel-react-8.0.0-rc20.tgz#0756fde4791b0f727dd90383c3cf798c17cdb539" + integrity sha512-02xhtl/qd5VQtzRbG3jQKVXy/YzP4J3nxQcJhz7cIY73nK3aPwxoZL+Fjk0VdS5eUIWowRBH5qIv3nVNsqeYZQ== + dependencies: + embla-carousel "8.0.0-rc20" + embla-carousel-reactive-utils "8.0.0-rc20" + +embla-carousel-reactive-utils@8.0.0-rc20: + version "8.0.0-rc20" + resolved "https://registry.yarnpkg.com/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.0.0-rc20.tgz#606c2708bdb95c4c3391a3a6cd01d3c7b81e36f3" + integrity sha512-fE7IeSS8HqwDnTDMP8eo0i4pcYQAemmJq53zCLXnp3Yj/p5+IpB1nC7aKQjd2ug1dGOSwwNRFaPI3shlAVVW/A== + +embla-carousel@8.0.0-rc20: + version "8.0.0-rc20" + resolved "https://registry.yarnpkg.com/embla-carousel/-/embla-carousel-8.0.0-rc20.tgz#6763e9db8db11a64d49f42ecdb15936a694fed7e" + integrity sha512-fhzhbIAcsjSpUsg5jWsg0+zVyJhY5x2SPXtuS4MPAWQWoVQpvkcbX9r0FvPBn6emTbgNFRtAcWczstJy2msdUw== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -2561,6 +2589,11 @@ enhanced-resolve@^5.15.0: graceful-fs "^4.2.4" tapable "^2.2.0" +enquire.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/enquire.js/-/enquire.js-2.1.6.tgz#3e8780c9b8b835084c3f60e166dbc3c2a3c89814" + integrity sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw== + envinfo@^7.7.3: version "7.11.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.11.0.tgz#c3793f44284a55ff8c82faf1ffd91bc6478ea01f" @@ -2621,6 +2654,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +ev-emitter@^1.0.0, ev-emitter@^1.0.1, ev-emitter@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ev-emitter/-/ev-emitter-1.1.1.tgz#8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" + integrity sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q== + eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -2884,11 +2922,30 @@ first-chunk-stream@^1.0.0: resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" integrity sha512-ArRi5axuv66gEsyl3UuK80CzW7t56hem73YGNYxNWTGNKFJUadSb9Gu9SHijYEUi8ulQMf1bJomYNwSCPHhtTQ== +fizzy-ui-utils@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz#7df45dcc4eb374a08b65d39bb9a4beedf7330505" + integrity sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg== + dependencies: + desandro-matches-selector "^2.0.0" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +flickity@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/flickity/-/flickity-2.3.0.tgz#6763eb4d9cfeee6eedd96d042a21fff4ad439944" + integrity sha512-x4cJBVywsaCWmId3I6wvBYJtWk3gcr+gz8UJQ48P57W5G7ER5OUgc3GUK0rtTrbMy/HYB9wL6u+I7EC4qrLO8g== + dependencies: + desandro-matches-selector "^2.0.0" + ev-emitter "^1.1.1" + fizzy-ui-utils "^2.0.7" + get-size "^2.0.3" + unidragger "^2.4.0" + unipointer "^2.4.0" + follow-redirects@^1.0.0: version "1.15.5" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" @@ -2996,6 +3053,11 @@ get-nonce@^1.0.0: resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== +get-size@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/get-size/-/get-size-2.0.3.tgz#54a1d0256b20ea7ac646516756202769941ad2ef" + integrity sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q== + get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -3295,6 +3357,13 @@ ignore@^5.1.9: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== +imagesloaded@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7" + integrity sha512-ltiBVcYpc/TYTF5nolkMNsnREHW+ICvfQ3Yla2Sgr71YFwQ86bDwV9hgpFhFtrGPuwEx5+LqOHIrdXBdoWwwsA== + dependencies: + ev-emitter "^1.0.0" + import-local@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" @@ -3548,6 +3617,13 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== +json2mq@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" + integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA== + dependencies: + string-convert "^0.2.0" + json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -4268,6 +4344,14 @@ react-fast-compare@^3.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== +react-flickity-component@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/react-flickity-component/-/react-flickity-component-4.0.7.tgz#e57987988f2bd0df83177fc7545a13ca2b252bad" + integrity sha512-Gn+MqLWeW2znG0sPAaaShckXKW1oXVR/RMnJd9EWUuZH33vxRVc7Q1gwpLGDKs0Aiq0D1/cmb8ltepEgDmu7Rw== + dependencies: + imagesloaded "^4.1.4" + prop-types "^15.7.2" + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -4325,6 +4409,17 @@ react-router@6.21.3: dependencies: "@remix-run/router" "1.14.2" +react-slick@^0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/react-slick/-/react-slick-0.29.0.tgz#0bed5ea42bf75a23d40c0259b828ed27627b51bb" + integrity sha512-TGdOKE+ZkJHHeC4aaoH85m8RnFyWqdqRfAGkhd6dirmATXMZWAxOpTLmw2Ll/jPTQ3eEG7ercFr/sbzdeYCJXA== + dependencies: + classnames "^2.2.5" + enquire.js "^2.1.6" + json2mq "^0.2.0" + lodash.debounce "^4.0.8" + resize-observer-polyfill "^1.5.0" + react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" @@ -4491,6 +4586,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +resize-observer-polyfill@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -4925,6 +5025,11 @@ streamsearch@~0.1.2: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA== +string-convert@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" + integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -5258,6 +5363,20 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +unidragger@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/unidragger/-/unidragger-2.4.0.tgz#4cd7e564317af0ef42632d5984a82d4ae6314d8d" + integrity sha512-MueZK2oXuGE6OAlGKIrSXK2zCq+8yb1QUZgqyTDCSJzvwYL0g2Llrad+TtoQTYxtFnNyxxSw0IMnKNIgEMia1w== + dependencies: + unipointer "^2.4.0" + +unipointer@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/unipointer/-/unipointer-2.4.0.tgz#ac7316aff6170ff87a4b008e55e842fb4bf13181" + integrity sha512-VjzDLPjGK7aYpQKH7bnDZS8X4axF5AFU/LQi+NQe1oyEHfaz6lWKhaQ7n4o7vJ1iJ4i2T0quCIfrQM139p05Sw== + dependencies: + ev-emitter "^1.0.1" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"