Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1346] Auto-save on blur/focus lost #1492

Merged
merged 2 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import FormatBoldIcon from '@material-ui/icons/FormatBold';
import FormatItalicIcon from '@material-ui/icons/FormatItalic';
import FormatListBulletedIcon from '@material-ui/icons/FormatListBulleted';
import FormatListNumberedIcon from '@material-ui/icons/FormatListNumbered';
import FormatUnderlinedIcon from '@material-ui/icons/FormatUnderlined';
import SaveIcon from '@material-ui/icons/Save';
import StrikethroughSIcon from '@material-ui/icons/StrikethroughS';
import SubjectIcon from '@material-ui/icons/Subject';
import TitleIcon from '@material-ui/icons/Title';
Expand All @@ -47,15 +45,9 @@ const useStyles = makeStyles((theme) => ({
paper: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
borderBottom: `1px solid ${theme.palette.divider}`,
flexWrap: 'wrap',
},
formattingActions: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
},
divider: {
margin: theme.spacing(1, 0.5),
},
Expand All @@ -82,9 +74,7 @@ const StyledToggleButtonGroup = withStyles((theme) => ({

export const RichTextWidget = ({ widget, selection }: RichTextWidgetProps) => {
const classes = useStyles();

const [selected, setSelected] = useState<boolean>(false);

const ref = useRef<HTMLInputElement | null>(null);

useEffect(() => {
Expand All @@ -103,67 +93,57 @@ export const RichTextWidget = ({ widget, selection }: RichTextWidgetProps) => {
</Typography>
<div onFocus={() => setSelected(true)} onBlur={() => setSelected(false)} ref={ref} tabIndex={0}>
<Paper elevation={0} className={classes.paper}>
<div className={classes.formattingActions}>
<StyledToggleButtonGroup size="small">
<ToggleButton
classes={{ root: classes.button }}
selected
disabled={false}
value={'paragraph'}
key={'paragraph'}>
<SubjectIcon fontSize="small" />
</ToggleButton>
<ToggleButton
classes={{ root: classes.button }}
selected={false}
disabled={false}
value={'header1'}
key={'header1'}>
<TitleIcon fontSize="small" />
</ToggleButton>
<ToggleButton
classes={{ root: classes.button }}
selected={false}
disabled={false}
value={'bullet-list'}
key={'bullet-list'}>
<FormatListBulletedIcon fontSize="small" />
</ToggleButton>
<ToggleButton
classes={{ root: classes.button }}
selected={false}
disabled={false}
value={'number-list'}
key={'number-list'}>
<FormatListNumberedIcon fontSize="small" />
</ToggleButton>
</StyledToggleButtonGroup>
<Divider flexItem orientation="vertical" className={classes.divider} />
<StyledToggleButtonGroup size="small">
<ToggleButton classes={{ root: classes.button }} disabled={false} value={'bold'} key={'bold'}>
<FormatBoldIcon fontSize="small" />
</ToggleButton>
<ToggleButton classes={{ root: classes.button }} value={'italic'} key={'italic'}>
<FormatItalicIcon fontSize="small" />
</ToggleButton>
<ToggleButton classes={{ root: classes.button }} disabled={false} value={'underline'} key={'underline'}>
<FormatUnderlinedIcon fontSize="small" />
</ToggleButton>
<ToggleButton classes={{ root: classes.button }} disabled={false} value={'code'} key={'code'}>
<CodeIcon fontSize="small" />
</ToggleButton>
<ToggleButton
classes={{ root: classes.button }}
disabled={false}
value={'strikethrough'}
key={'strikethrough'}>
<StrikethroughSIcon fontSize="small" />
</ToggleButton>
</StyledToggleButtonGroup>
</div>
<StyledToggleButtonGroup size="small">
<ToggleButton classes={{ root: classes.button }} disabled={false} value={'save'} key={'save'}>
<SaveIcon fontSize="small" />
<ToggleButton
classes={{ root: classes.button }}
selected
disabled={false}
value={'paragraph'}
key={'paragraph'}>
<SubjectIcon fontSize="small" />
</ToggleButton>
<ToggleButton
classes={{ root: classes.button }}
selected={false}
disabled={false}
value={'header1'}
key={'header1'}>
<TitleIcon fontSize="small" />
</ToggleButton>
<ToggleButton
classes={{ root: classes.button }}
selected={false}
disabled={false}
value={'bullet-list'}
key={'bullet-list'}>
<FormatListBulletedIcon fontSize="small" />
</ToggleButton>
<ToggleButton
classes={{ root: classes.button }}
selected={false}
disabled={false}
value={'number-list'}
key={'number-list'}>
<FormatListNumberedIcon fontSize="small" />
</ToggleButton>
</StyledToggleButtonGroup>
<Divider flexItem orientation="vertical" className={classes.divider} />
<StyledToggleButtonGroup size="small">
<ToggleButton classes={{ root: classes.button }} disabled={false} value={'bold'} key={'bold'}>
<FormatBoldIcon fontSize="small" />
</ToggleButton>
<ToggleButton classes={{ root: classes.button }} value={'italic'} key={'italic'}>
<FormatItalicIcon fontSize="small" />
</ToggleButton>
<ToggleButton classes={{ root: classes.button }} disabled={false} value={'code'} key={'code'}>
<CodeIcon fontSize="small" />
</ToggleButton>
<ToggleButton
classes={{ root: classes.button }}
disabled={false}
value={'strikethrough'}
key={'strikethrough'}>
<StrikethroughSIcon fontSize="small" />
</ToggleButton>
</StyledToggleButtonGroup>
</Paper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ import {
RichTextPropertySectionProps,
} from './RichTextPropertySection.types';
import {
ChangeValueEvent,
InitializeEvent,
RichTextPropertySectionContext,
RichTextPropertySectionEvent,
RichTextPropertySectionMachine,
Expand Down Expand Up @@ -83,17 +81,7 @@ export const RichTextPropertySection = ({
RichTextPropertySectionEvent
>(RichTextPropertySectionMachine);
const { toast } = schemaValue as SchemaValue;
const { value, message } = context;

useEffect(() => {
const initializeEvent: InitializeEvent = { type: 'INITIALIZE', value: widget.stringValue };
dispatch(initializeEvent);
}, [dispatch, widget.stringValue]);

const onChange = (newText: string) => {
const changeValueEvent: ChangeValueEvent = { type: 'CHANGE_VALUE', value: newText };
dispatch(changeValueEvent);
};
const { message } = context;

const [editRichText, { loading: updateRichTextLoading, data: updateRichTextData, error: updateRichTextError }] =
useMutation<GQLEditRichTextMutationData, GQLEditRichTextMutationVariables>(editRichTextMutation);
Expand All @@ -111,33 +99,23 @@ export const RichTextPropertySection = ({

useEffect(() => {
if (!updateRichTextLoading) {
let hasError = false;
if (updateRichTextError) {
const showToastEvent: ShowToastEvent = {
type: 'SHOW_TOAST',
message: 'An unexpected error has occurred, please refresh the page',
};
dispatch(showToastEvent);

hasError = true;
}
if (updateRichTextData) {
const { editRichText } = updateRichTextData;
if (isErrorPayload(editRichText)) {
const { message } = editRichText;
const showToastEvent: ShowToastEvent = { type: 'SHOW_TOAST', message };
dispatch(showToastEvent);

hasError = true;
}
}

if (hasError) {
const initializeEvent: InitializeEvent = { type: 'INITIALIZE', value: widget.stringValue };
dispatch(initializeEvent);
}
}
}, [updateRichTextLoading, updateRichTextData, updateRichTextError, widget, dispatch]);
}, [updateRichTextLoading, updateRichTextData, updateRichTextError, dispatch]);

const [
updateWidgetFocus,
Expand Down Expand Up @@ -178,21 +156,20 @@ export const RichTextPropertySection = ({
}, [updateWidgetFocusLoading, updateWidgetFocusData, updateWidgetFocusError, dispatch]);

const onFocus = () => sendUpdateWidgetFocus(true);
const onSave = (newValue: string) => {
const onBlur = (currentText: string) => {
sendUpdateWidgetFocus(false);
sendEditedValue(newValue);
sendEditedValue(currentText);
};

return (
<div>
<PropertySectionLabel label={widget.label} subscribers={subscribers} />
<div data-testid={widget.label}>
<RichTextEditor
value={value}
value={widget.stringValue}
placeholder={widget.label}
onChange={onChange}
onFocus={onFocus}
onSave={onSave}
onBlur={onBlur}
readOnly={readOnly}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,21 @@ export interface RichTextPropertySectionStateSchema {
hidden: {};
};
};
richTextPropertySection: {
states: {
pristine: {};
edited: {};
};
};
};
}

export type SchemaValue = {
toast: 'visible' | 'hidden';
richTextPropertySection: 'pristine' | 'edited';
};

export interface RichTextPropertySectionContext {
value: string;
message: string | null;
}

export type ShowToastEvent = { type: 'SHOW_TOAST'; message: string };
export type HideToastEvent = { type: 'HIDE_TOAST' };
export type InitializeEvent = { type: 'INITIALIZE'; value: string };
export type ChangeValueEvent = { type: 'CHANGE_VALUE'; value: string };

export type RichTextPropertySectionEvent = InitializeEvent | ChangeValueEvent | ShowToastEvent | HideToastEvent;
export type RichTextPropertySectionEvent = ShowToastEvent | HideToastEvent;

export const RichTextPropertySectionMachine = Machine<
RichTextPropertySectionContext,
Expand All @@ -54,7 +44,6 @@ export const RichTextPropertySectionMachine = Machine<
{
type: 'parallel',
context: {
value: '',
message: null,
},
states: {
Expand All @@ -79,56 +68,10 @@ export const RichTextPropertySectionMachine = Machine<
},
},
},
richTextPropertySection: {
initial: 'pristine',
states: {
pristine: {
on: {
INITIALIZE: {
target: 'pristine',
actions: 'updateValue',
},
CHANGE_VALUE: {
target: 'edited',
actions: 'updateValue',
},
},
},
edited: {
on: {
INITIALIZE: {
target: 'pristine',
actions: 'initializeValue',
},
CHANGE_VALUE: {
target: 'edited',
actions: 'updateValue',
},
},
},
},
},
},
},
{
actions: {
initializeValue: assign((context, event) => {
const { value } = event as InitializeEvent;
const { value: previousValue } = context;

if (value !== previousValue) {
// Similar issue as in EEFLifecycleManager, some update is coming from the server
// while we have started to enter some content locally. We are choosing here to drop
// the content entered locally but we will still log it.
console.trace(`The following content "${previousValue}" has been overwritten by "${value}"`);
}

return { value };
}),
updateValue: assign((_, event) => {
const { value } = event as ChangeValueEvent;
return { value };
}),
setMessage: assign((_, event) => {
const { message } = event as ShowToastEvent;
return { message };
Expand Down
Loading