import i18next from "i18next";
import { action, computed, observable, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { FC } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import AugmentedVirtuality from "../../../../Models/AugmentedVirtuality";
import Terria from "../../../../Models/Terria";
import ViewerMode from "../../../../Models/ViewerMode";
import ViewState from "../../../../ReactViewModels/ViewState";
import { GLYPHS, Icon } from "../../../../Styled/Icon";
import MapNavigationItemController from "../../../../ViewModels/MapNavigation/MapNavigationItemController";
import MapIconButton from "../../../MapIconButton/MapIconButton";
interface IAugmentedVirtuality {
augmentedVirtuality: AugmentedVirtuality;
}
interface IProps extends IAugmentedVirtuality {
terria: Terria;
viewState: ViewState;
experimentalWarning?: boolean;
}
export const AR_TOOL_ID = "AR_TOOL";
async function requestDeviceMotionPermission(): Promise<"granted" | "denied"> {
const requestPermission: () => Promise<"granted" | "denied"> =
window.DeviceMotionEvent &&
typeof (DeviceMotionEvent as any).requestPermission === "function"
? (DeviceMotionEvent as any).requestPermission
: () => Promise.resolve("granted");
return requestPermission();
}
async function requestDeviceOrientationPermission(): Promise<
"granted" | "denied"
> {
const requestPermission: () => Promise<"granted" | "denied"> =
window.DeviceOrientationEvent &&
typeof (DeviceOrientationEvent as any).requestPermission === "function"
? (DeviceOrientationEvent as any).requestPermission
: () => Promise.resolve("granted");
return requestPermission();
}
export class AugmentedVirtualityController extends MapNavigationItemController {
@observable experimentalWarningShown = false;
constructor(private props: IProps) {
super();
makeObservable(this);
}
@computed
get active(): boolean {
return this.props.augmentedVirtuality.active;
}
get glyph(): { id: string } {
return this.active ? GLYPHS.arOn : GLYPHS.arOff;
}
get viewerMode(): ViewerMode {
return ViewerMode.Cesium;
}
@action.bound
activate() {
// Make the AugmentedVirtuality module avaliable elsewhere.
this.props.terria.augmentedVirtuality = this.props.augmentedVirtuality;
// feature detect for new ios 13
// it seems you don't need to ask for both, but who knows, ios 14 / something
// could change again
requestDeviceMotionPermission()
.then((permissionState) => {
if (permissionState !== "granted") {
console.error("couldn't get access for motion events");
}
})
.catch(console.error);
requestDeviceOrientationPermission()
.then((permissionState) => {
if (permissionState !== "granted") {
console.error("couldn't get access for orientation events");
}
})
.catch(console.error);
const { experimentalWarning = true } = this.props;
if (experimentalWarning !== false && !this.experimentalWarningShown) {
this.experimentalWarningShown = true;
this.props.viewState.terria.notificationState.addNotificationToQueue({
title: i18next.t("AR.title"),
message: i18next.t("AR.experimentalFeatureMessage"),
confirmText: i18next.t("AR.confirmText")
});
}
this.props.augmentedVirtuality.activate();
}
deactivate() {
this.props.augmentedVirtuality.deactivate();
}
}
export class AugmentedVirtualityRealignController extends MapNavigationItemController {
@observable experimentalWarningShown = false;
@observable realignHelpShown = false;
@observable resetRealignHelpShown = false;
augmentedVirtuality: AugmentedVirtuality;
constructor(private props: IProps) {
super();
makeObservable(this);
this.augmentedVirtuality = props.augmentedVirtuality;
}
@computed
get glyph(): { id: string } {
return !this.augmentedVirtuality.manualAlignmentSet
? GLYPHS.arRealign
: GLYPHS.arResetAlignment;
}
get viewerMode(): ViewerMode {
return ViewerMode.Cesium;
}
@computed
get visible(): boolean {
return this.props.augmentedVirtuality.active && super.visible;
}
handleClick(): void {
if (!this.augmentedVirtuality.manualAlignmentSet) {
this.handleClickRealign();
} else if (!this.augmentedVirtuality.manualAlignment) {
this.handleClickResetRealign();
}
}
@action.bound
handleClickRealign() {
if (!this.realignHelpShown) {
this.realignHelpShown = true;
this.props.viewState.terria.notificationState.addNotificationToQueue({
title: i18next.t("AR.manualAlignmentTitle"),
message: i18next.t("AR.manualAlignmentMessage", {
img: '
'
}),
confirmText: i18next.t("AR.confirmText")
});
}
this.augmentedVirtuality.toggleManualAlignment();
}
@action.bound
handleClickResetRealign() {
if (!this.resetRealignHelpShown) {
this.resetRealignHelpShown = true;
this.props.viewState.terria.notificationState.addNotificationToQueue({
title: i18next.t("AR.resetAlignmentTitle"),
message: i18next.t("AR.resetAlignmentMessage"),
confirmText: i18next.t("AR.confirmText")
});
}
this.augmentedVirtuality.resetAlignment();
}
}
export const AugmentedVirtualityRealign: FC<{
arRealignController: AugmentedVirtualityRealignController;
}> = observer(
(props: { arRealignController: AugmentedVirtualityRealignController }) => {
const augmentedVirtuality = props.arRealignController.augmentedVirtuality;
const realignment = augmentedVirtuality.manualAlignment;
const { t } = useTranslation();
return !augmentedVirtuality.manualAlignmentSet ? (
}
title={t("AR.btnRealign")}
onClick={props.arRealignController.handleClickRealign}
/>
) : (
}
title={t("AR.btnResetRealign")}
onClick={props.arRealignController.handleClickResetRealign}
/>
);
}
);
export class AugmentedVirtualityHoverController extends MapNavigationItemController {
constructor(private props: IAugmentedVirtuality) {
super();
makeObservable(this);
}
get glyph(): { id: string } {
const hoverLevel = this.props.augmentedVirtuality.hoverLevel;
// Note: We use the image of the next level that we will be changing to, not the level the we are currently at.
switch (hoverLevel) {
case 0:
return GLYPHS.arHover0;
case 1:
return GLYPHS.arHover1;
case 2:
return GLYPHS.arHover2;
default:
return GLYPHS.arHover0;
}
}
get viewerMode(): ViewerMode {
return ViewerMode.Cesium;
}
@computed
get visible(): boolean {
return this.props.augmentedVirtuality.active && super.visible;
}
handleClick(): void {
this.props.augmentedVirtuality.toggleHoverHeight();
}
}
const StyledMapIconButton = styled(MapIconButton)<{ blink: boolean }>`
svg {
${(p) =>
p.blink &&
`
-webkit-animation-name: blinker;
-webkit-animation-duration: 1s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: infinite;
-moz-animation-name: blinker;
-moz-animation-duration: 1s;
-moz-animation-timing-function: linear;
-moz-animation-iteration-count: infinite;
animation-name: blinker;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
`}
}
@-moz-keyframes blinker {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes blinker {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes blinker {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
`;