Skip to content

Commit c9a2ccd

Browse files
pietropemettely
andcommitted
Fix #141 updating speaker names (#208)
* fixes issue #133 * Working if isEditable is true can edit speaker label, if false, cannot * adjusted CSS accordingly * moved CustomEditor in separate file * speaker prompt keeps current speaker name * brought in changes from previous PRs master...philmcmahon:update-all-speakers#diff-cdf6a9957d22ff2efacaaf5fb2876a8aR58 it works but does not seem to rerender after change * names change but component doesn't re-render * changes don't propagate straight away but they do after you edit text * cleaned up still no auto re-rendering * works added shoudl component update and component did mount in wrapper block to update speaker label * changing spekaer triggers auto save callback * Removed space * Update packages/components/timed-text-editor/CustomEditor.js Co-Authored-By: Eimi Okuno <[email protected]> * Update packages/components/timed-text-editor/WrapperBlock.js Co-Authored-By: Eimi Okuno <[email protected]>
1 parent 2e03334 commit c9a2ccd

File tree

4 files changed

+184
-103
lines changed

4 files changed

+184
-103
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import { Editor } from "draft-js";
4+
5+
6+
import WrapperBlock from './WrapperBlock';
7+
8+
// NOTE: custom editor is in a separate class to minimise re-renders
9+
// if considering refactoring, removing the separate class, please double check
10+
// that doing so does not introduce uncessary re-renders first.
11+
class CustomEditor extends React.Component {
12+
handleWordClick = e => {
13+
this.props.onWordClick(e);
14+
};
15+
16+
renderBlockWithTimecodes = () => {
17+
return {
18+
component: WrapperBlock,
19+
editable: true,
20+
props: {
21+
showSpeakers: this.props.showSpeakers,
22+
showTimecodes: this.props.showTimecodes,
23+
timecodeOffset: this.props.timecodeOffset,
24+
editorState: this.props.editorState,
25+
setEditorNewContentStateSpeakersUpdate: this.props.setEditorNewContentStateSpeakersUpdate,
26+
onWordClick: this.handleWordClick,
27+
handleAnalyticsEvents: this.props.handleAnalyticsEvents,
28+
isEditable: this.props.isEditable
29+
}
30+
};
31+
};
32+
33+
shouldComponentUpdate(nextProps) {
34+
// https://stackoverflow.com/questions/39182657/best-performance-method-to-check-if-contentstate-changed-in-draftjs-or-just-edi
35+
if (nextProps.editorState !== this.props.editorState) {
36+
return true;
37+
}
38+
39+
if (nextProps.isEditable !== this.props.isEditable) {
40+
return true;
41+
}
42+
43+
return false;
44+
}
45+
46+
handleOnChange = e => {
47+
this.props.onChange(e);
48+
};
49+
50+
render() {
51+
return (
52+
<Editor
53+
editorState={this.props.editorState}
54+
onChange={this.handleOnChange}
55+
stripPastedStyles
56+
blockRendererFn={this.renderBlockWithTimecodes}
57+
handleKeyCommand={this.props.handleKeyCommand}
58+
keyBindingFn={this.props.customKeyBindingFn}
59+
spellCheck={this.props.spellCheck}
60+
/>
61+
);
62+
}
63+
}
64+
65+
export default CustomEditor;

packages/components/timed-text-editor/WrapperBlock.js

Lines changed: 78 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import React from 'react';
2-
import { EditorBlock, Modifier, EditorState, SelectionState } from 'draft-js';
2+
import {
3+
EditorBlock,
4+
Modifier,
5+
EditorState,
6+
SelectionState,
7+
convertFromRaw,
8+
convertToRaw
9+
} from 'draft-js';
310

