mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 02:13:48 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37abd3fe47 | |||
| b509ee5b1f | |||
| dd2b59efe5 | |||
| 5788785b3e | |||
| e488ad19b3 | |||
| cbc03c898c | |||
| 155bfbdc42 | |||
| 5d3714a4fc | |||
| a8a1d210d8 | |||
| 49de8b63a7 | |||
| 03c9f51b47 | |||
| 8d00fd3966 | |||
| 80fd21354e | |||
| a87b15825a | |||
| 6a8f98c469 | |||
| 8685298430 | |||
| d43af2db57 | |||
| ef718c48e4 | |||
| 45e7373279 | |||
| 6af2d6e021 | |||
| 9b36f36f73 | |||
| 3a0dbf057e | |||
| dae84ab48a |
Binary file not shown.
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 259 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 149 KiB |
@@ -14,6 +14,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# This step is only needed to setup the permissions to update npm as pnpm will setup the correct node version
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
@@ -45,10 +50,9 @@ jobs:
|
|||||||
- name: Copy external
|
- name: Copy external
|
||||||
run: cp -R apps/server/src/external apps/cli/external
|
run: cp -R apps/server/src/external apps/cli/external
|
||||||
|
|
||||||
- name: Setup pnpm auth config
|
# This will be included in v24 of NodeJS so when the project upgrades to that this can be removed
|
||||||
run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}"
|
- name: Install newer version of npm
|
||||||
env:
|
run: npm install -g npm@11
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Publish to NPM
|
||||||
run: pnpm publish --access public --no-git-checks
|
run: pnpm publish --access public --no-git-checks
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
name: Ontime Resolver build
|
name: Ontime Resolver build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -14,6 +12,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# This step is only needed to setup the permissions to update npm as pnpm will setup the correct node version
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
@@ -34,10 +37,9 @@ jobs:
|
|||||||
- name: Build project packages
|
- name: Build project packages
|
||||||
run: pnpm build:resolver
|
run: pnpm build:resolver
|
||||||
|
|
||||||
- name: Setup pnpm auth config
|
# This will be included in v24 of NodeJS so when the project upgrades to that this can be removed
|
||||||
run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}"
|
- name: Install newer version of npm
|
||||||
env:
|
run: npm install -g npm@11
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Publish to NPM
|
||||||
run: pnpm publish --access public --no-git-checks
|
run: pnpm publish --access public --no-git-checks
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ We do our best to have most topics covered by the documentation. However, if you
|
|||||||
Let us know!
|
Let us know!
|
||||||
Ontime improves from the collaboration with its users. We would like to understand how you use Ontime and appreciate your feedback.
|
Ontime improves from the collaboration with its users. We would like to understand how you use Ontime and appreciate your feedback.
|
||||||
|
|
||||||
We would also like to include a testimonials section in our ✨new website✨. It would be great to showcase the diversity of users running Ontime.
|
|
||||||
|
|
||||||
# Ontime
|
# Ontime
|
||||||
|
|
||||||
Ontime is a browser-based application that manages event rundowns, scheduling, and cueing.
|
Ontime is a browser-based application that manages event rundowns, scheduling, and cueing.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getontime/cli",
|
"name": "@getontime/cli",
|
||||||
"version": "4.0.0",
|
"version": "4.0.2",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "4.0.0",
|
"version": "4.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export function useFlatRundown() {
|
|||||||
// update data whenever the revision changes
|
// update data whenever the revision changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.revision !== -1 || data.revision !== prevRevision) {
|
if (data.revision !== -1 || data.revision !== prevRevision) {
|
||||||
const flatRundown = data.order.map((id) => data.entries[id]);
|
const flatRundown = data.flatOrder.map((id) => data.entries[id]);
|
||||||
setFlatRundown(flatRundown);
|
setFlatRundown(flatRundown);
|
||||||
setPrevRevision(data.revision);
|
setPrevRevision(data.revision);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ describe('generateUrlPresetOptions', () => {
|
|||||||
expect(() => generateUrlPresetOptions('test', 'invalid-url')).toThrow();
|
expect(() => generateUrlPresetOptions('test', 'invalid-url')).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws on on invalid route', () => {
|
it('throws on invalid route', () => {
|
||||||
expect(() => generateUrlPresetOptions('test', 'www.getontime.no/somethingelse/')).toThrow();
|
expect(() => generateUrlPresetOptions('test', 'www.getontime.no/somethingelse/')).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+12
-7
@@ -2,16 +2,21 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
height: 1px;
|
|
||||||
background: $blue-500;
|
|
||||||
z-index: $zindex-floating;
|
z-index: $zindex-floating;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: $blue-500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.addButton {
|
.addButton {
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,18 @@ export default function GenerateLinkFormExport({ lockedPath }: GenerateLinkFormE
|
|||||||
const { data: infoData } = useInfo();
|
const { data: infoData } = useInfo();
|
||||||
const { data: urlPresetData } = useUrlPresets({ skip: lockedPath === undefined });
|
const { data: urlPresetData } = useUrlPresets({ skip: lockedPath === undefined });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hostOptions are only used for local networks
|
||||||
|
* the NIF address is a local IP address: 192.168.x.x or 10.x.x.x
|
||||||
|
* We need to inject the port and protocol to create a valid URL
|
||||||
|
* eg: http://192.168.x.x:port
|
||||||
|
*/
|
||||||
const hostOptions = useMemo(() => {
|
const hostOptions = useMemo(() => {
|
||||||
return infoData.networkInterfaces.map((nif) => ({
|
return infoData.networkInterfaces.map((nif) => ({
|
||||||
value: nif.address,
|
value: `http://${nif.address}:${infoData.serverPort}`,
|
||||||
label: `${nif.name} - ${nif.address}`,
|
label: `${nif.name} - ${nif.address}`,
|
||||||
}));
|
}));
|
||||||
}, [infoData.networkInterfaces]);
|
}, [infoData.networkInterfaces, infoData.serverPort]);
|
||||||
|
|
||||||
const pathOptions = useMemo(() => {
|
const pathOptions = useMemo(() => {
|
||||||
if (lockedPath) {
|
if (lockedPath) {
|
||||||
|
|||||||
@@ -92,10 +92,10 @@
|
|||||||
.end-message {
|
.end-message {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 11.5vw;
|
font-size: 11.5vw;
|
||||||
line-height: 0.9em;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--timer-overtime-color-override, $timer-finished-color);
|
color: var(--timer-overtime-color-override, $timer-finished-color);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer {
|
.timer {
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
|
|
||||||
// use a class instead of a phase, to allow suppressing overtime style
|
// use a class instead of a phase, to allow suppressing overtime style
|
||||||
&--finished {
|
&--finished {
|
||||||
color: var(--timer-colour, var(--timer-overtime-color-override, $timer-finished-color));
|
color: var(--timer-overtime-color-override, $timer-finished-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-phase="warning"] {
|
&[data-phase="warning"] {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export function getShowModifiers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showEndMessage: freezeOvertime && freezeMessage !== '',
|
showEndMessage: phase === TimerPhase.Overtime && freezeOvertime && freezeMessage !== '',
|
||||||
showFinished: phase === TimerPhase.Overtime,
|
showFinished: phase === TimerPhase.Overtime,
|
||||||
showWarning: phase === TimerPhase.Warning,
|
showWarning: phase === TimerPhase.Warning,
|
||||||
showDanger: phase === TimerPhase.Danger,
|
showDanger: phase === TimerPhase.Danger,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-electron",
|
"name": "ontime-electron",
|
||||||
"version": "4.0.0",
|
"version": "4.0.2",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "ontime-server",
|
"name": "ontime-server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"version": "4.0.0",
|
"version": "4.0.2",
|
||||||
"exports": "./src/index.js",
|
"exports": "./src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@googleapis/sheets": "^5.0.5",
|
"@googleapis/sheets": "^5.0.5",
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export async function projectDownload(req: Request, res: Response) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.download(pathToFile, filename, (error) => {
|
res.download(pathToFile, filename, { dotfiles: 'allow' }, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
const message = getErrorMessage(error);
|
const message = getErrorMessage(error);
|
||||||
res.status(500).send({ message });
|
res.status(500).send({ message });
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ export function migrateRundown(
|
|||||||
const newCustom: EntryCustomFields = {};
|
const newCustom: EntryCustomFields = {};
|
||||||
|
|
||||||
if (translationTable) {
|
if (translationTable) {
|
||||||
Object.entries(custom).map(([key, value]) => {
|
Object.entries(custom).forEach(([key, value]) => {
|
||||||
const newKey = translationTable.get(key);
|
const newKey = translationTable.get(key);
|
||||||
if (newKey) {
|
if (newKey) {
|
||||||
newCustom[newKey] = value;
|
newCustom[newKey] = value;
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ exports[`rundownToTabular() > returns an array of arrays describing a rundown 1`
|
|||||||
"Warning Time",
|
"Warning Time",
|
||||||
"Danger Time",
|
"Danger Time",
|
||||||
"Skip",
|
"Skip",
|
||||||
"Custom Field",
|
"Video Notes",
|
||||||
"Images",
|
"Audio Notes",
|
||||||
|
"PowerPoint Name",
|
||||||
|
"PowerPoint Slide",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"e2163f",
|
"e2163f",
|
||||||
@@ -33,16 +35,21 @@ exports[`rundownToTabular() > returns an array of arrays describing a rundown 1`
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"Go to Settings cog wheel top left of Editor, Project > Create",
|
"Moderator - Emma Thompson
|
||||||
|
|
||||||
|
Speakers
|
||||||
|
- Liam Carter + Sophia Patel
|
||||||
|
- Ethan Brooks
|
||||||
|
- Lucas Bennett",
|
||||||
"milestone",
|
"milestone",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"Create Custom Fields to store specific text information or images.
|
"",
|
||||||
|
"",
|
||||||
Go to Settings > Project data > Custom fields",
|
"",
|
||||||
"https://www.getontime.no/images/icons/ontime-logo.png",
|
"",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"7eaf99",
|
"7eaf99",
|
||||||
@@ -63,6 +70,8 @@ Go to Settings > Project data > Custom fields",
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"9bf60f",
|
"9bf60f",
|
||||||
@@ -81,8 +90,11 @@ Go to Settings > Project data > Custom fields",
|
|||||||
"00:10:00",
|
"00:10:00",
|
||||||
"00:05:00",
|
"00:05:00",
|
||||||
"",
|
"",
|
||||||
"Put additional info here",
|
"Camera + PowerPoint on stream
|
||||||
"",
|
PowerPoint on screens",
|
||||||
|
"2x Wireless Hand Helds",
|
||||||
|
"HoldingSlide.pptx",
|
||||||
|
"https://www.getontime.no/images/aux/demo-slide1.webp",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"bf71a2",
|
"bf71a2",
|
||||||
@@ -103,6 +115,8 @@ Go to Settings > Project data > Custom fields",
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"c2697f",
|
"c2697f",
|
||||||
@@ -121,8 +135,11 @@ Go to Settings > Project data > Custom fields",
|
|||||||
"00:02:00",
|
"00:02:00",
|
||||||
"00:01:00",
|
"00:01:00",
|
||||||
"",
|
"",
|
||||||
"",
|
"Cameras on stream
|
||||||
"",
|
PowerPoint on screens",
|
||||||
|
"1x Wireless Hand Held",
|
||||||
|
"HoldingSlide.pptx",
|
||||||
|
"https://www.getontime.no/images/aux/demo-slide1.webp",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"fa593e",
|
"fa593e",
|
||||||
@@ -141,8 +158,11 @@ Go to Settings > Project data > Custom fields",
|
|||||||
"00:02:00",
|
"00:02:00",
|
||||||
"00:01:00",
|
"00:01:00",
|
||||||
"",
|
"",
|
||||||
"",
|
"Camera + PowerPoint on stream
|
||||||
"",
|
PowerPoint on screens",
|
||||||
|
"2x Wireless Hand Helds",
|
||||||
|
"Session1.pptx",
|
||||||
|
"https://www.getontime.no/images/aux/demo-slide2.webp",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"a8b0b3",
|
"a8b0b3",
|
||||||
@@ -163,6 +183,8 @@ Go to Settings > Project data > Custom fields",
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
@@ -201,6 +223,8 @@ Go to Settings > Project data > Custom fields",
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"0aaa7d",
|
"0aaa7d",
|
||||||
@@ -213,15 +237,16 @@ Go to Settings > Project data > Custom fields",
|
|||||||
"01:00:00",
|
"01:00:00",
|
||||||
"TRUE",
|
"TRUE",
|
||||||
"",
|
"",
|
||||||
"Buffet in lobby
|
"Buffet in lobby",
|
||||||
Music plays, holding slide on screens",
|
|
||||||
"count-down",
|
"count-down",
|
||||||
"",
|
"",
|
||||||
"00:02:00",
|
"00:02:00",
|
||||||
"00:01:00",
|
"00:01:00",
|
||||||
"",
|
"",
|
||||||
"",
|
"Holding slide on screens",
|
||||||
"",
|
"House music",
|
||||||
|
"HoldingSlide.pptx",
|
||||||
|
"https://www.getontime.no/images/aux/demo-slide1.webp",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
@@ -260,6 +285,8 @@ Music plays, holding slide on screens",
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"02afca",
|
"02afca",
|
||||||
@@ -280,6 +307,8 @@ Music plays, holding slide on screens",
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"75ce86",
|
"75ce86",
|
||||||
@@ -298,8 +327,14 @@ Music plays, holding slide on screens",
|
|||||||
"00:02:00",
|
"00:02:00",
|
||||||
"00:01:00",
|
"00:01:00",
|
||||||
"",
|
"",
|
||||||
"",
|
"Camera + PPT + Video on stream
|
||||||
"",
|
PowerPoint + Video on screens
|
||||||
|
|
||||||
|
Video file: Session2.mp4",
|
||||||
|
"1x Wireless Hand Held
|
||||||
|
1x Video with audio",
|
||||||
|
"Session2.pptx",
|
||||||
|
"https://www.getontime.no/images/aux/demo-slide3.webp",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"e10ed9",
|
"e10ed9",
|
||||||
@@ -318,8 +353,10 @@ Music plays, holding slide on screens",
|
|||||||
"00:02:00",
|
"00:02:00",
|
||||||
"00:01:00",
|
"00:01:00",
|
||||||
"",
|
"",
|
||||||
"",
|
"Holding slide on screens",
|
||||||
"",
|
"1x Wireless Hand Held",
|
||||||
|
"HoldingSlide.pptx",
|
||||||
|
"https://www.getontime.no/images/aux/demo-slide1.webp",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"07df89",
|
"07df89",
|
||||||
@@ -340,6 +377,8 @@ Music plays, holding slide on screens",
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ router.get('/:rundownId/export', validateRundownExport, (req: Request, res: Resp
|
|||||||
const customFields = getProjectCustomFields();
|
const customFields = getProjectCustomFields();
|
||||||
const buffer = generateExcelFile(rundown, customFields);
|
const buffer = generateExcelFile(rundown, customFields);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', `attachment;`);
|
res.setHeader('Content-Disposition', 'attachment;');
|
||||||
res.setHeader('Content-Type', EXCEL_MIME);
|
res.setHeader('Content-Type', EXCEL_MIME);
|
||||||
res.setHeader('Content-Length', buffer.length.toString());
|
res.setHeader('Content-Length', buffer.length.toString());
|
||||||
res.status(200).send(buffer);
|
res.status(200).send(buffer);
|
||||||
|
|||||||
@@ -80,6 +80,18 @@ describe('generateAuthenticatedUrl()', () => {
|
|||||||
});
|
});
|
||||||
expect(withAuth.toString()).toBe('http://192.168.10.173:4001/?token=1234');
|
expect(withAuth.toString()).toBe('http://192.168.10.173:4001/?token=1234');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('throws if provided an IP address without protocol', () => {
|
||||||
|
expect(() =>
|
||||||
|
generateShareUrl('192.168.10.173', '<<companion>>', {
|
||||||
|
lockConfig: false,
|
||||||
|
lockNav: false,
|
||||||
|
authenticate: true,
|
||||||
|
preset: undefined,
|
||||||
|
hash: '1234',
|
||||||
|
}),
|
||||||
|
).toThrowError('Invalid URL');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('for ontime-cloud URLs', () => {
|
describe('for ontime-cloud URLs', () => {
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ export function generateShareUrl(
|
|||||||
canonicalPath: string,
|
canonicalPath: string,
|
||||||
{ authenticate, lockConfig, lockNav, preset, prefix = routerPrefix, hash = hashedPassword }: LinkOptions,
|
{ authenticate, lockConfig, lockNav, preset, prefix = routerPrefix, hash = hashedPassword }: LinkOptions,
|
||||||
): URL {
|
): URL {
|
||||||
|
/**
|
||||||
|
* URL constructor will throw if given an IP address without protocol
|
||||||
|
* for the case of IP addresses, we expect that the base URL provides protocol and port
|
||||||
|
* eg: http://192.168.10.1:4001
|
||||||
|
*/
|
||||||
const url = new URL(baseUrl);
|
const url = new URL(baseUrl);
|
||||||
|
|
||||||
// companion links point to the root
|
// companion links point to the root
|
||||||
|
|||||||
Vendored
+2
-2
@@ -123,8 +123,8 @@ function formatTimer(number) {
|
|||||||
* @param {number} number - The number to pad
|
* @param {number} number - The number to pad
|
||||||
* @returns {string} The padded number string
|
* @returns {string} The padded number string
|
||||||
*/
|
*/
|
||||||
function leftPad(number) {
|
function leftPad(val) {
|
||||||
return Math.floor(number).toString().padStart(2, '0');
|
return Math.floor(val).toString().padStart(2, '0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,13 +28,9 @@ export const demoDb: DatabaseModel = {
|
|||||||
type: SupportedEntry.Milestone,
|
type: SupportedEntry.Milestone,
|
||||||
cue: 'Demo',
|
cue: 'Demo',
|
||||||
title: 'Clear all, or Create New Project to start fresh',
|
title: 'Clear all, or Create New Project to start fresh',
|
||||||
note: 'Go to Settings cog wheel top left of Editor, Project > Create',
|
note: 'Moderator - Emma Thompson\n\nSpeakers\n- Liam Carter + Sophia Patel\n- Ethan Brooks\n- Lucas Bennett',
|
||||||
colour: '#9d9d9d',
|
colour: '#9d9d9d',
|
||||||
custom: {
|
custom: {},
|
||||||
Custom_Field:
|
|
||||||
'Create Custom Fields to store specific text information or images.\n\nGo to Settings > Project data > Custom fields',
|
|
||||||
Images: 'https://www.getontime.no/images/icons/ontime-logo.png',
|
|
||||||
},
|
|
||||||
parent: null,
|
parent: null,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
},
|
},
|
||||||
@@ -78,7 +74,10 @@ export const demoDb: DatabaseModel = {
|
|||||||
timeWarning: 600000,
|
timeWarning: 600000,
|
||||||
timeDanger: 300000,
|
timeDanger: 300000,
|
||||||
custom: {
|
custom: {
|
||||||
Custom_Field: 'Put additional info here',
|
PowerPoint_Slide: 'https://www.getontime.no/images/aux/demo-slide1.webp',
|
||||||
|
Video_Notes: 'Camera + PowerPoint on stream\nPowerPoint on screens',
|
||||||
|
Audio_Notes: '2x Wireless Hand Helds',
|
||||||
|
PowerPoint_Name: 'HoldingSlide.pptx',
|
||||||
},
|
},
|
||||||
triggers: [],
|
triggers: [],
|
||||||
},
|
},
|
||||||
@@ -117,7 +116,12 @@ export const demoDb: DatabaseModel = {
|
|||||||
revision: 0,
|
revision: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
custom: {},
|
custom: {
|
||||||
|
PowerPoint_Slide: 'https://www.getontime.no/images/aux/demo-slide1.webp',
|
||||||
|
Video_Notes: 'Cameras on stream\nPowerPoint on screens',
|
||||||
|
Audio_Notes: '1x Wireless Hand Held',
|
||||||
|
PowerPoint_Name: 'HoldingSlide.pptx',
|
||||||
|
},
|
||||||
triggers: [],
|
triggers: [],
|
||||||
},
|
},
|
||||||
fa593e: {
|
fa593e: {
|
||||||
@@ -144,7 +148,12 @@ export const demoDb: DatabaseModel = {
|
|||||||
revision: 0,
|
revision: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
custom: {},
|
custom: {
|
||||||
|
PowerPoint_Slide: 'https://www.getontime.no/images/aux/demo-slide2.webp',
|
||||||
|
Video_Notes: 'Camera + PowerPoint on stream\nPowerPoint on screens',
|
||||||
|
Audio_Notes: '2x Wireless Hand Helds',
|
||||||
|
PowerPoint_Name: 'Session1.pptx',
|
||||||
|
},
|
||||||
triggers: [],
|
triggers: [],
|
||||||
},
|
},
|
||||||
a8b0b3: {
|
a8b0b3: {
|
||||||
@@ -187,7 +196,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
countToEnd: false,
|
countToEnd: false,
|
||||||
skip: false,
|
skip: false,
|
||||||
note: 'Buffet in lobby\nMusic plays, holding slide on screens',
|
note: 'Buffet in lobby',
|
||||||
colour: '#779BE7',
|
colour: '#779BE7',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0,
|
||||||
@@ -197,7 +206,12 @@ export const demoDb: DatabaseModel = {
|
|||||||
revision: 0,
|
revision: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
custom: {},
|
custom: {
|
||||||
|
PowerPoint_Slide: 'https://www.getontime.no/images/aux/demo-slide1.webp',
|
||||||
|
Video_Notes: 'Holding slide on screens',
|
||||||
|
Audio_Notes: 'House music',
|
||||||
|
PowerPoint_Name: 'HoldingSlide.pptx',
|
||||||
|
},
|
||||||
triggers: [],
|
triggers: [],
|
||||||
},
|
},
|
||||||
'6b0edb': {
|
'6b0edb': {
|
||||||
@@ -250,7 +264,12 @@ export const demoDb: DatabaseModel = {
|
|||||||
revision: 0,
|
revision: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
custom: {},
|
custom: {
|
||||||
|
PowerPoint_Slide: 'https://www.getontime.no/images/aux/demo-slide3.webp',
|
||||||
|
Video_Notes: 'Camera + PPT + Video on stream\nPowerPoint + Video on screens\n\nVideo file: Session2.mp4',
|
||||||
|
Audio_Notes: '1x Wireless Hand Held\n1x Video with audio',
|
||||||
|
PowerPoint_Name: 'Session2.pptx',
|
||||||
|
},
|
||||||
triggers: [],
|
triggers: [],
|
||||||
},
|
},
|
||||||
e10ed9: {
|
e10ed9: {
|
||||||
@@ -277,7 +296,12 @@ export const demoDb: DatabaseModel = {
|
|||||||
revision: 0,
|
revision: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
custom: {},
|
custom: {
|
||||||
|
PowerPoint_Slide: 'https://www.getontime.no/images/aux/demo-slide1.webp',
|
||||||
|
Video_Notes: 'Holding slide on screens',
|
||||||
|
Audio_Notes: '1x Wireless Hand Held',
|
||||||
|
PowerPoint_Name: 'HoldingSlide.pptx',
|
||||||
|
},
|
||||||
triggers: [],
|
triggers: [],
|
||||||
},
|
},
|
||||||
'07df89': {
|
'07df89': {
|
||||||
@@ -341,15 +365,25 @@ export const demoDb: DatabaseModel = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
customFields: {
|
customFields: {
|
||||||
Custom_Field: {
|
Video_Notes: {
|
||||||
|
type: 'text',
|
||||||
|
colour: '#FFAB33',
|
||||||
|
label: 'Video Notes',
|
||||||
|
},
|
||||||
|
Audio_Notes: {
|
||||||
|
type: 'text',
|
||||||
|
colour: '#339E4E',
|
||||||
|
label: 'Audio Notes',
|
||||||
|
},
|
||||||
|
PowerPoint_Name: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
colour: '#3E75E8',
|
colour: '#3E75E8',
|
||||||
label: 'Custom Field',
|
label: 'PowerPoint Name',
|
||||||
},
|
},
|
||||||
Images: {
|
PowerPoint_Slide: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
colour: '#ED3333',
|
colour: '#ED3333',
|
||||||
label: 'Images',
|
label: 'PowerPoint Slide',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
automation: {
|
automation: {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function isRestorePoint(restorePoint: unknown): restorePoint is RestorePo
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is.number(restorePoint.startEpoch) && restorePoint.startEpoch !== null) {
|
if (!is.number(restorePoint.startEpoch) && restorePoint.startEpoch !== null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,13 +273,14 @@ class RuntimeService {
|
|||||||
public startByCue(cue: string): boolean {
|
public startByCue(cue: string): boolean {
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const rundown = getCurrentRundown();
|
const rundown = getCurrentRundown();
|
||||||
const { playableEventOrder } = getRundownMetadata();
|
const { timedEventOrder } = getRundownMetadata();
|
||||||
|
|
||||||
const event = findNextPlayableWithCue(
|
const event = findNextPlayableWithCue(
|
||||||
rundown,
|
rundown,
|
||||||
playableEventOrder,
|
timedEventOrder,
|
||||||
cue,
|
cue,
|
||||||
state.rundown.selectedEventIndex ?? undefined,
|
state.rundown.selectedEventIndex ?? undefined,
|
||||||
|
state.timer.playback === Playback.Armed, // If we are armed allow the armed event to be considered for playback
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
@@ -341,6 +342,7 @@ class RuntimeService {
|
|||||||
playableEventOrder,
|
playableEventOrder,
|
||||||
cue,
|
cue,
|
||||||
state.rundown.selectedEventIndex ?? undefined,
|
state.rundown.selectedEventIndex ?? undefined,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
|
|||||||
@@ -118,24 +118,25 @@ export function findNextPlayableId(playableEventsOrder: EntryId[], currentEventI
|
|||||||
*/
|
*/
|
||||||
export function findNextPlayableWithCue(
|
export function findNextPlayableWithCue(
|
||||||
rundown: Rundown,
|
rundown: Rundown,
|
||||||
playableEventsOrder: EntryId[],
|
timedEventsOrder: EntryId[],
|
||||||
targetCue: string,
|
targetCue: string,
|
||||||
currentEventIndex = 0,
|
currentEventIndex = 0,
|
||||||
|
allowCurrent = false,
|
||||||
): OntimeEvent | undefined {
|
): OntimeEvent | undefined {
|
||||||
const lowerCaseCue = targetCue.toLowerCase();
|
const startFromIndex = allowCurrent ? currentEventIndex : currentEventIndex + 1;
|
||||||
|
|
||||||
for (let i = currentEventIndex; i < playableEventsOrder.length; i++) {
|
for (let i = startFromIndex; i < timedEventsOrder.length; i++) {
|
||||||
const eventId = playableEventsOrder[i];
|
const eventId = timedEventsOrder[i];
|
||||||
const event = rundown.entries[eventId];
|
const event = rundown.entries[eventId];
|
||||||
if (isOntimeEvent(event) && isPlayableEvent(event) && event.cue.toLowerCase() === lowerCaseCue) {
|
if (isOntimeEvent(event) && isPlayableEvent(event) && event.cue === targetCue) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < currentEventIndex; i++) {
|
for (let i = 0; i < startFromIndex; i++) {
|
||||||
const eventId = playableEventsOrder[i];
|
const eventId = timedEventsOrder[i];
|
||||||
const event = rundown.entries[eventId];
|
const event = rundown.entries[eventId];
|
||||||
if (isOntimeEvent(event) && isPlayableEvent(event) && event.cue.toLowerCase() === lowerCaseCue) {
|
if (isOntimeEvent(event) && isPlayableEvent(event) && event.cue === targetCue) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -377,7 +377,7 @@ export async function upload(sheetId: string, options: ImportMap) {
|
|||||||
|
|
||||||
const titleMetadata = Object.values(sheetMetadata)[0];
|
const titleMetadata = Object.values(sheetMetadata)[0];
|
||||||
if (titleMetadata === undefined) {
|
if (titleMetadata === undefined) {
|
||||||
throw new Error(`Sheet read failed: failed to find title row`);
|
throw new Error('Sheet read failed: failed to find title row');
|
||||||
}
|
}
|
||||||
const titleRow = titleMetadata['row'];
|
const titleRow = titleMetadata['row'];
|
||||||
const updateRundown = Array<sheets_v4.Schema$Request>();
|
const updateRundown = Array<sheets_v4.Schema$Request>();
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ describe('parseExcelDate', () => {
|
|||||||
['1899-12-30T07:00:00.000Z', 28800000],
|
['1899-12-30T07:00:00.000Z', 28800000],
|
||||||
['1899-12-30T08:00:10.000Z', 32410000],
|
['1899-12-30T08:00:10.000Z', 32410000],
|
||||||
['1899-12-30T08:30:00.000Z', 34200000],
|
['1899-12-30T08:30:00.000Z', 34200000],
|
||||||
])(`handles %s`, (fromExcel, expected) => {
|
])('handles %s', (fromExcel, expected) => {
|
||||||
expect(parseExcelDate(fromExcel)).toBe(expected);
|
expect(parseExcelDate(fromExcel)).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parses a time string that passes validation', () => {
|
describe('parses a time string that passes validation', () => {
|
||||||
test.each([['10:00:00'], ['10:00'], ['10:00AM'], ['10:00am'], ['10:00PM'], ['10:00pm']])(
|
test.each([['10:00:00'], ['10:00'], ['10:00AM'], ['10:00am'], ['10:00PM'], ['10:00pm']])(
|
||||||
`handles %s`,
|
'handles %s',
|
||||||
(fromExcel) => {
|
(fromExcel) => {
|
||||||
expect(parseExcelDate(fromExcel)).not.toBe(0);
|
expect(parseExcelDate(fromExcel)).not.toBe(0);
|
||||||
},
|
},
|
||||||
@@ -26,13 +26,13 @@ describe('parseExcelDate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('uses numeric fields as minutes', () => {
|
describe('uses numeric fields as minutes', () => {
|
||||||
test.each([[1], [10], [100]])(`handles numeric fields %s`, (fromExcel) => {
|
test.each([[1], [10], [100]])('handles numeric fields %s', (fromExcel) => {
|
||||||
expect(parseExcelDate(fromExcel)).toBe(fromExcel * MILLIS_PER_MINUTE);
|
expect(parseExcelDate(fromExcel)).toBe(fromExcel * MILLIS_PER_MINUTE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('returns 0 on other strings', () => {
|
describe('returns 0 on other strings', () => {
|
||||||
test.each([['test'], [''], ['x']])(`handles invalid fields %s`, (fromExcel) => {
|
test.each([['test'], [''], ['x']])('handles invalid fields %s', (fromExcel) => {
|
||||||
expect(parseExcelDate(fromExcel)).toBe(0);
|
expect(parseExcelDate(fromExcel)).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "4.0.0",
|
"version": "4.0.2",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ontime",
|
"ontime",
|
||||||
|
|||||||
Reference in New Issue
Block a user