diff --git a/package-lock.json b/package-lock.json index 9cd9a63..fd9454d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,6 +83,7 @@ "slate-react": "^0.109.0", "tippy.js": "^6.3.7", "tiptap-extension-resize-image": "^1.1.8", + "ts-key-enum": "^2.0.12", "vite-plugin-top-level-await": "^1.4.4", "vite-plugin-wasm": "^3.3.0" }, @@ -16224,6 +16225,11 @@ "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": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", diff --git a/package.json b/package.json index b42878b..a688742 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,8 @@ "tippy.js": "^6.3.7", "tiptap-extension-resize-image": "^1.1.8", "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": { "@testing-library/dom": "^10.3.0", diff --git a/src/App.tsx b/src/App.tsx index 4bd817e..d78713c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -80,6 +80,8 @@ import { LoadingButton } from "@mui/lab"; import { Label } from "./components/Group/AddGroup"; import { CustomizedSnackbars } from "./components/Snackbar/Snackbar"; import SettingsIcon from "@mui/icons-material/Settings"; +import HelpIcon from '@mui/icons-material/Help'; + import { cleanUrl, getFee, @@ -128,6 +130,8 @@ import { CoreSyncStatus } from "./components/CoreSyncStatus"; import { Wallets } from "./Wallets"; import { RandomSentenceGenerator } from "./utils/seedPhrase/RandomSentenceGenerator"; import { useFetchResources } from "./common/useFetchResources"; +import { Tutorials } from "./components/Tutorials/Tutorials"; +import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials"; type extStates = | "not-authenticated" @@ -247,8 +251,12 @@ export const resumeAllQueues = () => { }; - +const defaultValuesGlobal = { + openTutorialModal: null, + setOpenTutorialModal: ()=> {} +} export const MyContext = createContext(defaultValues); +export const GlobalContext = createContext(defaultValuesGlobal); export let globalApiKey: string | null = null; @@ -339,6 +347,7 @@ function App() { const {downloadResource} = useFetchResources() const holdRefExtState = useRef("not-authenticated"); const isFocusedRef = useRef(true); + const {showTutorial, openTutorialModal, shownTutorialsInitiated, setOpenTutorialModal} = useHandleTutorials() const { isShow, onCancel, onOk, show, message } = useModal(); const { 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(() => { // Attach a global event listener for double-click const handleDoubleClick = () => { @@ -974,7 +994,7 @@ function App() { 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.", }); - } else { + } else if(extState === 'authenticated') { await showUnsavedChanges({ message: "Are you sure you would like to logout?", @@ -1541,6 +1561,23 @@ function App() { alignItems: 'center' }} > + {(desktopViewMode === "apps" || desktopViewMode === "home") && ( + { + if(desktopViewMode === "apps"){ + showTutorial('qapps', true) + + } else { + showTutorial('create-account', true) + + } + }} > + + + )} + + { setExtstate("download-wallet"); @@ -1569,6 +1606,13 @@ function App() { // backgroundRepeat: desktopViewMode === "apps" && "no-repeat", }} > + + {extState === "not-authenticated" && ( {renderProfile()} + + {extState === "create-wallet" && walletToBeDownloaded && ( + { + showTutorial('important-information', true) + }} sx={{ + position: 'fixed', + bottom: '25px', + right: '25px' + }}> + + + )} + ); } diff --git a/src/ExtStates/NotAuthenticated.tsx b/src/ExtStates/NotAuthenticated.tsx index a386186..e531337 100644 --- a/src/ExtStates/NotAuthenticated.tsx +++ b/src/ExtStates/NotAuthenticated.tsx @@ -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 { CustomButton, TextItalic, TextP, TextSpan } from "../App-styles"; import { Box, Button, + ButtonBase, Checkbox, Dialog, DialogActions, @@ -18,9 +19,11 @@ import { import Logo1 from "../assets/svgs/Logo1.svg"; import Logo1Dark from "../assets/svgs/Logo1Dark.svg"; import Info from "../assets/svgs/Info.svg"; +import HelpIcon from '@mui/icons-material/Help'; import { CustomizedSnackbars } from "../components/Snackbar/Snackbar"; import { set } from "lodash"; import { cleanUrl, isUsingLocal } from "../background"; +import { GlobalContext } from "../App"; const manifestData = { version: "0.3.8", @@ -53,6 +56,8 @@ export const NotAuthenticated = ({ const [customApikey, setCustomApiKey] = React.useState(""); const [customNodeToSaveIndex, setCustomNodeToSaveIndex] = React.useState(null); + const { showTutorial } = useContext(GlobalContext); + const importedApiKeyRef = useRef(null); const currentNodeRef = useRef(null); const hasLocalNodeRef = useRef(null); @@ -291,6 +296,7 @@ export const NotAuthenticated = ({ WELCOME TO YOUR

QORTAL WALLET + )} + { + showTutorial('getting-started', true) + }} sx={{ + position: 'fixed', + bottom: '25px', + right: '25px' + }}> + + ); }; diff --git a/src/components/Apps/AppsDesktop.tsx b/src/components/Apps/AppsDesktop.tsx index 2eb1005..b29f9d5 100644 --- a/src/components/Apps/AppsDesktop.tsx +++ b/src/components/Apps/AppsDesktop.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { AppsHomeDesktop } from "./AppsHomeDesktop"; import { Spacer } from "../../common/Spacer"; -import { MyContext, getBaseApiReact } from "../../App"; +import { GlobalContext, MyContext, getBaseApiReact } from "../../App"; import { AppInfo } from "./AppInfo"; import { executeEvent, @@ -39,6 +39,8 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop const [categories, setCategories] = useState([]) const iframeRefs = useRef({}); const [isEnabledDevMode, setIsEnabledDevMode] = useRecoilState(enabledDevModeAtom) + const { showTutorial } = useContext(GlobalContext); + const myApp = useMemo(()=> { 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') }, [myName, availableQapps]) + + useEffect(()=> { + if(show){ + showTutorial('qapps') + } + }, [show]) + useEffect(() => { setTimeout(() => { executeEvent("setTabsToNav", { diff --git a/src/components/Embeds/VideoPlayer.tsx b/src/components/Embeds/VideoPlayer.tsx new file mode 100644 index 0000000..8ddc717 --- /dev/null +++ b/src/components/Embeds/VideoPlayer.tsx @@ -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 = ({ + 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(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(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) => { + 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) => { + 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 ( + + + {isLoading && ( + + + {resourceStatus && ( + + {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... + )} + + )} + + )} + {((!src && !isLoading) || !startPlay) && ( + { + togglePlay() + }} + sx={{ + cursor: 'pointer' + }} + > + + + )} + + + + + {isMobileView && canPlay ? ( + <> + + {playing ? : } + + + + + + + + + + + + + + increaseSpeed()}> + + Speed: {playbackRate}x + + + + + + + + ) : canPlay ? ( + <> + + {playing ? : } + + + + + + + {progress && videoRef.current?.duration && formatTime(progress)}/ + {progress && + videoRef.current?.duration && + formatTime(videoRef.current?.duration)} + + + {isMuted ? : } + + + increaseSpeed()} + > + Speed: {playbackRate}x + + + + + + ) : null} + + + ) +} diff --git a/src/components/Tutorials/Tutorials.tsx b/src/components/Tutorials/Tutorials.tsx new file mode 100644 index 0000000..4f9ea49 --- /dev/null +++ b/src/components/Tutorials/Tutorials.tsx @@ -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 ( + + setMultiNumber(value)} aria-label="basic tabs example"> + {openTutorialModal?.multi?.map((item, index)=> { + return ( + + + ) + })} + + + {selectedTutorial?.title} {` Tutorial`} + + ({ + position: 'absolute', + right: 8, + top: 8, + color: theme.palette.grey[500], + })} + > + + + + + + + + + + + ) + } + return ( + <> + + + {openTutorialModal?.title} {` Tutorial`} + + ({ + position: 'absolute', + right: 8, + top: 8, + color: theme.palette.grey[500], + })} + > + + + + + + + + + + + + ) +} diff --git a/src/components/Tutorials/useHandleTutorials.tsx b/src/components/Tutorials/useHandleTutorials.tsx new file mode 100644 index 0000000..95450cd --- /dev/null +++ b/src/components/Tutorials/useHandleTutorials.tsx @@ -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(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 + }; +};