Refine control room upload settings UI defaults
This commit is contained in:
@@ -2923,7 +2923,7 @@ class EventPublicController extends BaseController
|
|||||||
$autoApproveUploads = $uploadVisibility === 'immediate';
|
$autoApproveUploads = $uploadVisibility === 'immediate';
|
||||||
$controlRoom = Arr::get($eventModel->settings ?? [], 'control_room', []);
|
$controlRoom = Arr::get($eventModel->settings ?? [], 'control_room', []);
|
||||||
$controlRoom = is_array($controlRoom) ? $controlRoom : [];
|
$controlRoom = is_array($controlRoom) ? $controlRoom : [];
|
||||||
$autoAddApprovedToLiveSetting = (bool) Arr::get($controlRoom, 'auto_add_approved_to_live', false);
|
$autoAddApprovedToLiveSetting = (bool) Arr::get($controlRoom, 'auto_add_approved_to_live', true);
|
||||||
$trustedUploaders = Arr::get($controlRoom, 'trusted_uploaders', []);
|
$trustedUploaders = Arr::get($controlRoom, 'trusted_uploaders', []);
|
||||||
$forceReviewUploaders = Arr::get($controlRoom, 'force_review_uploaders', []);
|
$forceReviewUploaders = Arr::get($controlRoom, 'force_review_uploaders', []);
|
||||||
$autoAddApprovedToLiveDefault = $autoAddApprovedToLiveSetting || $autoApproveUploads;
|
$autoAddApprovedToLiveDefault = $autoAddApprovedToLiveSetting || $autoApproveUploads;
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class PhotoController extends Controller
|
|||||||
$photo->status = $validated['visible'] ? 'approved' : 'hidden';
|
$photo->status = $validated['visible'] ? 'approved' : 'hidden';
|
||||||
$photo->save();
|
$photo->save();
|
||||||
|
|
||||||
$autoRemoveLiveOnHide = (bool) Arr::get($event->settings ?? [], 'control_room.auto_remove_live_on_hide', false);
|
$autoRemoveLiveOnHide = (bool) Arr::get($event->settings ?? [], 'control_room.auto_remove_live_on_hide', true);
|
||||||
if ($autoRemoveLiveOnHide && ! $validated['visible']) {
|
if ($autoRemoveLiveOnHide && ! $validated['visible']) {
|
||||||
$photo->rejectForLiveShow($request->user(), 'hidden');
|
$photo->rejectForLiveShow($request->user(), 'hidden');
|
||||||
}
|
}
|
||||||
@@ -537,7 +537,7 @@ class PhotoController extends Controller
|
|||||||
|
|
||||||
$photo->update($validated);
|
$photo->update($validated);
|
||||||
|
|
||||||
$autoRemoveLiveOnHide = (bool) Arr::get($event->settings ?? [], 'control_room.auto_remove_live_on_hide', false);
|
$autoRemoveLiveOnHide = (bool) Arr::get($event->settings ?? [], 'control_room.auto_remove_live_on_hide', true);
|
||||||
if ($autoRemoveLiveOnHide && ($validated['status'] ?? null) === 'rejected') {
|
if ($autoRemoveLiveOnHide && ($validated['status'] ?? null) === 'rejected') {
|
||||||
$photo->rejectForLiveShow($request->user());
|
$photo->rejectForLiveShow($request->user());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2484,6 +2484,7 @@
|
|||||||
},
|
},
|
||||||
"automation": {
|
"automation": {
|
||||||
"title": "Automatik",
|
"title": "Automatik",
|
||||||
|
"sectionTitle": "Einstellungen für Uploads",
|
||||||
"autoApproveHighlights": {
|
"autoApproveHighlights": {
|
||||||
"label": "Highlights automatisch freigeben",
|
"label": "Highlights automatisch freigeben",
|
||||||
"help": "Wenn du ein ausstehendes Foto highlightest, wird es automatisch freigegeben."
|
"help": "Wenn du ein ausstehendes Foto highlightest, wird es automatisch freigegeben."
|
||||||
|
|||||||
@@ -2486,6 +2486,7 @@
|
|||||||
},
|
},
|
||||||
"automation": {
|
"automation": {
|
||||||
"title": "Automation",
|
"title": "Automation",
|
||||||
|
"sectionTitle": "Einstellungen für Uploads",
|
||||||
"autoApproveHighlights": {
|
"autoApproveHighlights": {
|
||||||
"label": "Auto-approve highlights",
|
"label": "Auto-approve highlights",
|
||||||
"help": "When you highlight a pending photo it will be approved automatically."
|
"help": "When you highlight a pending photo it will be approved automatically."
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Check, Eye, EyeOff, Image as ImageIcon, RefreshCcw, Settings, Sparkles } from 'lucide-react';
|
import { Check, ChevronDown, Eye, EyeOff, Image as ImageIcon, RefreshCcw, Settings, Sparkles } from 'lucide-react';
|
||||||
import { YStack, XStack } from '@tamagui/stacks';
|
import { YStack, XStack } from '@tamagui/stacks';
|
||||||
import { SizableText as Text } from '@tamagui/text';
|
import { SizableText as Text } from '@tamagui/text';
|
||||||
import { Pressable } from '@tamagui/react-native-web-lite';
|
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||||
import { Switch } from '@tamagui/switch';
|
import { Switch } from '@tamagui/switch';
|
||||||
|
import { Accordion } from '@tamagui/accordion';
|
||||||
import { MobileShell, HeaderActionButton } from './components/MobileShell';
|
import { MobileShell, HeaderActionButton } from './components/MobileShell';
|
||||||
import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives';
|
import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives';
|
||||||
import { MobileField, MobileSelect } from './components/FormControls';
|
import { MobileField, MobileSelect } from './components/FormControls';
|
||||||
@@ -302,9 +303,9 @@ export default function MobileEventControlRoomPage() {
|
|||||||
const [liveBusyId, setLiveBusyId] = React.useState<number | null>(null);
|
const [liveBusyId, setLiveBusyId] = React.useState<number | null>(null);
|
||||||
|
|
||||||
const [controlRoomSettings, setControlRoomSettings] = React.useState<ControlRoomSettings>({
|
const [controlRoomSettings, setControlRoomSettings] = React.useState<ControlRoomSettings>({
|
||||||
auto_approve_highlights: false,
|
auto_approve_highlights: true,
|
||||||
auto_add_approved_to_live: false,
|
auto_add_approved_to_live: true,
|
||||||
auto_remove_live_on_hide: false,
|
auto_remove_live_on_hide: true,
|
||||||
trusted_uploaders: [],
|
trusted_uploaders: [],
|
||||||
force_review_uploaders: [],
|
force_review_uploaders: [],
|
||||||
});
|
});
|
||||||
@@ -463,10 +464,11 @@ export default function MobileEventControlRoomPage() {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const settings = (activeEvent?.settings?.control_room ?? {}) as ControlRoomSettings;
|
const settings = (activeEvent?.settings?.control_room ?? {}) as ControlRoomSettings;
|
||||||
|
const resolveFlag = (value: boolean | undefined) => (typeof value === 'boolean' ? value : true);
|
||||||
setControlRoomSettings({
|
setControlRoomSettings({
|
||||||
auto_approve_highlights: Boolean(settings.auto_approve_highlights),
|
auto_approve_highlights: resolveFlag(settings.auto_approve_highlights),
|
||||||
auto_add_approved_to_live: Boolean(settings.auto_add_approved_to_live),
|
auto_add_approved_to_live: resolveFlag(settings.auto_add_approved_to_live),
|
||||||
auto_remove_live_on_hide: Boolean(settings.auto_remove_live_on_hide),
|
auto_remove_live_on_hide: resolveFlag(settings.auto_remove_live_on_hide),
|
||||||
trusted_uploaders: Array.isArray(settings.trusted_uploaders) ? settings.trusted_uploaders : [],
|
trusted_uploaders: Array.isArray(settings.trusted_uploaders) ? settings.trusted_uploaders : [],
|
||||||
force_review_uploaders: Array.isArray(settings.force_review_uploaders) ? settings.force_review_uploaders : [],
|
force_review_uploaders: Array.isArray(settings.force_review_uploaders) ? settings.force_review_uploaders : [],
|
||||||
});
|
});
|
||||||
@@ -1018,251 +1020,267 @@ export default function MobileEventControlRoomPage() {
|
|||||||
</XStack>
|
</XStack>
|
||||||
|
|
||||||
<MobileCard>
|
<MobileCard>
|
||||||
<YStack space="$2">
|
<Accordion type="single" collapsible defaultValue="upload-settings">
|
||||||
<Text fontSize="$sm" fontWeight="800" color={text}>
|
<Accordion.Item value="upload-settings">
|
||||||
{t('controlRoom.automation.title', 'Automation')}
|
<Accordion.Trigger
|
||||||
</Text>
|
{...({
|
||||||
<MobileField
|
padding: '$2',
|
||||||
label={t('controlRoom.automation.autoApproveHighlights.label', 'Auto-approve highlights')}
|
borderWidth: 1,
|
||||||
hint={t(
|
borderColor: border,
|
||||||
'controlRoom.automation.autoApproveHighlights.help',
|
borderRadius: 14,
|
||||||
'When you highlight a pending photo it will be approved automatically.',
|
backgroundColor: surfaceMuted,
|
||||||
)}
|
} as any)}
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
size="$3"
|
|
||||||
checked={autoApproveHighlights}
|
|
||||||
disabled={controlRoomSaving}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
saveControlRoomSettings({
|
|
||||||
...controlRoomSettings,
|
|
||||||
auto_approve_highlights: Boolean(checked),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
aria-label={t('controlRoom.automation.autoApproveHighlights.label', 'Auto-approve highlights')}
|
|
||||||
>
|
>
|
||||||
<Switch.Thumb />
|
<XStack justifyContent="space-between" alignItems="center" flex={1}>
|
||||||
</Switch>
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
</MobileField>
|
{t('controlRoom.automation.sectionTitle', 'Einstellungen für Uploads')}
|
||||||
<MobileField
|
</Text>
|
||||||
label={t('controlRoom.automation.autoAddApproved.label', 'Auto-add approved to Live Show')}
|
<ChevronDown size={16} color={muted} />
|
||||||
hint={
|
</XStack>
|
||||||
isImmediateUploads
|
</Accordion.Trigger>
|
||||||
? t(
|
<Accordion.Content {...({ paddingTop: '$2' } as any)}>
|
||||||
'controlRoom.automation.autoAddApproved.locked',
|
<YStack space="$3">
|
||||||
'Enabled because uploads are visible immediately.',
|
<YStack space="$2">
|
||||||
)
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
: t(
|
{t('controlRoom.automation.title', 'Automation')}
|
||||||
'controlRoom.automation.autoAddApproved.help',
|
</Text>
|
||||||
'Approved or highlighted photos go straight to the Live Show.',
|
<MobileField
|
||||||
)
|
label={t('controlRoom.automation.autoApproveHighlights.label', 'Auto-approve highlights')}
|
||||||
}
|
hint={t(
|
||||||
>
|
'controlRoom.automation.autoApproveHighlights.help',
|
||||||
<Switch
|
'When you highlight a pending photo it will be approved automatically.',
|
||||||
size="$3"
|
)}
|
||||||
checked={autoAddApprovedToLive}
|
|
||||||
disabled={controlRoomSaving || isImmediateUploads}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
if (isImmediateUploads) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
saveControlRoomSettings({
|
|
||||||
...controlRoomSettings,
|
|
||||||
auto_add_approved_to_live: Boolean(checked),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
aria-label={t('controlRoom.automation.autoAddApproved.label', 'Auto-add approved to Live Show')}
|
|
||||||
>
|
|
||||||
<Switch.Thumb />
|
|
||||||
</Switch>
|
|
||||||
</MobileField>
|
|
||||||
<MobileField
|
|
||||||
label={t('controlRoom.automation.autoRemoveLive.label', 'Auto-remove from Live Show')}
|
|
||||||
hint={t(
|
|
||||||
'controlRoom.automation.autoRemoveLive.help',
|
|
||||||
'Hidden or rejected photos are removed from the Live Show automatically.',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
size="$3"
|
|
||||||
checked={autoRemoveLiveOnHide}
|
|
||||||
disabled={controlRoomSaving}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
saveControlRoomSettings({
|
|
||||||
...controlRoomSettings,
|
|
||||||
auto_remove_live_on_hide: Boolean(checked),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
aria-label={t('controlRoom.automation.autoRemoveLive.label', 'Auto-remove from Live Show')}
|
|
||||||
>
|
|
||||||
<Switch.Thumb />
|
|
||||||
</Switch>
|
|
||||||
</MobileField>
|
|
||||||
</YStack>
|
|
||||||
</MobileCard>
|
|
||||||
|
|
||||||
<MobileCard>
|
|
||||||
<YStack space="$2">
|
|
||||||
<Text fontSize="$sm" fontWeight="800" color={text}>
|
|
||||||
{t('controlRoom.automation.uploaders.title', 'Uploader overrides')}
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{t(
|
|
||||||
'controlRoom.automation.uploaders.subtitle',
|
|
||||||
'Pick devices from recent uploads to always approve or always review their photos.',
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<MobileField
|
|
||||||
label={t('controlRoom.automation.uploaders.trustedLabel', 'Always approve')}
|
|
||||||
hint={t(
|
|
||||||
'controlRoom.automation.uploaders.trustedHelp',
|
|
||||||
'Uploads from these devices skip the approval queue.',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<XStack alignItems="center" space="$2">
|
|
||||||
<MobileSelect
|
|
||||||
value={trustedUploaderSelection}
|
|
||||||
onChange={(event) => setTrustedUploaderSelection(event.target.value)}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
>
|
|
||||||
<option value="">{t('common.select', 'Select')}</option>
|
|
||||||
{uploaderOptions.map((option) => (
|
|
||||||
<option key={option.deviceId} value={option.deviceId}>
|
|
||||||
{option.display}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</MobileSelect>
|
|
||||||
<CTAButton
|
|
||||||
label={t('controlRoom.automation.uploaders.add', 'Add')}
|
|
||||||
onPress={() => addUploaderRule('trusted')}
|
|
||||||
tone="ghost"
|
|
||||||
fullWidth={false}
|
|
||||||
disabled={controlRoomSaving || !trustedUploaderSelection}
|
|
||||||
style={{ width: 100 }}
|
|
||||||
/>
|
|
||||||
</XStack>
|
|
||||||
{trustedUploaders.length ? (
|
|
||||||
<YStack space="$2">
|
|
||||||
{trustedUploaders.map((rule) => (
|
|
||||||
<XStack
|
|
||||||
key={`trusted-${rule.device_id}`}
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="space-between"
|
|
||||||
padding="$2"
|
|
||||||
borderRadius={14}
|
|
||||||
borderWidth={1}
|
|
||||||
borderColor={border}
|
|
||||||
backgroundColor={surfaceMuted}
|
|
||||||
>
|
>
|
||||||
<YStack space="$0.5">
|
<Switch
|
||||||
<Text fontSize="$sm" fontWeight="700" color={text}>
|
size="$3"
|
||||||
{rule.label ?? t('common.anonymous', 'Anonymous')}
|
checked={autoApproveHighlights}
|
||||||
</Text>
|
|
||||||
<Text fontSize="$xs" color={muted}>
|
|
||||||
{formatDeviceId(rule.device_id)}
|
|
||||||
</Text>
|
|
||||||
</YStack>
|
|
||||||
<Pressable
|
|
||||||
onPress={() => removeUploaderRule('trusted', rule.device_id)}
|
|
||||||
disabled={controlRoomSaving}
|
disabled={controlRoomSaving}
|
||||||
aria-label={t('controlRoom.automation.uploaders.remove', 'Remove')}
|
onCheckedChange={(checked) =>
|
||||||
style={{
|
saveControlRoomSettings({
|
||||||
paddingHorizontal: 12,
|
...controlRoomSettings,
|
||||||
paddingVertical: 6,
|
auto_approve_highlights: Boolean(checked),
|
||||||
borderRadius: 999,
|
})
|
||||||
backgroundColor: withAlpha(danger, 0.12),
|
}
|
||||||
}}
|
aria-label={t('controlRoom.automation.autoApproveHighlights.label', 'Auto-approve highlights')}
|
||||||
>
|
>
|
||||||
<Text fontSize="$xs" fontWeight="700" color={danger}>
|
<Switch.Thumb />
|
||||||
{t('controlRoom.automation.uploaders.remove', 'Remove')}
|
</Switch>
|
||||||
</Text>
|
</MobileField>
|
||||||
</Pressable>
|
<MobileField
|
||||||
</XStack>
|
label={t('controlRoom.automation.autoAddApproved.label', 'Auto-add approved to Live Show')}
|
||||||
))}
|
hint={
|
||||||
</YStack>
|
isImmediateUploads
|
||||||
) : (
|
? t(
|
||||||
<Text fontSize="$xs" color={muted}>
|
'controlRoom.automation.autoAddApproved.locked',
|
||||||
{t('controlRoom.automation.uploaders.empty', 'No overrides yet.')}
|
'Enabled because uploads are visible immediately.',
|
||||||
</Text>
|
)
|
||||||
)}
|
: t(
|
||||||
</MobileField>
|
'controlRoom.automation.autoAddApproved.help',
|
||||||
|
'Approved or highlighted photos go straight to the Live Show.',
|
||||||
<MobileField
|
)
|
||||||
label={t('controlRoom.automation.uploaders.forceLabel', 'Always review')}
|
}
|
||||||
hint={t(
|
|
||||||
'controlRoom.automation.uploaders.forceHelp',
|
|
||||||
'Uploads from these devices always need approval.',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<XStack alignItems="center" space="$2">
|
|
||||||
<MobileSelect
|
|
||||||
value={forceReviewSelection}
|
|
||||||
onChange={(event) => setForceReviewSelection(event.target.value)}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
>
|
|
||||||
<option value="">{t('common.select', 'Select')}</option>
|
|
||||||
{uploaderOptions.map((option) => (
|
|
||||||
<option key={option.deviceId} value={option.deviceId}>
|
|
||||||
{option.display}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</MobileSelect>
|
|
||||||
<CTAButton
|
|
||||||
label={t('controlRoom.automation.uploaders.add', 'Add')}
|
|
||||||
onPress={() => addUploaderRule('force')}
|
|
||||||
tone="ghost"
|
|
||||||
fullWidth={false}
|
|
||||||
disabled={controlRoomSaving || !forceReviewSelection}
|
|
||||||
style={{ width: 100 }}
|
|
||||||
/>
|
|
||||||
</XStack>
|
|
||||||
{forceReviewUploaders.length ? (
|
|
||||||
<YStack space="$2">
|
|
||||||
{forceReviewUploaders.map((rule) => (
|
|
||||||
<XStack
|
|
||||||
key={`force-${rule.device_id}`}
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="space-between"
|
|
||||||
padding="$2"
|
|
||||||
borderRadius={14}
|
|
||||||
borderWidth={1}
|
|
||||||
borderColor={border}
|
|
||||||
backgroundColor={surfaceMuted}
|
|
||||||
>
|
>
|
||||||
<YStack space="$0.5">
|
<Switch
|
||||||
<Text fontSize="$sm" fontWeight="700" color={text}>
|
size="$3"
|
||||||
{rule.label ?? t('common.anonymous', 'Anonymous')}
|
checked={autoAddApprovedToLive}
|
||||||
</Text>
|
disabled={controlRoomSaving || isImmediateUploads}
|
||||||
<Text fontSize="$xs" color={muted}>
|
onCheckedChange={(checked) => {
|
||||||
{formatDeviceId(rule.device_id)}
|
if (isImmediateUploads) {
|
||||||
</Text>
|
return;
|
||||||
</YStack>
|
}
|
||||||
<Pressable
|
saveControlRoomSettings({
|
||||||
onPress={() => removeUploaderRule('force', rule.device_id)}
|
...controlRoomSettings,
|
||||||
disabled={controlRoomSaving}
|
auto_add_approved_to_live: Boolean(checked),
|
||||||
aria-label={t('controlRoom.automation.uploaders.remove', 'Remove')}
|
});
|
||||||
style={{
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
paddingVertical: 6,
|
|
||||||
borderRadius: 999,
|
|
||||||
backgroundColor: withAlpha(danger, 0.12),
|
|
||||||
}}
|
}}
|
||||||
|
aria-label={t('controlRoom.automation.autoAddApproved.label', 'Auto-add approved to Live Show')}
|
||||||
>
|
>
|
||||||
<Text fontSize="$xs" fontWeight="700" color={danger}>
|
<Switch.Thumb />
|
||||||
{t('controlRoom.automation.uploaders.remove', 'Remove')}
|
</Switch>
|
||||||
|
</MobileField>
|
||||||
|
<MobileField
|
||||||
|
label={t('controlRoom.automation.autoRemoveLive.label', 'Auto-remove from Live Show')}
|
||||||
|
hint={t(
|
||||||
|
'controlRoom.automation.autoRemoveLive.help',
|
||||||
|
'Hidden or rejected photos are removed from the Live Show automatically.',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
size="$3"
|
||||||
|
checked={autoRemoveLiveOnHide}
|
||||||
|
disabled={controlRoomSaving}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
saveControlRoomSettings({
|
||||||
|
...controlRoomSettings,
|
||||||
|
auto_remove_live_on_hide: Boolean(checked),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
aria-label={t('controlRoom.automation.autoRemoveLive.label', 'Auto-remove from Live Show')}
|
||||||
|
>
|
||||||
|
<Switch.Thumb />
|
||||||
|
</Switch>
|
||||||
|
</MobileField>
|
||||||
|
</YStack>
|
||||||
|
|
||||||
|
<YStack space="$2">
|
||||||
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
|
{t('controlRoom.automation.uploaders.title', 'Uploader overrides')}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="$xs" color={muted}>
|
||||||
|
{t(
|
||||||
|
'controlRoom.automation.uploaders.subtitle',
|
||||||
|
'Pick devices from recent uploads to always approve or always review their photos.',
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<MobileField
|
||||||
|
label={t('controlRoom.automation.uploaders.trustedLabel', 'Always approve')}
|
||||||
|
hint={t(
|
||||||
|
'controlRoom.automation.uploaders.trustedHelp',
|
||||||
|
'Uploads from these devices skip the approval queue.',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<YStack space="$2">
|
||||||
|
<MobileSelect
|
||||||
|
value={trustedUploaderSelection}
|
||||||
|
onChange={(event) => setTrustedUploaderSelection(event.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">{t('common.select', 'Select')}</option>
|
||||||
|
{uploaderOptions.map((option) => (
|
||||||
|
<option key={option.deviceId} value={option.deviceId}>
|
||||||
|
{option.display}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</MobileSelect>
|
||||||
|
<CTAButton
|
||||||
|
label={t('controlRoom.automation.uploaders.add', 'Add')}
|
||||||
|
onPress={() => addUploaderRule('trusted')}
|
||||||
|
tone="ghost"
|
||||||
|
disabled={controlRoomSaving || !trustedUploaderSelection}
|
||||||
|
/>
|
||||||
|
</YStack>
|
||||||
|
{trustedUploaders.length ? (
|
||||||
|
<YStack space="$2">
|
||||||
|
{trustedUploaders.map((rule) => (
|
||||||
|
<XStack
|
||||||
|
key={`trusted-${rule.device_id}`}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
padding="$2"
|
||||||
|
borderRadius={14}
|
||||||
|
borderWidth={1}
|
||||||
|
borderColor={border}
|
||||||
|
backgroundColor={surfaceMuted}
|
||||||
|
>
|
||||||
|
<YStack space="$0.5">
|
||||||
|
<Text fontSize="$sm" fontWeight="700" color={text}>
|
||||||
|
{rule.label ?? t('common.anonymous', 'Anonymous')}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="$xs" color={muted}>
|
||||||
|
{formatDeviceId(rule.device_id)}
|
||||||
|
</Text>
|
||||||
|
</YStack>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => removeUploaderRule('trusted', rule.device_id)}
|
||||||
|
disabled={controlRoomSaving}
|
||||||
|
aria-label={t('controlRoom.automation.uploaders.remove', 'Remove')}
|
||||||
|
style={{
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 999,
|
||||||
|
backgroundColor: withAlpha(danger, 0.12),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text fontSize="$xs" fontWeight="700" color={danger}>
|
||||||
|
{t('controlRoom.automation.uploaders.remove', 'Remove')}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</XStack>
|
||||||
|
))}
|
||||||
|
</YStack>
|
||||||
|
) : (
|
||||||
|
<Text fontSize="$xs" color={muted}>
|
||||||
|
{t('controlRoom.automation.uploaders.empty', 'No overrides yet.')}
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
)}
|
||||||
</XStack>
|
</MobileField>
|
||||||
))}
|
|
||||||
|
<MobileField
|
||||||
|
label={t('controlRoom.automation.uploaders.forceLabel', 'Always review')}
|
||||||
|
hint={t(
|
||||||
|
'controlRoom.automation.uploaders.forceHelp',
|
||||||
|
'Uploads from these devices always need approval.',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<YStack space="$2">
|
||||||
|
<MobileSelect
|
||||||
|
value={forceReviewSelection}
|
||||||
|
onChange={(event) => setForceReviewSelection(event.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">{t('common.select', 'Select')}</option>
|
||||||
|
{uploaderOptions.map((option) => (
|
||||||
|
<option key={option.deviceId} value={option.deviceId}>
|
||||||
|
{option.display}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</MobileSelect>
|
||||||
|
<CTAButton
|
||||||
|
label={t('controlRoom.automation.uploaders.add', 'Add')}
|
||||||
|
onPress={() => addUploaderRule('force')}
|
||||||
|
tone="ghost"
|
||||||
|
disabled={controlRoomSaving || !forceReviewSelection}
|
||||||
|
/>
|
||||||
|
</YStack>
|
||||||
|
{forceReviewUploaders.length ? (
|
||||||
|
<YStack space="$2">
|
||||||
|
{forceReviewUploaders.map((rule) => (
|
||||||
|
<XStack
|
||||||
|
key={`force-${rule.device_id}`}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
padding="$2"
|
||||||
|
borderRadius={14}
|
||||||
|
borderWidth={1}
|
||||||
|
borderColor={border}
|
||||||
|
backgroundColor={surfaceMuted}
|
||||||
|
>
|
||||||
|
<YStack space="$0.5">
|
||||||
|
<Text fontSize="$sm" fontWeight="700" color={text}>
|
||||||
|
{rule.label ?? t('common.anonymous', 'Anonymous')}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="$xs" color={muted}>
|
||||||
|
{formatDeviceId(rule.device_id)}
|
||||||
|
</Text>
|
||||||
|
</YStack>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => removeUploaderRule('force', rule.device_id)}
|
||||||
|
disabled={controlRoomSaving}
|
||||||
|
aria-label={t('controlRoom.automation.uploaders.remove', 'Remove')}
|
||||||
|
style={{
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 999,
|
||||||
|
backgroundColor: withAlpha(danger, 0.12),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text fontSize="$xs" fontWeight="700" color={danger}>
|
||||||
|
{t('controlRoom.automation.uploaders.remove', 'Remove')}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</XStack>
|
||||||
|
))}
|
||||||
|
</YStack>
|
||||||
|
) : (
|
||||||
|
<Text fontSize="$xs" color={muted}>
|
||||||
|
{t('controlRoom.automation.uploaders.empty', 'No overrides yet.')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</MobileField>
|
||||||
|
</YStack>
|
||||||
</YStack>
|
</YStack>
|
||||||
) : (
|
</Accordion.Content>
|
||||||
<Text fontSize="$xs" color={muted}>
|
</Accordion.Item>
|
||||||
{t('controlRoom.automation.uploaders.empty', 'No overrides yet.')}
|
</Accordion>
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</MobileField>
|
|
||||||
</YStack>
|
|
||||||
</MobileCard>
|
</MobileCard>
|
||||||
|
|
||||||
{activeTab === 'moderation' ? (
|
{activeTab === 'moderation' ? (
|
||||||
|
|||||||
@@ -108,6 +108,17 @@ vi.mock('@tamagui/switch', () => ({
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('@tamagui/accordion', () => ({
|
||||||
|
Accordion: Object.assign(
|
||||||
|
({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
|
{
|
||||||
|
Item: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
|
Trigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
|
Content: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('@tamagui/react-native-web-lite', () => ({
|
vi.mock('@tamagui/react-native-web-lite', () => ({
|
||||||
Pressable: ({
|
Pressable: ({
|
||||||
children,
|
children,
|
||||||
|
|||||||
Reference in New Issue
Block a user