Elius94 / react-photo-sphere-viewer

Photosphere Viewer for React.JS
MIT License
63 stars 21 forks source link

Cannot add property transition, object is not extensible #57

Closed bakermanbrian closed 1 month ago

bakermanbrian commented 1 month ago

What happened?

It seems transitioning between panoramas causes a problem. Here is the thread in the photo sphere library on the issue: https://github.com/mistic100/Photo-Sphere-Viewer/issues/1291

I have run into the problem doing a slightly different thing, but I believe it is from the same underlying issue. The code sandbox and my provided code are different.

What should have happened?

No error

Code

import React, { useEffect, useState } from 'react';
import { getStorage, ref as storageRef, getDownloadURL } from "firebase/storage";
import { getDatabase, ref as databaseRef, get } from "firebase/database";
import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer";
import { MarkersPlugin } from '@photo-sphere-viewer/markers-plugin';
import { VideoPlugin } from '@photo-sphere-viewer/video-plugin';
import {EquirectangularVideoAdapter} from '@photo-sphere-viewer/equirectangular-video-adapter';

import '@photo-sphere-viewer/markers-plugin/index.css';
import '@photo-sphere-viewer/video-plugin/index.css';

const PanoramaViewer = ({ setCoordinates, panoramaSetUid, initialUid }) => {
  const [panoramaUrl, setPanoramaUrl] = useState(null);
  const [videoUrl, setVideoUrl] = useState(null);
  const [nextPanoramaUID, setNextPanoramaUID] = useState(null);
  const [pitch, setPitch] = useState(0);
  const [yaw, setYaw] = useState(0);
  const [hotspots, setHotspots] = useState([]);
  const [panoReady, setPanoReady] = useState(false);
  const [videoReady, setVideoReady] = useState(false);
  const [videoEnded, setVideoEnded] = useState(true);

  useEffect(() => {
    loadPanorama(initialUid);  // Trigger initial panorama load
  }, [initialUid]);

  const loadPanorama = async (uidLoad) => {
    const storage = getStorage();
    const database = getDatabase();

    const url = await getDownloadURL(storageRef(storage, `location`));
    const snapshot = await get(databaseRef(database, `location`));
    const data = snapshot.val();

    setCoordinates({"x": data.x, "y": data.y});
    setPanoramaUrl(url);
    prepareHotspots(data);
  };

  const onReadyPano = (inst) => {
    inst.getPlugin(MarkersPlugin).addEventListener('select-marker', ({ marker }) => {
      setNextPanoramaUID(marker.config.id)
    });
    setPanoReady(true);
    if (videoEnded){
      setVideoReady(false);
      setVideoUrl(null);
    }
  }

  const onReadyVideo = (inst) => {
    setVideoReady(true);
    setPanoramaUrl(null);
    inst.getPlugin(VideoPlugin).addEventListener('progress', ({time, duration, progress}) => {
      console.log(time, duration, progress);
    })
  }

  const onVideoEnded = (inst) => {
    setVideoEnded(true);
    if (panoReady){
      setVideoReady(false);
      setVideoUrl(null);
    }
  }

  const prepareHotspots = (data) => {
    if (!data) return;

    const hs = Object.keys(data).map(key => ({
      id: key,
      circle: 20,
      position: { yaw: data.yaw, pitch: data.pitch },
      tooltip: 'A circle marker',
      tooltip: "Jump to New Location",
    }));
    setHotspots(hs);
  };

  const prepareTransition = async (currentUid, nextUid) => {
    const storage = getStorage();
    const videoUrl = await getDownloadURL(storageRef(storage, `url`));
    setVideoUrl(videoUrl);
    setNextPanoramaUID(nextUid);
  };

  useEffect(() => {
    if (nextPanoramaUID) {
      loadPanorama(nextPanoramaUID);  // Load next panorama when video ends
    }
  }, [nextPanoramaUID]);

  return (
    <div>
      <div>{panoramaUrl}</div>
      {panoramaUrl && (
        <div style={{ display: panoReady && videoEnded ? 'block' : 'none' }}>
        <ReactPhotoSphereViewer
          src={panoramaUrl}
          height={'100vh'}
          width={"100%"}
          defaultPitch={pitch}
          defaultYaw={yaw}
          onReady={onReadyPano}
          plugins={[[MarkersPlugin, {markers: hotspots}] ]}
        >
        </ReactPhotoSphereViewer>
        </div>
      )}

      {videoUrl && (
        <div style={{ display: videoReady ? 'block' : 'none' }}>
        <ReactPhotoSphereViewer
          adapter={[EquirectangularVideoAdapter, {autoplay: true}]}
          panorama={{source: videoUrl}}
          navbar={[]}
          height={'100vh'}
          width={"100%"}
          defaultPitch={pitch}
          defaultYaw={yaw}
          onReady={onReadyVideo}
          plugins={[[VideoPlugin, {progressbar: false, bigbutton: false}] ]}
        >
        </ReactPhotoSphereViewer>
        </div>
      )}
    </div>
  );
};

export default PanoramaViewer;

Sandbox Link

https://codesandbox.io/p/sandbox/delicate-firefly-nvkmdf?file=%2Fsrc%2Findex.tsx

Library Version

latest

What operating system are you using?

macOS

What browser are you using?

None

Logs

No response

Interest to fix the bug

Elius94 commented 1 month ago

fixed in release 6.0.0

Edit gifted-dewdney-4646l2

Elius94 commented 1 month ago

Please, remember that you can change panorama source by react prop and also using setPanorama Method. If you bind "src" to a state that changes when you call "setPanorama" it will load the same panorama twice! So, in this particular case, you can follow this pattern:

import { Viewer } from "@photo-sphere-viewer/core";
import * as React from "react";
import { render } from "react-dom";
import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer";

const baseUrl = "https://photo-sphere-viewer-data.netlify.app/assets/";
const IMAGES = {
  a: `${baseUrl}/artist-workshop.jpg`,
  b: `${baseUrl}/sphere.jpg`,
};
const INITIAL_IMG = Object.keys(IMAGES)[0];

import "./styles.css";

function App() {
  const [step, setStep] = React.useState(INITIAL_IMG);
  const [viewer, setViewer] = React.useState<Viewer | undefined>();

  React.useEffect(() => {
    if (viewer) {
      // When step changes, you call "setPanorama()"
      viewer?.setPanorama(IMAGES[step], {
        zoom: 0,
        position: {
          yaw: 1,
          pitch: 1,
        },
        transition: true,
      });
    }
  }, [viewer, step]);

  const toggle = () => {
    const nextStep = step === "a" ? "b" : "a";
    console.log(nextStep, IMAGES[nextStep]);
    setStep(nextStep); // you change the step on the state
  };

  return (
    <div className="App">
      <button onClick={toggle} className="btn">
        change scene
      </button>
      <ReactPhotoSphereViewer
        src={IMAGES[INITIAL_IMG]} // src is binded to a constant that can't change
        defaultYaw={0}
        defaultPitch={0}
        defaultZoomLvl={0}
        height={"100vh"}
        width={"100%"}
        onReady={setViewer}
      ></ReactPhotoSphereViewer>
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

Thanks