mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 08:39:34 +00:00
feat: share link from cuesheet
This commit is contained in:
committed by
Carlos Valente
parent
84b73fc02a
commit
c6c1e4a5d7
@@ -13,7 +13,7 @@
|
|||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: $box-shadow-l1;
|
box-shadow: $box-shadow-l1;
|
||||||
border: 1px solid $gray-1200;
|
border: 1px solid $gray-1100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backdrop {
|
.backdrop {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
|
||||||
z-index: $zindex-dialog;
|
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
padding-inline: 1rem;
|
padding-inline: 1rem;
|
||||||
@@ -14,7 +13,7 @@
|
|||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: $box-shadow-l1;
|
box-shadow: $box-shadow-l1;
|
||||||
border: 1px solid $gray-1200;
|
border: 1px solid $gray-1100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backdrop {
|
.backdrop {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
background-color: $gray-1250;
|
background-color: $gray-1250;
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
border-right: 1px solid $gray-1200;
|
border-right: 1px solid $gray-1100;
|
||||||
|
|
||||||
&[data-open] {
|
&[data-open] {
|
||||||
transform: translateX(0%);
|
transform: translateX(0%);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
background-color: $gray-1250;
|
background-color: $gray-1250;
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
border-left: 1px solid $gray-1200;
|
border-left: 1px solid $gray-1100;
|
||||||
|
|
||||||
&[data-open] {
|
&[data-open] {
|
||||||
transform: translateX(0%);
|
transform: translateX(0%);
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
|||||||
import { URL_PRESETS } from '../api/constants';
|
import { URL_PRESETS } from '../api/constants';
|
||||||
import { getUrlPresets } from '../api/urlPresets';
|
import { getUrlPresets } from '../api/urlPresets';
|
||||||
|
|
||||||
export default function useUrlPresets() {
|
interface FetchProps {
|
||||||
|
skip?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useUrlPresets({ skip = false }: FetchProps = {}) {
|
||||||
const { data, status, isError, refetch } = useQuery({
|
const { data, status, isError, refetch } = useQuery({
|
||||||
queryKey: URL_PRESETS,
|
queryKey: URL_PRESETS,
|
||||||
queryFn: getUrlPresets,
|
queryFn: getUrlPresets,
|
||||||
@@ -13,6 +17,7 @@ export default function useUrlPresets() {
|
|||||||
retryDelay: (attempt) => attempt * 2500,
|
retryDelay: (attempt) => attempt * 2500,
|
||||||
refetchInterval: queryRefetchIntervalSlow,
|
refetchInterval: queryRefetchIntervalSlow,
|
||||||
networkMode: 'always',
|
networkMode: 'always',
|
||||||
|
enabled: !skip,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { data: data ?? [], status, isError, refetch };
|
return { data: data ?? [], status, isError, refetch };
|
||||||
|
|||||||
@@ -12,3 +12,8 @@
|
|||||||
color: $label-gray;
|
color: $label-gray;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copiableLink {
|
||||||
|
user-select: text;
|
||||||
|
color: $ui-white;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
|
|
||||||
@@ -8,8 +8,6 @@ import Button from '../../../../common/components/buttons/Button';
|
|||||||
import Info from '../../../../common/components/info/Info';
|
import Info from '../../../../common/components/info/Info';
|
||||||
import Select from '../../../../common/components/select/Select';
|
import Select from '../../../../common/components/select/Select';
|
||||||
import Switch from '../../../../common/components/switch/Switch';
|
import Switch from '../../../../common/components/switch/Switch';
|
||||||
import useInfo from '../../../../common/hooks-query/useInfo';
|
|
||||||
import useUrlPresets from '../../../../common/hooks-query/useUrlPresets';
|
|
||||||
import copyToClipboard from '../../../../common/utils/copyToClipboard';
|
import copyToClipboard from '../../../../common/utils/copyToClipboard';
|
||||||
import { preventEscape } from '../../../../common/utils/keyEvent';
|
import { preventEscape } from '../../../../common/utils/keyEvent';
|
||||||
import { linkToOtherHost } from '../../../../common/utils/linkUtils';
|
import { linkToOtherHost } from '../../../../common/utils/linkUtils';
|
||||||
@@ -18,6 +16,12 @@ import * as Panel from '../../panel-utils/PanelUtils';
|
|||||||
|
|
||||||
import style from './GenerateLinkForm.module.scss';
|
import style from './GenerateLinkForm.module.scss';
|
||||||
|
|
||||||
|
interface GenerateLinkFormProps {
|
||||||
|
hostOptions: { value: string; label: string }[];
|
||||||
|
pathOptions: { value: string; label: string }[];
|
||||||
|
isLockedToView?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface GenerateLinkFormOptions {
|
interface GenerateLinkFormOptions {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
path: string;
|
path: string;
|
||||||
@@ -27,9 +31,7 @@ interface GenerateLinkFormOptions {
|
|||||||
|
|
||||||
type GenerateLinkState = 'pending' | 'loading' | 'success' | 'error';
|
type GenerateLinkState = 'pending' | 'loading' | 'success' | 'error';
|
||||||
|
|
||||||
export default function GenerateLinkForm() {
|
export default function GenerateLinkForm({ hostOptions, pathOptions, isLockedToView }: GenerateLinkFormProps) {
|
||||||
const { data: infoData } = useInfo();
|
|
||||||
const { data: urlPresetData } = useUrlPresets();
|
|
||||||
const [formState, setFormState] = useState<GenerateLinkState>('pending');
|
const [formState, setFormState] = useState<GenerateLinkState>('pending');
|
||||||
const [url, setUrl] = useState(serverURL);
|
const [url, setUrl] = useState(serverURL);
|
||||||
|
|
||||||
@@ -43,7 +45,7 @@ export default function GenerateLinkForm() {
|
|||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
baseUrl: currentHostName,
|
baseUrl: currentHostName,
|
||||||
path: 'timer',
|
path: isLockedToView ? pathOptions[0].value : 'timer',
|
||||||
lock: false,
|
lock: false,
|
||||||
authenticate: false,
|
authenticate: false,
|
||||||
},
|
},
|
||||||
@@ -70,37 +72,20 @@ export default function GenerateLinkForm() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const hostOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
infoData.networkInterfaces.map((nif) => ({
|
|
||||||
value: nif.address,
|
|
||||||
label: `${nif.name} - ${nif.address}`,
|
|
||||||
})),
|
|
||||||
[infoData.networkInterfaces],
|
|
||||||
);
|
|
||||||
|
|
||||||
const pathOptions = useMemo(
|
|
||||||
() => [
|
|
||||||
{ value: 'timer', label: 'Timer' },
|
|
||||||
{ value: 'cuesheet', label: 'Cuesheet' },
|
|
||||||
{ value: 'op', label: 'Operator' },
|
|
||||||
{ value: '', label: 'Companion' },
|
|
||||||
...urlPresetData.map((preset) => ({
|
|
||||||
value: preset.alias,
|
|
||||||
label: `Preset: ${preset.alias}`,
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
[urlPresetData],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel.Section as='form' onSubmit={handleSubmit(onSubmit)} onKeyDown={(event) => preventEscape(event)}>
|
<Panel.Section as='form' onSubmit={handleSubmit(onSubmit)} onKeyDown={(event) => preventEscape(event)}>
|
||||||
{errors.root && <Panel.Error>{errors.root.message}</Panel.Error>}
|
{errors.root && <Panel.Error>{errors.root.message}</Panel.Error>}
|
||||||
<Info>
|
{!isLockedToView ? (
|
||||||
<Panel.Paragraph>
|
<Info>
|
||||||
You can generate a link to share with your team or to use in automation (such as companion).
|
<Panel.Paragraph>
|
||||||
</Panel.Paragraph>
|
You can generate a link to share with your team or to use in automation (such as companion).
|
||||||
</Info>
|
</Panel.Paragraph>
|
||||||
|
</Info>
|
||||||
|
) : (
|
||||||
|
<Info>
|
||||||
|
<Panel.Paragraph>You can generate a link to share with your team</Panel.Paragraph>
|
||||||
|
</Info>
|
||||||
|
)}
|
||||||
<Panel.ListGroup>
|
<Panel.ListGroup>
|
||||||
<Panel.ListItem>
|
<Panel.ListItem>
|
||||||
<Panel.Field
|
<Panel.Field
|
||||||
@@ -114,13 +99,15 @@ export default function GenerateLinkForm() {
|
|||||||
onValueChange={(value) => setValue('baseUrl', value)}
|
onValueChange={(value) => setValue('baseUrl', value)}
|
||||||
/>
|
/>
|
||||||
</Panel.ListItem>
|
</Panel.ListItem>
|
||||||
<Panel.ListItem>
|
{isLockedToView ? (
|
||||||
<Panel.Field
|
<input type='hidden' value={watch('path')} />
|
||||||
title='URL Preset'
|
) : (
|
||||||
description='Which preset will the link point to (will default to /timer if none is given)'
|
<Panel.ListItem>
|
||||||
/>
|
<Panel.Field title='Ontime view' description='Which view or preset will the link point to' />
|
||||||
<Select options={pathOptions} value={watch('path')} onValueChange={(value) => setValue('path', value)} />
|
<Select options={pathOptions} value={watch('path')} onValueChange={(value) => setValue('path', value)} />
|
||||||
</Panel.ListItem>
|
</Panel.ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
<Panel.ListItem>
|
<Panel.ListItem>
|
||||||
<Panel.Field
|
<Panel.Field
|
||||||
title='Lock navigation'
|
title='Lock navigation'
|
||||||
@@ -145,7 +132,7 @@ export default function GenerateLinkForm() {
|
|||||||
</Button>
|
</Button>
|
||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
<QRCode size={172} value={url} className={style.qrCode} />
|
<QRCode size={172} value={url} className={style.qrCode} />
|
||||||
<div>{url}</div>
|
<div className={style.copiableLink}>{url}</div>
|
||||||
</div>
|
</div>
|
||||||
</Panel.ListItem>
|
</Panel.ListItem>
|
||||||
</Panel.ListGroup>
|
</Panel.ListGroup>
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import useInfo from '../../../../common/hooks-query/useInfo';
|
||||||
|
import useUrlPresets from '../../../../common/hooks-query/useUrlPresets';
|
||||||
|
|
||||||
|
import GenerateLinkForm from './GenerateLinkForm';
|
||||||
|
|
||||||
|
interface GenerateLinkFormExportProps {
|
||||||
|
lockedPath?: { value: string; label: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GenerateLinkFormExport({ lockedPath }: GenerateLinkFormExportProps) {
|
||||||
|
const { data: infoData } = useInfo();
|
||||||
|
const { data: urlPresetData } = useUrlPresets({ skip: lockedPath === undefined });
|
||||||
|
|
||||||
|
const hostOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
infoData.networkInterfaces.map((nif) => ({
|
||||||
|
value: nif.address,
|
||||||
|
label: `${nif.name} - ${nif.address}`,
|
||||||
|
})),
|
||||||
|
[infoData.networkInterfaces],
|
||||||
|
);
|
||||||
|
|
||||||
|
const pathOptions = useMemo(() => {
|
||||||
|
if (lockedPath) {
|
||||||
|
return [{ value: lockedPath.value, label: lockedPath.label }];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ value: 'timer', label: 'Timer' },
|
||||||
|
{ value: 'cuesheet', label: 'Cuesheet' },
|
||||||
|
{ value: 'op', label: 'Operator' },
|
||||||
|
{ value: '', label: 'Companion' },
|
||||||
|
...urlPresetData.map((preset) => ({
|
||||||
|
value: preset.alias,
|
||||||
|
label: `Preset: ${preset.alias}`,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}, [lockedPath, urlPresetData]);
|
||||||
|
|
||||||
|
return <GenerateLinkForm hostOptions={hostOptions} pathOptions={pathOptions} isLockedToView={Boolean(lockedPath)} />;
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import type { PanelBaseProps } from '../../panel-list/PanelList';
|
|||||||
import * as Panel from '../../panel-utils/PanelUtils';
|
import * as Panel from '../../panel-utils/PanelUtils';
|
||||||
import ClientControlPanel from '../client-control-panel/ClientControlPanel';
|
import ClientControlPanel from '../client-control-panel/ClientControlPanel';
|
||||||
|
|
||||||
import GenerateLinkForm from './GenerateLinkForm';
|
import GenerateLinkFormExport from './GenerateLinkFormExport';
|
||||||
import InfoNif from './NetworkInterfaces';
|
import InfoNif from './NetworkInterfaces';
|
||||||
import LogExport from './NetworkLogExport';
|
import LogExport from './NetworkLogExport';
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export default function NetworkLogPanel({ location }: PanelBaseProps) {
|
|||||||
<InfoNif />
|
<InfoNif />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<GenerateLinkForm />
|
<GenerateLinkFormExport />
|
||||||
</Panel.Card>
|
</Panel.Card>
|
||||||
</Panel.Section>
|
</Panel.Section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: $box-shadow-l1;
|
box-shadow: $box-shadow-l1;
|
||||||
border: 1px solid $gray-1200;
|
border: 1px solid $gray-1100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
|
||||||
|
import Button from '../../../../common/components/buttons/Button';
|
||||||
|
import RotatedLink from '../../../../common/components/icons/RotatedLink';
|
||||||
|
import Modal from '../../../../common/components/modal/Modal';
|
||||||
|
import useInfo from '../../../../common/hooks-query/useInfo';
|
||||||
|
import useUrlPresets from '../../../../common/hooks-query/useUrlPresets';
|
||||||
|
import GenerateLinkFormExport from '../../../../features/app-settings/panel/network-panel/GenerateLinkFormExport';
|
||||||
|
|
||||||
|
function CuesheetShareModal() {
|
||||||
|
const { data: infoData } = useInfo();
|
||||||
|
const { data: urlPresetData } = useUrlPresets();
|
||||||
|
const [isOpen, handler] = useDisclosure();
|
||||||
|
|
||||||
|
// Don't render the modal content until it's open and data is loaded
|
||||||
|
const showModalContent = isOpen && infoData && urlPresetData;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button variant='subtle' onClick={handler.open}>
|
||||||
|
<RotatedLink />
|
||||||
|
Share...
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={handler.close}
|
||||||
|
title='Share cuesheet view'
|
||||||
|
showCloseButton
|
||||||
|
bodyElements={
|
||||||
|
showModalContent ? <GenerateLinkFormExport lockedPath={{ value: 'cuesheet', label: 'Cuesheet' }} /> : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CuesheetShareModal;
|
||||||
+3
-7
@@ -7,10 +7,11 @@ import { OntimeEntry } from 'ontime-types';
|
|||||||
import Button from '../../../../common/components/buttons/Button';
|
import Button from '../../../../common/components/buttons/Button';
|
||||||
import Checkbox from '../../../../common/components/checkbox/Checkbox';
|
import Checkbox from '../../../../common/components/checkbox/Checkbox';
|
||||||
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
||||||
import RotatedLink from '../../../../common/components/icons/RotatedLink';
|
|
||||||
import PopoverContents from '../../../../common/components/popover/Popover';
|
import PopoverContents from '../../../../common/components/popover/Popover';
|
||||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||||
|
|
||||||
|
import CuesheetShareModal from './CuesheetShareModal';
|
||||||
|
|
||||||
import style from './CuesheetTableSettings.module.scss';
|
import style from './CuesheetTableSettings.module.scss';
|
||||||
|
|
||||||
interface CuesheetTableSettingsProps {
|
interface CuesheetTableSettingsProps {
|
||||||
@@ -41,13 +42,8 @@ function CuesheetTableSettings({
|
|||||||
|
|
||||||
<div className={style.inline}>
|
<div className={style.inline}>
|
||||||
<ViewSettingsFollowButton />
|
<ViewSettingsFollowButton />
|
||||||
|
|
||||||
<Editor.Separator orientation='vertical' />
|
<Editor.Separator orientation='vertical' />
|
||||||
|
<CuesheetShareModal />
|
||||||
<Button variant='subtle'>
|
|
||||||
<RotatedLink />
|
|
||||||
Share...
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user