PluginProbe
FV Player 8 / trunk
FV Player 8 vtrunk
trunk 8.0.18 8.0.19 8.0.20 8.0.21 8.0.25 8.0.27 8.1 8.1.3
fv-player / blocks / index.js

index.js in FV Player 8 trunk, at blocks/index.js

411 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { __ } from '@wordpress/i18n';
2 import ServerSideRender from '@wordpress/server-side-render';
3 import { createElement, RawHTML, useEffect, useState } from '@wordpress/element';
4 import { registerBlockType } from '@wordpress/blocks';
5 import { InspectorControls, MediaUpload, MediaUploadCheck, URLPopover, useBlockProps } from '@wordpress/block-editor';
6 import { SVG, Path, Panel, PanelBody, TextControl, Button, PanelRow } from '@wordpress/components';
7
8 registerBlockType( 'fv-player-gutenberg/basic', {
9 apiVersion: 3,
10 icon: {
11 foreground: '#C20B33',
12 src: createElement(
13 SVG, {
14 viewBox: "0 0 24 24"
15 }, createElement(Path, {
16 d: "M21.8 8s-.195-1.377-.795-1.984c-.76-.797-1.613-.8-2.004-.847-2.798-.203-6.996-.203-6.996-.203h-.01s-4.197 0-6.996.202c-.39.046-1.242.05-2.003.846C2.395 6.623 2.2 8 2.2 8S2 9.62 2 11.24v1.517c0 1.618.2 3.237.2 3.237s.195 1.378.795 1.985c.76.797 1.76.77 2.205.855 1.6.153 6.8.2 6.8.2s4.203-.005 7-.208c.392-.047 1.244-.05 2.005-.847.6-.607.795-1.985.795-1.985s.2-1.618.2-3.237v-1.517C22 9.62 21.8 8 21.8 8zM9.935 14.595v-5.62l5.403 2.82-5.403 2.8z"
17 })
18 )
19 },
20 title: __( 'FV Player', 'fv-player-gutenberg' ),
21 description: __( 'Embed a video from your Media Library or one of your video hosting services.', 'fv-player-gutenberg' ),
22 category: 'media',
23 keywords: ['fv player', 'player', 'fv', 'flowplayer', 'freedomplayer', 'video', 'embed', 'media', 'stream'],
24 supports: {
25 align: true,
26 },
27 attributes: {
28 src: {
29 type: 'string',
30 default: ''
31 },
32 splash: {
33 type: 'string',
34 default: ''
35 },
36 timeline_previews: {
37 type: 'string',
38 default: ''
39 },
40 hls_hlskey: {
41 type: 'string',
42 default: ''
43 },
44 title: {
45 type: 'string',
46 default: ''
47 },
48 shortcodeContent: {
49 type: 'string',
50 default: '',
51 source: 'text'
52 },
53 player_id: {
54 type: 'string',
55 default: '0',
56 },
57 splash_attachment_id: {
58 type: 'string',
59 default: '0',
60 },
61 forceUpdate: {
62 type: 'string',
63 default: '0',
64 }
65 },
66 edit: ({ isSelected ,attributes, setAttributes, context, clientId}) => {
67 const { src, splash, title, shortcodeContent, player_id, splash_attachment_id, timeline_previews, hls_hlskey } = attributes;
68 const isEmpty = player_id == 'undefined' || player_id == 0;
69
70 // Required for apiVersion 3: mark the outermost edit element so selection works.
71 const blockProps = useBlockProps(
72 isEmpty
73 ? {
74 className:
75 'components-placeholder block-editor-media-placeholder is-large fv-player-editor-wrapper fv-player-gutenberg',
76 }
77 : {}
78 );
79
80 // interval
81 const [count, setCount] = useState(0);
82
83 // debounce
84 const [debouncedSrc, setDebouncedSrc] = useState(src);
85 const [debouncedTitle, setDebouncedTitle] = useState(title);
86 const [debouncedSplash, setDebouncedSplash] = useState(splash);
87 const [debouncedHlskey, setDebouncedHlskey] = useState(hls_hlskey);
88 const [debounceTimelinePreviews, setDebounceTimelinePreviews] = useState(timeline_previews);
89
90 // popover
91 const [URLPopoverIsOpen, setURLPopoverIsOpen] = useState(false);
92
93 // we need to handle first load
94 let firstLoad = true;
95 let firstInsert = false;
96
97 // debounce block ajax
98 useEffect(() => {
99 const timeoutId = setTimeout(() => {
100 if (debouncedSrc !== src || debouncedTitle !== title || debouncedSplash !== splash || debounceTimelinePreviews !== timeline_previews || debouncedHlskey !== hls_hlskey ) {
101 setDebouncedSrc(src);
102 setDebouncedTitle(title);
103 setDebouncedSplash(splash);
104 setDebouncedHlskey(hls_hlskey);
105 setDebounceTimelinePreviews(timeline_previews);
106 ajaxUpdateAttributes({ ...attributes });
107 }
108 }, 500);
109
110 return () => {
111 clearTimeout(timeoutId);
112 };
113 }, [src, title, splash, timeline_previews, hls_hlskey]);
114
115 // block interval to load player and resize
116 useEffect(() => {
117 const intervalId = setInterval(() => {
118 fv_player_load();
119 fv_flowplayer_safety_resize();
120 setCount(count + 1);
121 }, 1000);
122
123 return () => {
124 clearInterval(intervalId);
125 };
126 }, [count]);
127
128 // block is being loaded
129 useEffect(() => {
130 if (firstLoad && player_id > 0) {
131 firstLoad = false;
132 ajaxUpdateFromDB();
133 }
134 }, []);
135
136 // used shorctcode editor or media library
137 useEffect(() => {
138 if( isSelected && player_id > 0 && player_id != 'undefined' ) { // run only when block is selected
139 ajaxUpdateAttributes({ ...attributes });
140 }
141 }, [shortcodeContent, player_id, splash_attachment_id]);
142
143 const ajaxUpdateFromDB = (id = null) => {
144 console.log( 'FV Player Block: attributes_load', id ? id : player_id );
145
146 const data = new FormData();
147 data.append('action', 'fv_player_guttenberg_attributes_load');
148 data.append('player_id', id ? id : player_id);
149
150 // nonce is required for security
151 data.append('security', fv_player_gutenberg.nonce);
152
153 fetch(ajaxurl, {
154 method: 'POST',
155 body: data,
156 credentials: 'same-origin',
157 })
158 .then((response) => response.json())
159 .then((data) => {
160 if( data.src != 'undefined' && data.splash != 'undefined' && data.title != 'undefined' ) {
161 setAttributes({ splash: String(data.splash) });
162 setAttributes({ title: String(data.title) });
163 setAttributes({ src: String(data.src) });
164 setAttributes({ splash_attachment_id: String(data.splash_attachment_id) });
165 setAttributes({ hls_hlskey: String(data.hls_hlskey) });
166 setAttributes({ timeline_previews: String(data.timeline_previews) });
167 setAttributes({ forceUpdate: String(Math.random()) });
168 }
169 })
170 .catch((error) => {
171 console.error('Error:', error);
172 });
173 };
174
175 // handle ajax update of attributes
176 const ajaxUpdateAttributes = (newAttributes) => {
177 console.log( 'FV Player Block: attributes_save', player_id );
178
179 if( player_id == 'undefined' || player_id == 0 ) {
180 firstInsert = true;
181 }
182
183 const data = new FormData();
184 data.append('action', 'fv_player_guttenberg_attributes_save');
185 data.append('player_id', newAttributes.player_id);
186 data.append('src', newAttributes.src);
187 data.append('splash', newAttributes.splash);
188 data.append('title', newAttributes.title);
189 data.append('splash_attachment_id', newAttributes.splash_attachment_id);
190 data.append('hls_hlskey', newAttributes.hls_hlskey);
191 data.append('timeline_previews', newAttributes.timeline_previews);
192
193 // nonce is required for security
194 data.append('security', fv_player_gutenberg.nonce);
195
196 fetch(ajaxurl, {
197 method: 'POST',
198 body: data,
199 credentials: 'same-origin',
200 })
201 .then((response) => response.json())
202 .then((data) => {
203 if( data.shortcodeContent != 'undefined' && data.player_id != 'undefined' ) {
204 // update the shortcode content and player id
205 setAttributes({ shortcodeContent: String(data.shortcodeContent) });
206 setAttributes({ player_id: String(data.player_id) });
207 setAttributes({ forceUpdate: String(Math.random()) });
208
209 if( firstInsert ) {
210 ajaxUpdateFromDB(data.player_id);
211 firstInsert = false;
212 }
213
214 }
215 })
216 .catch((error) => {
217 console.error('Error:', error);
218 });
219 }
220
221 // show initial state when no player
222 if ( isEmpty ) {
223 return(
224 <div { ...blockProps }>
225 <div className="components-placeholder__label">
226 <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false">
227 <path d="M21.8 8s-.195-1.377-.795-1.984c-.76-.797-1.613-.8-2.004-.847-2.798-.203-6.996-.203-6.996-.203h-.01s-4.197 0-6.996.202c-.39.046-1.242.05-2.003.846C2.395 6.623 2.2 8 2.2 8S2 9.62 2 11.24v1.517c0 1.618.2 3.237.2 3.237s.195 1.378.795 1.985c.76.797 1.76.77 2.205.855 1.6.153 6.8.2 6.8.2s4.203-.005 7-.208c.392-.047 1.244-.05 2.005-.847.6-.607.795-1.985.795-1.985s.2-1.618.2-3.237v-1.517C22 9.62 21.8 8 21.8 8zM9.935 14.595v-5.62l5.403 2.82-5.403 2.8z"></path>
228 </svg>
229 FV Player
230 </div>
231 <div className='components-placeholder__instructions'>{__('Select Media or Use FV Player Editor to see all the features.', 'fv-player-gutenberg')}</div>
232 <input className='fv-player-gutenberg-client-id' type="hidden" value={clientId} />
233 <input
234 className="attachement-shortcode fv-player-editor-field"
235 type="hidden"
236 value=""
237 />
238 <div className="components-placeholder__fieldset">
239 <Button
240 className='is-primary fv-player-gutenberg-media'
241 name="fv-player-gutenberg-media"
242 onClick={() => {
243 setURLPopoverIsOpen(false);
244 }}
245 >Select Media
246 </Button>
247 <Button
248 className="fv-wordpress-flowplayer-button is-secondary"
249 onClick={() => {
250 setURLPopoverIsOpen(false);
251 }}
252 >FV Player Editor
253 </Button>
254 <Button
255 className="is-secondary"
256 onClick={() => setURLPopoverIsOpen(!URLPopoverIsOpen)}
257 >Insert from URL
258 </Button>
259 </div>
260 {URLPopoverIsOpen && (
261 <URLPopover>
262 <form
263 className="block-editor-media-placeholder__url-input-form"
264 onSubmit={(event) => {
265 event.preventDefault();
266 // get input value
267 const input = event.target.querySelector(
268 ".block-editor-media-placeholder__url-input-field, .fv-player-gutenberg-url-input-field"
269 );
270 setAttributes({ src: input.value });
271 setURLPopoverIsOpen(false);
272 }}
273 >
274 <input
275 data-cy="url-input"
276 className="block-editor-media-placeholder__url-input-field fv-player-gutenberg-url-input"
277 type="url"
278 aria-label={__("URL", "fv-player-gutenberg/basic")}
279 placeholder={__(
280 "Add video URL",
281 "fv-player-gutenberg/basic"
282 )}
283 />
284 <Button
285 data-cy="url-submit"
286 className="block-editor-media-placeholder__url-input-submit-button"
287 icon={"editor-break"}
288 label={__("Submit", "fv-player-gutenberg/basic")}
289 type="submit"
290 />
291 </form>
292 </URLPopover>
293 )}
294 </div>
295 )
296 }
297
298 return (
299 <div { ...blockProps }>
300 <InspectorControls>
301 <Panel>
302 <PanelBody title="Video Settings" initialOpen={true}>
303 <TextControl
304 label="Source URL"
305 className='fv-player-gutenberg-src'
306 name="fv-player-gutenberg-media"
307 value={src}
308 onChange={(newSrc) => {
309 setAttributes({ src: newSrc });
310 }}
311 />
312 <Button
313 className={ ( src ? 'is-secondary' : 'is-primary' ) + ' fv-player-gutenberg-media' }
314 style={{ marginBottom: '10px' }}
315 >{ src ? 'Change Media' : 'Select Media' }
316 </Button>
317 <TextControl
318 label="Splash URL"
319 className='fv-player-gutenberg-splash'
320 value={splash}
321 onChange={(newSplash) => {
322 setAttributes({ splash: newSplash });
323 }}
324 />
325 <MediaUploadCheck>
326 <MediaUpload
327 onSelect={(attachment) => {
328 setAttributes({ splash: attachment.url });
329 setAttributes({ splash_attachment_id: String(attachment.id) });
330
331 let newAttributes = { ...attributes, splash: attachment.url };
332 newAttributes.splash_attachment_id = attachment.id;
333
334 ajaxUpdateAttributes(newAttributes);
335 }
336 }
337 allowedTypes={['image']}
338 render={({ open }) => (
339 <Button
340 onClick={open}
341 className={ splash ? 'is-secondary' : 'is-primary' }
342 style={{ marginBottom: '10px' }}
343 >{ splash ? 'Change Image' : 'Select Image' }
344 </Button>
345 )}
346 />
347 </MediaUploadCheck>
348 {timeline_previews && (
349 <TextControl
350 label="Timeline Previews"
351 className='fv-player-gutenberg-timeline-previews'
352 value={timeline_previews}
353 onChange={(newTimelinePreviews) => {
354 setAttributes({ timeline_previews: newTimelinePreviews });
355 }}
356 />
357 )}
358 {hls_hlskey && (
359 <TextControl
360 label="HLS Key"
361 className='fv-player-gutenberg-hls-hlskey'
362 value={hls_hlskey}
363 onChange={(newHlsHlskey) => {
364 setAttributes({ hls_hlskey: newHlsHlskey });
365 }}
366 />
367 )}
368 <TextControl
369 label="Video Title"
370 className='fv-player-gutenberg-title'
371 value={title}
372 onChange={(newTitle) => {
373 setAttributes({ title: newTitle });
374 }}
375 />
376 <div className="fv-player-gutenberg">
377 <p>{__('Looking for subtitles or player settings?', 'fv-player-gutenberg')}</p>
378 <Button className="fv-wordpress-flowplayer-button is-primary">Open in Editor</Button>
379 <input className='fv-player-gutenberg-splash-attachement-id' type="hidden" value={splash_attachment_id} />
380 <input className='fv-player-gutenberg-client-id' type="hidden" value={clientId} />
381 <input className='fv-player-gutenberg-player-id' type="hidden" value={player_id} />
382 <input
383 className="attachement-shortcode fv-player-editor-field"
384 type="hidden"
385 value={shortcodeContent}
386 onChange={() =>{
387 setAttributes({ shortcodeContent: shortcodeContent });
388 }}
389 />
390 </div>
391 </PanelBody>
392 </Panel>
393 </InspectorControls>
394
395 <ServerSideRender
396 block="fv-player-gutenberg/basic"
397 attributes={ attributes }
398 />
399
400 </div>
401 );
402 },
403 // Keep RawHTML save markup unchanged so existing posts do not fail block validation.
404 // Frontend output comes from render_callback.
405 save: (props) => {
406 return (
407 <RawHTML>{props.attributes.shortcodeContent}</RawHTML>
408 );
409 }
410 });
411