diff --git a/src/WebClient/app/components/Graveyard.tsx b/src/WebClient/app/components/Graveyard.tsx index 2de4341..65d0081 100644 --- a/src/WebClient/app/components/Graveyard.tsx +++ b/src/WebClient/app/components/Graveyard.tsx @@ -1,17 +1,14 @@ import { useCallback, useEffect, useState, JSX } from 'react'; -import { - Button, - Card, - CardHeader, - Image, - Text, - makeStyles, - tokens, -} from '@fluentui/react-components'; -import { News16Regular } from '@fluentui/react-icons'; +import { makeStyles, tokens } from '@fluentui/react-components'; import corpsesDocument from '@microsoftgraveyard/data/corpses.json'; import GraveyardHeader from '@microsoftgraveyard/components/GraveyardHeader'; import GraveyardFooter from '@microsoftgraveyard/components/GraveyardFooter'; +import { + Corpse, + CorpseRecord, + CorpsesDocument, +} from '@microsoftgraveyard/types/corpse'; +import Headstone from '@microsoftgraveyard/components/Headstone'; const useStyles = makeStyles({ main: { @@ -52,94 +49,6 @@ const useStyles = makeStyles({ }, }); -interface Corpse { - name: string; - qualifier: string | null; - birthDate: Date | null; - deathDate: Date; - description: string; - link: string; -} - -interface CorpseRecord { - name: string; - qualifier: string | null; - birthDate: string | null; - deathDate: string; - description: string; - link: string; -} - -interface CorpsesDocument { - $schema: string; - corpses: CorpseRecord[]; -} - -const getAge = (start: Date, end: Date): { age: number; period: string } => { - let years = end.getFullYear() - start.getFullYear(); - if ( - end.getMonth() < start.getMonth() || - (end.getMonth() === start.getMonth() && end.getDate() < start.getDate()) - ) { - years--; - } - - if (years >= 1) { - return { age: years, period: years === 1 ? 'year' : 'years' }; - } - - const months = - end.getMonth() - - start.getMonth() + - 12 * (end.getFullYear() - start.getFullYear()); - if (months >= 1) { - return { age: months, period: months === 1 ? 'month' : 'months' }; - } - - const days = end.getDate() - start.getDate(); - return { age: days, period: days === 1 ? 'day' : 'days' }; -}; - -const getExpectedDeathDate = (corpse: Corpse): string => - corpse.deathDate.toLocaleDateString(undefined, { - month: 'long', - year: 'numeric', - }); - -const getFullName = (corpse: Corpse): string => - corpse.qualifier ? `${corpse.name} (${corpse.qualifier})` : corpse.name; - -const getLifeDates = (corpse: Corpse): string => - corpse.birthDate - ? `${corpse.birthDate.getFullYear()} - ${corpse.deathDate.getFullYear()}` - : `${corpse.deathDate.getFullYear()}`; - -const getObituary = (corpse: Corpse, today: Date): string => { - let obituary = ''; - - const dead = isDead(corpse, today); - if (dead) { - const { age, period } = getAge(corpse.deathDate, today); - const message = age === 0 ? 'today' : `${age} ${period} ago`; - obituary += `Killed by Microsoft ${message}, `; - } else { - const { age, period } = getAge(today, corpse.deathDate); - obituary += `To be killed by Microsoft in ${age} ${period}, `; - } - - obituary += `${corpse.name} ${dead ? 'was' : 'is'} ${corpse.description}.`; - - if (dead && corpse.birthDate) { - const { age, period } = getAge(corpse.birthDate, corpse.deathDate); - obituary += ` It was ${age} ${period} old.`; - } - - return obituary; -}; - -const isDead = (corpse: Corpse, today: Date): boolean => - corpse.deathDate <= today; - const Graveyard = () => { const [corpses, setCorpses] = useState([]); const today: Date = new Date(); @@ -168,48 +77,10 @@ const Graveyard = () => { ); }, []); - const renderGraves = (): JSX.Element[] => { + const renderHeadstones = (): JSX.Element[] => { return corpses.map((corpse, index) => (
  • - - - } - header={ - - {getFullName(corpse)} - - } - description={ - - {isDead(corpse, today) - ? getLifeDates(corpse) - : getExpectedDeathDate(corpse)} - - } - action={ -
  • )); }; @@ -222,7 +93,7 @@ const Graveyard = () => {
    -
      {renderGraves()}
    +
      {renderHeadstones()}
    diff --git a/src/WebClient/app/components/Headstone.tsx b/src/WebClient/app/components/Headstone.tsx new file mode 100644 index 0000000..49290e4 --- /dev/null +++ b/src/WebClient/app/components/Headstone.tsx @@ -0,0 +1,158 @@ +import { + Card, + CardHeader, + Button, + Image, + tokens, + makeStyles, + Skeleton, + SkeletonItem, + Body1, + Subtitle1, +} from '@fluentui/react-components'; +import { News16Regular } from '@fluentui/react-icons'; +import useCorpse from '@microsoftgraveyard/hooks/useCorpse'; +import { Corpse } from '@microsoftgraveyard/types/corpse'; +import { FC } from 'react'; + +const useStyles = makeStyles({ + container: { + width: '100%', + }, + skeletonImage: { + height: '72px', + width: '72px', + }, + skeletonAction: { + height: '16px', + width: '16px', + }, + skeletonFirstRow: { + alignItems: 'center', + display: 'grid', + paddingTop: '10px', + paddingBottom: '10px', + position: 'relative', + gap: '10px', + gridTemplateColumns: '20% 15% 30% 25% min-content', + }, + skeletonSecondRow: { + alignItems: 'center', + display: 'grid', + paddingBottom: '10px', + position: 'relative', + gap: '10px', + gridTemplateColumns: '15% 30% 20% min-content 20%', + }, + skeletonThirdRow: { + alignItems: 'center', + display: 'grid', + paddingBottom: '10px', + position: 'relative', + gap: '10px', + gridTemplateColumns: '10% 15% 20% 35% min-content', + }, + title: { + margin: `${tokens.spacingVerticalXS} ${tokens.spacingHorizontalNone}`, + }, + lifeDates: { + color: tokens.colorBrandForeground2, + lineHeight: tokens.lineHeightBase200, + margin: `${tokens.spacingVerticalXS} ${tokens.spacingHorizontalNone}`, + }, +}); + +interface HeadstoneProps { + corpse: Corpse; + today: Date; +} + +const Headstone: FC = ({ corpse, today }) => { + const styles = useStyles(); + const { name, lifeDates, obituary, isDead, loading } = useCorpse( + corpse, + today + ); + + return ( + + {loading ? ( + + + } + header={} + description={ + + } + action={ + + } + /> +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + ) : ( + <> + + } + header={ + + {name} + + } + description={ + + {lifeDates} + + } + action={ +