import * as React from 'react';
import { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import Split from 'react-split'
import { Box, Typography, CircularProgress, Slider } from '@mui/material';
import cytoscape, { LayoutOptions } from 'cytoscape'
import klay from 'cytoscape-klay';
import './welcome.css'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faGlobe, faBook, faHome, faCircleInfo, faArrowRight, faArrowLeft, faShield, faRotateLeft } from '@fortawesome/free-solid-svg-icons'
import { GameIdContext } from '../app';
import { changedDifficulty, changedOpenedIntro, selectCompleted, selectDifficulty, selectOpenedIntro } from '../state/progress';
import { useGetGameInfoQuery, useLoadInventoryOverviewQuery } from '../state/api';
import Markdown from './markdown';
import WorldSelectionMenu, { WelcomeMenu } from './world_selection_menu';
import {PrivacyPolicy} from './privacy_policy';
import { Button } from './button';
import { Documentation, Inventory } from './inventory';
import { store } from '../state/store';
import { useWindowDimensions } from '../window_width';
import { MobileContext } from './infoview/context';
import { useAppDispatch } from '../hooks';
cytoscape.use( klay );
const N = 18 // max number of levels per world
const R = 64 // radius of a world
const r = 12 // radius of a level
const s = 10 // global scale
const padding = R + 2*r // padding of the graphic (on a different scale)
const ds = .75 // scale the resulting svg image
function LevelIcon({ worldId, levelId, position, completed, available }) {
const gameId = React.useContext(GameIdContext)
const difficulty = useSelector(selectDifficulty(gameId))
const x = s * position.x + Math.sin(levelId * 2 * Math.PI / N) * (R + 1.2*r + 2.4*r*Math.floor((levelId - 1)/N))
const y = s * position.y - Math.cos(levelId * 2 * Math.PI / N) * (R + 1.2*r + 2.4*r*Math.floor((levelId - 1)/N))
let levelDisabled = (difficulty >= 2 && !(available || completed))
// TODO: relative positioning?
return (
{levelId}
)
}
function Welcome() {
const navigate = useNavigate();
const gameId = React.useContext(GameIdContext)
const openedIntro = useSelector(selectOpenedIntro(gameId))
/** Only for mobile layout */
const [pageNumber, setPageNumber] = useState(openedIntro ? 1 : 0)
const dispatch = useAppDispatch()
const gameInfo = useGetGameInfoQuery({game: gameId})
const {mobile} = React.useContext(MobileContext)
const inventory = useLoadInventoryOverviewQuery({game: gameId})
const difficulty = useSelector(selectDifficulty(gameId))
// When clicking on an inventory item, the inventory is overlayed by the item's doc.
// If this state is set to a pair `(name, type)` then the according doc will be open.
const [inventoryDoc, setInventoryDoc] = useState<{name: string, type: string}>(null)
// Open the doc of the clicked inventory item
function openInventoryDoc(name, type) {
setInventoryDoc({name, type})
}
// Set `inventoryDoc` to `null` to close the doc
const closeInventoryDoc = () => setInventoryDoc(null);
const { nodes, bounds }: any = gameInfo.data ? computeWorldLayout(gameInfo.data?.worlds) : {nodes: []}
useEffect(() => {
if (gameInfo.data?.title) {
window.document.title = gameInfo.data.title
}
}, [gameInfo.data?.title])
// Scroll to playable world
useEffect(() => {
let elems = Array.from(document.getElementsByClassName("playable-world"))
if (elems.length) {
// It seems that the last element is the one furthest up in the tree
// TODO: I think they appear in random order. Check there position and select the lowest one
// of these positions to scroll to.
let elem = elems[0]
console.debug(`scrolling to ${elem.textContent}`)
elem.scrollIntoView({block: "center"})
}
}, [gameInfo])
const svgElements = []
// For each `worldId` as index, this contains a list of booleans with indices
// 0, 1, …, n. Index `0` will be set to `false` if any dependency is not completely solved.
// Indices `1, …, n` indicate if the corresponding level is completed
var completed = {}
if (gameInfo.data) {
// Fill `completed` with the level data.
for (let worldId in nodes) {
let position: cytoscape.Position = nodes[worldId].position
let state = store.getState()
completed[worldId] = Array.from({ length: gameInfo.data.worldSize[worldId] + 1 }, (_, i) => {
// Index `0` might be set to `false` in the loop over the edges
if (!i) {return true}
return selectCompleted(gameId, worldId, i)(state)
})
}
for (let i in gameInfo.data.worlds.edges) {
const edge = gameInfo.data.worlds.edges[i]
// If the origin world is not completed, mark the target world as non-playable
let unlocked = completed[edge[0]].slice(1).every(Boolean)
if (!unlocked) {completed[edge[1]][0] = false}
// Draw the connection edges
svgElements.push(
)
}
for (let worldId in nodes) {
// Draw the level bubbles
let position: cytoscape.Position = nodes[worldId].position
for (let i = 1; i <= gameInfo.data.worldSize[worldId]; i++) {
svgElements.push(
)
}
let worldUnlocked = completed[worldId][0]
let worldCompleted = completed[worldId].slice(1).every(Boolean)
// This selects the first uncompleted level
let nextLevel: number = completed[worldId].findIndex(c => !c)
if (nextLevel <= 1) {
// This uses the fact that `findIndex` returns `-1` if it does not find an uncompleted entry
// so `-1, 0, 1` are all the indices where we want to show the introduction.
nextLevel = 0
}
let worldDisabled = (difficulty >= 2 && !(worldUnlocked || worldCompleted))
// Draw the worlds
svgElements.push(
)
}
}
let dx = bounds ? s*(bounds.x2 - bounds.x1) + 2*padding : null
// TODO: Pack the three columns into components, so we dont need to
// copy them for mobile layout
return
{ gameInfo.isLoading?
: mobile ?
(pageNumber == 0 ?