Skip to content

Commit

Permalink
[1346] Auto-save on blur/focus lost
Browse files Browse the repository at this point in the history
Bug: #1346
Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
  • Loading branch information
pcdavid committed Dec 1, 2022
1 parent d8be075 commit 802d7af
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 390 deletions.
Expand Up @@ -20,7 +20,6 @@ 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 +46,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 +75,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 +94,60 @@ 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={'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>
</Paper>
Expand Down
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,15 +99,12 @@ 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;
Expand All @@ -131,13 +116,8 @@ export const RichTextPropertySection = ({
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 +158,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
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

0 comments on commit 802d7af

Please sign in to comment.