diff --git a/package.json b/package.json index da547f4e..59349e63 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "start": "NODE_ENV=production node src/backend/index.ts", "build:frontend": "vite build", "build:backend:ts": "tsc -p src/backend", + "build:frontend:ts": "tsc -p src/client", "build:backend": "npm run -s build:backend:ts", "build": "npm run -s build:backend && npm run -s build:frontend && npm run -s docs:build", "build:parallel": "concurrently --kill-others-on-fail --names backend,frontend,docs \"npm run -s build:backend\" \"npm run -s build:frontend\" \"npm run docs:build\"", diff --git a/src/client/components/SideNav.tsx b/src/client/components/SideNav.tsx index ab584caf..06004d2b 100644 --- a/src/client/components/SideNav.tsx +++ b/src/client/components/SideNav.tsx @@ -11,7 +11,7 @@ import { Link as RouterLink } from "react-router"; import { ExternalLinkIconRaw } from "./icons/ChakraIcons" interface SideNavItem { - title: React.ReactNode + title: string url: LinkProps["href"] | undefined external?: boolean status?: string @@ -19,7 +19,7 @@ interface SideNavItem { interface SideNavProps { currentUrl?: string - title: React.ReactNode + title: string id: string status?: string items: Array diff --git a/src/client/components/buttonMenus/PrimaryButtonMenu.tsx b/src/client/components/buttonMenus/PrimaryButtonMenu.tsx index 24a7ea62..b178f954 100644 --- a/src/client/components/buttonMenus/PrimaryButtonMenu.tsx +++ b/src/client/components/buttonMenus/PrimaryButtonMenu.tsx @@ -34,7 +34,7 @@ export const PrimaryButtonMenu = (props: PrimaryButtonMenuProps) => { if(React.isValidElement(x)) { return {x}; } - return {x({ disabled: props.disabled })} + return {(x as MenuItemRender)({ disabled: props.disabled, value: i.toString() })} })} diff --git a/src/client/components/msComponent/MSComponentDetailed.tsx b/src/client/components/msComponent/MSComponentDetailed.tsx index 8263d49e..19eba868 100644 --- a/src/client/components/msComponent/MSComponentDetailed.tsx +++ b/src/client/components/msComponent/MSComponentDetailed.tsx @@ -462,7 +462,7 @@ export const ComponentDetailedDesktop = (props: {data?: ComponentsApiJson, live? {errors.length > 0 ? <>{errors.map(x => )} : undefined } {warnings.length > 0 ? <>{warnings.map(x => )} : undefined } {props.live ? : } - + ) diff --git a/src/client/components/msComponent/MSComponentSummary.tsx b/src/client/components/msComponent/MSComponentSummary.tsx index 776c8f51..50ab8b6a 100644 --- a/src/client/components/msComponent/MSComponentSummary.tsx +++ b/src/client/components/msComponent/MSComponentSummary.tsx @@ -1,6 +1,6 @@ import React, { type ComponentProps, Fragment, useEffect } from "react" import { Stack, Heading, Separator, HStack, Flex, Badge, Card, LinkOverlay, LinkBox } from '@chakra-ui/react'; -import { type ComponentCommonApiJson, isComponentClientApiJson, isComponentSourceApiJson, type MsSseEvent, type MsSseEventPayload } from "../../../core/Api.js"; +import { type ComponentClientApiJson, type ComponentCommonApiJson, type ComponentsApiJson, isComponentClientApiJson, isComponentSourceApiJson, type MsSseEvent, type MsSseEventPayload } from "../../../core/Api.js"; import { Link } from "react-router"; import { TextMuted } from "../TextMuted.js"; import { capitalize } from "../../../core/StringUtils.js"; @@ -21,7 +21,7 @@ const presentPlayersContainerProps: ComponentProps = { //borderTopWidth: '1px' }; -export const MSComponentSummary = (props: { data: ComponentCommonApiJson, fetchable?: boolean }) => { +export const MSComponentSummary = (props: { data: ComponentsApiJson, fetchable?: boolean }) => { const { data, fetchable @@ -133,5 +133,5 @@ export const MSComponentSummaryFetchable = (props: {componentId: number, data: C type ComponentSummaryQueryKey = ['components', number, 'summary']; const queryFn = async (context: QueryFunctionContext) => { - return {} as ComponentCommonApiJson; + return {} as ComponentsApiJson; } \ No newline at end of file diff --git a/src/client/components/playActivity/ActivityList.tsx b/src/client/components/playActivity/ActivityList.tsx index 6fbd0cb9..89de2936 100644 --- a/src/client/components/playActivity/ActivityList.tsx +++ b/src/client/components/playActivity/ActivityList.tsx @@ -1,26 +1,24 @@ -import { Accordion, Container, Stack, Heading, type MenuSelectionDetails, HStack, Dialog, Portal, CloseButton, Button, Text, RadioGroup } from '@chakra-ui/react'; +import { Container, Stack, Heading, type MenuSelectionDetails, HStack, Dialog, Portal, CloseButton, Button, Text } from '@chakra-ui/react'; import { useSSEAnyEvent, useSSEContext } from '@flamefrontend/sse-runtime-react'; import { type InfiniteData, useInfiniteQuery, type UseInfiniteQueryResult, useMutation, useQueryClient } from '@tanstack/react-query'; import dayjs from 'dayjs'; import doy from 'dayjs/plugin/dayOfYear.js'; -import { type ComponentProps, Fragment, useCallback, useMemo, useState } from "react"; +import { type ComponentProps, useCallback, useMemo, useState } from "react"; import type {MsSseEvent, MsSseEventPayload, PaginatedResponse, PlayApiCommonDetailed, QueryPlaysOptsJson} from '../../../core/Api.js'; -import {actionContextSchema, queueContextSchema, type ComponentType, type QueueContext} from '../../../core/Atomic.js'; +import {actionContextSchema, type ComponentType, type QueueContext} from '../../../core/Atomic.js'; import { type QueryPlaysOptsJsonRefreshable, tanQueries, useQueryWatcher } from '../../queries/index.js'; -import { ActivityDetailFetchable, ActivityDetails, ActivitySummary, ActivitySummaryFetchable, ActivitySummarySkeleton } from '../ActivityDetail.js'; +import { ActivitySummarySkeleton } from '../ActivityDetail.js'; import { ErrorAlert } from '../ErrorAlert.js'; import { ListFilters, ListRefereshButton, todayRange } from './ListFilters.js'; -import { type ActivityLogProps, generateGroupPlays, GroupHeader } from './ListParts.js'; +import { type ActivityLogProps } from './ListParts.js'; import { NoPlayResults, VirtualizedListDynamic } from './VirtualListDynamic.js'; -import { VirtualizedListExp } from './VirtualListExperimental.js'; -import { VirtualizedListNormal } from './VirtualListNormal.js'; import { menuItem, type MenuItemRender } from '../buttonMenus/menuItemUtils.js'; import { RetryIcon } from '../icons/ChakraIcons.js'; import { PrimaryButtonMenu } from '../buttonMenus/PrimaryButtonMenu.js'; import ky from 'ky'; import { formOptions, useForm } from '@tanstack/react-form'; -import z from 'zod'; import { FormCheckbox, FormRadio, type RadioFormItem } from '../form/formComponents.js'; +import { queryPlayOptsRefreshableToJson } from '../../utils/ComponentUtils.js'; dayjs.extend(doy); @@ -29,60 +27,22 @@ export const ActivityList = (props: ActivityLogProps & Pick - } - if (render === 'virtNormal') { - return - } - if (render === 'virtDynamic') { - return - } - if (render === 'virtExp') { - return - } -} - + return -const PlainAccordian = (props: ActivityLogProps) => { - const { - data = [], - sortBy, - live = false - } = props; - const groups = generateGroupPlays(data); - return ( - - {groups.map((g) => { - return ( - - - - {g.plays.map((activity, index) => { - const { play } = activity; - return ( - - - - {live ? : } - - - - {live ? : } - - - - ) - })} - - - ) - })} - - ); + // if (render === 'accordian') { + // return + // } + // if (render === 'virtNormal') { + // return + // } + // if (render === 'virtDynamic') { + // return + // } + // if (render === 'virtExp') { + // return + // } } @@ -90,7 +50,7 @@ export const ListContainer = (props?: ComponentProps) => { return } -export const ListContainerFetchable = (props: { componentId: number, componentType: ComponentType, filters?: QueryPlaysOptsJsonRefreshable } & Pick, 'render'>) => { +export const ListContainerFetchable = (props: { componentId: number, componentType: ComponentType, filters?: QueryPlaysOptsJsonRefreshable }) => { const { componentId, filters = {} @@ -117,6 +77,8 @@ export const ListContainerFetchable = (props: { componentId: number, componentTy }, }); + const queryJson = queryPlayOptsRefreshableToJson(query); + const allPlays = useMemo(() => data === undefined ? [] : data.pages.flatMap(x => x.data).filter(x => x !== null && x !== undefined),[data]); const queryClient = useQueryClient(); @@ -128,7 +90,7 @@ export const ListContainerFetchable = (props: { componentId: number, componentTy { const componentData = payload.data as MsSseEventPayload; console.debug(`[Insert Check ${componentData.data.uid}] Recieved playInsert for Component ${componentId}, checking if Play can be inserted...`); - if(playInWindow(componentData.data, query)) { + if(playInWindow(componentData.data, queryJson)) { queryClient.setQueryData(tanQueries.activities.list(componentId, query).queryKey, (old: InfiniteData, unknown>) => { return insertInfinitePlay(componentData.data, old); }); @@ -175,12 +137,11 @@ export const ListContainerFetchable = (props: { componentId: number, componentTy hasNextPage={hasNextPage} fetchNextPage={fetchNextPage} isFetchingNextPage={isFetchingNextPage} - render="virtDynamic" data={allPlays} live {...props} sortBy="played" - query={query} />; + query={queryJson} />; } return rendered; @@ -387,7 +348,7 @@ const defaultFilter = (): QueryPlaysOptsJsonRefreshable => ({ sort: 'playedAt' }); -export const ListContainerFilterable = (props: { componentId: number, componentType: ComponentType } & Pick, 'render'>) => { +export const ListContainerFilterable = (props: { componentId: number, componentType: ComponentType }) => { const { componentType } = props; const [filters, setFilter] = useState(defaultFilter()); const primaryRefresh = ; diff --git a/src/client/components/playActivity/ListParts.tsx b/src/client/components/playActivity/ListParts.tsx index 7847b058..941fc7c9 100644 --- a/src/client/components/playActivity/ListParts.tsx +++ b/src/client/components/playActivity/ListParts.tsx @@ -1,10 +1,9 @@ -import { Box, Flex, IconButton, Separator, Text } from '@chakra-ui/react'; +import { Box, Flex, Separator, Text } from '@chakra-ui/react'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; import doy from 'dayjs/plugin/dayOfYear.js'; import type {ComponentProps} from "react"; -import { VscDebugRestart } from 'react-icons/vsc'; -import type {PlayApiCommon, PlayApiCommonDetailed, QueryPlaysOptsJson, SortPlaysByProps} from '../../../core/Api.js'; +import type {PlayApiCommonDetailed, QueryPlaysOptsJson, SortPlaysByProps} from '../../../core/Api.js'; import type {ComponentType} from '../../../core/Atomic.js'; import { getAllIndexes } from '../../../core/DataUtils.js'; import { sortByNewestDate } from '../../../core/PlayUtils.js'; @@ -14,6 +13,7 @@ dayjs.extend(doy); export interface GroupInfo { count: number date: Dayjs + uid: string } export interface GroupData { @@ -25,14 +25,17 @@ export interface ActivityLogProps extends SortPlaysByProps { data: PlayApiCommonDetailed[] componentId: number componentType: ComponentType - render?: 'virtNormal' | 'virtDynamic' | 'virtExp' | 'accordian' +// render?: 'virtNormal' | 'virtDynamic' | 'virtExp' | 'accordian' query: QueryPlaysOptsJson live?: boolean total?: number } +export interface GroupHeaderProps { + data: GroupInfo +} -export const GroupHeader = (props: { data: GroupInfo } & ComponentProps) => { +export const GroupHeader = (props: GroupHeaderProps & ComponentProps) => { const { data, ...rest @@ -64,7 +67,9 @@ export const GroupHeader = (props: { data: GroupInfo } & ComponentProps val.date !== undefined; -export const generateGroupPlays = (data: PlayApiCommon[]): GroupData[] => { +export const isGroupHeader = (val: any): val is ComponentProps => `data` in val && val.data.date !== undefined; + +export const generateGroupPlays = (data: PlayApiCommonDetailed[]): GroupData[] => { if(data.length === 0) { return []; @@ -88,7 +93,7 @@ export const generateGroupPlays = (data: PlayApiCommon[]): GroupData[] => { return groupsReduced.groups; } -export const generateFlatItems = (data: PlayApiCommonDetailed[]) => { +export const generateFlatItems = (data: PlayApiCommonDetailed[]): (PlayApiCommonDetailed | GroupInfo)[] => { // ensure there are no duplicates // this may happen if a play is "bumped" from one "page" to another, based on offset, // when new plays are inserted out of order (playedAt) diff --git a/src/client/components/playActivity/VirtualListDynamic.tsx b/src/client/components/playActivity/VirtualListDynamic.tsx index 7bce4022..e4dd5f76 100644 --- a/src/client/components/playActivity/VirtualListDynamic.tsx +++ b/src/client/components/playActivity/VirtualListDynamic.tsx @@ -4,7 +4,8 @@ import { useVirtualizer } from "@tanstack/react-virtual"; import React, { type ComponentProps, useCallback, useMemo } from "react"; import { LuCaptionsOff } from "react-icons/lu"; import { ActivityCollapsible } from '../ActivityDetail.js'; -import { type ActivityLogProps, generateFlatItems, GroupHeader, isGroupInfo } from './ListParts.js'; +import { type ActivityLogProps, generateFlatItems, GroupHeader, type GroupInfo, isGroupHeader, isGroupInfo } from './ListParts.js'; +import type { PlayApiCommonDetailed } from '../../../core/Api.js'; const itemContainerStyle: React.ComponentProps<"div"> = { style: { @@ -154,7 +155,7 @@ export const VirtualizedListDynamic = (props: ActivityLogProps & Pick ) : - : } + : } ) })} diff --git a/src/client/queries/index.ts b/src/client/queries/index.ts index 37cdc2e9..62d36182 100644 --- a/src/client/queries/index.ts +++ b/src/client/queries/index.ts @@ -5,7 +5,8 @@ import ky from 'ky'; import qs from 'qs'; import { baseUrl } from "../utils"; import type {ComponentsApiJson, PaginatedResponse, PlayApiCommonDetailed, PlayStateUI, QueryPlaysOptsJson} from "../../core/Api"; -import { INGRESS_QUEUE, isPlayState, type SourcePlayerJson } from "../../core/Atomic"; +import { type SourcePlayerJson } from "../../core/Atomic"; +import { queryPlayOptsRefreshableToJson } from "../utils/ComponentUtils"; export type QueryPlaysOptsJsonRefreshable = Omit & {nonce?: string, state?: PlayStateUI[]}; @@ -32,23 +33,7 @@ const activities = createQueryKeys('activities', { list: (componentId: number, filters: QueryPlaysOptsJsonRefreshable) => ({ queryKey: ['components', componentId, 'plays', filters], queryFn: (ctx) => { - const { - nonce, - state, - ...rest - } = filters; - const derived: QueryPlaysOptsJson = rest; - if(state !== undefined) { - derived.state = state.filter(x => isPlayState(x)); - - if(state.includes('failed TBR') && state.includes('queued')) { - derived.queues = [{queueName: INGRESS_QUEUE, queueStatus: 'queued'},{queueName: INGRESS_QUEUE, queueStatus: 'failed'}]; - derived.state = Array.from(new Set([...derived.state, 'failed', 'queued'])); - } else if(state.includes('failed TBR')) { - derived.queues = [{queueName: INGRESS_QUEUE, queueStatus: 'failed'}]; - derived.state = Array.from(new Set([...derived.state, 'failed'])); - } - } + const derived: QueryPlaysOptsJson = queryPlayOptsRefreshableToJson(filters) return ky.get(`components/${componentId}/plays`, { baseUrl: baseUrl, searchParams: qs.stringify({...derived, offset: ctx.pageParam}) diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index 7c4ced3b..1613ba56 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -7,5 +7,19 @@ "paths": { "@/*": ["./src/client/*"] } - } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "../core/**/*.ts" + ], + "exclude": [ + "../../node_modules", + "../../coverage", + + "../../_site", + "../../docsite", + "../../build", + "../stories" + ] } diff --git a/src/client/utils/ComponentUtils.tsx b/src/client/utils/ComponentUtils.tsx index b48776ac..46d8344d 100644 --- a/src/client/utils/ComponentUtils.tsx +++ b/src/client/utils/ComponentUtils.tsx @@ -1,8 +1,9 @@ import type { Card, IconProps, HTMLChakraProps} from '@chakra-ui/react'; import { Span } from '@chakra-ui/react'; -import type {PlayApiCommonDetailed} from '../../core/Api'; -import { type LifecycleStep, QUEUE_STATUS_COMPLETED, QUEUE_STATUS_FAILED } from '../../core/Atomic'; +import type {PlayApiCommonDetailed, QueryPlaysOptsJson} from '../../core/Api'; +import { INGRESS_QUEUE, isPlayState, type LifecycleStep, QUEUE_STATUS_COMPLETED, QUEUE_STATUS_FAILED } from '../../core/Atomic'; import { isErrorIsh, type ErrorIsh } from '../../core/ErrorUtils'; +import type { QueryPlaysOptsJsonRefreshable } from '../queries'; export const cardHeaderSeparator: Card.HeaderProps = { borderBottomWidth: "1px", @@ -121,4 +122,25 @@ export const findAnyAuthError = (e: ErrorIsh): [ErrorIsh, boolean] => { return [aCheckError, false]; } return [undefined, false]; +} + +export const queryPlayOptsRefreshableToJson = (query: QueryPlaysOptsJsonRefreshable): QueryPlaysOptsJson => { + const { + nonce, + state, + ...rest + } = query; + const derived: QueryPlaysOptsJson = rest; + if (state !== undefined) { + derived.state = state.filter(x => isPlayState(x)); + + if (state.includes('failed TBR') && state.includes('queued')) { + derived.queues = [{ queueName: INGRESS_QUEUE, queueStatus: 'queued' }, { queueName: INGRESS_QUEUE, queueStatus: 'failed' }]; + derived.state = Array.from(new Set([...derived.state, 'failed', 'queued'])); + } else if (state.includes('failed TBR')) { + derived.queues = [{ queueName: INGRESS_QUEUE, queueStatus: 'failed' }]; + derived.state = Array.from(new Set([...derived.state, 'failed'])); + } + } + return derived; } \ No newline at end of file diff --git a/src/stories/Configure.tsx b/src/stories/Configure.tsx deleted file mode 100644 index dca091c1..00000000 --- a/src/stories/Configure.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import { Meta } from "@storybook/addon-docs/blocks"; - -import Github from "./assets/github.svg"; -import Discord from "./assets/discord.svg"; -import Youtube from "./assets/youtube.svg"; -import Tutorials from "./assets/tutorials.svg"; -import Styling from "./assets/styling.png"; -import Context from "./assets/context.png"; -import Assets from "./assets/assets.png"; -import Docs from "./assets/docs.png"; -import Share from "./assets/share.png"; -import FigmaPlugin from "./assets/figma-plugin.png"; -import Testing from "./assets/testing.png"; -import Accessibility from "./assets/accessibility.png"; -import Theming from "./assets/theming.png"; -import AddonLibrary from "./assets/addon-library.png"; - -export const RightArrow = () => - - - - - -
-
- # Configure your project - - Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community. -
-
-
- A wall of logos representing different styling technologies -

Add styling and CSS

-

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

- Learn more -
-
- An abstraction representing the composition of data for a component -

Provide context and mocking

-

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

- Learn more -
-
- A representation of typography and image assets -
-

Load assets and resources

-

To link static files (like fonts) to your projects and stories, use the - `staticDirs` configuration option to specify folders to load when - starting Storybook.

- Learn more -
-
-
-
-
-
- # Do more with Storybook - - Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs. -
- -
-
-
- A screenshot showing the autodocs tag being set, pointing a docs page being generated -

Autodocs

-

Auto-generate living, - interactive reference documentation from your components and stories.

- Learn more -
-
- A browser window showing a Storybook being published to a chromatic.com URL -

Publish to Chromatic

-

Publish your Storybook to review and collaborate with your entire team.

- Learn more -
-
- Windows showing the Storybook plugin in Figma -

Figma Plugin

-

Embed your stories into Figma to cross-reference the design and live - implementation in one place.

- Learn more -
-
- Screenshot of tests passing and failing -

Testing

-

Use stories to test a component in all its variations, no matter how - complex.

- Learn more -
-
- Screenshot of accessibility tests passing and failing -

Accessibility

-

Automatically test your components for a11y issues as you develop.

- Learn more -
-
- Screenshot of Storybook in light and dark mode -

Theming

-

Theme Storybook's UI to personalize it to your project.

- Learn more -
-
-
-
-
-
-

Addons

-

Integrate your tools with Storybook to connect workflows.

- Discover all addons -
-
- Integrate your tools with Storybook to connect workflows. -
-
- -
-
- Github logo - Join our contributors building the future of UI development. - - Star on GitHub -
-
- Discord logo -
- Get support and chat with frontend developers. - - Join Discord server -
-
-
- Youtube logo -
- Watch tutorials, feature previews and interviews. - - Watch on YouTube -
-
-
- A book -

Follow guided walkthroughs on for key workflows.

- - Discover tutorials -
-
- -