Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
WrappedOverridableItemDeleted,
WrappedOverridableItemNormal,
} from '../../util/OverrideOpHelper.js'
import { faCheck, faPencilAlt, faSync, faTrash } from '@fortawesome/free-solid-svg-icons'
import { faCheck, faPencilAlt, faSync, faTrash, faSave, faBan } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { JSONBlob, JSONBlobParse, JSONSchema } from '@sofie-automation/blueprints-integration'
import { DropdownInputControl, DropdownInputOption } from '../../../../lib/Components/DropdownInput.js'
Expand All @@ -32,12 +32,24 @@ interface PeripheralDeviceTranslated {
export interface SubDevicesTableProps {
subDevices: WrappedOverridableItem<any>[]
overrideHelper: OverrideOpHelper
instantSaveOverrideHelper: OverrideOpHelper
peripheralDevices: PeripheralDevice[]
hasUnsavedChanges: boolean
saveChanges: () => void
discardChanges: () => void
updateObjectId: (oldId: string, newId: string) => void
updatedIds: Map<string, string>
}
export function GenericSubDevicesTable({
subDevices,
overrideHelper,
peripheralDevices,
hasUnsavedChanges,
instantSaveOverrideHelper,
saveChanges,
discardChanges,
updateObjectId,
updatedIds,
}: Readonly<SubDevicesTableProps>): JSX.Element {
const { t } = useTranslation()
const { toggleExpanded, isExpanded } = useToggleExpandHelper()
Expand All @@ -59,15 +71,14 @@ export function GenericSubDevicesTable({

return devicesMap
}, [peripheralDevices])

const confirmRemove = useCallback(
(subdeviceId: string) => {
doModalDialog({
title: t('Remove this device?'),
no: t('Cancel'),
yes: t('Remove'),
onAccept: () => {
overrideHelper().deleteItem(subdeviceId).commit()
instantSaveOverrideHelper().deleteItem(subdeviceId).commit()
},
message: (
<React.Fragment>
Expand All @@ -82,7 +93,7 @@ export function GenericSubDevicesTable({
),
})
},
[t, overrideHelper]
[t, instantSaveOverrideHelper]
)

const peripheralDeviceOptions = useMemo(() => {
Expand Down Expand Up @@ -145,6 +156,7 @@ export function GenericSubDevicesTable({
isEdited={isExpanded(item.id)}
editItemWithId={toggleExpanded}
removeItemWithId={confirmRemove}
updatedIds={updatedIds}
/>
{isExpanded(item.id) && (
<SubDeviceEditRow
Expand All @@ -153,6 +165,11 @@ export function GenericSubDevicesTable({
editItemWithId={toggleExpanded}
item={item}
overrideHelper={overrideHelper}
hasUnsavedChanges={hasUnsavedChanges}
saveChanges={saveChanges}
discardChanges={discardChanges}
updateObjectId={updateObjectId}
updatedIds={updatedIds}
/>
)}
</React.Fragment>
Expand All @@ -170,13 +187,15 @@ interface SummaryRowProps {
isEdited: boolean
editItemWithId: (itemId: string) => void
removeItemWithId: (itemId: string) => void
updatedIds: Map<string, string>
}
function SummaryRow({
item,
peripheralDevice,
isEdited,
editItemWithId,
removeItemWithId,
updatedIds,
}: Readonly<SummaryRowProps>): JSX.Element {
const editItem = useCallback(() => editItemWithId(item.id), [editItemWithId, item.id])
const removeItem = useCallback(() => removeItemWithId(item.id), [removeItemWithId, item.id])
Expand All @@ -185,13 +204,17 @@ function SummaryRow({
? (peripheralDevice.subdeviceManifest?.[item.computed.options.type]?.displayName ?? '-')
: '-'

const idChanged = Array.from(updatedIds?.entries() || []).some(([key, value]) => value === item.id || key === item.id)

return (
<tr
className={classNames({
hl: isEdited,
})}
>
<th className="settings-studio-device__name c2">{item.id}</th>
<th className="settings-studio-device__name c2">
{item.id} {idChanged && '(pending save)'}
</th>

<th className="settings-studio-device__parent c2">
{peripheralDevice?.name || item.computed.peripheralDeviceId || '-'}
Expand Down Expand Up @@ -252,31 +275,41 @@ interface SubDeviceEditRowProps {
editItemWithId: (subdeviceId: string, forceState?: boolean) => void
item: WrappedOverridableItemNormal<any>
overrideHelper: OverrideOpHelper
hasUnsavedChanges: boolean
saveChanges: () => void
discardChanges: () => void
updateObjectId: (oldId: string, newId: string) => void
updatedIds: Map<string, string>
}
function SubDeviceEditRow({
peripheralDevice,
peripheralDeviceOptions,
editItemWithId,
item,
overrideHelper,
hasUnsavedChanges,
saveChanges,
discardChanges,
updateObjectId,
updatedIds,
}: Readonly<SubDeviceEditRowProps>) {
const { t } = useTranslation()

const finishEditItem = useCallback(() => editItemWithId(item.id, false), [editItemWithId, item.id])

const updateObjectId = useCallback(
const handleUpdateId = useCallback(
(newId: string) => {
if (item.id === newId) return

overrideHelper().changeItemId(item.id, newId).commit()
updateObjectId(item.id, newId)

// toggle ui visibility
editItemWithId(item.id, false)
editItemWithId(newId, true)
},
[item.id, overrideHelper, editItemWithId]
[item.id, updateObjectId]
)

const idToShowInInput = updatedIds?.get(item.id) || item.id

return (
<tr className="expando-details hl" key={item.id + '-details'}>
<td colSpan={99}>
Expand All @@ -294,7 +327,7 @@ function SubDeviceEditRow({
</LabelAndOverridesForDropdown>
<label className="field">
<LabelActual label={t('Device ID')} />
<TextInputControl value={item.id} handleUpdate={updateObjectId} disabled={!!item.defaults} />
<TextInputControl value={idToShowInInput} handleUpdate={handleUpdateId} disabled={!!item.defaults} />
</label>

{!item.computed.peripheralDeviceId && (
Expand All @@ -308,9 +341,23 @@ function SubDeviceEditRow({
)}
</div>
<div className="m-1 me-2 text-end">
<button className={classNames('btn btn-primary')} onClick={finishEditItem}>
<FontAwesomeIcon icon={faCheck} />
</button>
{hasUnsavedChanges ? (
<>
<button className="btn btn-warning ms-2" onClick={discardChanges}>
<FontAwesomeIcon icon={faBan} />
&nbsp;{t('Discard')}
</button>

<button className="btn btn-primary ms-2" onClick={saveChanges}>
<FontAwesomeIcon icon={faSave} />
&nbsp;{t('Save')}
</button>
</>
) : (
<button className={classNames('btn btn-primary')} onClick={finishEditItem}>
<FontAwesomeIcon icon={faCheck} />
</button>
)}
</div>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { Studios } from '../../../../collections/index.js'
import { StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { useTracker } from '../../../../lib/ReactMeteorData/ReactMeteorData.js'
Expand Down Expand Up @@ -30,6 +30,13 @@ export function StudioIngestSubDevices({

const studio = useTracker(() => Studios.findOne(studioId), [studioId])

const [unsavedOverrides, setUnsavedOverrides] = useState<SomeObjectOverrideOp[] | undefined>(undefined)

const baseSettings = useMemo(
() => studio?.peripheralDeviceSettings?.ingestDevices ?? wrapDefaultObject({}),
[studio?.peripheralDeviceSettings?.ingestDevices]
)

const saveOverrides = useCallback(
(newOps: SomeObjectOverrideOp[]) => {
if (studio?._id) {
Expand All @@ -43,17 +50,25 @@ export function StudioIngestSubDevices({
[studio?._id]
)

const baseSettings = useMemo(
() => studio?.peripheralDeviceSettings?.ingestDevices ?? wrapDefaultObject({}),
[studio?.peripheralDeviceSettings?.ingestDevices]
)
const settingsWithOverrides = useMemo(() => {
if (unsavedOverrides) {
return {
...baseSettings,
overrides: unsavedOverrides,
}
}
return baseSettings
}, [baseSettings, unsavedOverrides])

const overrideHelper = useOverrideOpHelper(saveOverrides, baseSettings)
const batchedOverrideHelper = useOverrideOpHelper(setUnsavedOverrides, settingsWithOverrides)
const instantSaveOverrideHelper = useOverrideOpHelper(saveOverrides, settingsWithOverrides)

const wrappedSubDevices = useMemo(
() =>
getAllCurrentAndDeletedItemsFromOverrides<StudioIngestDevice>(baseSettings, (a, b) => a[0].localeCompare(b[0])),
[baseSettings]
getAllCurrentAndDeletedItemsFromOverrides<StudioIngestDevice>(settingsWithOverrides, (a, b) =>
a[0].localeCompare(b[0])
),
[settingsWithOverrides]
)

const filteredPeripheralDevices = useMemo(
Expand Down Expand Up @@ -85,7 +100,42 @@ export function StudioIngestSubDevices({
'peripheralDeviceSettings.ingestDevices.overrides': addOp,
},
})
}, [studioId, wrappedSubDevices])
}, [wrappedSubDevices, settingsWithOverrides.overrides])

// key is subDevice's old id, value is it's new id if it was changed
const [updatedIds, setUpdatedIds] = useState(new Map<string, string>())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really appreciate a comment here describing what the strings in key and value of this Map actually mean, that the key is the oldId for a sub-device, and the value is the newId.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


const updateObjectId = useCallback(
(oldId: string, newId: string) => {
if (oldId === newId) return

batchedOverrideHelper().changeItemId(oldId, newId).commit()
setUpdatedIds((prev) => new Map(prev).set(oldId, newId))
},
[batchedOverrideHelper, setUpdatedIds]
)

const discardChanges = useCallback(() => {
setUnsavedOverrides(undefined)
setUpdatedIds(new Map<string, string>())
}, [])

const saveChanges = useCallback(() => {
if (studio?._id && unsavedOverrides) {
Studios.update(studio._id, {
$set: {
'peripheralDeviceSettings.ingestDevices.overrides': unsavedOverrides,
},
})
setUnsavedOverrides(undefined)
}

if (updatedIds.size > 0) {
setUpdatedIds(new Map<string, string>())
}
}, [studio?._id, unsavedOverrides])

const hasUnsavedChanges = useMemo(() => !!unsavedOverrides || updatedIds.size > 0, [unsavedOverrides, updatedIds])

return (
<div className="mb-4">
Expand All @@ -101,8 +151,14 @@ export function StudioIngestSubDevices({

<GenericSubDevicesTable
subDevices={wrappedSubDevices}
overrideHelper={overrideHelper}
overrideHelper={batchedOverrideHelper}
peripheralDevices={filteredPeripheralDevices}
hasUnsavedChanges={!!unsavedOverrides}
instantSaveOverrideHelper={instantSaveOverrideHelper}
saveChanges={saveChanges}
discardChanges={discardChanges}
updateObjectId={updateObjectId}
updatedIds={updatedIds}
/>

<div className="my-1 mx-2">
Expand Down
Loading
Loading