Skip to content

Commit

Permalink
render markdown in editor and cuesheet
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-Arc committed Aug 29, 2024
1 parent 628441a commit e467e6c
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 22 deletions.
88 changes: 88 additions & 0 deletions apps/client/src/common/components/input/markdown/MarkdownArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { useState } from 'react';
import Markdown from 'react-markdown';
import { Checkbox, Container } from '@chakra-ui/react';
import remarkGfm from 'remark-gfm';

interface MarkdownAreaProps {
value: string;
variant?: string;
size?: string;
submitHandler: (newValue: string) => void;
editHandler: () => void;
}

export default function MarkdownArea(props: MarkdownAreaProps) {
const { value, submitHandler, size, editHandler } = props;
const [text, setText] = useState(value);
// const styles = useStyleConfig('Textarea', { size, variant }); //TODO: this works with <Box> but can't get box to size properly around Markdown
const markDownChecker = (line: number) => {
const txtLines = text.split('\n');
const activeLine = txtLines[line - 1];
const isChecked = activeLine.indexOf('- [x] ') === 0;
const isNotChecked = activeLine.indexOf('- [ ] ') === 0;

if (isNotChecked) {
txtLines[line - 1] = activeLine.replace('- [ ] ', '- [x] ');
submitHandler(txtLines.join('\n'));
setText(txtLines.join('\n'));
}
if (isChecked) {
txtLines[line - 1] = activeLine.replace('- [x] ', '- [ ] ');
submitHandler(txtLines.join('\n'));
setText(txtLines.join('\n'));
}
};
// __css={styles}
return (
<Container
fontWeight='300'
maxW='full'
borderRadius='3px'
color='#f6f6f6'
background='#262626'
style={{ cursor: 'text', paddingTop: '7px', paddingBottom: '7px' }}
onClick={() => editHandler()}
>
<Markdown
remarkPlugins={[remarkGfm]}
components={{
li(props) {
// eslint-disable-next-line react/prop-types
const { children, className, node, ...rest } = props;
if (className === 'task-list-item') {
// eslint-disable-next-line react/prop-types
const position = node?.position ?? null;
const check = [...(children as Array<{ props: { checked: boolean } }>)].shift();
const text = [...(children as Array<string>)].pop();
return (
<li style={{ listStyleType: 'none' }}>
<Checkbox
variant='ontime-subtle-white'
size={size}
defaultChecked={check?.props.checked}
onClickCapture={(_e) => {
markDownChecker(position?.start.line ?? 0);
}}
>
{text}
</Checkbox>
</li>
);
}
return (
<li
{...rest}
className={className}
style={{ listStyleType: 'disc', marginLeft: '1.5em', padding: '0px' }}
>
{children}
</li>
);
},
}}
>
{`${text}\n`}
</Markdown>
</Container>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@ import { ChangeEvent, memo, useCallback, useEffect, useRef, useState } from 'rea
import { getHotkeyHandler } from '@mantine/hooks';

import { AutoTextArea } from '../../../common/components/input/auto-text-area/AutoTextArea';
import MarkdownArea from '../../../common/components/input/markdown/MarkdownArea';

interface EditableCellProps {
value: string;
isMarkdown?: boolean;
handleUpdate: (newValue: string) => void;
}

const EditableCell = (props: EditableCellProps) => {
const { value: initialValue, handleUpdate } = props;
const { value: initialValue, handleUpdate, isMarkdown } = props;

// We need to keep and update the state of the cell normally
const [value, setValue] = useState(initialValue);
const [editMarkdown, setEditMarkdown] = useState(false);
const ref = useRef<HTMLAreaElement>();
const onChange = useCallback((event: ChangeEvent<HTMLTextAreaElement>) => setValue(event.target.value), []);

// We'll only update the external data when the input is blurred
const onBlur = useCallback(() => handleUpdate(value), [handleUpdate, value]);
const onBlur = useCallback(() => {
handleUpdate(value);
setEditMarkdown(false);
}, [handleUpdate, value]);

//TODO: maybe we can unify this with `useReactiveTextInput`
const onKeyDown = getHotkeyHandler([
Expand All @@ -36,6 +42,18 @@ const EditableCell = (props: EditableCellProps) => {
setValue(initialValue);
}, [initialValue]);

if (isMarkdown && !editMarkdown) {
return (
<MarkdownArea
variant='ontime-filled'
size='sm'
value={value}
submitHandler={setValue}
editHandler={() => setEditMarkdown(true)}
/>
);
}

return (
<AutoTextArea
size='sm'
Expand All @@ -48,6 +66,7 @@ const EditableCell = (props: EditableCellProps) => {
transition='none'
spellCheck={false}
style={{ padding: 0 }}
autoFocus={editMarkdown}
/>
);
};
Expand Down
8 changes: 5 additions & 3 deletions apps/client/src/features/cuesheet/cuesheetCols.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark';
import { CellContext, ColumnDef } from '@tanstack/react-table';
import { CustomFields, isOntimeEvent, OntimeEvent, OntimeRundownEntry } from 'ontime-types';
import { CustomFields, CustomFieldType, isOntimeEvent, OntimeEvent, OntimeRundownEntry } from 'ontime-types';
import { millisToString } from 'ontime-utils';

import DelayIndicator from '../../common/components/delay-indicator/DelayIndicator';
Expand Down Expand Up @@ -48,18 +48,20 @@ function MakeCustomField({ row, column, table }: CellContext<OntimeRundownEntry,
return null;
}

const meta = column.columnDef.meta ?? {};
const type = 'type' in meta ? meta.type : CustomFieldType.String;
// events dont necessarily contain all custom fields
const initialValue = event.custom[column.id] ?? '';

return <EditableCell value={initialValue} handleUpdate={update} />;
return <EditableCell value={initialValue} handleUpdate={update} isMarkdown={type === CustomFieldType.Markdown} />;
}

export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<OntimeRundownEntry>[] {
const dynamicCustomFields = Object.keys(customFields).map((key) => ({
accessorKey: key,
id: key,
header: customFields[key].label,
meta: { colour: customFields[key].colour },
meta: { colour: customFields[key].colour, type: customFields[key].type },
cell: MakeCustomField,
size: 250,
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CSSProperties, useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Button } from '@chakra-ui/react';
import { CustomFieldLabel, isOntimeEvent, OntimeEvent } from 'ontime-types';
import { CustomFieldLabel, CustomFieldType, isOntimeEvent, OntimeEvent } from 'ontime-types';

import CopyTag from '../../../common/components/copy-tag/CopyTag';
import { useEventAction } from '../../../common/hooks/useEventAction';
Expand Down Expand Up @@ -120,6 +120,7 @@ export default function EventEditor() {
initialValue={initialValue}
submitHandler={handleSubmit}
className={style.decorated}
isMarkdown={customFields[fieldKey].type === CustomFieldType.Markdown}
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CSSProperties, useCallback, useRef } from 'react';
import { ChangeEvent, CSSProperties, useCallback, useRef, useState } from 'react';

import { AutoTextArea } from '../../../../common/components/input/auto-text-area/AutoTextArea';
import MarkdownArea from '../../../../common/components/input/markdown/MarkdownArea';
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
import { cx } from '../../../../common/utils/styleUtils';
import { EditorUpdateFields } from '../EventEditor';
Expand All @@ -13,37 +14,57 @@ interface CountedTextAreaProps {
label: string;
initialValue: string;
style?: CSSProperties;
isMarkdown?: boolean;
submitHandler: (field: EditorUpdateFields, value: string) => void;
}

export default function EventTextArea(props: CountedTextAreaProps) {
const { className, field, label, initialValue, style: givenStyles, submitHandler } = props;
const { className, field, label, initialValue, style: givenStyles, submitHandler, isMarkdown } = props;
const ref = useRef<HTMLInputElement | null>(null);
const [editMarkdown, setEditMarkdown] = useState(false);
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);

const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(initialValue, submitCallback, ref, {
submitOnCtrlEnter: true,
});

const onBlurIntercept = (event: ChangeEvent) => {
onBlur(event);
setEditMarkdown(false);
};

const classes = cx([style.inputLabel, className]);
const showMarkdown = isMarkdown && !editMarkdown;

return (
<div>
<label className={classes} htmlFor={field} style={givenStyles}>
{label}
</label>
<AutoTextArea
id={field}
inputref={ref}
rows={1}
size='sm'
resize='none'
variant='ontime-filled'
data-testid='input-textarea'
value={value}
onChange={onChange}
onBlur={onBlur}
onKeyDown={onKeyDown}
/>
{showMarkdown ? (
<MarkdownArea
variant='ontime-filled'
size='sm'
value={value}
submitHandler={submitCallback}
editHandler={() => setEditMarkdown(true)}
/>
) : (
<AutoTextArea
id={field}
inputref={ref}
rows={1}
size='sm'
resize='none'
variant='ontime-filled'
data-testid='input-textarea'
value={value}
onChange={onChange}
onBlur={onBlurIntercept}
onKeyDown={onKeyDown}
autoFocus={editMarkdown}
/>
)}
</div>
);
}
34 changes: 34 additions & 0 deletions apps/client/src/theme/ontimeCheckbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,37 @@ export const ontimeCheckboxOnDark = {
},
},
};

export const ontimeCheckboxWhite = {
control: {
border: '1px',
borderColor: '#b1b1b1', // $gray-400
backgroundColor: '#0000',
_disabled: {
color: 'white',
borderColor: '#2d2d2d', // $gray-1100
backgroundColor: '#2d2d2d', // $gray-1100
opacity: 0.6,
},
_checked: {
borderColor: '#3182ce', // $action-blue
backgroundColor: '#3182ce', //$action-blue
_disabled: {
color: 'white',
borderColor: '#3182ce', // $action-blue
backgroundColor: '#3182ce', //$action-blue
opacity: 0.6,
},
},
_focus: {
boxShadow: 'none',
},
},
label: {
// fontWeight: '200',
color: '#f6f6f6', // $ui-white: $gray-50;
_checked: {
color: '#f6f6f6', // $ui-white: $gray-50;
},
},
};
8 changes: 8 additions & 0 deletions apps/client/src/theme/ontimeTextInputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,11 @@ export const ontimeTextAreaTransparent = {
backgroundColor: 'rgba(255, 255, 255, 0.10)', // $white-10
},
};

export const containerStyles = {
fontWeight: '400',
backgroundColor: '#262626', // $gray-1200
color: '#e2e2e2', // $gray-200
border: '1px solid transparent',
borderRadius: '3px',
};
8 changes: 7 additions & 1 deletion apps/client/src/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ontimeButtonSubtle,
ontimeButtonSubtleWhite,
} from './ontimeButton';
import { ontimeCheckboxOnDark } from './ontimeCheckbox';
import { ontimeCheckboxOnDark, ontimeCheckboxWhite } from './ontimeCheckbox';
import { ontimeDrawer } from './ontimeDrawer';
import { ontimeEditable } from './ontimeEditable';
import { ontimeMenuOnDark } from './ontimeMenu';
Expand All @@ -19,6 +19,7 @@ import { ontimeSelect } from './ontimeSelect';
import { ontimeSwitch } from './ontimeSwitch';
import { ontimeTab } from './ontimeTab';
import {
containerStyles,
ontimeInputFilled,
ontimeInputGhosted,
ontimeTextAreaFilled,
Expand Down Expand Up @@ -58,6 +59,7 @@ const theme = extendTheme({
Checkbox: {
variants: {
'ontime-ondark': { ...ontimeCheckboxOnDark },
'ontime-subtle-white': { ...ontimeCheckboxWhite },
},
},
Drawer: {
Expand Down Expand Up @@ -124,6 +126,10 @@ const theme = extendTheme({
'ontime-transparent': { ...ontimeTextAreaTransparent },
},
},
Container: {
'ontime-filled': { ...containerStyles },
'ontime-transparent': { ...containerStyles },
},
Tooltip: {
baseStyle: { ...ontimeTooltip },
},
Expand Down

0 comments on commit e467e6c

Please sign in to comment.