From 14df5c377df398b9366d1f8f58b09facad2f48d5 Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Tue, 30 Dec 2025 11:30:36 +0000 Subject: [PATCH] feat: Enhance Table component with new props and styles for better customization and usability feat: Implement StreamCast package with detailed module structure and initialization refactor: Simplify player module by re-exporting single-function modules for better maintainability feat: Add detailed documentation for player controls and overlay components feat: Improve scene management with new component structures and enhanced functionality feat: Revamp scheduling facade with modular rendering and action handling --- fakemui/fakemui/data-display/Table.tsx | 288 ++++++++++++--- fakemui/styles/components/Table.module.scss | 347 ++++++++++++++++++ packages/stream_cast/seed/scripts/init.lua | 38 +- packages/stream_cast/seed/scripts/player.lua | 62 +--- .../seed/scripts/player/controls.lua | 6 +- .../stream_cast/seed/scripts/player/init.lua | 7 +- .../seed/scripts/player/overlay.lua | 5 +- packages/stream_cast/seed/scripts/scenes.lua | 47 +-- .../seed/scripts/scenes/camera.lua | 18 +- .../stream_cast/seed/scripts/scenes/init.lua | 8 +- .../seed/scripts/scenes/layout.lua | 7 +- .../seed/scripts/scenes/screen.lua | 5 +- .../stream_cast/seed/scripts/schedule.lua | 48 +-- 13 files changed, 698 insertions(+), 188 deletions(-) create mode 100644 fakemui/styles/components/Table.module.scss diff --git a/fakemui/fakemui/data-display/Table.tsx b/fakemui/fakemui/data-display/Table.tsx index ff4bcffb8..f0affb779 100644 --- a/fakemui/fakemui/data-display/Table.tsx +++ b/fakemui/fakemui/data-display/Table.tsx @@ -1,21 +1,59 @@ import React from 'react' +import styles from '../../styles/components/Table.module.scss' + +export type TableSize = 'small' | 'medium' | 'large' +export type TablePadding = 'checkbox' | 'none' | 'normal' +export type TableAlign = 'left' | 'center' | 'right' | 'justify' export interface TableProps extends React.TableHTMLAttributes { children?: React.ReactNode + /** Size variant */ + size?: TableSize + /** Enable sticky header */ + stickyHeader?: boolean + /** Enable striped rows */ + striped?: boolean + /** Enable bordered variant */ + bordered?: boolean + /** Compact mode */ + compact?: boolean } -export const Table: React.FC = ({ children, className = '', ...props }) => ( - - {children} -
-) +export const Table: React.FC = ({ + children, + className = '', + size = 'medium', + stickyHeader = false, + striped = false, + bordered = false, + compact = false, + ...props +}) => { + const classes = [ + styles.table, + styles[size], + stickyHeader && styles.stickyHeader, + striped && styles.striped, + bordered && styles.bordered, + compact && styles.compact, + className, + ] + .filter(Boolean) + .join(' ') + + return ( + + {children} +
+ ) +} export interface TableHeadProps extends React.HTMLAttributes { children?: React.ReactNode } export const TableHead: React.FC = ({ children, className = '', ...props }) => ( - + {children} ) @@ -25,7 +63,7 @@ export interface TableBodyProps extends React.HTMLAttributes = ({ children, className = '', ...props }) => ( - + {children} ) @@ -35,36 +73,88 @@ export interface TableFooterProps extends React.HTMLAttributes = ({ children, className = '', ...props }) => ( - + {children} ) export interface TableRowProps extends React.HTMLAttributes { children?: React.ReactNode + /** Enable hover effect */ hover?: boolean + /** Selected state */ selected?: boolean + /** Make row clickable */ + onClick?: React.MouseEventHandler } -export const TableRow: React.FC = ({ children, hover, selected, className = '', ...props }) => ( - - {children} - -) +export const TableRow: React.FC = ({ + children, + hover, + selected, + onClick, + className = '', + ...props +}) => { + const classes = [ + styles.row, + hover && styles.hover, + selected && styles.selected, + onClick && styles.clickable, + className, + ] + .filter(Boolean) + .join(' ') + + return ( + + {children} + + ) +} export interface TableCellProps extends React.TdHTMLAttributes { children?: React.ReactNode + /** Render as th element */ header?: boolean - align?: 'left' | 'center' | 'right' + /** Text alignment */ + align?: TableAlign + /** Cell padding variant */ + padding?: TablePadding + /** Scope for header cells */ + scope?: 'col' | 'row' | 'colgroup' | 'rowgroup' + /** Enable sorting on this cell */ + sortDirection?: 'asc' | 'desc' | false } -export const TableCell: React.FC = ({ children, header, align, className = '', ...props }) => { +export const TableCell: React.FC = ({ + children, + header, + align = 'left', + padding = 'normal', + scope, + sortDirection, + className = '', + ...props +}) => { const Tag = header ? 'th' : 'td' + const classes = [ + styles.cell, + align && styles[`align${align.charAt(0).toUpperCase() + align.slice(1)}`], + padding === 'checkbox' && styles.paddingCheckbox, + padding === 'none' && styles.paddingNone, + className, + ] + .filter(Boolean) + .join(' ') + return ( - + {children} ) @@ -72,10 +162,22 @@ export const TableCell: React.FC = ({ children, header, align, c export interface TableContainerProps extends React.HTMLAttributes { children?: React.ReactNode + /** Max height for scrollable container */ + maxHeight?: number | string } -export const TableContainer: React.FC = ({ children, className = '', ...props }) => ( -
+export const TableContainer: React.FC = ({ + children, + maxHeight, + className = '', + style, + ...props +}) => ( +
{children}
) @@ -84,19 +186,34 @@ export interface TablePaginationLabelDisplayedRowsArgs { from: number to: number count: number + page: number } export interface TablePaginationProps extends React.HTMLAttributes { + /** Total number of rows */ count?: number + /** Current page (0-indexed) */ page?: number + /** Number of rows per page */ rowsPerPage?: number + /** Options for rows per page dropdown */ rowsPerPageOptions?: number[] - onPageChange?: (event: React.MouseEvent, page: number) => void + /** Callback for page change */ + onPageChange?: (event: React.MouseEvent | null, page: number) => void + /** Callback for rows per page change */ onRowsPerPageChange?: (event: React.ChangeEvent) => void - labelRowsPerPage?: string - labelDisplayedRows?: (args: TablePaginationLabelDisplayedRowsArgs) => string + /** Label for rows per page dropdown */ + labelRowsPerPage?: React.ReactNode + /** Custom label for displayed rows */ + labelDisplayedRows?: (args: TablePaginationLabelDisplayedRowsArgs) => React.ReactNode + /** Show first page button */ showFirstButton?: boolean + /** Show last page button */ showLastButton?: boolean + /** Disable the back button beyond first page */ + backIconButtonDisabled?: boolean + /** Disable the next button beyond last page */ + nextIconButtonDisabled?: boolean } export const TablePagination: React.FC = ({ @@ -107,23 +224,46 @@ export const TablePagination: React.FC = ({ onPageChange, onRowsPerPageChange, labelRowsPerPage = 'Rows per page:', - labelDisplayedRows = ({ from, to, count }) => `${from}–${to} of ${count}`, + labelDisplayedRows = ({ from, to, count }) => + `${from}–${to} of ${count !== -1 ? count : `more than ${to}`}`, showFirstButton = false, showLastButton = false, + backIconButtonDisabled, + nextIconButtonDisabled, className = '', ...props }) => { const from = count === 0 ? 0 : page * rowsPerPage + 1 - const to = Math.min((page + 1) * rowsPerPage, count) - const totalPages = Math.ceil(count / rowsPerPage) + const to = count !== -1 ? Math.min((page + 1) * rowsPerPage, count) : (page + 1) * rowsPerPage + const totalPages = count !== -1 ? Math.ceil(count / rowsPerPage) : -1 + + const handleFirstPage = (e: React.MouseEvent) => { + onPageChange?.(e, 0) + } + + const handlePrevPage = (e: React.MouseEvent) => { + onPageChange?.(e, page - 1) + } + + const handleNextPage = (e: React.MouseEvent) => { + onPageChange?.(e, page + 1) + } + + const handleLastPage = (e: React.MouseEvent) => { + onPageChange?.(e, Math.max(0, totalPages - 1)) + } + + const isBackDisabled = backIconButtonDisabled ?? page === 0 + const isNextDisabled = nextIconButtonDisabled ?? (totalPages !== -1 && page >= totalPages - 1) return ( -
- {labelRowsPerPage} +
+ {labelRowsPerPage} - {labelDisplayedRows({ from, to, count })} -
+ + {labelDisplayedRows({ from, to, count, page })} + +
{showFirstButton && ( )} {showLastButton && ( )}
@@ -171,12 +331,18 @@ export const TablePagination: React.FC = ({ } export interface TableSortLabelProps extends React.HTMLAttributes { + /** Whether this column is currently sorted */ active?: boolean + /** Sort direction */ direction?: 'asc' | 'desc' + /** Click handler */ onClick?: (event: React.MouseEvent | React.KeyboardEvent) => void + /** Label content */ children?: React.ReactNode + /** Hide sort icon when inactive */ hideSortIcon?: boolean - IconComponent?: React.ComponentType<{ className?: string }> + /** Custom icon component */ + IconComponent?: React.ComponentType<{ className?: string; direction?: 'asc' | 'desc' }> } export const TableSortLabel: React.FC = ({ @@ -189,23 +355,37 @@ export const TableSortLabel: React.FC = ({ className = '', ...props }) => { + const classes = [styles.sortLabel, active && styles.active, className].filter(Boolean).join(' ') + const sortIcon = IconComponent ? ( - + ) : ( - {direction === 'asc' ? '↑' : '↓'} + + + + + ) + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onClick?.(e) + } + } + return ( e.key === 'Enter' && onClick?.(e)} + aria-pressed={active} {...props} > {children} - {!hideSortIcon && (active || !hideSortIcon) && sortIcon} + {(!hideSortIcon || active) && sortIcon} ) } diff --git a/fakemui/styles/components/Table.module.scss b/fakemui/styles/components/Table.module.scss new file mode 100644 index 000000000..3501def3f --- /dev/null +++ b/fakemui/styles/components/Table.module.scss @@ -0,0 +1,347 @@ +/* Table component styles */ + +/* ===== Table Container ===== */ +.tableContainer { + width: 100%; + overflow-x: auto; + border-radius: var(--shape-border-radius, 4px); + + &::-webkit-scrollbar { + height: 8px; + } + + &::-webkit-scrollbar-track { + background: var(--color-background-default, #f5f5f5); + } + + &::-webkit-scrollbar-thumb { + background: var(--color-text-disabled, #bdbdbd); + border-radius: 4px; + } +} + +/* ===== Base Table ===== */ +.table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + font-family: var(--font-family-body, 'IBM Plex Sans', sans-serif); + font-size: var(--font-size-body1, 14px); + background-color: var(--color-background-paper, #fff); + + /* Size variants */ + &.small { + .cell { + padding: 6px 12px; + font-size: var(--font-size-body2, 13px); + } + } + + &.medium { + .cell { + padding: 12px 16px; + } + } + + &.large { + .cell { + padding: 16px 20px; + font-size: var(--font-size-body1, 14px); + } + } + + /* Sticky header */ + &.stickyHeader { + .head { + position: sticky; + top: 0; + z-index: 2; + background-color: var(--color-background-paper, #fff); + } + } +} + +/* ===== Table Head ===== */ +.head { + background-color: var(--color-background-default, #fafafa); + + .cell { + font-weight: 600; + color: var(--color-text-primary, rgba(0, 0, 0, 0.87)); + border-bottom: 2px solid var(--color-divider, rgba(0, 0, 0, 0.12)); + } +} + +/* ===== Table Body ===== */ +.body { + .row:last-child .cell { + border-bottom: none; + } +} + +/* ===== Table Footer ===== */ +.footer { + background-color: var(--color-background-default, #fafafa); + + .cell { + font-weight: 500; + border-top: 2px solid var(--color-divider, rgba(0, 0, 0, 0.12)); + border-bottom: none; + } +} + +/* ===== Table Row ===== */ +.row { + transition: background-color 150ms ease-in-out; + + &.hover:hover { + background-color: var(--color-action-hover, rgba(0, 0, 0, 0.04)); + } + + &.selected { + background-color: var(--color-primary-light, rgba(25, 118, 210, 0.08)); + + &:hover { + background-color: var(--color-primary-light, rgba(25, 118, 210, 0.12)); + } + } + + &.clickable { + cursor: pointer; + } +} + +/* Striped rows */ +.striped .body .row:nth-child(odd) { + background-color: var(--color-action-hover, rgba(0, 0, 0, 0.02)); + + &:hover { + background-color: var(--color-action-hover, rgba(0, 0, 0, 0.06)); + } +} + +/* ===== Table Cell ===== */ +.cell { + padding: 12px 16px; + text-align: left; + border-bottom: 1px solid var(--color-divider, rgba(0, 0, 0, 0.12)); + vertical-align: middle; + color: var(--color-text-primary, rgba(0, 0, 0, 0.87)); + + /* Alignment */ + &.alignLeft { + text-align: left; + } + + &.alignCenter { + text-align: center; + } + + &.alignRight { + text-align: right; + } + + &.alignJustify { + text-align: justify; + } + + /* Padding variants */ + &.paddingCheckbox { + width: 48px; + padding: 0 4px; + } + + &.paddingNone { + padding: 0; + } +} + +/* ===== Sort Label ===== */ +.sortLabel { + display: inline-flex; + align-items: center; + gap: 4px; + cursor: pointer; + user-select: none; + font-weight: inherit; + color: inherit; + transition: color 150ms ease-in-out; + + &:hover { + color: var(--color-text-primary, rgba(0, 0, 0, 0.87)); + } + + &:focus { + outline: none; + color: var(--color-primary-main, #1976d2); + } + + &.active { + color: var(--color-text-primary, rgba(0, 0, 0, 0.87)); + } + + &:not(.active) { + color: var(--color-text-secondary, rgba(0, 0, 0, 0.6)); + + .sortIcon { + opacity: 0; + transition: opacity 150ms ease-in-out; + } + + &:hover .sortIcon { + opacity: 0.5; + } + } +} + +.sortIcon { + display: inline-flex; + align-items: center; + font-size: 16px; + opacity: 1; + transition: transform 200ms ease-in-out, opacity 150ms ease-in-out; + + &.asc { + transform: rotate(0deg); + } + + &.desc { + transform: rotate(180deg); + } +} + +/* ===== Pagination ===== */ +.pagination { + display: flex; + align-items: center; + justify-content: flex-end; + padding: 12px 16px; + gap: 16px; + font-size: var(--font-size-body2, 13px); + color: var(--color-text-secondary, rgba(0, 0, 0, 0.6)); + border-top: 1px solid var(--color-divider, rgba(0, 0, 0, 0.12)); + + &.toolbar { + min-height: 52px; + padding: 8px 2px; + } +} + +.paginationLabel { + flex-shrink: 0; +} + +.paginationSelect { + appearance: none; + padding: 6px 24px 6px 8px; + border: 1px solid var(--color-divider, rgba(0, 0, 0, 0.23)); + border-radius: 4px; + font-size: inherit; + font-family: inherit; + background-color: var(--color-background-paper, #fff); + cursor: pointer; + transition: border-color 150ms ease-in-out; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 4px center; + background-size: 16px; + + &:hover { + border-color: var(--color-text-primary, rgba(0, 0, 0, 0.87)); + } + + &:focus { + outline: none; + border-color: var(--color-primary-main, #1976d2); + } +} + +.paginationSpacer { + flex: 1 1 100%; +} + +.paginationDisplayed { + flex-shrink: 0; + margin-left: 8px; +} + +.paginationActions { + display: flex; + align-items: center; + gap: 4px; + flex-shrink: 0; +} + +.paginationBtn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + border: none; + border-radius: 50%; + background-color: transparent; + color: var(--color-text-secondary, rgba(0, 0, 0, 0.6)); + cursor: pointer; + transition: background-color 150ms ease-in-out, color 150ms ease-in-out; + + &:hover:not(:disabled) { + background-color: var(--color-action-hover, rgba(0, 0, 0, 0.04)); + color: var(--color-text-primary, rgba(0, 0, 0, 0.87)); + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } + + svg { + width: 20px; + height: 20px; + } +} + +/* ===== Bordered variant ===== */ +.bordered { + border: 1px solid var(--color-divider, rgba(0, 0, 0, 0.12)); + border-radius: var(--shape-border-radius, 4px); + + .cell { + border: 1px solid var(--color-divider, rgba(0, 0, 0, 0.12)); + } +} + +/* ===== Compact variant ===== */ +.compact { + .cell { + padding: 4px 8px; + font-size: var(--font-size-caption, 12px); + } +} + +/* ===== Dark mode support ===== */ +@media (prefers-color-scheme: dark) { + .table { + background-color: var(--color-background-paper-dark, #1e1e1e); + } + + .head { + background-color: var(--color-background-default-dark, #121212); + } + + .cell { + color: var(--color-text-primary-dark, #fff); + border-color: var(--color-divider-dark, rgba(255, 255, 255, 0.12)); + } + + .row.hover:hover { + background-color: var(--color-action-hover-dark, rgba(255, 255, 255, 0.08)); + } + + .paginationSelect { + background-color: var(--color-background-paper-dark, #1e1e1e); + border-color: var(--color-divider-dark, rgba(255, 255, 255, 0.23)); + color: var(--color-text-primary-dark, #fff); + } +} diff --git a/packages/stream_cast/seed/scripts/init.lua b/packages/stream_cast/seed/scripts/init.lua index 4821d7b00..1e745c687 100644 --- a/packages/stream_cast/seed/scripts/init.lua +++ b/packages/stream_cast/seed/scripts/init.lua @@ -1,9 +1,45 @@ --- Stream Cast initialization +--- Stream Cast package entry point +--- Provides streaming and broadcasting components +---@class StreamCast +---@field name string Package name +---@field version string Package version +---@field player StreamPlayerModule Player module +---@field scenes StreamScenesModule Scenes module +---@field schedule StreamScheduleModule Schedule module +---@field init fun(): { name: string, version: string, loaded: boolean } + +---@class StreamPlayerModule +---@field PLAYING PlayerState +---@field PAUSED PlayerState +---@field BUFFERING PlayerState +---@field OFFLINE PlayerState +---@field render fun(stream: Stream): VideoPlayerComponent +---@field render_controls fun(state: PlayerState): PlayerControlsComponent +---@field render_status fun(state: PlayerState, viewers: number): StatusBarComponent + +---@class StreamScenesModule +---@field render_scene fun(scene: Scene): ScenePreviewComponent +---@field render_list fun(scenes: Scene[]): SceneListComponent +---@field switch fun(scene_id: string): SwitchSceneAction +---@field create fun(name: string, sources?: table[]): CreateSceneAction + +---@class StreamScheduleModule +---@field render_item fun(stream: ScheduledStream): ScheduleItemComponent +---@field render_list fun(streams: ScheduledStream[]): ScheduleListComponent +---@field create fun(data: { title: string, start_time: string, duration?: number }): ScheduleStreamAction +---@field cancel fun(stream_id: string): CancelStreamAction + local M = {} M.name = "stream_cast" M.version = "1.0.0" +M.player = require("player") +M.scenes = require("scenes") +M.schedule = require("schedule") + +--- Initialize the stream cast module +---@return { name: string, version: string, loaded: boolean } function M.init() return { name = M.name, diff --git a/packages/stream_cast/seed/scripts/player.lua b/packages/stream_cast/seed/scripts/player.lua index 9f1abe7ec..ee423ab86 100644 --- a/packages/stream_cast/seed/scripts/player.lua +++ b/packages/stream_cast/seed/scripts/player.lua @@ -1,48 +1,24 @@ --- Stream player controls +--- Stream player facade +--- Re-exports single-function modules for backward compatibility + +---@type PlayerState +local PLAYING = "playing" +---@type PlayerState +local PAUSED = "paused" +---@type PlayerState +local BUFFERING = "buffering" +---@type PlayerState +local OFFLINE = "offline" + local M = {} -M.PLAYING = "playing" -M.PAUSED = "paused" -M.BUFFERING = "buffering" -M.OFFLINE = "offline" +M.PLAYING = PLAYING +M.PAUSED = PAUSED +M.BUFFERING = BUFFERING +M.OFFLINE = OFFLINE -function M.render(stream) - return { - type = "video_player", - props = { - src = stream.url, - poster = stream.thumbnail, - autoplay = true, - controls = true - } - } -end - -function M.render_controls(state) - return { - type = "player_controls", - children = { - { type = "button", props = { icon = state == M.PLAYING and "pause" or "play" } }, - { type = "volume_slider", props = { min = 0, max = 100 } }, - { type = "button", props = { icon = "maximize" } } - } - } -end - -function M.render_status(state, viewers) - local colors = { - playing = "success", - paused = "warning", - buffering = "info", - offline = "error" - } - return { - type = "status_bar", - children = { - { type = "badge", props = { label = state, color = colors[state] } }, - { type = "text", props = { text = viewers .. " viewers" } } - } - } -end +M.render = require("render_player") +M.render_controls = require("render_player_controls") +M.render_status = require("render_status") return M diff --git a/packages/stream_cast/seed/scripts/player/controls.lua b/packages/stream_cast/seed/scripts/player/controls.lua index 799c48b7c..17d92e042 100644 --- a/packages/stream_cast/seed/scripts/player/controls.lua +++ b/packages/stream_cast/seed/scripts/player/controls.lua @@ -1,4 +1,8 @@ --- Stream player controls +--- Stream player controls configuration +---@param show_play? boolean Show play button (default true) +---@param show_volume? boolean Show volume slider (default true) +---@param show_fullscreen? boolean Show fullscreen button (default true) +---@return PlayerControlsConfig Controls configuration local function controls(show_play, show_volume, show_fullscreen) return { type = "player_controls", diff --git a/packages/stream_cast/seed/scripts/player/init.lua b/packages/stream_cast/seed/scripts/player/init.lua index 65d6c7a71..bd05ef6a2 100644 --- a/packages/stream_cast/seed/scripts/player/init.lua +++ b/packages/stream_cast/seed/scripts/player/init.lua @@ -1,4 +1,9 @@ --- Stream player module +--- Stream player sub-module +--- Provides controls and overlay components +---@class StreamPlayerSubModule +---@field controls fun(show_play?: boolean, show_volume?: boolean, show_fullscreen?: boolean): PlayerControlsConfig +---@field overlay fun(title: string, subtitle: string): PlayerOverlayComponent + local player = { controls = require("player.controls"), overlay = require("player.overlay") diff --git a/packages/stream_cast/seed/scripts/player/overlay.lua b/packages/stream_cast/seed/scripts/player/overlay.lua index c3c9aeedb..d42016c48 100644 --- a/packages/stream_cast/seed/scripts/player/overlay.lua +++ b/packages/stream_cast/seed/scripts/player/overlay.lua @@ -1,4 +1,7 @@ --- Stream player overlay +--- Stream player overlay component +---@param title string Overlay title +---@param subtitle string Overlay subtitle +---@return PlayerOverlayComponent Overlay component local function overlay(title, subtitle) return { type = "player_overlay", diff --git a/packages/stream_cast/seed/scripts/scenes.lua b/packages/stream_cast/seed/scripts/scenes.lua index 5dae53098..aeaa9e822 100644 --- a/packages/stream_cast/seed/scripts/scenes.lua +++ b/packages/stream_cast/seed/scripts/scenes.lua @@ -1,44 +1,11 @@ --- Scene management +--- Scene management facade +--- Re-exports single-function modules for backward compatibility + local M = {} -function M.render_scene(scene) - return { - type = "scene_preview", - props = { - id = scene.id, - name = scene.name, - thumbnail = scene.thumbnail, - active = scene.active or false - } - } -end - -function M.render_list(scenes) - local items = {} - for _, scene in ipairs(scenes) do - table.insert(items, M.render_scene(scene)) - end - return { - type = "scene_list", - children = items - } -end - -function M.switch(scene_id) - return { - action = "switch_scene", - scene_id = scene_id - } -end - -function M.create(name, sources) - return { - action = "create_scene", - data = { - name = name, - sources = sources or {} - } - } -end +M.render_scene = require("render_scene") +M.render_list = require("render_scene_list") +M.switch = require("switch_scene") +M.create = require("create_scene") return M diff --git a/packages/stream_cast/seed/scripts/scenes/camera.lua b/packages/stream_cast/seed/scripts/scenes/camera.lua index cd105da14..038ec84d6 100644 --- a/packages/stream_cast/seed/scripts/scenes/camera.lua +++ b/packages/stream_cast/seed/scripts/scenes/camera.lua @@ -1,5 +1,19 @@ --- Stream scene camera component -local function camera(id, label, source) +--- Stream scene camera component +---@param id string Camera ID +---@param label string Camera label +---@param source string Camera source +---@return SceneCameraComponent Camera component +---@class SceneCameraComponent +---@field type "scene_camera" Component type +---@field id string Camera ID +---@field label string Camera label +---@field source string Source identifier + +---Create a camera scene source +---@param id string Camera ID +---@param label string Camera label +---@param source string Source identifier +---@return SceneCameraComponentlocal function camera(id, label, source) return { type = "scene_camera", id = id, diff --git a/packages/stream_cast/seed/scripts/scenes/init.lua b/packages/stream_cast/seed/scripts/scenes/init.lua index 86c4e2a55..afc7811f3 100644 --- a/packages/stream_cast/seed/scripts/scenes/init.lua +++ b/packages/stream_cast/seed/scripts/scenes/init.lua @@ -1,4 +1,10 @@ --- Stream scenes module +--- Stream scenes sub-module +--- Provides camera, screen, and layout components +---@class StreamScenesSubModule +---@field camera fun(id: string, label: string, source: string): SceneCameraComponent +---@field screen fun(id: string, label: string): SceneScreenComponent +---@field layout fun(type: "single"|"pip"|"split", sources?: table[]): SceneLayoutComponent + local scenes = { camera = require("scenes.camera"), screen = require("scenes.screen"), diff --git a/packages/stream_cast/seed/scripts/scenes/layout.lua b/packages/stream_cast/seed/scripts/scenes/layout.lua index 0db2aa482..a9dec9ce3 100644 --- a/packages/stream_cast/seed/scripts/scenes/layout.lua +++ b/packages/stream_cast/seed/scripts/scenes/layout.lua @@ -1,8 +1,11 @@ --- Stream scene layout component +--- Stream scene layout component +---@param type "single"|"pip"|"split" Layout type +---@param sources? table[] Layout sources +---@return SceneLayoutComponent Layout component local function layout(type, sources) return { type = "scene_layout", - layoutType = type, -- "single", "pip", "split" + layoutType = type, sources = sources or {} } end diff --git a/packages/stream_cast/seed/scripts/scenes/screen.lua b/packages/stream_cast/seed/scripts/scenes/screen.lua index 330739b80..73772a3eb 100644 --- a/packages/stream_cast/seed/scripts/scenes/screen.lua +++ b/packages/stream_cast/seed/scripts/scenes/screen.lua @@ -1,4 +1,7 @@ --- Stream scene screen share component +--- Stream scene screen share component +---@param id string Screen ID +---@param label string Screen label +---@return SceneScreenComponent Screen component local function screen(id, label) return { type = "scene_screen", diff --git a/packages/stream_cast/seed/scripts/schedule.lua b/packages/stream_cast/seed/scripts/schedule.lua index 64c8f7b9f..203a90ef3 100644 --- a/packages/stream_cast/seed/scripts/schedule.lua +++ b/packages/stream_cast/seed/scripts/schedule.lua @@ -1,45 +1,11 @@ --- Stream scheduling +--- Stream scheduling facade +--- Re-exports single-function modules for backward compatibility + local M = {} -function M.render_item(stream) - return { - type = "schedule_item", - props = { - title = stream.title, - start_time = stream.start_time, - duration = stream.duration, - thumbnail = stream.thumbnail - } - } -end - -function M.render_list(streams) - local items = {} - for _, stream in ipairs(streams) do - table.insert(items, M.render_item(stream)) - end - return { - type = "schedule_list", - children = items - } -end - -function M.create(data) - return { - action = "schedule_stream", - data = { - title = data.title, - start_time = data.start_time, - duration = data.duration or 60 - } - } -end - -function M.cancel(stream_id) - return { - action = "cancel_stream", - stream_id = stream_id - } -end +M.render_item = require("render_schedule_item") +M.render_list = require("render_schedule_list") +M.create = require("schedule_stream") +M.cancel = require("cancel_stream") return M