mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-26 04:47:52 +00:00
added tutorials
This commit is contained in:
parent
ea3e102bba
commit
98785e0571
6
package-lock.json
generated
6
package-lock.json
generated
@ -83,6 +83,7 @@
|
|||||||
"slate-react": "^0.109.0",
|
"slate-react": "^0.109.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tiptap-extension-resize-image": "^1.1.8",
|
"tiptap-extension-resize-image": "^1.1.8",
|
||||||
|
"ts-key-enum": "^2.0.12",
|
||||||
"vite-plugin-top-level-await": "^1.4.4",
|
"vite-plugin-top-level-await": "^1.4.4",
|
||||||
"vite-plugin-wasm": "^3.3.0"
|
"vite-plugin-wasm": "^3.3.0"
|
||||||
},
|
},
|
||||||
@ -16224,6 +16225,11 @@
|
|||||||
"typescript": ">=4.2.0"
|
"typescript": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-key-enum": {
|
||||||
|
"version": "2.0.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.13.tgz",
|
||||||
|
"integrity": "sha512-zixs6j8+NhzazLUQ1SiFrlo1EFWG/DbqLuUGcWWZ5zhwjRT7kbi1hBlofxdqel+h28zrby2It5TrOyKp04kvqw=="
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
|
@ -88,7 +88,8 @@
|
|||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tiptap-extension-resize-image": "^1.1.8",
|
"tiptap-extension-resize-image": "^1.1.8",
|
||||||
"vite-plugin-top-level-await": "^1.4.4",
|
"vite-plugin-top-level-await": "^1.4.4",
|
||||||
"vite-plugin-wasm": "^3.3.0"
|
"vite-plugin-wasm": "^3.3.0",
|
||||||
|
"ts-key-enum": "^2.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/dom": "^10.3.0",
|
"@testing-library/dom": "^10.3.0",
|
||||||
|
66
src/App.tsx
66
src/App.tsx
@ -80,6 +80,8 @@ import { LoadingButton } from "@mui/lab";
|
|||||||
import { Label } from "./components/Group/AddGroup";
|
import { Label } from "./components/Group/AddGroup";
|
||||||
import { CustomizedSnackbars } from "./components/Snackbar/Snackbar";
|
import { CustomizedSnackbars } from "./components/Snackbar/Snackbar";
|
||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
|
import HelpIcon from '@mui/icons-material/Help';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
cleanUrl,
|
cleanUrl,
|
||||||
getFee,
|
getFee,
|
||||||
@ -128,6 +130,8 @@ import { CoreSyncStatus } from "./components/CoreSyncStatus";
|
|||||||
import { Wallets } from "./Wallets";
|
import { Wallets } from "./Wallets";
|
||||||
import { RandomSentenceGenerator } from "./utils/seedPhrase/RandomSentenceGenerator";
|
import { RandomSentenceGenerator } from "./utils/seedPhrase/RandomSentenceGenerator";
|
||||||
import { useFetchResources } from "./common/useFetchResources";
|
import { useFetchResources } from "./common/useFetchResources";
|
||||||
|
import { Tutorials } from "./components/Tutorials/Tutorials";
|
||||||
|
import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials";
|
||||||
|
|
||||||
type extStates =
|
type extStates =
|
||||||
| "not-authenticated"
|
| "not-authenticated"
|
||||||
@ -247,8 +251,12 @@ export const resumeAllQueues = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const defaultValuesGlobal = {
|
||||||
|
openTutorialModal: null,
|
||||||
|
setOpenTutorialModal: ()=> {}
|
||||||
|
}
|
||||||
export const MyContext = createContext<MyContextInterface>(defaultValues);
|
export const MyContext = createContext<MyContextInterface>(defaultValues);
|
||||||
|
export const GlobalContext = createContext<MyContextInterface>(defaultValuesGlobal);
|
||||||
|
|
||||||
export let globalApiKey: string | null = null;
|
export let globalApiKey: string | null = null;
|
||||||
|
|
||||||
@ -339,6 +347,7 @@ function App() {
|
|||||||
const {downloadResource} = useFetchResources()
|
const {downloadResource} = useFetchResources()
|
||||||
const holdRefExtState = useRef<extStates>("not-authenticated");
|
const holdRefExtState = useRef<extStates>("not-authenticated");
|
||||||
const isFocusedRef = useRef<boolean>(true);
|
const isFocusedRef = useRef<boolean>(true);
|
||||||
|
const {showTutorial, openTutorialModal, shownTutorialsInitiated, setOpenTutorialModal} = useHandleTutorials()
|
||||||
const { isShow, onCancel, onOk, show, message } = useModal();
|
const { isShow, onCancel, onOk, show, message } = useModal();
|
||||||
const {
|
const {
|
||||||
isShow: isShowUnsavedChanges,
|
isShow: isShowUnsavedChanges,
|
||||||
@ -412,6 +421,17 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
if(!shownTutorialsInitiated) return
|
||||||
|
if(extState === 'not-authenticated'){
|
||||||
|
showTutorial('create-account')
|
||||||
|
} else if(extState === "create-wallet" && walletToBeDownloaded){
|
||||||
|
showTutorial('important-information')
|
||||||
|
} else if(extState === "authenticated"){
|
||||||
|
showTutorial('getting-started')
|
||||||
|
}
|
||||||
|
}, [extState, walletToBeDownloaded, shownTutorialsInitiated])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Attach a global event listener for double-click
|
// Attach a global event listener for double-click
|
||||||
const handleDoubleClick = () => {
|
const handleDoubleClick = () => {
|
||||||
@ -974,7 +994,7 @@ function App() {
|
|||||||
message:
|
message:
|
||||||
"Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.",
|
"Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.",
|
||||||
});
|
});
|
||||||
} else {
|
} else if(extState === 'authenticated') {
|
||||||
await showUnsavedChanges({
|
await showUnsavedChanges({
|
||||||
message:
|
message:
|
||||||
"Are you sure you would like to logout?",
|
"Are you sure you would like to logout?",
|
||||||
@ -1541,6 +1561,23 @@ function App() {
|
|||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{(desktopViewMode === "apps" || desktopViewMode === "home") && (
|
||||||
|
<ButtonBase onClick={()=> {
|
||||||
|
if(desktopViewMode === "apps"){
|
||||||
|
showTutorial('qapps', true)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
showTutorial('create-account', true)
|
||||||
|
|
||||||
|
}
|
||||||
|
}} >
|
||||||
|
<HelpIcon sx={{
|
||||||
|
color: 'var(--unread)'
|
||||||
|
}} />
|
||||||
|
</ButtonBase>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Spacer height="20px" />
|
||||||
<img
|
<img
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setExtstate("download-wallet");
|
setExtstate("download-wallet");
|
||||||
@ -1569,6 +1606,13 @@ function App() {
|
|||||||
// backgroundRepeat: desktopViewMode === "apps" && "no-repeat",
|
// backgroundRepeat: desktopViewMode === "apps" && "no-repeat",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<GlobalContext.Provider value={{
|
||||||
|
showTutorial,
|
||||||
|
openTutorialModal,
|
||||||
|
setOpenTutorialModal,
|
||||||
|
downloadResource
|
||||||
|
}}>
|
||||||
|
<Tutorials />
|
||||||
{extState === "not-authenticated" && (
|
{extState === "not-authenticated" && (
|
||||||
<NotAuthenticated
|
<NotAuthenticated
|
||||||
getRootProps={getRootProps}
|
getRootProps={getRootProps}
|
||||||
@ -1602,7 +1646,8 @@ function App() {
|
|||||||
setOpenSnackGlobal: setOpenSnack,
|
setOpenSnackGlobal: setOpenSnack,
|
||||||
infoSnackCustom: infoSnack,
|
infoSnackCustom: infoSnack,
|
||||||
setInfoSnackCustom: setInfoSnack,
|
setInfoSnackCustom: setInfoSnack,
|
||||||
downloadResource
|
downloadResource,
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -3174,6 +3219,21 @@ function App() {
|
|||||||
>
|
>
|
||||||
{renderProfile()}
|
{renderProfile()}
|
||||||
</DrawerComponent>
|
</DrawerComponent>
|
||||||
|
</GlobalContext.Provider>
|
||||||
|
{extState === "create-wallet" && walletToBeDownloaded && (
|
||||||
|
<ButtonBase onClick={()=> {
|
||||||
|
showTutorial('important-information', true)
|
||||||
|
}} sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '25px',
|
||||||
|
right: '25px'
|
||||||
|
}}>
|
||||||
|
<HelpIcon sx={{
|
||||||
|
color: 'var(--unread)'
|
||||||
|
}} />
|
||||||
|
</ButtonBase>
|
||||||
|
)}
|
||||||
|
|
||||||
</AppContainer>
|
</AppContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { Spacer } from "../common/Spacer";
|
import { Spacer } from "../common/Spacer";
|
||||||
import { CustomButton, TextItalic, TextP, TextSpan } from "../App-styles";
|
import { CustomButton, TextItalic, TextP, TextSpan } from "../App-styles";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
ButtonBase,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
@ -18,9 +19,11 @@ import {
|
|||||||
import Logo1 from "../assets/svgs/Logo1.svg";
|
import Logo1 from "../assets/svgs/Logo1.svg";
|
||||||
import Logo1Dark from "../assets/svgs/Logo1Dark.svg";
|
import Logo1Dark from "../assets/svgs/Logo1Dark.svg";
|
||||||
import Info from "../assets/svgs/Info.svg";
|
import Info from "../assets/svgs/Info.svg";
|
||||||
|
import HelpIcon from '@mui/icons-material/Help';
|
||||||
import { CustomizedSnackbars } from "../components/Snackbar/Snackbar";
|
import { CustomizedSnackbars } from "../components/Snackbar/Snackbar";
|
||||||
import { set } from "lodash";
|
import { set } from "lodash";
|
||||||
import { cleanUrl, isUsingLocal } from "../background";
|
import { cleanUrl, isUsingLocal } from "../background";
|
||||||
|
import { GlobalContext } from "../App";
|
||||||
|
|
||||||
const manifestData = {
|
const manifestData = {
|
||||||
version: "0.3.8",
|
version: "0.3.8",
|
||||||
@ -53,6 +56,8 @@ export const NotAuthenticated = ({
|
|||||||
const [customApikey, setCustomApiKey] = React.useState("");
|
const [customApikey, setCustomApiKey] = React.useState("");
|
||||||
const [customNodeToSaveIndex, setCustomNodeToSaveIndex] =
|
const [customNodeToSaveIndex, setCustomNodeToSaveIndex] =
|
||||||
React.useState(null);
|
React.useState(null);
|
||||||
|
const { showTutorial } = useContext(GlobalContext);
|
||||||
|
|
||||||
const importedApiKeyRef = useRef(null);
|
const importedApiKeyRef = useRef(null);
|
||||||
const currentNodeRef = useRef(null);
|
const currentNodeRef = useRef(null);
|
||||||
const hasLocalNodeRef = useRef(null);
|
const hasLocalNodeRef = useRef(null);
|
||||||
@ -291,6 +296,7 @@ export const NotAuthenticated = ({
|
|||||||
WELCOME TO <TextItalic>YOUR</TextItalic> <br></br>
|
WELCOME TO <TextItalic>YOUR</TextItalic> <br></br>
|
||||||
<TextSpan> QORTAL WALLET</TextSpan>
|
<TextSpan> QORTAL WALLET</TextSpan>
|
||||||
</TextP>
|
</TextP>
|
||||||
|
|
||||||
<Spacer height="30px" />
|
<Spacer height="30px" />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -686,6 +692,17 @@ export const NotAuthenticated = ({
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
|
<ButtonBase onClick={()=> {
|
||||||
|
showTutorial('getting-started', true)
|
||||||
|
}} sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '25px',
|
||||||
|
right: '25px'
|
||||||
|
}}>
|
||||||
|
<HelpIcon sx={{
|
||||||
|
color: 'var(--unread)'
|
||||||
|
}} />
|
||||||
|
</ButtonBase>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { AppsHomeDesktop } from "./AppsHomeDesktop";
|
import { AppsHomeDesktop } from "./AppsHomeDesktop";
|
||||||
import { Spacer } from "../../common/Spacer";
|
import { Spacer } from "../../common/Spacer";
|
||||||
import { MyContext, getBaseApiReact } from "../../App";
|
import { GlobalContext, MyContext, getBaseApiReact } from "../../App";
|
||||||
import { AppInfo } from "./AppInfo";
|
import { AppInfo } from "./AppInfo";
|
||||||
import {
|
import {
|
||||||
executeEvent,
|
executeEvent,
|
||||||
@ -39,6 +39,8 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
|||||||
const [categories, setCategories] = useState([])
|
const [categories, setCategories] = useState([])
|
||||||
const iframeRefs = useRef({});
|
const iframeRefs = useRef({});
|
||||||
const [isEnabledDevMode, setIsEnabledDevMode] = useRecoilState(enabledDevModeAtom)
|
const [isEnabledDevMode, setIsEnabledDevMode] = useRecoilState(enabledDevModeAtom)
|
||||||
|
const { showTutorial } = useContext(GlobalContext);
|
||||||
|
|
||||||
const myApp = useMemo(()=> {
|
const myApp = useMemo(()=> {
|
||||||
|
|
||||||
return availableQapps.find((app)=> app.name === myName && app.service === 'APP')
|
return availableQapps.find((app)=> app.name === myName && app.service === 'APP')
|
||||||
@ -48,6 +50,13 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
|||||||
return availableQapps.find((app)=> app.name === myName && app.service === 'WEBSITE')
|
return availableQapps.find((app)=> app.name === myName && app.service === 'WEBSITE')
|
||||||
}, [myName, availableQapps])
|
}, [myName, availableQapps])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
if(show){
|
||||||
|
showTutorial('qapps')
|
||||||
|
}
|
||||||
|
}, [show])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
executeEvent("setTabsToNav", {
|
executeEvent("setTabsToNav", {
|
||||||
|
724
src/components/Embeds/VideoPlayer.tsx
Normal file
724
src/components/Embeds/VideoPlayer.tsx
Normal file
@ -0,0 +1,724 @@
|
|||||||
|
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import { Box, IconButton, Slider } from '@mui/material'
|
||||||
|
import { CircularProgress, Typography } from '@mui/material'
|
||||||
|
import { Key } from 'ts-key-enum'
|
||||||
|
import {
|
||||||
|
PlayArrow,
|
||||||
|
Pause,
|
||||||
|
VolumeUp,
|
||||||
|
Fullscreen,
|
||||||
|
PictureInPicture, VolumeOff, Calculate
|
||||||
|
} from '@mui/icons-material'
|
||||||
|
import { styled } from '@mui/system'
|
||||||
|
import { Refresh } from '@mui/icons-material'
|
||||||
|
|
||||||
|
import { Menu, MenuItem } from '@mui/material'
|
||||||
|
import { MoreVert as MoreIcon } from '@mui/icons-material'
|
||||||
|
import { GlobalContext, getBaseApiReact } from '../../App'
|
||||||
|
import { resourceKeySelector } from '../../atoms/global'
|
||||||
|
import { useRecoilValue } from 'recoil'
|
||||||
|
const VideoContainer = styled(Box)`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const VideoElement = styled('video')`
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: calc(100vh - 150px);
|
||||||
|
background: rgb(33, 33, 33);
|
||||||
|
`
|
||||||
|
|
||||||
|
const ControlsContainer = styled(Box)`
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
`
|
||||||
|
|
||||||
|
interface VideoPlayerProps {
|
||||||
|
src?: string
|
||||||
|
poster?: string
|
||||||
|
name?: string
|
||||||
|
identifier?: string
|
||||||
|
service?: string
|
||||||
|
autoplay?: boolean
|
||||||
|
from?: string | null
|
||||||
|
customStyle?: any
|
||||||
|
user?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
|
poster,
|
||||||
|
name,
|
||||||
|
identifier,
|
||||||
|
service,
|
||||||
|
autoplay = true,
|
||||||
|
from = null,
|
||||||
|
customStyle = {},
|
||||||
|
node
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const keyIdentifier = useMemo(()=> {
|
||||||
|
|
||||||
|
if(name && identifier && service){
|
||||||
|
return `${service}-${name}-${identifier}`
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}, [service, name, identifier])
|
||||||
|
const download = useRecoilValue(resourceKeySelector(keyIdentifier));
|
||||||
|
const { downloadResource } = useContext(GlobalContext);
|
||||||
|
|
||||||
|
const videoRef = useRef<HTMLVideoElement | null>(null)
|
||||||
|
const [playing, setPlaying] = useState(false)
|
||||||
|
const [volume, setVolume] = useState(1)
|
||||||
|
const [mutedVolume, setMutedVolume] = useState(1)
|
||||||
|
const [isMuted, setIsMuted] = useState(false)
|
||||||
|
const [progress, setProgress] = useState(0)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [canPlay, setCanPlay] = useState(false)
|
||||||
|
const [startPlay, setStartPlay] = useState(false)
|
||||||
|
const [isMobileView, setIsMobileView] = useState(false)
|
||||||
|
const [playbackRate, setPlaybackRate] = useState(1)
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
|
const reDownload = useRef<boolean>(false)
|
||||||
|
|
||||||
|
const resetVideoState = () => {
|
||||||
|
// Reset all states to their initial values
|
||||||
|
setPlaying(false);
|
||||||
|
setVolume(1);
|
||||||
|
setMutedVolume(1);
|
||||||
|
setIsMuted(false);
|
||||||
|
setProgress(0);
|
||||||
|
setIsLoading(false);
|
||||||
|
setCanPlay(false);
|
||||||
|
setStartPlay(false);
|
||||||
|
setIsMobileView(false);
|
||||||
|
setPlaybackRate(1);
|
||||||
|
setAnchorEl(null);
|
||||||
|
|
||||||
|
// Reset refs to their initial values
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.pause(); // Ensure the video is paused
|
||||||
|
videoRef.current.currentTime = 0; // Reset video progress
|
||||||
|
}
|
||||||
|
reDownload.current = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const src = useMemo(() => {
|
||||||
|
if(name && identifier && service){
|
||||||
|
return `${node || getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}, [service, name, identifier])
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
resetVideoState()
|
||||||
|
}, [keyIdentifier])
|
||||||
|
const resourceStatus = useMemo(() => {
|
||||||
|
return download?.status || {}
|
||||||
|
}, [download])
|
||||||
|
|
||||||
|
const minSpeed = 0.25;
|
||||||
|
const maxSpeed = 4.0;
|
||||||
|
const speedChange = 0.25;
|
||||||
|
|
||||||
|
const updatePlaybackRate = (newSpeed: number) => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
if (newSpeed > maxSpeed || newSpeed < minSpeed)
|
||||||
|
newSpeed = minSpeed
|
||||||
|
videoRef.current.playbackRate = newSpeed
|
||||||
|
setPlaybackRate(newSpeed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const increaseSpeed = (wrapOverflow = true) => {
|
||||||
|
const changedSpeed = playbackRate + speedChange
|
||||||
|
let newSpeed = wrapOverflow ? changedSpeed : Math.min(changedSpeed, maxSpeed)
|
||||||
|
|
||||||
|
|
||||||
|
if (videoRef.current) {
|
||||||
|
updatePlaybackRate(newSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decreaseSpeed = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
updatePlaybackRate(playbackRate - speedChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const togglePlay = async () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
setStartPlay(true)
|
||||||
|
if (!src || resourceStatus?.status !== 'READY') {
|
||||||
|
ReactDOM.flushSync(() => {
|
||||||
|
setIsLoading(true)
|
||||||
|
})
|
||||||
|
getSrc()
|
||||||
|
}
|
||||||
|
if (playing) {
|
||||||
|
videoRef.current.pause()
|
||||||
|
} else {
|
||||||
|
videoRef.current.play()
|
||||||
|
}
|
||||||
|
setPlaying(!playing)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const onVolumeChange = (_: any, value: number | number[]) => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
videoRef.current.volume = value as number
|
||||||
|
setVolume(value as number)
|
||||||
|
setIsMuted(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onProgressChange = (_: any, value: number | number[]) => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
videoRef.current.currentTime = value as number
|
||||||
|
setProgress(value as number)
|
||||||
|
if (!playing) {
|
||||||
|
videoRef.current.play()
|
||||||
|
setPlaying(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEnded = () => {
|
||||||
|
setPlaying(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProgress = () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
setProgress(videoRef.current.currentTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(false)
|
||||||
|
|
||||||
|
const enterFullscreen = () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
if (videoRef.current.requestFullscreen) {
|
||||||
|
videoRef.current.requestFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exitFullscreen = () => {
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
isFullscreen ? exitFullscreen() : enterFullscreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleFullscreenChange = () => {
|
||||||
|
setIsFullscreen(!!document.fullscreenElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleCanPlay = () => {
|
||||||
|
setIsLoading(false)
|
||||||
|
setCanPlay(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSrc = React.useCallback(async () => {
|
||||||
|
if (!name || !identifier || !service) return
|
||||||
|
try {
|
||||||
|
downloadResource({
|
||||||
|
name,
|
||||||
|
service,
|
||||||
|
identifier
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}, [identifier, name, service])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function formatTime(seconds: number): string {
|
||||||
|
seconds = Math.floor(seconds)
|
||||||
|
let minutes: number | string = Math.floor(seconds / 60)
|
||||||
|
let hours: number | string = Math.floor(minutes / 60)
|
||||||
|
|
||||||
|
let remainingSeconds: number | string = seconds % 60
|
||||||
|
let remainingMinutes: number | string = minutes % 60
|
||||||
|
|
||||||
|
if (remainingSeconds < 10) {
|
||||||
|
remainingSeconds = '0' + remainingSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingMinutes < 10) {
|
||||||
|
remainingMinutes = '0' + remainingMinutes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours === 0) {
|
||||||
|
hours = ''
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hours = hours + ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
return hours + remainingMinutes + ':' + remainingSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadVideo = () => {
|
||||||
|
if (!videoRef.current) return
|
||||||
|
const currentTime = videoRef.current.currentTime
|
||||||
|
videoRef.current.src = src
|
||||||
|
videoRef.current.load()
|
||||||
|
videoRef.current.currentTime = currentTime
|
||||||
|
if (playing) {
|
||||||
|
videoRef.current.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
resourceStatus?.status === 'DOWNLOADED' &&
|
||||||
|
reDownload?.current === false
|
||||||
|
) {
|
||||||
|
getSrc()
|
||||||
|
reDownload.current = true
|
||||||
|
}
|
||||||
|
}, [getSrc, resourceStatus])
|
||||||
|
|
||||||
|
const handleMenuOpen = (event: any) => {
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMenuClose = () => {
|
||||||
|
setAnchorEl(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const videoWidth = videoRef?.current?.offsetWidth
|
||||||
|
if (videoWidth && videoWidth <= 600) {
|
||||||
|
setIsMobileView(true)
|
||||||
|
}
|
||||||
|
}, [canPlay])
|
||||||
|
|
||||||
|
const getDownloadProgress = (current: number, total: number) => {
|
||||||
|
const progress = current / total * 100;
|
||||||
|
return Number.isNaN(progress) ? '' : progress.toFixed(0) + '%'
|
||||||
|
}
|
||||||
|
const mute = () => {
|
||||||
|
setIsMuted(true)
|
||||||
|
setMutedVolume(volume)
|
||||||
|
setVolume(0)
|
||||||
|
if (videoRef.current) videoRef.current.volume = 0
|
||||||
|
}
|
||||||
|
const unMute = () => {
|
||||||
|
setIsMuted(false)
|
||||||
|
setVolume(mutedVolume)
|
||||||
|
if (videoRef.current) videoRef.current.volume = mutedVolume
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleMute = () => {
|
||||||
|
isMuted ? unMute() : mute();
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeVolume = (volumeChange: number) => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
const minVolume = 0;
|
||||||
|
const maxVolume = 1;
|
||||||
|
|
||||||
|
|
||||||
|
let newVolume = volumeChange + volume
|
||||||
|
|
||||||
|
newVolume = Math.max(newVolume, minVolume)
|
||||||
|
newVolume = Math.min(newVolume, maxVolume)
|
||||||
|
|
||||||
|
setIsMuted(false)
|
||||||
|
setMutedVolume(newVolume)
|
||||||
|
videoRef.current.volume = newVolume
|
||||||
|
setVolume(newVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
const setProgressRelative = (secondsChange: number) => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
const currentTime = videoRef.current?.currentTime
|
||||||
|
const minTime = 0
|
||||||
|
const maxTime = videoRef.current?.duration || 100
|
||||||
|
|
||||||
|
let newTime = currentTime + secondsChange;
|
||||||
|
newTime = Math.max(newTime, minTime)
|
||||||
|
newTime = Math.min(newTime, maxTime)
|
||||||
|
videoRef.current.currentTime = newTime;
|
||||||
|
setProgress(newTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setProgressAbsolute = (videoPercent: number) => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoPercent = Math.min(videoPercent, 100)
|
||||||
|
videoPercent = Math.max(videoPercent, 0)
|
||||||
|
const finalTime = videoRef.current?.duration * videoPercent / 100
|
||||||
|
videoRef.current.currentTime = finalTime
|
||||||
|
setProgress(finalTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case Key.Add: increaseSpeed(false); break;
|
||||||
|
case '+': increaseSpeed(false); break;
|
||||||
|
case '>': increaseSpeed(false); break;
|
||||||
|
|
||||||
|
case Key.Subtract: decreaseSpeed(); break;
|
||||||
|
case '-': decreaseSpeed(); break;
|
||||||
|
case '<': decreaseSpeed(); break;
|
||||||
|
|
||||||
|
case Key.ArrowLeft: {
|
||||||
|
if (e.shiftKey) setProgressRelative(-300);
|
||||||
|
else if (e.ctrlKey) setProgressRelative(-60);
|
||||||
|
else if (e.altKey) setProgressRelative(-10);
|
||||||
|
else setProgressRelative(-5);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Key.ArrowRight: {
|
||||||
|
if (e.shiftKey) setProgressRelative(300);
|
||||||
|
else if (e.ctrlKey) setProgressRelative(60);
|
||||||
|
else if (e.altKey) setProgressRelative(10);
|
||||||
|
else setProgressRelative(5);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Key.ArrowDown: changeVolume(-0.05); break;
|
||||||
|
case Key.ArrowUp: changeVolume(0.05); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyboardShortcutsUp = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case ' ': togglePlay(); break;
|
||||||
|
case 'm': toggleMute(); break;
|
||||||
|
|
||||||
|
case 'f': enterFullscreen(); break;
|
||||||
|
case Key.Escape: exitFullscreen(); break;
|
||||||
|
|
||||||
|
case '0': setProgressAbsolute(0); break;
|
||||||
|
case '1': setProgressAbsolute(10); break;
|
||||||
|
case '2': setProgressAbsolute(20); break;
|
||||||
|
case '3': setProgressAbsolute(30); break;
|
||||||
|
case '4': setProgressAbsolute(40); break;
|
||||||
|
case '5': setProgressAbsolute(50); break;
|
||||||
|
case '6': setProgressAbsolute(60); break;
|
||||||
|
case '7': setProgressAbsolute(70); break;
|
||||||
|
case '8': setProgressAbsolute(80); break;
|
||||||
|
case '9': setProgressAbsolute(90); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('!src && !isLoading) || !startPlay', startPlay, resourceStatus?.status === 'READY')
|
||||||
|
return (
|
||||||
|
<VideoContainer
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyUp={keyboardShortcutsUp}
|
||||||
|
onKeyDown={keyboardShortcutsDown}
|
||||||
|
style={{
|
||||||
|
padding: from === 'create' ? '8px' : 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
{isLoading && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={resourceStatus?.status === 'READY' ? '55px ' : 0}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
zIndex={25}
|
||||||
|
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress color="secondary" />
|
||||||
|
{resourceStatus && (
|
||||||
|
<Typography
|
||||||
|
variant="subtitle2"
|
||||||
|
component="div"
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '15px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{resourceStatus?.status === 'REFETCHING' ? (
|
||||||
|
<>
|
||||||
|
<>
|
||||||
|
{getDownloadProgress(resourceStatus?.localChunkCount, resourceStatus?.totalChunkCount)}
|
||||||
|
</>
|
||||||
|
|
||||||
|
<> Refetching data in 25 seconds</>
|
||||||
|
</>
|
||||||
|
) : resourceStatus?.status === 'DOWNLOADED' ? (
|
||||||
|
<>Download Completed: building tutorial video...</>
|
||||||
|
) : resourceStatus?.status !== 'READY' ? (
|
||||||
|
<>
|
||||||
|
{getDownloadProgress(resourceStatus?.localChunkCount, resourceStatus?.totalChunkCount)}
|
||||||
|
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>Fetching tutorial from the Qortal Network...</>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{((!src && !isLoading) || !startPlay) && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
zIndex={500}
|
||||||
|
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||||
|
onClick={() => {
|
||||||
|
togglePlay()
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlayArrow
|
||||||
|
sx={{
|
||||||
|
width: '50px',
|
||||||
|
height: '50px',
|
||||||
|
color: 'white'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexGrow: 1,
|
||||||
|
width: '100%',
|
||||||
|
height: 'calc(100% - 60px)',
|
||||||
|
}}>
|
||||||
|
<VideoElement
|
||||||
|
id={identifier}
|
||||||
|
ref={videoRef}
|
||||||
|
src={!startPlay ? '' : resourceStatus?.status === 'READY' ? src : ''}
|
||||||
|
poster={!startPlay ? poster : ""}
|
||||||
|
onTimeUpdate={updateProgress}
|
||||||
|
autoPlay={autoplay}
|
||||||
|
onClick={togglePlay}
|
||||||
|
onEnded={handleEnded}
|
||||||
|
// onLoadedMetadata={handleLoadedMetadata}
|
||||||
|
onCanPlay={handleCanPlay}
|
||||||
|
preload="metadata"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
...customStyle
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<ControlsContainer
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
background: 'var(--bg-primary)',
|
||||||
|
width: '100%',
|
||||||
|
flexShrink: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isMobileView && canPlay ? (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)'
|
||||||
|
}}
|
||||||
|
onClick={togglePlay}
|
||||||
|
>
|
||||||
|
{playing ? <Pause /> : <PlayArrow />}
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
marginLeft: '15px'
|
||||||
|
}}
|
||||||
|
onClick={reloadVideo}
|
||||||
|
>
|
||||||
|
<Refresh />
|
||||||
|
</IconButton>
|
||||||
|
<Slider
|
||||||
|
value={progress}
|
||||||
|
onChange={onProgressChange}
|
||||||
|
min={0}
|
||||||
|
max={videoRef.current?.duration || 100}
|
||||||
|
sx={{ flexGrow: 1, mx: 2 }}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
color="inherit"
|
||||||
|
aria-label="menu"
|
||||||
|
onClick={handleMenuOpen}
|
||||||
|
>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
id="simple-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
keepMounted
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleMenuClose}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
width: '250px'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem>
|
||||||
|
<VolumeUp />
|
||||||
|
<Slider
|
||||||
|
value={volume}
|
||||||
|
onChange={onVolumeChange}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => increaseSpeed()}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Speed: {playbackRate}x
|
||||||
|
</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={toggleFullscreen}>
|
||||||
|
<Fullscreen />
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
) : canPlay ? (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)'
|
||||||
|
}}
|
||||||
|
onClick={togglePlay}
|
||||||
|
>
|
||||||
|
{playing ? <Pause /> : <PlayArrow />}
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
marginLeft: '15px'
|
||||||
|
}}
|
||||||
|
onClick={reloadVideo}
|
||||||
|
>
|
||||||
|
<Refresh />
|
||||||
|
</IconButton>
|
||||||
|
<Slider
|
||||||
|
value={progress}
|
||||||
|
onChange={onProgressChange}
|
||||||
|
min={0}
|
||||||
|
max={videoRef.current?.duration || 100}
|
||||||
|
sx={{ flexGrow: 1, mx: 2, color: 'var(--Mail-Background)' }}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '5px',
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
visibility:
|
||||||
|
!videoRef.current?.duration || !progress
|
||||||
|
? 'hidden'
|
||||||
|
: 'visible',
|
||||||
|
flexShrink: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{progress && videoRef.current?.duration && formatTime(progress)}/
|
||||||
|
{progress &&
|
||||||
|
videoRef.current?.duration &&
|
||||||
|
formatTime(videoRef.current?.duration)}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
marginRight: '10px'
|
||||||
|
}}
|
||||||
|
onClick={toggleMute}
|
||||||
|
>
|
||||||
|
{isMuted ? <VolumeOff /> : <VolumeUp />}
|
||||||
|
</IconButton>
|
||||||
|
<Slider
|
||||||
|
value={volume}
|
||||||
|
onChange={onVolumeChange}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
sx={{
|
||||||
|
maxWidth: '100px',
|
||||||
|
color: 'var(--Mail-Background)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginLeft: '5px'
|
||||||
|
}}
|
||||||
|
onClick={(e) => increaseSpeed()}
|
||||||
|
>
|
||||||
|
Speed: {playbackRate}x
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)'
|
||||||
|
}}
|
||||||
|
onClick={toggleFullscreen}
|
||||||
|
>
|
||||||
|
<Fullscreen />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</ControlsContainer>
|
||||||
|
</VideoContainer>
|
||||||
|
)
|
||||||
|
}
|
108
src/components/Tutorials/Tutorials.tsx
Normal file
108
src/components/Tutorials/Tutorials.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import React, { useContext, useState } from 'react'
|
||||||
|
import { GlobalContext, MyContext } from '../../App';
|
||||||
|
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tab, Tabs, Typography } from '@mui/material';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import { VideoPlayer } from '../Embeds/VideoPlayer';
|
||||||
|
|
||||||
|
export const Tutorials = () => {
|
||||||
|
const { openTutorialModal, setOpenTutorialModal } = useContext(GlobalContext);
|
||||||
|
const [multiNumber, setMultiNumber] = useState(0)
|
||||||
|
const handleClose = ()=> {
|
||||||
|
setOpenTutorialModal(null)
|
||||||
|
setMultiNumber(0)
|
||||||
|
}
|
||||||
|
if(!openTutorialModal) return null
|
||||||
|
if(openTutorialModal?.multi){
|
||||||
|
const selectedTutorial = openTutorialModal?.multi[multiNumber]
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
onClose={handleClose}
|
||||||
|
aria-labelledby="customized-dialog-title"
|
||||||
|
open={!!openTutorialModal}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth="xl"
|
||||||
|
>
|
||||||
|
<Tabs sx={{
|
||||||
|
"& .MuiTabs-indicator": {
|
||||||
|
backgroundColor: "white",
|
||||||
|
},
|
||||||
|
}} value={multiNumber} onChange={(e, value)=> setMultiNumber(value)} aria-label="basic tabs example">
|
||||||
|
{openTutorialModal?.multi?.map((item, index)=> {
|
||||||
|
return (
|
||||||
|
<Tab sx={{
|
||||||
|
"&.Mui-selected": {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
}} label={item?.title} value={index} />
|
||||||
|
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Tabs>
|
||||||
|
<DialogTitle sx={{ m: 0, p: 2 }} >
|
||||||
|
{selectedTutorial?.title} {` Tutorial`}
|
||||||
|
</DialogTitle>
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
onClick={handleClose}
|
||||||
|
sx={(theme) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
right: 8,
|
||||||
|
top: 8,
|
||||||
|
color: theme.palette.grey[500],
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
<DialogContent dividers sx={{
|
||||||
|
height: '85vh'
|
||||||
|
}}>
|
||||||
|
|
||||||
|
<VideoPlayer node="https://ext-node.qortal.link" {...selectedTutorial?.resource || {}} />
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" onClick={handleClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
onClose={handleClose}
|
||||||
|
aria-labelledby="customized-dialog-title"
|
||||||
|
open={!!openTutorialModal}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth="xl"
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ m: 0, p: 2 }} >
|
||||||
|
{openTutorialModal?.title} {` Tutorial`}
|
||||||
|
</DialogTitle>
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
onClick={handleClose}
|
||||||
|
sx={(theme) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
right: 8,
|
||||||
|
top: 8,
|
||||||
|
color: theme.palette.grey[500],
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
<DialogContent dividers sx={{
|
||||||
|
height: '85vh'
|
||||||
|
}}>
|
||||||
|
|
||||||
|
<VideoPlayer node="https://ext-node.qortal.link" {...openTutorialModal?.resource || {}} />
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" onClick={handleClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
169
src/components/Tutorials/useHandleTutorials.tsx
Normal file
169
src/components/Tutorials/useHandleTutorials.tsx
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { saveToLocalStorage } from "../Apps/AppsNavBar";
|
||||||
|
|
||||||
|
|
||||||
|
const checkIfGatewayIsOnline = async () => {
|
||||||
|
try {
|
||||||
|
const url = `https://ext-node.qortal.link/admin/status`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data?.height) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const useHandleTutorials = () => {
|
||||||
|
const [openTutorialModal, setOpenTutorialModal] = useState<any>(null);
|
||||||
|
const [shownTutorials, setShowTutorials] = useState(null)
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
try {
|
||||||
|
const storedData = localStorage.getItem('shown-tutorials');
|
||||||
|
|
||||||
|
|
||||||
|
if (storedData) {
|
||||||
|
setShowTutorials(JSON.parse(storedData));
|
||||||
|
} else {
|
||||||
|
setShowTutorials({})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
//error
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const saveShowTutorial = useCallback((type)=> {
|
||||||
|
try {
|
||||||
|
|
||||||
|
setShowTutorials((prev)=> {
|
||||||
|
return {
|
||||||
|
...(prev || {}),
|
||||||
|
[type]: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saveToLocalStorage('shown-tutorials', type, true)
|
||||||
|
} catch (error) {
|
||||||
|
//error
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
const showTutorial = useCallback(async (type, isForce) => {
|
||||||
|
try {
|
||||||
|
const isOnline = await checkIfGatewayIsOnline()
|
||||||
|
if(!isOnline) return
|
||||||
|
switch (type) {
|
||||||
|
case "create-account":
|
||||||
|
{
|
||||||
|
if((shownTutorials || {})['create-account'] && !isForce) return
|
||||||
|
saveShowTutorial('create-account')
|
||||||
|
setOpenTutorialModal({
|
||||||
|
title: "Account Creation",
|
||||||
|
resource: {
|
||||||
|
name: "a-test",
|
||||||
|
service: "VIDEO",
|
||||||
|
identifier: "account-creation-hub",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "important-information":
|
||||||
|
{
|
||||||
|
if((shownTutorials || {})['important-information'] && !isForce) return
|
||||||
|
saveShowTutorial('important-information')
|
||||||
|
|
||||||
|
setOpenTutorialModal({
|
||||||
|
title: "Important Information!",
|
||||||
|
resource: {
|
||||||
|
name: "a-test",
|
||||||
|
service: "VIDEO",
|
||||||
|
identifier: "important-information-hub",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "getting-started":
|
||||||
|
{
|
||||||
|
if((shownTutorials || {})['getting-started'] && !isForce) return
|
||||||
|
saveShowTutorial('getting-started')
|
||||||
|
|
||||||
|
setOpenTutorialModal({
|
||||||
|
multi: [
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Getting Started",
|
||||||
|
resource: {
|
||||||
|
name: "a-test",
|
||||||
|
service: "VIDEO",
|
||||||
|
identifier: "getting-started-hub",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Overview",
|
||||||
|
resource: {
|
||||||
|
name: "a-test",
|
||||||
|
service: "VIDEO",
|
||||||
|
identifier: "overview-hub",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Qortal Groups",
|
||||||
|
resource: {
|
||||||
|
name: "a-test",
|
||||||
|
service: "VIDEO",
|
||||||
|
identifier: "groups-hub",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "qapps":
|
||||||
|
{
|
||||||
|
if((shownTutorials || {})['qapps'] && !isForce) return
|
||||||
|
saveShowTutorial('qapps')
|
||||||
|
|
||||||
|
setOpenTutorialModal({
|
||||||
|
multi: [
|
||||||
|
{
|
||||||
|
title: "Apps Dashboard",
|
||||||
|
resource: {
|
||||||
|
name: "a-test",
|
||||||
|
service: "VIDEO",
|
||||||
|
identifier: "apps-dashboard-hub",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Apps Navigation",
|
||||||
|
resource: {
|
||||||
|
name: "a-test",
|
||||||
|
service: "VIDEO",
|
||||||
|
identifier: "apps-navigation-hub",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
//error
|
||||||
|
}
|
||||||
|
}, [shownTutorials]);
|
||||||
|
return {
|
||||||
|
showTutorial,
|
||||||
|
openTutorialModal,
|
||||||
|
setOpenTutorialModal,
|
||||||
|
shownTutorialsInitiated: !!shownTutorials
|
||||||
|
};
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user