Skip to content

Fix #141 updating speaker names #208

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

Merged
merged 19 commits into from
Oct 10, 2019
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
65 changes: 65 additions & 0 deletions packages/components/timed-text-editor/CustomEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from "react";
import PropTypes from "prop-types";
import { Editor } from "draft-js";


import WrapperBlock from './WrapperBlock';

// NOTE: custom editor is in a separate class to minimise re-renders
// if considering refactoring, removing the separate class, please double check
// that doing so does not introduce uncessary re-renders first.
class CustomEditor extends React.Component {
handleWordClick = e => {
this.props.onWordClick(e);
};

renderBlockWithTimecodes = () => {
return {
component: WrapperBlock,
editable: true,
props: {
showSpeakers: this.props.showSpeakers,
showTimecodes: this.props.showTimecodes,
timecodeOffset: this.props.timecodeOffset,
editorState: this.props.editorState,
setEditorNewContentStateSpeakersUpdate: this.props.setEditorNewContentStateSpeakersUpdate,
onWordClick: this.handleWordClick,
handleAnalyticsEvents: this.props.handleAnalyticsEvents,
isEditable: this.props.isEditable
}
};
};

shouldComponentUpdate(nextProps) {
// https://stackoverflow.com/questions/39182657/best-performance-method-to-check-if-contentstate-changed-in-draftjs-or-just-edi
if (nextProps.editorState !== this.props.editorState) {
return true;
}

if (nextProps.isEditable !== this.props.isEditable) {
return true;
}

return false;
}

handleOnChange = e => {
this.props.onChange(e);
};

render() {
return (
<Editor
editorState={this.props.editorState}
onChange={this.handleOnChange}
stripPastedStyles
blockRendererFn={this.renderBlockWithTimecodes}
handleKeyCommand={this.props.handleKeyCommand}
keyBindingFn={this.props.customKeyBindingFn}
spellCheck={this.props.spellCheck}
/>
);
}
}

export default CustomEditor;
110 changes: 78 additions & 32 deletions packages/components/timed-text-editor/WrapperBlock.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React from 'react';
import { EditorBlock, Modifier, EditorState, SelectionState } from 'draft-js';
import {
EditorBlock,
Modifier,
EditorState,
SelectionState,
convertFromRaw,
convertToRaw
} from 'draft-js';

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

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

const updateSpeakerName = (oldName, newName, state) => {
const contentToUpdate = convertToRaw(state);

contentToUpdate.blocks.forEach(block => {
if (block.data.speaker === oldName) {
block.data.speaker = newName;
}
})

return convertFromRaw(contentToUpdate);
}


