import { addTag, } from '../../tags/addTag'; import { Article, } from '../Article'; import classNames from 'classnames'; import { createStoryStateAction, } from '../../actions/creators/createStoryStateAction'; import { getInkMutators, } from '../../mutators/getInkMutators'; import { InkContainerDispatchProps, } from './InkContainerDispatchProps'; import { InkContainerStateProps, } from './InkContainerStateProps'; import { InkContainerOwnProps, } from './InkContainerOwnProps'; import { InkContainerState, } from './InkContainerState'; import { InkLineObject, } from '../InkSection/InkLineObject'; import { InkSection, } from '../InkSection'; import { IState, } from '../../state/IState'; import { ISetStoryStateAware, } from '../../interfaces/ISetStoryStateAware'; import { mergeInkStateWithStoryState, } from './mergeInkStateWithStoryState'; import { connect, MapDispatchToProps, MapStateToProps, } from 'react-redux'; import { StoryWithDoneEvent, } from '../../../lib/ink/StoryWithDoneEvent'; import { assertValid, } from 'ts-assertions'; import * as React from 'react'; import styles from './index.less'; export class InkContainerUnconnected extends React.PureComponent< InkContainerOwnProps & InkContainerStateProps & InkContainerDispatchProps, InkContainerState > { public readonly state: InkContainerState = { choiceCounter: 0, sections: [], }; private ref: React.RefObject; private story: StoryWithDoneEvent; public readonly render = () => { const { className, inkModule, mutatorObjects, ref, } = this.props; const { choiceCounter, sections, } = this.state; const story = this.story; const mergedMutatorsObjects = [ ...getInkMutators(), ...(Array.isArray(mutatorObjects) ? mutatorObjects : []), ]; this.ref = ref || this.ref || React.createRef(); return (
{sections.map( ( { choices, lines, }, key, ) => ( {mergedMutatorsObjects.reduce(( currentNode, { content: mutator }, ) => ( mutator({ currentNode, inkModule, story, lines, }) ), lines.map(({ text }) => text).join('\n'))} ) )}
); }; public readonly componentDidMount = async () => { const { doneCallback, inkModule: { storyContent }, setStoryState, storyState, } = this.props; const story = new StoryWithDoneEvent(storyContent); if (typeof doneCallback === 'function') { story.__registerDoneCallback(this.defaultDoneCallback); story.__registerDoneCallback(doneCallback); } this.story = story; const mergedStoryState = { ...storyState, ...story.state.variablesState.jsonToken, }; Object.keys(mergedStoryState).forEach((key) => { story.variablesState.SetGlobal(key, storyState[key]); story.ObserveVariable(key, this.observeVariable); }); setStoryState(mergedStoryState); this.continueStory(); }; public readonly showAfter = ( delay: number, { classList }: HTMLElement, ) => setTimeout(() => classList.add('show'), delay); public readonly scrollToBottom = () => { const start = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; const dist = document.body.scrollHeight - window.innerHeight - start; if (dist < 0) { return; } const duration = 300 + 300 * dist / 100; let startTime: number | null = null; const step = (time: number) => { if (startTime === null) { startTime = time; } const t = (time - startTime) / duration; const lerp = 3 * t * t - 2 * t * t * t; window.scrollTo(0, start + lerp * dist); if (t < 1) { requestAnimationFrame(step); } }; requestAnimationFrame(step); }; public readonly continueStory = () => { const { sections } = this.state; const story = this.story; /* Generate story text - loop through available content. * Get ink to generate the next paragraph. */ const lines: InkLineObject[] = []; while (story.canContinue) { const inkResponse = story.Continue() || ''; lines.push({ tags: (story.currentTags || []).map((val) => ( addTag(val)[0] )), text: inkResponse, }); } const choices = story.currentChoices; this.setState({ sections: sections.concat([ { choices, lines, }, ]), }); this.scrollToBottom(); }; public readonly clickChoice = (index: number) => { this.setState({ choiceCounter: this.state.choiceCounter + 1 }); this.story.ChooseChoiceIndex(index); this.continueStory(); }; public readonly getRefElement = () => assertValid( this.ref.current, 'The ref element could not be found in InkContainer.getRefElement.', ); public readonly observeVariable = (variableName: string, newValue: any) => { this.props.setStoryState({ [variableName]: newValue }); if (typeof this.props.observerCallback === 'function') { this.props.observerCallback(variableName, newValue); } }; public readonly defaultDoneCallback = ( story: StoryWithDoneEvent, setStoryState: ISetStoryStateAware['setStoryState'], ) => void mergeInkStateWithStoryState(story, setStoryState); }; export const mapStateToProps: MapStateToProps< InkContainerStateProps, InkContainerOwnProps, IState > = ({ history: { present: { storyState }, }, }) => ({ storyState }); export const mapDispatchToProps: MapDispatchToProps< InkContainerDispatchProps, InkContainerOwnProps > = (dispatch) => ({ setStoryState: (updatedStoryState) => ( dispatch(createStoryStateAction(updatedStoryState)) ), }); export const InkContainer = connect( mapStateToProps, mapDispatchToProps, )(InkContainerUnconnected)