mirror of
https://github.com/zoriya/v10.git
synced 2026-08-11 00:19:28 +00:00
209 lines
6.4 KiB
TypeScript
209 lines
6.4 KiB
TypeScript
import { type CompilerPlugin, defineConfig, transform } from '@videojs/compiler';
|
|
import { tailwind } from '@videojs/compiler/tailwind';
|
|
import { kebabCase } from '@videojs/utils/string';
|
|
|
|
const SOURCE_CORE = '@videojs/core/components';
|
|
const SOURCE_ICONS = '@videojs/icons/components';
|
|
|
|
const SOURCE_TAILWIND_CSS = '../skins/src/default/video.tailwind.css';
|
|
const SOURCE_ANIMATIONS_CSS = '../skins/src/shared/animations.css';
|
|
const SOURCE_REGISTERED_PROPERTIES_CSS = '../skins/src/shared/registered-properties.css';
|
|
const SOURCE_SHARED_VARIABLES_CSS = '../skins/src/shared/variables.css';
|
|
const SOURCE_DEFAULT_VIDEO_CSS = '../skins/src/default/video.css';
|
|
|
|
const INPUT = {
|
|
'default-video': '../skins/src/default/video.skin.tsx',
|
|
};
|
|
|
|
const GENERATED_DIR = 'src/presets/video/__generated__';
|
|
const GENERATED_BANNER = '// Generated by @videojs/compiler. Do not edit.\n';
|
|
|
|
export default defineConfig({
|
|
input: INPUT,
|
|
output: {
|
|
dir: GENERATED_DIR,
|
|
entryFileNames: '[name].css.tsx',
|
|
banner: GENERATED_BANNER,
|
|
},
|
|
plugins: [
|
|
tailwind({
|
|
mode: 'extract',
|
|
input: SOURCE_TAILWIND_CSS,
|
|
output: 'default-video.css',
|
|
emit: {
|
|
mode: 'split',
|
|
base: [
|
|
SOURCE_ANIMATIONS_CSS,
|
|
SOURCE_REGISTERED_PROPERTIES_CSS,
|
|
SOURCE_SHARED_VARIABLES_CSS,
|
|
SOURCE_DEFAULT_VIDEO_CSS,
|
|
],
|
|
},
|
|
vars: {
|
|
properties: { mode: 'inline' },
|
|
},
|
|
resolve: {
|
|
element(context) {
|
|
const className = resolveMediaClassName(context.tag, context.tokenName ?? context.defaultName);
|
|
|
|
return {
|
|
className,
|
|
chunk: resolveMediaChunk(className),
|
|
};
|
|
},
|
|
classList({ className, classes }) {
|
|
return [...classes, ...sharedClassNames(className)];
|
|
},
|
|
},
|
|
}),
|
|
react(),
|
|
],
|
|
});
|
|
|
|
function react(): CompilerPlugin {
|
|
return transform((code) => {
|
|
// Types
|
|
const BaseVideoSkinProps = code.import('./src/presets/types', 'BaseVideoSkinProps', { type: true });
|
|
const ReactNode = code.import('react', 'ReactNode', { type: true });
|
|
|
|
// Utils
|
|
const cn = code.import('@videojs/utils/style', 'cn');
|
|
const isString = code.import('@videojs/utils/predicate', 'isString');
|
|
const isRenderProp = code.import('@/utils/use-render', 'isRenderProp');
|
|
|
|
// Components
|
|
const Poster = code.import('@/ui/poster', 'Poster');
|
|
|
|
return [
|
|
code.imports({
|
|
[SOURCE_CORE]: resolveComponentImport,
|
|
[SOURCE_ICONS]: '@/icons',
|
|
}),
|
|
|
|
code
|
|
.interface(/Props$/)
|
|
.property('children')
|
|
.setType(() => code.type.union(code.type.named(ReactNode), code.type.undefined())),
|
|
|
|
code.interface('DefaultVideoSkinProps').extends(BaseVideoSkinProps),
|
|
|
|
code.jsx.element(/^(Tooltip|Popover)\.Trigger$/).childToProp('render'),
|
|
code.jsx.element('Controls.Root').addProp('data-controls', ''),
|
|
code.jsx.element('Container').spreadProps('rest'),
|
|
code.jsx.element('Text').replace('span'),
|
|
|
|
code.jsx.element('Poster').replace(() =>
|
|
code.jsx.if(
|
|
'poster',
|
|
code.jsx.create(Poster, {
|
|
src: code.value.when('poster', isString),
|
|
render: code.value.when('poster', isRenderProp),
|
|
})
|
|
)
|
|
),
|
|
|
|
code.jsx
|
|
.props('className')
|
|
.where(code.value.isArray())
|
|
.replace(({ value }) => code.value.call(cn, code.value.arrayItems(value))),
|
|
|
|
code.function('DefaultVideoSkin').addProps(['poster', { name: 'rest', spread: true }]),
|
|
];
|
|
});
|
|
}
|
|
|
|
function resolveComponentImport(name: string): { source: string; name: string } {
|
|
return {
|
|
source: name === 'Container' ? '@/player/context' : `@/ui/${resolveComponentImportPath(name)}`,
|
|
name,
|
|
};
|
|
}
|
|
|
|
function resolveComponentImportPath(name: string): string {
|
|
const id = name.replace(/^AirPlay/, 'Airplay').replace(/^PiP/, 'Pip');
|
|
return kebabCase(id).replace(/^-/, '');
|
|
}
|
|
|
|
function resolveMediaClassName(tag: string, defaultName: string): string {
|
|
const tokenName = normalizeTokenName(defaultName);
|
|
|
|
if (tag === 'Container') return 'media-root';
|
|
if (/Button$/.test(tag)) return 'media-button';
|
|
if (tag === 'ErrorDialog.Close') return 'media-error-close';
|
|
|
|
return getPrefixedDefaultName(tokenName);
|
|
}
|
|
|
|
function normalizeTokenName(name: string): string {
|
|
if (name.startsWith('icons-')) {
|
|
const iconName = stripRootSuffix(name.slice('icons-'.length));
|
|
return iconName === 'root' ? 'icon' : iconName;
|
|
}
|
|
|
|
return stripRootSuffix(name);
|
|
}
|
|
|
|
function stripRootSuffix(name: string): string {
|
|
return name.endsWith('-root') ? name.slice(0, -'-root'.length) : name;
|
|
}
|
|
|
|
function resolveMediaChunk(className: string): string {
|
|
if (matchesPrefix(className, 'media-tooltip', 'media-popover', 'media-volume-popover')) return 'popup';
|
|
|
|
if (matchesPrefix(className, 'media-error')) return 'dialog';
|
|
|
|
if (matchesPrefix(className, 'media-buffering')) return 'loading';
|
|
|
|
if (matchesPrefix(className, 'media-controls')) return 'controls';
|
|
|
|
if (matchesPrefix(className, 'media-time')) return 'time';
|
|
|
|
if (matchesPrefix(className, 'media-slider', 'media-spinner')) return 'slider';
|
|
|
|
if (
|
|
matchesPrefix(
|
|
className,
|
|
'media-indicator',
|
|
'media-volume-indicator',
|
|
'media-status-indicator',
|
|
'media-seek-indicator'
|
|
)
|
|
) {
|
|
return 'feedback';
|
|
}
|
|
|
|
if (matchesPrefix(className, 'media-button', 'media-seek-label')) return 'button';
|
|
|
|
if (className.endsWith('-icon') && className !== 'media-spinner') return 'button';
|
|
|
|
return 'base';
|
|
}
|
|
|
|
function sharedClassNames(className: string): string[] {
|
|
if (matchesPrefix(className, 'media-controls-group-start', 'media-controls-group-end')) {
|
|
return ['media-controls-group'];
|
|
}
|
|
|
|
if (className === 'media-slider-thumb-persistent') return ['media-slider-thumb'];
|
|
|
|
if (matchesPrefix(className, 'media-status-indicator-top', 'media-status-indicator-center')) {
|
|
return ['media-status-indicator'];
|
|
}
|
|
|
|
if (matchesPrefix(className, 'media-seek-label-backward', 'media-seek-label-forward')) return ['media-seek-label'];
|
|
|
|
if (className === 'media-error-close') return ['media-button'];
|
|
|
|
if (className === 'media-volume-popover') return ['media-popover'];
|
|
|
|
return [];
|
|
}
|
|
|
|
function getPrefixedDefaultName(name: string): string {
|
|
return name.startsWith('media-') ? name : `media-${name}`;
|
|
}
|
|
|
|
function matchesPrefix(className: string, ...prefixes: string[]): boolean {
|
|
return prefixes.some((prefix) => className === prefix || className.startsWith(`${prefix}-`));
|
|
}
|