mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 07:29:08 +00:00
feat: lower third
This commit is contained in:
+3
-1
@@ -4,6 +4,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.App {
|
.App {
|
||||||
background: linear-gradient(90deg, #202020 0%, #121212 100%);
|
margin: 0px auto;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -8,6 +8,7 @@ import SocketProvider from './app/context/socketContext';
|
|||||||
import PresenterSimple from './features/viewers/presenter/PresenterSimple';
|
import PresenterSimple from './features/viewers/presenter/PresenterSimple';
|
||||||
import StageManager from './features/viewers/backstage/StageManager';
|
import StageManager from './features/viewers/backstage/StageManager';
|
||||||
import withSocket from './features/viewers/ViewWrapper';
|
import withSocket from './features/viewers/ViewWrapper';
|
||||||
|
import Lower from './features/viewers/lower/Lower';
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
@@ -15,20 +16,20 @@ const SDefault = withSocket(DefaultPresenter);
|
|||||||
const SSpeaker = withSocket(PresenterView);
|
const SSpeaker = withSocket(PresenterView);
|
||||||
const SSpeakerSimple = withSocket(PresenterSimple);
|
const SSpeakerSimple = withSocket(PresenterSimple);
|
||||||
const SStageManager = withSocket(StageManager);
|
const SStageManager = withSocket(StageManager);
|
||||||
|
const SLowerThird = withSocket(Lower);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<SocketProvider>
|
<SocketProvider>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<div className='App'>
|
<div className='App'>
|
||||||
<Switch>
|
|
||||||
<Route exact path='/' component={SDefault} />
|
<Route exact path='/' component={SDefault} />
|
||||||
<Route exact path='/sm' component={SStageManager} />
|
<Route exact path='/sm' component={SStageManager} />
|
||||||
<Route exact path='/speaker' component={SSpeaker} />
|
<Route exact path='/speaker' component={SSpeaker} />
|
||||||
<Route exact path='/speakersimple' component={SSpeakerSimple} />
|
<Route exact path='/speakersimple' component={SSpeakerSimple} />
|
||||||
<Route exact path='/editor' component={Editor} />
|
<Route exact path='/editor' component={Editor} />
|
||||||
</Switch>
|
<Route path='/lower' component={SLowerThird} />
|
||||||
</div>
|
</div>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</SocketProvider>
|
</SocketProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.mainContainer {
|
.mainContainer {
|
||||||
width: 95%;
|
background: linear-gradient(90deg, #202020 0%, #121212 100%);
|
||||||
|
width: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
height: 95vh;
|
height: 95vh;
|
||||||
color: #fffd;
|
color: #fffd;
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import style from './Lower.module.css';
|
||||||
|
|
||||||
|
export default function Lower(props) {
|
||||||
|
const { pres, publ, lower, title, time, events, selectedId, general } = props;
|
||||||
|
|
||||||
|
// getting config from URL
|
||||||
|
// like http://localhost:3000/lower?bg=ff2&text=f00&size=0.6&transition=5
|
||||||
|
let params = new URLSearchParams(props.location.search);
|
||||||
|
|
||||||
|
const preset = params.get('preset') ? params.get('preset') : 1;
|
||||||
|
const size = params.get('size') ? params.get('size') : 1;
|
||||||
|
const transitionIn = params.get('transition') ? params.get('transition') : 3;
|
||||||
|
const textColour = params.get('bg') ? `#${params.get('text')}` : '#fffffa';
|
||||||
|
const bgColour = params.get('bg') ? `#${params.get('bg')}` : '#00000033';
|
||||||
|
const key = params.get('key') ? `#${params.get('key')}` : 'null';
|
||||||
|
|
||||||
|
// Format messages
|
||||||
|
const showLower = lower.text !== '' && lower.visible;
|
||||||
|
|
||||||
|
// transition segments
|
||||||
|
const eight = transitionIn / 8;
|
||||||
|
const quarter = transitionIn / 4;
|
||||||
|
const third = transitionIn / 3;
|
||||||
|
const half = transitionIn / 2;
|
||||||
|
|
||||||
|
// motion
|
||||||
|
const lowerThirdVariants = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
duration: third,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleContainerVariants = {
|
||||||
|
hidden: {
|
||||||
|
left: -1000,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
left: 0,
|
||||||
|
transition: {
|
||||||
|
duration: third,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleVariants = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
delay: quarter,
|
||||||
|
duration: half,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const subtitleContainerVariants = {
|
||||||
|
hidden: {
|
||||||
|
left: -1000,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
left: 0,
|
||||||
|
transition: {
|
||||||
|
delay: eight,
|
||||||
|
duration: third,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const subtitleVariants = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
delay: third,
|
||||||
|
duration: third * 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={style.lowerThird}
|
||||||
|
style={{
|
||||||
|
backgroundColor: key,
|
||||||
|
color: textColour,
|
||||||
|
fontSize: `${size * 4}vh`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
className={style.lowerContainer}
|
||||||
|
style={{ backgroundColor: bgColour }}
|
||||||
|
variants={lowerThirdVariants}
|
||||||
|
initial='hidden'
|
||||||
|
animate='visible'
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
className={style.titleContainer}
|
||||||
|
variants={titleContainerVariants}
|
||||||
|
>
|
||||||
|
<motion.div className={style.title} variants={titleVariants}>
|
||||||
|
{title.titleNow}
|
||||||
|
</motion.div>
|
||||||
|
<div className={style.titleDecor} />
|
||||||
|
</motion.div>
|
||||||
|
<motion.div
|
||||||
|
className={style.subtitleContainer}
|
||||||
|
variants={subtitleContainerVariants}
|
||||||
|
>
|
||||||
|
<div className={style.subDecor} />
|
||||||
|
<motion.div className={style.subtitle} variants={subtitleVariants}>
|
||||||
|
{title.presenterNow}
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{showLower && (
|
||||||
|
<motion.div
|
||||||
|
className={style.messageContainer}
|
||||||
|
key='modal'
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ scaleY: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
>
|
||||||
|
<div className={style.message}>{lower.text}</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
.lowerThird {
|
||||||
|
color: #fffffa;
|
||||||
|
font-size: 4vh;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lowerContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 70vh;
|
||||||
|
background-color: #00000033;
|
||||||
|
|
||||||
|
padding: 1vw 1vw 1vw 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 45vw;
|
||||||
|
gap: 3vh;
|
||||||
|
border-radius: 0 1vh 1vh 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageContainer,
|
||||||
|
.messageContainerHidden {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2vh;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleContainer,
|
||||||
|
.subtitleContainer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto max-content;
|
||||||
|
grid-template-areas: 'decor text';
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleDecor,
|
||||||
|
.subDecor {
|
||||||
|
grid-area: decor;
|
||||||
|
height: 4vh;
|
||||||
|
background-color: #ff6969;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255, 105, 105, 1) 0%,
|
||||||
|
rgba(255, 132, 132, 1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleDecor,
|
||||||
|
.subDecor {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title,
|
||||||
|
.subtitle {
|
||||||
|
grid-area: text;
|
||||||
|
padding-left: 1vw;
|
||||||
|
justify-self: right;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitleContainer {
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageContainer {
|
||||||
|
background-color: #00000033;
|
||||||
|
opacity: 1;
|
||||||
|
transition: 0.5s;
|
||||||
|
transition-property: opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageContainerHidden {
|
||||||
|
background-color: #00000033;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 0.5s;
|
||||||
|
transition-property: opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
font-size: 3.5vh;
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
font-size: 18vw;
|
font-size: 18vw;
|
||||||
line-height: 18vw;
|
line-height: 18vw;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #ff5353;
|
color: #ff6969;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container__grayFinished {
|
.container__grayFinished {
|
||||||
border: 1vw solid #ff5353;
|
border: 1vw solid #ff6969;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =================== OVERLAY ===================*/
|
/* =================== OVERLAY ===================*/
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^4.1.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@emotion/is-prop-valid@^0.8.2":
|
||||||
|
version "0.8.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
|
||||||
|
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
|
||||||
|
dependencies:
|
||||||
|
"@emotion/memoize" "0.7.4"
|
||||||
|
|
||||||
|
"@emotion/memoize@0.7.4":
|
||||||
|
version "0.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
|
||||||
|
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
||||||
|
|
||||||
|
framer-motion@^4.1.5:
|
||||||
|
version "4.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.1.5.tgz#451ac3a41c682190bf247d5e209e24572e45cfc3"
|
||||||
|
integrity sha512-ExZ/BGKecRDs91W9ZebbCW5HgO8PaVT5V2ZUs28/jqLyef7VrTho0J5BRH/oAvwc9Qdnl0nRS/YRJWNOCt/PYQ==
|
||||||
|
dependencies:
|
||||||
|
framesync "5.3.0"
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
popmotion "9.3.5"
|
||||||
|
style-value-types "4.1.4"
|
||||||
|
tslib "^2.1.0"
|
||||||
|
optionalDependencies:
|
||||||
|
"@emotion/is-prop-valid" "^0.8.2"
|
||||||
|
|
||||||
|
framesync@5.3.0:
|
||||||
|
version "5.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.3.0.tgz#0ecfc955e8f5a6ddc8fdb0cc024070947e1a0d9b"
|
||||||
|
integrity sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.1.0"
|
||||||
|
|
||||||
|
hey-listen@^1.0.8:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
|
||||||
|
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
|
||||||
|
|
||||||
|
popmotion@9.3.5:
|
||||||
|
version "9.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.5.tgz#e821aff3424a021b0f2c93922db31c55cfe64149"
|
||||||
|
integrity sha512-Lr2rq8OP0j8D7CO2/6eO17ALeFCxjx1hfTGbMg+TLqFj+KZSGOoj6gRBVTzDINGqo6LQrORQSSSDaCL5OrB3bw==
|
||||||
|
dependencies:
|
||||||
|
framesync "5.3.0"
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
style-value-types "4.1.4"
|
||||||
|
tslib "^2.1.0"
|
||||||
|
|
||||||
|
style-value-types@4.1.4:
|
||||||
|
version "4.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
|
||||||
|
integrity sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==
|
||||||
|
dependencies:
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
tslib "^2.1.0"
|
||||||
|
|
||||||
|
tslib@^2.1.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
|
||||||
|
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
|
||||||
Reference in New Issue
Block a user