import classNames from "classnames";
import { runInAction } from "mobx";
import { observer } from "mobx-react";
import { Component, RefObject, createRef, type ReactNode } from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { useSwipeable, type SwipeableProps } from "react-swipeable";
import { DefaultTheme, withTheme } from "styled-components";
import {
Category,
StoryAction
} from "../../../Core/AnalyticEvents/analyticEvents";
import { animateEnd } from "../../../Core/animation";
import getPath from "../../../Core/getPath";
import TerriaError from "../../../Core/TerriaError";
import Terria from "../../../Models/Terria";
import Box from "../../../Styled/Box";
import { WithViewState, withViewState } from "../../Context";
import { onStoryButtonClick } from "../../Map/MenuBar/StoryButton/StoryButton";
import { Story } from "../Story";
import Styles from "../story-panel.scss";
import StoryBody from "./StoryBody";
import FooterBar from "./StoryFooterBar";
import TitleBar from "./TitleBar";
import DragWrapper from "../../Drag/DragWrapper";
/**
*
* @param scene The story scene to activate
* @param terria The Terria instance
*/
export async function activateStory(scene: Story, terria: Terria) {
terria.analytics?.logEvent(
Category.story,
StoryAction.viewScene,
JSON.stringify(scene)
);
if (scene.shareData) {
const errors: TerriaError[] = [];
await Promise.all(
scene.shareData.initSources.map(async (initSource: any) => {
try {
await terria.applyInitData({
initData: initSource,
replaceStratum: true,
canUnsetFeaturePickingState: true
});
} catch (e) {
errors.push(TerriaError.from(e));
}
})
);
if (errors.length > 0) {
terria.raiseErrorToUser(
TerriaError.combine(errors, {
title: { key: "story.loadSceneErrorTitle" },
message: {
key: "story.loadSceneErrorMessage",
parameters: { title: scene.title ?? scene.id }
}
})
);
}
}
terria.workbench.items.forEach((item) => {
terria.analytics?.logEvent(
Category.story,
StoryAction.datasetView,
getPath(item)
);
});
}
interface Props extends WithTranslation, WithViewState {
theme: DefaultTheme;
}
interface State {
inView: boolean;
isCollapsed: boolean;
}
const Swipeable = ({
children,
...props
}: { children: ReactNode } & SwipeableProps) => {
const handlers = useSwipeable(props);
return
{children}
;
};
@observer
class StoryPanel extends Component {
keydownListener: EventListener | undefined;
slideRef: RefObject;
constructor(props: Props) {
super(props);
this.state = {
isCollapsed: false,
inView: false
};
this.slideRef = createRef();
}
componentDidMount() {
const stories = this.props.viewState.terria.stories || [];
if (
this.props.viewState.currentStoryId > stories.length - 1 ||
this.props.viewState.currentStoryId < 0
) {
this.props.viewState.currentStoryId = 0;
}
this.activateStory(stories[this.props.viewState.currentStoryId]);
this.slideIn();
this.keydownListener = (e: Event) => {
// Use else if for keydown events so only first one is recognised in case of multiple key presses
if ((e as KeyboardEvent).key === "Escape") {
this.exitStory();
} else if (
(e as KeyboardEvent).key === "ArrowRight" ||
(e as KeyboardEvent).key === "ArrowDown"
) {
if (this.props.viewState.currentStoryId + 1 !== stories.length) {
this.goToNextStory();
}
} else if (
(e as KeyboardEvent).key === "ArrowLeft" ||
(e as KeyboardEvent).key === "ArrowUp"
) {
if (this.props.viewState.currentStoryId !== 0) {
this.goToPrevStory();
}
}
};
window.addEventListener("keydown", this.keydownListener, true);
}
slideIn() {
this.setState({
inView: true
});
}
slideOut() {
this.setState({
inView: false
});
}
toggleCollapse() {
this.setState({
isCollapsed: !this.state.isCollapsed
});
}
onClickContainer() {
runInAction(() => {
this.props.viewState.topElement = "StoryPanel";
});
}
componentWillUnmount() {
if (this.keydownListener) {
window.removeEventListener("keydown", this.keydownListener, true);
}
}
navigateStory(index: number) {
if (index < 0) {
index = this.props.viewState.terria.stories.length - 1;
} else if (index >= this.props.viewState.terria.stories.length) {
index = 0;
}
if (index !== this.props.viewState.currentStoryId) {
runInAction(() => {
this.props.viewState.currentStoryId = index;
});
if (index < (this.props.viewState.terria.stories || []).length) {
this.activateStory(this.props.viewState.terria.stories[index]);
}
}
}
// This is in StoryPanel and StoryBuilder
activateStory(_story: Story | any) {
const story = _story ? _story : this.props.viewState.terria.stories[0];
activateStory(story, this.props.viewState.terria);
}
onCenterScene(story: Story) {
activateStory(story, this.props.viewState.terria);
}
goToPrevStory() {
this.navigateStory(this.props.viewState.currentStoryId - 1);
}
goToNextStory() {
this.navigateStory(this.props.viewState.currentStoryId + 1);
}
exitStory() {
animateEnd(this.slideRef.current).finally(() => {
runInAction(() => {
this.props.viewState.storyShown = false;
});
this.props.viewState.terria.currentViewer.notifyRepaintRequired();
});
this.slideOut();
}
render() {
const stories = this.props.viewState.terria.stories || [];
const story = stories[this.props.viewState.currentStoryId];
if (!story) {
return null;
}
return (
this.onClickContainer()}>
}
css={`
border-radius: 6px;
overflow: hidden;
`}
>
this.toggleCollapse()}
closeHandler={() => this.exitStory()}
/>
this.goToNextStory()}
onSwipedRight={() => this.goToPrevStory()}
>
this.goToPrevStory()}
goNext={() => this.goToNextStory()}
jumpToStory={(index: number) => this.navigateStory(index)}
zoomTo={() => this.onCenterScene(story)}
currentHumanIndex={this.props.viewState.currentStoryId + 1}
totalStories={stories.length}
listStories={() => {
runInAction(() => {
this.props.viewState.storyShown = false;
});
onStoryButtonClick({
terria: this.props.viewState.terria,
theme: this.props.theme,
viewState: this.props.viewState,
animationDuration: 250
})();
}}
/>
);
}
}
export default withTranslation()(withViewState(withTheme(StoryPanel)));