@flamingo-stack/openframe-frontend-core 0.0.182 → 0.0.183-snapshot.20260514223203
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-IWMK4MH4.cjs → chunk-3OVOM5WT.cjs} +281 -246
- package/dist/chunk-3OVOM5WT.cjs.map +1 -0
- package/dist/{chunk-CLZ3QQMJ.js → chunk-G7HSS5WZ.js} +105 -70
- package/dist/chunk-G7HSS5WZ.js.map +1 -0
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/features/video-source-selector.d.ts +3 -3
- package/dist/components/features/video-source-selector.d.ts.map +1 -1
- package/dist/components/features/video.d.ts.map +1 -1
- package/dist/components/index.cjs +2 -2
- package/dist/components/index.js +1 -1
- package/dist/components/media-carousel.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/dist/types/case-study.d.ts +2 -2
- package/dist/types/case-study.d.ts.map +1 -1
- package/dist/types/supabase.d.ts +6 -6
- package/dist/types/supabase.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/features/video-source-selector.tsx +11 -11
- package/src/components/features/video.tsx +134 -76
- package/src/components/media-carousel.tsx +2 -3
- package/src/styles/ods-colors.css +57 -0
- package/src/types/case-study.ts +2 -2
- package/src/types/supabase.ts +6 -6
- package/dist/chunk-CLZ3QQMJ.js.map +0 -1
- package/dist/chunk-IWMK4MH4.cjs.map +0 -1
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
* layout="native" → intrinsic aspect ratio. Bites grid, blog cards.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
33
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
34
34
|
import MuxPlayer from '@mux/mux-player-react';
|
|
35
|
+
import { PlayIcon } from '../icons-v2-generated/media-playback/play-icon';
|
|
35
36
|
|
|
36
37
|
// =============================================================================
|
|
37
38
|
// URL classifiers (private — `<Video>` is the only consumer)
|
|
@@ -279,7 +280,14 @@ function FilePlayer({
|
|
|
279
280
|
playsInline
|
|
280
281
|
muted={muted}
|
|
281
282
|
preferCmcd="header"
|
|
282
|
-
|
|
283
|
+
// MuxPlayer's built-in default is `#fa50b5` (Mux brand pink) — when
|
|
284
|
+
// its `--media-accent-color` resolves to nothing the player falls
|
|
285
|
+
// through to that hardcoded pink. The `var(--ods-accent,
|
|
286
|
+
// var(--color-accent-primary))` chain hits the platform-aware
|
|
287
|
+
// ODS token first, then the semantic accent alias if `--ods-accent`
|
|
288
|
+
// is ever undefined on a `data-app-type` we haven't themed yet.
|
|
289
|
+
// NEVER let Mux pink leak onto a non-Flamingo platform.
|
|
290
|
+
accentColor="var(--ods-accent, var(--color-accent-primary))"
|
|
283
291
|
className={className}
|
|
284
292
|
// Fill the wrapping aspect-ratio container instead of MuxPlayer's
|
|
285
293
|
// intrinsic size. Without this, MuxPlayer renders at its default
|
|
@@ -338,6 +346,26 @@ interface YouTubeFacadeInnerProps {
|
|
|
338
346
|
minimalControls?: boolean;
|
|
339
347
|
}
|
|
340
348
|
|
|
349
|
+
const YT_NOCOOKIE_ORIGIN = 'https://www.youtube-nocookie.com';
|
|
350
|
+
|
|
351
|
+
// YouTube IFrame Player API state codes — documented integers.
|
|
352
|
+
// https://developers.google.com/youtube/iframe_api_reference#Playback_status
|
|
353
|
+
const YT_STATE_ENDED = 0;
|
|
354
|
+
const YT_STATE_PLAYING = 1;
|
|
355
|
+
|
|
356
|
+
// Sub-second delay before we blur the iframe after PLAYING. Zero would
|
|
357
|
+
// cancel YouTube's mount-time "controls visible" intro flash entirely
|
|
358
|
+
// (jarring); ~1s lets the user briefly see playback started, then we
|
|
359
|
+
// kick YouTube's internal idle timer by removing DOM focus from the
|
|
360
|
+
// iframe. Net result: controls fade ~1s after playback begins,
|
|
361
|
+
// matching the user-locked target.
|
|
362
|
+
const YT_PLAYING_BLUR_DELAY_MS = 1000;
|
|
363
|
+
|
|
364
|
+
interface YouTubeInfoDeliveryMessage {
|
|
365
|
+
event?: string;
|
|
366
|
+
info?: { playerState?: number };
|
|
367
|
+
}
|
|
368
|
+
|
|
341
369
|
function YouTubeFacadeInner({
|
|
342
370
|
videoId,
|
|
343
371
|
title,
|
|
@@ -346,92 +374,124 @@ function YouTubeFacadeInner({
|
|
|
346
374
|
minimalControls,
|
|
347
375
|
}: YouTubeFacadeInnerProps): React.ReactElement {
|
|
348
376
|
const [activated, setActivated] = useState(false);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
377
|
+
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
|
378
|
+
|
|
379
|
+
// Embed URL + poster URLs only change when `videoId` or `minimalControls`
|
|
380
|
+
// do — memoize so we don't rebuild URLSearchParams on every render.
|
|
381
|
+
//
|
|
382
|
+
// `enablejsapi=1` opens the postMessage state channel we subscribe to
|
|
383
|
+
// below — without it, YouTube ignores `event:listening` messages and
|
|
384
|
+
// we can't detect PLAYING / ENDED to drive the auto-hide accelerator.
|
|
385
|
+
const { embedUrl, posterJpg, posterWebp } = useMemo(() => {
|
|
386
|
+
const params = new URLSearchParams({
|
|
387
|
+
autoplay: '1',
|
|
388
|
+
rel: '0',
|
|
389
|
+
modestbranding: '1',
|
|
390
|
+
playsinline: '1',
|
|
391
|
+
enablejsapi: '1',
|
|
392
|
+
});
|
|
393
|
+
if (minimalControls) {
|
|
394
|
+
params.set('controls', '0');
|
|
395
|
+
params.set('fs', '0');
|
|
396
|
+
params.set('iv_load_policy', '3');
|
|
397
|
+
params.set('cc_load_policy', '0');
|
|
398
|
+
params.set('disablekb', '1');
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
embedUrl: `${YT_NOCOOKIE_ORIGIN}/embed/${videoId}?${params.toString()}`,
|
|
402
|
+
posterJpg: `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`,
|
|
403
|
+
posterWebp: `https://i.ytimg.com/vi_webp/${videoId}/mqdefault.webp`,
|
|
404
|
+
};
|
|
405
|
+
}, [videoId, minimalControls]);
|
|
406
|
+
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// YouTube control-fade accelerator (user-locked target: ~1s).
|
|
409
|
+
//
|
|
410
|
+
// YouTube's native idle timer for the bottom control bar is 5–10s when
|
|
411
|
+
// the iframe holds DOM focus. The IFrame Player API has no public
|
|
412
|
+
// `hideControls` command (full `func` list verified — none expose
|
|
413
|
+
// visibility). The one legal lever from outside the iframe is
|
|
414
|
+
// `iframe.blur()`, which kicks YouTube into its post-focus idle path
|
|
415
|
+
// (~2s minimum).
|
|
416
|
+
//
|
|
417
|
+
// Subscribe to YouTube's state channel via the documented lite-mode
|
|
418
|
+
// postMessage handshake — no full `iframe_api.js` library needed:
|
|
419
|
+
// <https://developers.google.com/youtube/iframe_api_reference>.
|
|
382
420
|
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
421
|
+
// - PLAYING (1) arrives once autoplay kicks in. Wait ~1s (so the user
|
|
422
|
+
// briefly sees that the player started), then blur the iframe so
|
|
423
|
+
// YouTube's idle timer fires immediately.
|
|
424
|
+
//
|
|
425
|
+
// - ENDED (0) → tear down the iframe. Playback is already over so
|
|
426
|
+
// there's nothing to interrupt, and removing the iframe kills the
|
|
427
|
+
// residual "More videos" suggestion grid YouTube leaves on screen.
|
|
428
|
+
//
|
|
429
|
+
// PAUSED (2) is deliberately unhandled — the user paused on purpose,
|
|
430
|
+
// leave YouTube's UI alone so they can resume.
|
|
431
|
+
//
|
|
432
|
+
// Playback is NEVER stopped by anything in this facade. Outside-click,
|
|
433
|
+
// Escape, tab-switch — all no-ops. The only state flip is on natural
|
|
434
|
+
// end-of-video (ENDED).
|
|
435
|
+
// ---------------------------------------------------------------------------
|
|
386
436
|
useEffect(() => {
|
|
387
437
|
if (!activated) return;
|
|
438
|
+
const iframe = iframeRef.current;
|
|
439
|
+
if (!iframe) return;
|
|
388
440
|
|
|
389
|
-
function
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
441
|
+
function subscribe() {
|
|
442
|
+
iframe?.contentWindow?.postMessage(
|
|
443
|
+
'{"event":"listening"}',
|
|
444
|
+
YT_NOCOOKIE_ORIGIN,
|
|
445
|
+
);
|
|
394
446
|
}
|
|
395
447
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
448
|
+
iframe.addEventListener('load', subscribe);
|
|
449
|
+
subscribe();
|
|
450
|
+
|
|
451
|
+
let blurTimer: ReturnType<typeof setTimeout> | null = null;
|
|
452
|
+
|
|
453
|
+
function handleMessage(event: MessageEvent) {
|
|
454
|
+
if (event.origin !== YT_NOCOOKIE_ORIGIN) return;
|
|
455
|
+
if (typeof event.data !== 'string') return;
|
|
456
|
+
let payload: YouTubeInfoDeliveryMessage | null = null;
|
|
457
|
+
try {
|
|
458
|
+
payload = JSON.parse(event.data);
|
|
459
|
+
} catch {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (!payload || payload.event !== 'infoDelivery') return;
|
|
463
|
+
const state = payload.info?.playerState;
|
|
464
|
+
if (typeof state !== 'number') return;
|
|
465
|
+
|
|
466
|
+
if (state === YT_STATE_PLAYING) {
|
|
467
|
+
if (blurTimer !== null) return;
|
|
468
|
+
blurTimer = setTimeout(() => {
|
|
469
|
+
blurTimer = null;
|
|
470
|
+
iframeRef.current?.blur();
|
|
471
|
+
}, YT_PLAYING_BLUR_DELAY_MS);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (state === YT_STATE_ENDED) {
|
|
475
|
+
setActivated(false);
|
|
476
|
+
}
|
|
407
477
|
}
|
|
408
478
|
|
|
409
|
-
|
|
410
|
-
return () =>
|
|
479
|
+
window.addEventListener('message', handleMessage);
|
|
480
|
+
return () => {
|
|
481
|
+
iframe.removeEventListener('load', subscribe);
|
|
482
|
+
window.removeEventListener('message', handleMessage);
|
|
483
|
+
if (blurTimer !== null) clearTimeout(blurTimer);
|
|
484
|
+
};
|
|
411
485
|
}, [activated]);
|
|
412
486
|
|
|
413
|
-
// Early-return rendering. The previous imperative implementation
|
|
414
|
-
// (`document.createElement('iframe')` + state flip) had a subtle bug
|
|
415
|
-
// where the play-button overlay could linger past activation because
|
|
416
|
-
// React's commit phase and the imperative DOM mutation raced. Two
|
|
417
|
-
// mutually-exclusive return paths eliminate that race entirely — when
|
|
418
|
-
// `activated` flips, React unmounts the button branch and mounts the
|
|
419
|
-
// iframe branch in a single commit.
|
|
420
|
-
//
|
|
421
|
-
// Autoplay on iOS Safari: the user gesture (the button's onClick) and
|
|
422
|
-
// the iframe mount happen in the SAME React commit, which flushes
|
|
423
|
-
// synchronously inside event handlers. iOS treats the iframe insertion
|
|
424
|
-
// as still being inside the user-activation tick, so `autoplay=1` plays.
|
|
425
|
-
// (Verified empirically; lite-youtube-embed uses imperative DOM for
|
|
426
|
-
// legacy-React compatibility — modern React's sync-commit-on-event
|
|
427
|
-
// makes the JSX path equivalent.)
|
|
428
487
|
const wrapperClass = `relative w-full ${className ?? ''}`;
|
|
429
488
|
const wrapperStyle = { paddingBottom: '56.25%' as const };
|
|
430
489
|
|
|
431
490
|
if (activated) {
|
|
432
491
|
return (
|
|
433
|
-
<div
|
|
492
|
+
<div className={wrapperClass} style={wrapperStyle}>
|
|
434
493
|
<iframe
|
|
494
|
+
ref={iframeRef}
|
|
435
495
|
src={embedUrl}
|
|
436
496
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
437
497
|
allowFullScreen
|
|
@@ -443,7 +503,7 @@ function YouTubeFacadeInner({
|
|
|
443
503
|
}
|
|
444
504
|
|
|
445
505
|
return (
|
|
446
|
-
<div
|
|
506
|
+
<div className={wrapperClass} style={wrapperStyle}>
|
|
447
507
|
<button
|
|
448
508
|
type="button"
|
|
449
509
|
aria-label={`Play: ${title}`}
|
|
@@ -463,9 +523,7 @@ function YouTubeFacadeInner({
|
|
|
463
523
|
</picture>
|
|
464
524
|
<div className="absolute inset-0 flex items-center justify-center bg-ods-bg-inverse bg-opacity-20 transition-opacity duration-200 group-hover:bg-opacity-30">
|
|
465
525
|
<span className="flex items-center justify-center w-16 h-16 rounded-full bg-ods-accent text-ods-text-on-accent shadow-lg transition-transform duration-200 group-hover:scale-110">
|
|
466
|
-
<
|
|
467
|
-
<polygon points="5,3 19,12 5,21" />
|
|
468
|
-
</svg>
|
|
526
|
+
<PlayIcon size={24} color="currentColor" className="ml-1" />
|
|
469
527
|
</span>
|
|
470
528
|
</div>
|
|
471
529
|
</button>
|
|
@@ -4,6 +4,7 @@ import { useState, useRef, useEffect, memo, useCallback } from 'react';
|
|
|
4
4
|
import { cn } from "../utils/cn";
|
|
5
5
|
import { MediaItem } from '../utils/media-carousel-utils-stub';
|
|
6
6
|
import { Video, extractYouTubeId } from './features/video';
|
|
7
|
+
import { PlayIcon } from './icons-v2-generated/media-playback/play-icon';
|
|
7
8
|
|
|
8
9
|
// Navigation icons
|
|
9
10
|
const ChevronLeftIcon = () => (
|
|
@@ -215,9 +216,7 @@ export const MediaCarousel = memo(function MediaCarousel({
|
|
|
215
216
|
{(item.type === 'video' || item.type === 'youtube') && (
|
|
216
217
|
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
|
|
217
218
|
<div className="bg-black/70 rounded-full p-1">
|
|
218
|
-
<
|
|
219
|
-
<path d="M8 5v14l11-7z"/>
|
|
220
|
-
</svg>
|
|
219
|
+
<PlayIcon size={12} color="white" />
|
|
221
220
|
</div>
|
|
222
221
|
</div>
|
|
223
222
|
)}
|
|
@@ -173,6 +173,16 @@
|
|
|
173
173
|
|
|
174
174
|
/* Adaptive Current Color (Platform-Specific) */
|
|
175
175
|
--ods-current: var(--color-text-primary); /* Default to primary text */
|
|
176
|
+
|
|
177
|
+
/* Adaptive Accent Color (Platform-Specific).
|
|
178
|
+
* Defaults to yellow — matches OpenMSP / OpenFrame brand. Every
|
|
179
|
+
* `[data-app-type="X"]` block below overrides this for its platform.
|
|
180
|
+
* Critical: MuxPlayer reads this via `accentColor="var(--ods-accent)"`
|
|
181
|
+
* and falls back to its built-in `#fa50b5` (pink) if the variable is
|
|
182
|
+
* undefined. Keeping a `:root` default guarantees the video player
|
|
183
|
+
* never paints with Mux's brand color on a platform we haven't
|
|
184
|
+
* explicitly themed yet. */
|
|
185
|
+
--ods-accent: var(--ods-open-yellow-base);
|
|
176
186
|
}
|
|
177
187
|
|
|
178
188
|
|
|
@@ -196,6 +206,53 @@
|
|
|
196
206
|
--ods-accent: var(--ods-open-yellow-base);
|
|
197
207
|
}
|
|
198
208
|
|
|
209
|
+
[data-app-type="openmsp"] {
|
|
210
|
+
/* OpenMSP brand: yellow primary (#FFC008). Matches `openmsp.config.tsx`
|
|
211
|
+
* brandColors.primary. Before this block existed, OpenMSP had no
|
|
212
|
+
* `--ods-accent` defined, so the MuxPlayer video controls inherited
|
|
213
|
+
* its built-in pink (`#fa50b5`) — wrong brand color on every video
|
|
214
|
+
* surface in the OpenMSP platform. */
|
|
215
|
+
--color-accent-primary: var(--ods-open-yellow-base);
|
|
216
|
+
--color-accent-hover: var(--ods-open-yellow-hover);
|
|
217
|
+
--color-accent-active: var(--ods-open-yellow-action);
|
|
218
|
+
--color-accent-focus: var(--ods-open-yellow-base);
|
|
219
|
+
--color-focus-ring: var(--ods-open-yellow-base);
|
|
220
|
+
--color-focus-visible: var(--ods-open-yellow-base);
|
|
221
|
+
--color-link: var(--ods-open-yellow-base);
|
|
222
|
+
--color-link-hover: var(--ods-open-yellow-hover);
|
|
223
|
+
--color-bg: var(--ods-system-greys-background);
|
|
224
|
+
--color-bg-card: var(--ods-system-greys-black);
|
|
225
|
+
--color-bg-hover: var(--ods-system-greys-black-hover);
|
|
226
|
+
--color-bg-active: var(--ods-system-greys-black-action);
|
|
227
|
+
|
|
228
|
+
/* OpenMSP uses yellow as adaptive color */
|
|
229
|
+
--ods-current: var(--ods-open-yellow-base);
|
|
230
|
+
--ods-accent: var(--ods-open-yellow-base);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
[data-app-type="flamingo-teaser"] {
|
|
234
|
+
/* Flamingo Teaser uses the same Flamingo pink brand as the full
|
|
235
|
+
* Flamingo app — matches `flamingo-teaser.config.tsx`. Without this
|
|
236
|
+
* block the video player would fall back to MuxPlayer's pink default,
|
|
237
|
+
* which is coincidentally close but not the brand pink. */
|
|
238
|
+
--color-accent-primary: var(--ods-flamingo-pink-base);
|
|
239
|
+
--color-accent-hover: var(--ods-flamingo-pink-hover);
|
|
240
|
+
--color-accent-active: var(--ods-flamingo-pink-action);
|
|
241
|
+
--color-accent-focus: var(--ods-flamingo-pink-base);
|
|
242
|
+
--color-focus-ring: var(--ods-flamingo-pink-base);
|
|
243
|
+
--color-focus-visible: var(--ods-flamingo-pink-base);
|
|
244
|
+
--color-link: var(--ods-flamingo-pink-base);
|
|
245
|
+
--color-link-hover: var(--ods-flamingo-pink-hover);
|
|
246
|
+
--color-bg: var(--ods-system-greys-background);
|
|
247
|
+
--color-bg-card: var(--ods-system-greys-black);
|
|
248
|
+
--color-bg-hover: var(--ods-system-greys-black-hover);
|
|
249
|
+
--color-bg-active: var(--ods-system-greys-black-action);
|
|
250
|
+
|
|
251
|
+
/* Flamingo Teaser uses pink as adaptive color */
|
|
252
|
+
--ods-current: var(--ods-flamingo-pink-base);
|
|
253
|
+
--ods-accent: var(--ods-flamingo-pink-base);
|
|
254
|
+
}
|
|
255
|
+
|
|
199
256
|
[data-app-type="flamingo"] {
|
|
200
257
|
--color-accent-primary: var(--ods-flamingo-pink-base);
|
|
201
258
|
--color-accent-hover: var(--ods-flamingo-pink-hover);
|
package/src/types/case-study.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface CaseStudy {
|
|
|
25
25
|
|
|
26
26
|
// Testimonial video (text testimonials come from MSP profile)
|
|
27
27
|
testimonial_video_url: string | null // YouTube URL (preferred when both exist)
|
|
28
|
-
|
|
28
|
+
main_video_url: string | null // Uploaded video file URL (fallback when no YouTube)
|
|
29
29
|
main_video_thumbnail?: string | null // Manual poster image URL for testimonial video. Standardized name matches main_video_thumbnail on customer_interviews/webinars/podcasts/investor_updates/product_releases per lib/data/entity-video-utils.ts ENTITY_FIELD_CONFIG. Optional for backward-compat with existing literal CaseStudy constructions across hub + external lib consumers.
|
|
30
30
|
|
|
31
31
|
// Video enhancement fields
|
|
@@ -80,7 +80,7 @@ export interface CreateCaseStudyData {
|
|
|
80
80
|
solution?: string
|
|
81
81
|
results?: string
|
|
82
82
|
testimonial_video_url?: string // YouTube URL
|
|
83
|
-
|
|
83
|
+
main_video_url?: string // Uploaded video file URL
|
|
84
84
|
main_video_thumbnail?: string | null // Manual poster image URL for testimonial video (standardized name across all video-bearing entities). Nullable so admin form can explicitly clear a stale poster from the DB when the testimonial video is removed or the source is switched to YouTube.
|
|
85
85
|
// Video enhancement fields
|
|
86
86
|
video_source_type?: 'youtube' | 'uploaded' // Deprecated
|
package/src/types/supabase.ts
CHANGED
|
@@ -292,7 +292,7 @@ export type Database = {
|
|
|
292
292
|
description: string | null
|
|
293
293
|
cover_url: string | null
|
|
294
294
|
audio_url: string | null
|
|
295
|
-
|
|
295
|
+
main_video_url: string | null
|
|
296
296
|
media_type: string | null
|
|
297
297
|
duration_seconds: number | null
|
|
298
298
|
status: string | null
|
|
@@ -313,7 +313,7 @@ export type Database = {
|
|
|
313
313
|
description?: string | null
|
|
314
314
|
cover_url?: string | null
|
|
315
315
|
audio_url?: string | null
|
|
316
|
-
|
|
316
|
+
main_video_url?: string | null
|
|
317
317
|
media_type?: string | null
|
|
318
318
|
duration_seconds?: number | null
|
|
319
319
|
status?: string | null
|
|
@@ -334,7 +334,7 @@ export type Database = {
|
|
|
334
334
|
description?: string | null
|
|
335
335
|
cover_url?: string | null
|
|
336
336
|
audio_url?: string | null
|
|
337
|
-
|
|
337
|
+
main_video_url?: string | null
|
|
338
338
|
media_type?: string | null
|
|
339
339
|
duration_seconds?: number | null
|
|
340
340
|
status?: string | null
|
|
@@ -368,7 +368,7 @@ export type Database = {
|
|
|
368
368
|
end_at: string | null
|
|
369
369
|
timezone: string | null
|
|
370
370
|
registration_url: string | null
|
|
371
|
-
|
|
371
|
+
main_video_url: string | null
|
|
372
372
|
hosts: Json | null
|
|
373
373
|
platform_id: string
|
|
374
374
|
is_deleted: boolean | null
|
|
@@ -386,7 +386,7 @@ export type Database = {
|
|
|
386
386
|
end_at?: string | null
|
|
387
387
|
timezone?: string | null
|
|
388
388
|
registration_url?: string | null
|
|
389
|
-
|
|
389
|
+
main_video_url?: string | null
|
|
390
390
|
hosts?: Json | null
|
|
391
391
|
platform_id: string
|
|
392
392
|
is_deleted?: boolean | null
|
|
@@ -404,7 +404,7 @@ export type Database = {
|
|
|
404
404
|
end_at?: string | null
|
|
405
405
|
timezone?: string | null
|
|
406
406
|
registration_url?: string | null
|
|
407
|
-
|
|
407
|
+
main_video_url?: string | null
|
|
408
408
|
hosts?: Json | null
|
|
409
409
|
platform_id?: string
|
|
410
410
|
is_deleted?: boolean | null
|