class WrapperBlock extends React.Component {
constructor(props) {
super(props);
Expand All @@ -25,7 +45,7 @@ class WrapperBlock extends React.Component {
componentDidMount() {
const { block } = this.props;
const speaker = block.getData().get('speaker');

const start = block.getData().get('start');

this.setState({
Expand Down Expand Up @@ -59,14 +79,33 @@ class WrapperBlock extends React.Component {
return true;
}

if(nextProps.block.getData().get('speaker') !== this.state.speaker){
console.log('shouldComponentUpdate wrapper speaker', nextProps.block.getData().get('speaker') , this.state.speaker )
return true;
}
return false;
};

handleOnClickEdit = () => {
const newSpeakerName = prompt('New Speaker Name?');
componentDidUpdate = (prevProps, prevState) =>{

if(prevProps.block.getData().get('speaker') !== prevState.speaker){
console.log('componentDidUpdate wrapper speaker', prevProps.block.getData().get('speaker') , prevState.speaker );

this.setState({
speaker: prevProps.block.getData().get('speaker')
})

return true;
}
}

handleOnClickEdit = () => {
const oldSpeakerName = this.state.speaker;
const newSpeakerName = prompt('New Speaker Name?', this.state.speaker);
if (newSpeakerName !== '' && newSpeakerName !== null) {
this.setState({ speaker: newSpeakerName });
const isUpdateAllSpeakerInstances = confirm(`Would you like to replace all occurrences of ${oldSpeakerName} with ${newSpeakerName}?`);

if (this.props.blockProps.handleAnalyticsEvents) {
this.props.blockProps.handleAnalyticsEvents({
category: 'WrapperBlock',
Expand All @@ -76,34 +115,41 @@ class WrapperBlock extends React.Component {
});
}

// From docs: https://draftjs.org/docs/api-reference-selection-state#keys-and-offsets
// selection points are tracked as key/offset pairs,
// where the key value is the key of the ContentBlock where the point is positioned
// and the offset value is the character offset within the block.

// Get key of the currentBlock
const keyForCurrentCurrentBlock = this.props.block.getKey();
// create empty selection for current block
// https://draftjs.org/docs/api-reference-selection-state#createempty
const currentBlockSelection = SelectionState.createEmpty(
keyForCurrentCurrentBlock
);
const editorStateWithSelectedCurrentBlock = EditorState.acceptSelection(
this.props.blockProps.editorState,
currentBlockSelection
);

const currentBlockSelectionState = editorStateWithSelectedCurrentBlock.getSelection();
const newBlockDataWithSpeakerName = { speaker: newSpeakerName };

// https://draftjs.org/docs/api-reference-modifier#mergeblockdata
const newContentState = Modifier.mergeBlockData(
this.props.contentState,
currentBlockSelectionState,
newBlockDataWithSpeakerName
);

this.props.blockProps.setEditorNewContentState(newContentState);
if(isUpdateAllSpeakerInstances){
const ContentState = this.props.blockProps.editorState.getCurrentContent();
const contentToUpdateWithSpekaers = updateSpeakerName(oldSpeakerName, newSpeakerName, ContentState);
this.props.blockProps.setEditorNewContentStateSpeakersUpdate(contentToUpdateWithSpekaers);
}
else{
// From docs: https://draftjs.org/docs/api-reference-selection-state#keys-and-offsets
// selection points are tracked as key/offset pairs,
// where the key value is the key of the ContentBlock where the point is positioned
// and the offset value is the character offset within the block.

// Get key of the currentBlock
const keyForCurrentCurrentBlock = this.props.block.getKey();
// create empty selection for current block
// https://draftjs.org/docs/api-reference-selection-state#createempty
const currentBlockSelection = SelectionState.createEmpty(
keyForCurrentCurrentBlock
);
const editorStateWithSelectedCurrentBlock = EditorState.acceptSelection(
this.props.blockProps.editorState,
currentBlockSelection
);

const currentBlockSelectionState = editorStateWithSelectedCurrentBlock.getSelection();
const newBlockDataWithSpeakerName = { speaker: newSpeakerName };

// https://draftjs.org/docs/api-reference-modifier#mergeblockdata
const newContentState = Modifier.mergeBlockData(
this.props.contentState,
currentBlockSelectionState,
newBlockDataWithSpeakerName
);

this.props.blockProps.setEditorNewContentStateSpeakersUpdate(newContentState);
}
}
};

Expand Down
107 changes: 38 additions & 69 deletions packages/components/timed-text-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from "react";
import PropTypes from "prop-types";

import {
Editor,
EditorState,
CompositeDecorator,
convertFromRaw,
Expand All @@ -11,8 +10,10 @@ import {
Modifier
} from "draft-js";


import CustomEditor from './CustomEditor.js';
import Word from './Word';
import WrapperBlock from './WrapperBlock';

import sttJsonAdapter from '../../stt-adapters';
import exportAdapter from '../../export-adapters';
import updateTimestamps from './UpdateTimestamps/index.js';
Expand Down Expand Up @@ -230,20 +231,18 @@ class TimedTextEditor extends React.Component {
});
}

this.setState({ editorState });
this.setState({ editorState }, ()=>{
this.forceRenderDecorator();
});
};

// Helper function to re-render this component
// used to re-render WrapperBlock on timecode offset change
// or when show / hide preferences for speaker labels and timecodes change
forceRenderDecorator = () => {
// const { editorState, updateEditorState } = this.props;
const contentState = this.state.editorState.getCurrentContent();
const decorator = this.state.editorState.getDecorator();

const newState = EditorState.createWithContent(contentState, decorator);

// this.setEditorNewContentState(newState);
const newEditorState = EditorState.push(newState, contentState);
this.setState({ editorState: newEditorState });
};
Expand All @@ -252,13 +251,42 @@ class TimedTextEditor extends React.Component {
* Update Editor content state
*/
setEditorNewContentState = newContentState => {
const decorator = this.state.editorState.getDecorator();
const newState = EditorState.createWithContent(newContentState, decorator);
const newEditorState = EditorState.push(
this.state.editorState,
newState,
newContentState
);
this.setState({ editorState: newEditorState });
};

setEditorNewContentStateSpeakersUpdate = newContentState => {
const decorator = this.state.editorState.getDecorator();
const newState = EditorState.createWithContent(newContentState, decorator);
const editorState = EditorState.push(
newState,
newContentState
);

this.setState(
() => ({
editorState
}),
() => {
const format = this.props.autoSaveContentType;
const title = this.props.title;

const data = exportAdapter(
convertToRaw(editorState.getCurrentContent()),
format,
title
);

this.props.handleAutoSaveChanges(data);
}
);
};

/**
* Listen for draftJs custom key bindings
*/
Expand Down Expand Up @@ -522,14 +550,13 @@ class TimedTextEditor extends React.Component {
editorState={this.state.editorState}
onChange={this.onChange}
stripPastedStyles
// renderBlockWithTimecodes={ this.renderBlockWithTimecodes }
handleKeyCommand={this.handleKeyCommand}
customKeyBindingFn={this.customKeyBindingFn}
spellCheck={this.props.spellCheck}
showSpeakers={this.props.showSpeakers}
showTimecodes={this.props.showTimecodes}
timecodeOffset={this.props.timecodeOffset}
setEditorNewContentState={this.setEditorNewContentState}
setEditorNewContentStateSpeakersUpdate={this.setEditorNewContentStateSpeakersUpdate}
onWordClick={this.onWordClick}
handleAnalyticsEvents={this.props.handleAnalyticsEvents}
isEditable={this.props.isEditable}
Expand Down Expand Up @@ -588,62 +615,4 @@ TimedTextEditor.propTypes = {
fileName: PropTypes.string
};

export default TimedTextEditor;

// TODO: move CustomEditor in separate file
// NOTE: custom editor is in a separate class to minimise re-renders
// if considering refactoring, removing the separate class, please double check
// that doing so does not introduce uncessary re-renders first.
class CustomEditor extends React.Component {
handleWordClick = e => {
this.props.onWordClick(e);
};

renderBlockWithTimecodes = () => {
return {
component: WrapperBlock,
editable: true,
props: {
showSpeakers: this.props.showSpeakers,
showTimecodes: this.props.showTimecodes,
timecodeOffset: this.props.timecodeOffset,
editorState: this.props.editorState,
setEditorNewContentState: this.props.setEditorNewContentState,
onWordClick: this.handleWordClick,
handleAnalyticsEvents: this.props.handleAnalyticsEvents,
isEditable: this.props.isEditable
}
};
};

shouldComponentUpdate(nextProps) {
// https://stackoverflow.com/questions/39182657/best-performance-method-to-check-if-contentstate-changed-in-draftjs-or-just-edi
if (nextProps.editorState !== this.props.editorState) {
return true;
}

if (nextProps.isEditable !== this.props.isEditable) {
return true;
}

return false;
}

handleOnChange = e => {
this.props.onChange(e);
};

render() {
return (
<Editor
editorState={this.props.editorState}
onChange={this.handleOnChange}
stripPastedStyles
blockRendererFn={this.renderBlockWithTimecodes}
handleKeyCommand={this.props.handleKeyCommand}
keyBindingFn={this.props.customKeyBindingFn}
spellCheck={this.props.spellCheck}
/>
);
}
}
export default TimedTextEditor;
5 changes: 3 additions & 2 deletions packages/components/transcript-editor/src/ExportOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import style from './index.module.css';
class ExportOptions extends React.Component {

render() {
const btns = this.props.exportOptionsList.map((opt) => {
return (<><button
const btns = this.props.exportOptionsList.map((opt, index) => {
return (<><button
key={opt.label+index}
title={ opt.label }
className={ style.playerButton }
key={ opt.value }
Expand Down