PluginProbe ʕ •ᴥ•ʔ
Presto Player / 4.3.3
Presto Player v4.3.3
4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / src / admin / dashboard / pages / settings / general / MediaHubPage.js
presto-player / src / admin / dashboard / pages / settings / general Last commit date
BrandingPage.js 3 months ago ContributePage.js 3 months ago CustomCssPage.js 3 months ago LicensePage.js 3 months ago MediaHubPage.js 3 months ago PresetsPage.js 3 months ago UninstallPage.js 3 months ago ViewingAnalyticsPage.js 3 months ago
MediaHubPage.js
198 lines
1 import { __ } from '@wordpress/i18n';
2 import { useCallback, useMemo } from 'react';
3 import { flushSync } from 'react-dom';
4 import { Container, Label, Select, Input, toast, Tooltip } from '@bsf/force-ui';
5 import { Info } from 'lucide-react';
6 import SettingsPageShell from '../shared/SettingsPageShell';
7 import SectionCard from '../shared/SectionCard';
8 import useSettingOption from '../../../hooks/useSettingOption';
9 import useRegisterActivePage from '../../../hooks/useRegisterActivePage';
10 import { OPTION_KEYS } from '../config';
11
12 const UNIT_OPTIONS = [
13 { label: 'px', value: 'px' },
14 { label: '%', value: '%' },
15 { label: 'vw', value: 'vw' },
16 { label: 'em', value: 'em' },
17 { label: 'rem', value: 'rem' },
18 ];
19
20 const UNIT_VALUES = UNIT_OPTIONS.map( ( u ) => u.value );
21 // Anchored to the unit list so a stray non-numeric character in the value
22 // can't get swallowed into the unit slot (typing "8abc" used to save as
23 // "8abcpx" and then parse back as unit="abcpx", which the Select rendered).
24 const WIDTH_PATTERN = new RegExp( `^([\\d.]+)(${ UNIT_VALUES.join( '|' ) })$` );
25
26 const DEFAULT_WIDTH = '800px';
27 const DEFAULT_WIDTH_PLACEHOLDER = '800';
28
29 // Empty / unparseable values resolve to an empty value with the default unit
30 // so the user can clear the field while editing. The default (800px) is
31 // snapped back in on save so the saved option always renders as a valid
32 // CSS width.
33 const parseWidth = ( raw ) => {
34 if ( ! raw ) {
35 return { value: '', unit: 'px' };
36 }
37 const match = raw.match( WIDTH_PATTERN );
38 if ( match ) {
39 return { value: match[ 1 ], unit: match[ 2 ] };
40 }
41 return { value: '', unit: 'px' };
42 };
43
44 // Strip anything that isn't a digit or decimal point so the value field
45 // can't introduce characters that would corrupt the saved "<value><unit>"
46 // string.
47 const sanitizeWidthValue = ( raw ) => ( raw || '' ).replace( /[^\d.]/g, '' );
48
49 const MediaHubPage = ( { registerActivePage } ) => {
50 const widthOption = useSettingOption( OPTION_KEYS.mediaHubWidth, '' );
51 const syncOption = useSettingOption( OPTION_KEYS.mediaHubSync, true );
52
53 const { value, unit } = useMemo(
54 () => parseWidth( widthOption.data ),
55 [ widthOption.data ]
56 );
57
58 const updateWidth = ( next ) => {
59 const sanitized = sanitizeWidthValue( next );
60 widthOption.setData( sanitized ? `${ sanitized }${ unit }` : '' );
61 };
62 const updateUnit = ( next ) =>
63 widthOption.setData( value ? `${ value }${ next }` : '' );
64
65 const isDirty = widthOption.isDirty || syncOption.isDirty;
66 const isSaving = widthOption.isSaving || syncOption.isSaving;
67 const isLoading = widthOption.isLoading;
68
69 const handleSave = useCallback( async () => {
70 try {
71 // Blank field → fall back to the default so the input always shows
72 // a usable value after save instead of staying empty.
73 if ( ! widthOption.data ) {
74 flushSync( () => widthOption.setData( DEFAULT_WIDTH ) );
75 }
76 await Promise.all( [ widthOption.save(), syncOption.save() ] );
77 toast.success( __( 'Settings saved.', 'presto-player' ), {
78 autoDismiss: 3000,
79 } );
80 } catch ( err ) {
81 toast.error( __( 'Could not save settings.', 'presto-player' ), {
82 autoDismiss: 5000,
83 } );
84 }
85 }, [ widthOption, syncOption ] );
86
87 const handleDiscard = useCallback( () => {
88 widthOption.reset();
89 syncOption.reset();
90 }, [ widthOption, syncOption ] );
91
92 useRegisterActivePage( registerActivePage, {
93 isDirty,
94 save: handleSave,
95 discard: handleDiscard,
96 } );
97
98 return (
99 <SettingsPageShell
100 title={ __( 'Media Hub', 'presto-player' ) }
101 isDirty={ isDirty }
102 isSaving={ isSaving }
103 isLoading={ isLoading }
104 onSave={ handleSave }
105 >
106 <SectionCard>
107 <Container direction="column" className="gap-4">
108 <Container.Item className="flex flex-col gap-2">
109 <div className="flex items-center gap-1.5">
110 <Label size="sm">
111 { __( 'Default Sync Behavior', 'presto-player' ) }
112 </Label>
113 <Tooltip
114 content={ __(
115 'When enabled, blocks stay linked to their Media Hub entry and automatically reflect any changes made there.',
116 'presto-player'
117 ) }
118 arrow
119 placement="right"
120 >
121 <Info className="size-3.5 text-icon-secondary cursor-help shrink-0" />
122 </Tooltip>
123 </div>
124 <Select
125 size="md"
126 value={ syncOption.data ? 'true' : 'false' }
127 onChange={ ( val ) => syncOption.setData( val === 'true' ) }
128 >
129 <Select.Button
130 render={ ( val ) =>
131 val === 'true'
132 ? __( 'Synced', 'presto-player' )
133 : __( 'Not Synced', 'presto-player' )
134 }
135 />
136 <Select.Options>
137 <Select.Option value="true">
138 { __( 'Synced', 'presto-player' ) }
139 </Select.Option>
140 <Select.Option value="false">
141 { __( 'Not Synced', 'presto-player' ) }
142 </Select.Option>
143 </Select.Options>
144 </Select>
145 <Label size="xs" variant="help">
146 { __(
147 'Choose the default sync behavior of Presto Player blocks with the Media Hub.',
148 'presto-player'
149 ) }
150 </Label>
151 </Container.Item>
152
153 <Container.Item className="flex flex-col gap-2">
154 <Label size="sm">
155 { __( 'Instant Video Page Width', 'presto-player' ) }
156 </Label>
157 <div className="flex gap-2 items-stretch">
158 <div className="flex-1">
159 <Input
160 type="text"
161 size="md"
162 placeholder={ DEFAULT_WIDTH_PLACEHOLDER }
163 value={ value }
164 onChange={ ( val ) => updateWidth( val ) }
165 />
166 </div>
167 <div className="w-24">
168 <Select
169 size="md"
170 value={ unit }
171 onChange={ ( val ) => updateUnit( val ) }
172 >
173 <Select.Button />
174 <Select.Options>
175 { UNIT_OPTIONS.map( ( u ) => (
176 <Select.Option key={ u.value } value={ u.value }>
177 { u.label }
178 </Select.Option>
179 ) ) }
180 </Select.Options>
181 </Select>
182 </div>
183 </div>
184 <Label size="xs" variant="help">
185 { __(
186 'Customize the video player width on the instant video page.',
187 'presto-player'
188 ) }
189 </Label>
190 </Container.Item>
191 </Container>
192 </SectionCard>
193 </SettingsPageShell>
194 );
195 };
196
197 export default MediaHubPage;
198