411
import SpeakerLabel from './SpeakerLabel';
512
// import { shortTimecode, secondsToTimecode } from '../../Util/timecode-converter/';
@@ -11,6 +18,19 @@ import {
1118

1219
import style from './WrapperBlock.module.css';
1320

21+
const updateSpeakerName = (oldName, newName, state) => {
22+
const contentToUpdate = convertToRaw(state);
23+
24+
contentToUpdate.blocks.forEach(block => {
25+
if (block.data.speaker === oldName) {
26+
block.data.speaker = newName;
27+
}
28+
})
29+
30+
return convertFromRaw(contentToUpdate);
31+
}
32+
33+
1434
class WrapperBlock extends React.Component {
1535
constructor(props) {
1636
super(props);
@@ -25,7 +45,7 @@ class WrapperBlock extends React.Component {
2545
componentDidMount() {
2646
const { block } = this.props;
2747
const speaker = block.getData().get('speaker');
28-
48+
2949
const start = block.getData().get('start');
3050

3151
this.setState({
@@ -59,14 +79,33 @@ class WrapperBlock extends React.Component {
5979
return true;
6080
}
6181

82+
if(nextProps.block.getData().get('speaker') !== this.state.speaker){
83+
console.log('shouldComponentUpdate wrapper speaker', nextProps.block.getData().get('speaker') , this.state.speaker )
84+
return true;
85+
}
6286
return false;
6387
};
6488

65-
handleOnClickEdit = () => {
66-
const newSpeakerName = prompt('New Speaker Name?');
89+
componentDidUpdate = (prevProps, prevState) =>{
90+
91+
if(prevProps.block.getData().get('speaker') !== prevState.speaker){
92+
console.log('componentDidUpdate wrapper speaker', prevProps.block.getData().get('speaker') , prevState.speaker );
93+
94+
this.setState({
95+
speaker: prevProps.block.getData().get('speaker')
96+
})
97+
98+
return true;
99+
}
100+
}
67101

102+
handleOnClickEdit = () => {
103+
const oldSpeakerName = this.state.speaker;
104+
const newSpeakerName = prompt('New Speaker Name?', this.state.speaker);
68105
if (newSpeakerName !== '' && newSpeakerName !== null) {
69106
this.setState({ speaker: newSpeakerName });
107+
const isUpdateAllSpeakerInstances = confirm(`Would you like to replace all occurrences of ${oldSpeakerName} with ${newSpeakerName}?`);
108+
70109
if (this.props.blockProps.handleAnalyticsEvents) {
71110
this.props.blockProps.handleAnalyticsEvents({
72111
category: 'WrapperBlock',
@@ -76,34 +115,41 @@ class WrapperBlock extends React.Component {
76115
});
77116
}
78117

79-
// From docs: https://draftjs.org/docs/api-reference-selection-state#keys-and-offsets
80-
// selection points are tracked as key/offset pairs,
81-
// where the key value is the key of the ContentBlock where the point is positioned
82-
// and the offset value is the character offset within the block.
83-
84-
// Get key of the currentBlock
85-
const keyForCurrentCurrentBlock = this.props.block.getKey();
86-
// create empty selection for current block
87-
// https://draftjs.org/docs/api-reference-selection-state#createempty
88-
const currentBlockSelection = SelectionState.createEmpty(
89-
keyForCurrentCurrentBlock
90-
);
91-
const editorStateWithSelectedCurrentBlock = EditorState.acceptSelection(
92-
this.props.blockProps.editorState,
93-
currentBlockSelection
94-
);
95-
96-
const currentBlockSelectionState = editorStateWithSelectedCurrentBlock.getSelection();
97-
const newBlockDataWithSpeakerName = { speaker: newSpeakerName };
98-
99-
// https://draftjs.org/docs/api-reference-modifier#mergeblockdata
100-
const newContentState = Modifier.mergeBlockData(
101-
this.props.contentState,
102-
currentBlockSelectionState,
103-
newBlockDataWithSpeakerName
104-
);
105-
106-
this.props.blockProps.setEditorNewContentState(newContentState);
118+
if(isUpdateAllSpeakerInstances){
119+
const ContentState = this.props.blockProps.editorState.getCurrentContent();
120+
const contentToUpdateWithSpekaers = updateSpeakerName(oldSpeakerName, newSpeakerName, ContentState);
121+
this.props.blockProps.setEditorNewContentStateSpeakersUpdate(contentToUpdateWithSpekaers);
122+
}
123+
else{
124+
// From docs: https://draftjs.org/docs/api-reference-selection-state#keys-and-offsets
125+
// selection points are tracked as key/offset pairs,
126+
// where the key value is the key of the ContentBlock where the point is positioned
127+
// and the offset value is the character offset within the block.
128+
129+
// Get key of the currentBlock
130+
const keyForCurrentCurrentBlock = this.props.block.getKey();
131+
// create empty selection for current block
132+
// https://draftjs.org/docs/api-reference-selection-state#createempty
133+
const currentBlockSelection = SelectionState.createEmpty(
134+
keyForCurrentCurrentBlock
135+
);
136+
const editorStateWithSelectedCurrentBlock = EditorState.acceptSelection(
137+
this.props.blockProps.editorState,
138+
currentBlockSelection
139+
);
140+
141+
const currentBlockSelectionState = editorStateWithSelectedCurrentBlock.getSelection();
142+
const newBlockDataWithSpeakerName = { speaker: newSpeakerName };
143+
144+
// https://draftjs.org/docs/api-reference-modifier#mergeblockdata
145+
const newContentState = Modifier.mergeBlockData(
146+
this.props.contentState,
147+
currentBlockSelectionState,
148+
newBlockDataWithSpeakerName
149+
);
150+
151+
this.props.blockProps.setEditorNewContentStateSpeakersUpdate(newContentState);
152+
}
107153
}
108154
};
109155

packages/components/timed-text-editor/index.js

Lines changed: 38 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React from "react";
22
import PropTypes from "prop-types";
33

44
import {
5-
Editor,
65
EditorState,
76
CompositeDecorator,
87
convertFromRaw,
@@ -11,8 +10,10 @@ import {
1110
Modifier
1211
} from "draft-js";
1312

13+
14+
import CustomEditor from './CustomEditor.js';
1415
import Word from './Word';
15-
import WrapperBlock from './WrapperBlock';
16+
1617
import sttJsonAdapter from '../../stt-adapters';
1718
import exportAdapter from '../../export-adapters';
1819
import updateTimestamps from './UpdateTimestamps/index.js';
@@ -230,20 +231,18 @@ class TimedTextEditor extends React.Component {
230231
});
231232
}
232233

233-
this.setState({ editorState });
234+
this.setState({ editorState }, ()=>{
235+
this.forceRenderDecorator();
236+
});
234237
};
235238

236239
// Helper function to re-render this component
237240
// used to re-render WrapperBlock on timecode offset change
238241
// or when show / hide preferences for speaker labels and timecodes change
239242
forceRenderDecorator = () => {
240-
// const { editorState, updateEditorState } = this.props;
241243
const contentState = this.state.editorState.getCurrentContent();
242244
const decorator = this.state.editorState.getDecorator();
243-
244245
const newState = EditorState.createWithContent(contentState, decorator);
245-
246-
// this.setEditorNewContentState(newState);
247246
const newEditorState = EditorState.push(newState, contentState);
248247
this.setState({ editorState: newEditorState });
249248
};
@@ -252,13 +251,42 @@ class TimedTextEditor extends React.Component {
252251
* Update Editor content state
253252
*/
254253
setEditorNewContentState = newContentState => {
254+
const decorator = this.state.editorState.getDecorator();
255+
const newState = EditorState.createWithContent(newContentState, decorator);
255256
const newEditorState = EditorState.push(
256-
this.state.editorState,
257+
newState,
257258
newContentState
258259
);
259260
this.setState({ editorState: newEditorState });
260261
};
261262

263+
setEditorNewContentStateSpeakersUpdate = newContentState => {
264+
const decorator = this.state.editorState.getDecorator();
265+
const newState = EditorState.createWithContent(newContentState, decorator);
266+
const editorState = EditorState.push(
267+
newState,
268+
newContentState
269+
);
270+
271+
this.setState(
272+
() => ({
273+
editorState
274+
}),
275+
() => {
276+
const format = this.props.autoSaveContentType;
277+
const title = this.props.title;
278+
279+
const data = exportAdapter(
280+
convertToRaw(editorState.getCurrentContent()),
281+
format,
282+
title
283+
);
284+
285+
this.props.handleAutoSaveChanges(data);
286+
}
287+
);
288+
};
289+
262290
/**
263291
* Listen for draftJs custom key bindings
264292
*/
@@ -522,14 +550,13 @@ class TimedTextEditor extends React.Component {
522550
editorState={this.state.editorState}
523551
onChange={this.onChange}
524552
stripPastedStyles
525-
// renderBlockWithTimecodes={ this.renderBlockWithTimecodes }
526553
handleKeyCommand={this.handleKeyCommand}
527554
customKeyBindingFn={this.customKeyBindingFn}
528555
spellCheck={this.props.spellCheck}
529556
showSpeakers={this.props.showSpeakers}
530557
showTimecodes={this.props.showTimecodes}
531558
timecodeOffset={this.props.timecodeOffset}
532-
setEditorNewContentState={this.setEditorNewContentState}
559+
setEditorNewContentStateSpeakersUpdate={this.setEditorNewContentStateSpeakersUpdate}
533560
onWordClick={this.onWordClick}
534561
handleAnalyticsEvents={this.props.handleAnalyticsEvents}
535562
isEditable={this.props.isEditable}
@@ -588,62 +615,4 @@ TimedTextEditor.propTypes = {
588615
fileName: PropTypes.string
589616
};
590617

591-
export default TimedTextEditor;
592-
593-
// TODO: move CustomEditor in separate file
594-
// NOTE: custom editor is in a separate class to minimise re-renders
595-
// if considering refactoring, removing the separate class, please double check
596-
// that doing so does not introduce uncessary re-renders first.
597-
class CustomEditor extends React.Component {
598-
handleWordClick = e => {
599-
this.props.onWordClick(e);
600-
};
601-
602-
renderBlockWithTimecodes = () => {
603-
return {
604-
component: WrapperBlock,
605-
editable: true,
606-
props: {
607-
showSpeakers: this.props.showSpeakers,
608-
showTimecodes: this.props.showTimecodes,
609-
timecodeOffset: this.props.timecodeOffset,
610-
editorState: this.props.editorState,
611-
setEditorNewContentState: this.props.setEditorNewContentState,
612-
onWordClick: this.handleWordClick,
613-
handleAnalyticsEvents: this.props.handleAnalyticsEvents,
614-
isEditable: this.props.isEditable
615-
}
616-
};
617-
};
618-
619-
shouldComponentUpdate(nextProps) {
620-
// https://stackoverflow.com/questions/39182657/best-performance-method-to-check-if-contentstate-changed-in-draftjs-or-just-edi
621-
if (nextProps.editorState !== this.props.editorState) {
622-
return true;
623-
}
624-
625-
if (nextProps.isEditable !== this.props.isEditable) {
626-
return true;
627-
}
628-
629-
return false;
630-
}
631-
632-
handleOnChange = e => {
633-
this.props.onChange(e);
634-
};
635-
636-
render() {
637-
return (
638-
<Editor
639-
editorState={this.props.editorState}
640-
onChange={this.handleOnChange}
641-
stripPastedStyles
642-
blockRendererFn={this.renderBlockWithTimecodes}
643-
handleKeyCommand={this.props.handleKeyCommand}
644-
keyBindingFn={this.props.customKeyBindingFn}
645-
spellCheck={this.props.spellCheck}
646-
/>
647-
);
648-
}
649-
}
618+
export default TimedTextEditor;

packages/components/transcript-editor/src/ExportOptions.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import style from './index.module.css';
88
class ExportOptions extends React.Component {
99

1010
render() {
11-
const btns = this.props.exportOptionsList.map((opt) => {
12-
return (<><button
11+
const btns = this.props.exportOptionsList.map((opt, index) => {
12+
return (<><button
13+
key={opt.label+index}
1314
title={ opt.label }
1415
className={ style.playerButton }
1516
key={ opt.value }

0 commit comments

Comments
 (0)