diff --git a/client/src/App.css b/client/src/App.css index 6c035b17..e18a9ac6 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -1,6 +1,6 @@ -body { +html { background: rgb(0,131,77); - background: radial-gradient(circle, rgba(0,131,77,1) 0%, rgba(17,66,0,1) 100%); + /*background: radial-gradient(circle, rgba(0,131,77,1) 0%, rgba(17,66,0,1) 100%);*/ font-family: sans-serif; } @@ -41,4 +41,14 @@ header hr { margin: 1rem; padding: 1rem; border-radius: 1rem; +} + +input { + background: none; + border: none; + padding: 0; + font-size: unset; + color: unset; + width: auto; + overflow: visible; } \ No newline at end of file diff --git a/client/src/App.jsx b/client/src/App.jsx index 880043b7..782cc9d7 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -8,6 +8,8 @@ import Profile from '/src/login/Profile.jsx'; import GhostPage from '/src/page/GhostPage.jsx'; import Live from '/src/embodied/Live.jsx'; +//import Browsing from '/src/entwined/Browsing.jsx'; + import AuthProvider from '/src/AuthProvider.jsx'; import './App.css'; diff --git a/client/src/apiTools.jsx b/client/src/apiTools.jsx index 4cec4b57..d47d4b5f 100644 --- a/client/src/apiTools.jsx +++ b/client/src/apiTools.jsx @@ -42,6 +42,10 @@ export async function fetchPage(number) { return shoofetch(`${apiUrl}/page/${number}`, {method: 'GET'}); } +export async function fetchPageAttributes(number) { + return shoofetch(`${apiUrl}/page/${number}/attributes`, {method: 'GET'}); +} + export async function fetchPageHistory(number) { return shoofetch(`${apiUrl}/page/${number}/history`, {method: 'GET'}); } @@ -50,10 +54,10 @@ export async function fetchPageAtEdit(number, id) { return shoofetch(`${apiUrl}/page/${number}/${id}`, {method: 'GET'}); } -export async function postPage({id, title, description, type}) { - return shoofetch(`${apiUrl}/page/${id}`, { +export async function postPage({number, title, description, type}) { + return shoofetch(`${apiUrl}/page/${number}`, { method: 'POST', - body: JSON.stringify({id: id, title: title, description: description, type: type}), + body: JSON.stringify({number: number, title: title, description: description, type: type}), }) } diff --git a/client/src/embodied/CommandEntry.css b/client/src/embodied/CommandEntry.css index 22f2ef94..ad415b34 100644 --- a/client/src/embodied/CommandEntry.css +++ b/client/src/embodied/CommandEntry.css @@ -10,6 +10,7 @@ left: 20%; right: 20%; border-radius: 1rem; + border: 1px solid gray; } .commandline form { display: flex; @@ -21,7 +22,7 @@ font-size: 16pt; border: none; padding: 2rem; - + border-radius: 1rem; background: transparent; } diff --git a/client/src/embodied/CommandEntry.jsx b/client/src/embodied/CommandEntry.jsx index 84d536d4..cf73a5d2 100644 --- a/client/src/embodied/CommandEntry.jsx +++ b/client/src/embodied/CommandEntry.jsx @@ -1,18 +1,30 @@ +import { forwardRef } from 'react'; import { useForm } from "react-hook-form"; import './CommandEntry.css'; -function CommandEntry({ onSubmit }) { +const CommandEntry = forwardRef(({ setCommand, onSubmitCommand, command }, ref) => { const { register, handleSubmit, setValue } = useForm(); return (
-
{ setValue('command', ""); onSubmit(data.command); })}> - + { onSubmitCommand(data.command); }) + }> + { + setCommand(e.target.value); + setValue("command", e.target.value); + }} + autoFocus + type="text" + placeholder="Enter a command!" + value={command} />
); -} +}); export default CommandEntry; \ No newline at end of file diff --git a/client/src/embodied/Live.jsx b/client/src/embodied/Live.jsx index 5c89726c..805340f9 100644 --- a/client/src/embodied/Live.jsx +++ b/client/src/embodied/Live.jsx @@ -1,70 +1,179 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useParams, useNavigate } from 'react-router-dom'; -import { apiUrl, wsUrl } from '../apiTools.jsx'; +import { apiUrl, wsUrl, fetchPage, fetchPageAttributes, fetchPageAtEdit, postPage, fetchCurrentVerbs } from '../apiTools.jsx'; import { useFixLinks } from '../clientStuff.jsx'; import { useLoggedIn } from '../AuthProvider.jsx'; import useWebSocket, { ReadyState } from 'react-use-websocket'; import Page from '../page/Page.jsx'; +import ExtendedAttributes from '../page/ExtendedAttributes.jsx'; import MessageFeed from './MessageFeed.jsx'; import Sidebar from './Sidebar.jsx'; import CommandEntry from './CommandEntry.jsx'; -function Live({editing, ...props}) { +function Live({...props}) { const navigate = useNavigate(); const loggedIn = useLoggedIn(); + const queryClient = useQueryClient(); + const [ command, setCommand ] = useState(''); const [ messageHistory, setMessageHistory ] = useState([]); const [ currentNumber, setCurrentNumber ] = useState(1); + const [ editing, setEditing ] = useState(false); const [ connecting, setConnecting ] = useState(true); + const commandEntryRef = useRef(null); + + //setting up the websocket and using its data! const { sendMessage, lastMessage, lastJsonMessage, readyState } = useWebSocket(`${wsUrl}/embody`, { - onClose: () => setConnecting(false) + onClose: () => console.log("broke connection!"), + onOpen: () => console.log("opened connection"), + shouldReconnect: (closeEvent) => true, }, connecting); + useEffect(() => console.log(lastMessage), [lastMessage]); + useEffect(() => { - if (lastMessage !== null) { - console.log(lastMessage); - setMessageHistory((prev) => prev.concat(lastMessage)); - - if (lastMessage.data.startsWith("location change to: ")) { - const num = Number(lastMessage.data.replace("location change to: #", "")); - setCurrentNumber(num); - } + if (!(lastJsonMessage === null || lastJsonMessage === undefined)) { + setMessageHistory((prev) => prev.concat(lastJsonMessage)); + //if (lastJsonMessage["error"]) alert(lastJsonMessage["error"]); + if (lastJsonMessage["setPageNumber"]) + setCurrentNumber(Number(lastJsonMessage["setPageNumber"])); } - }, [lastMessage]); + }, [lastJsonMessage, lastMessage]); + + + // first class object data values! + const fetchPageQuery = useQuery({ + queryKey: ['page', currentNumber, null], + queryFn: () => fetchPage(currentNumber), + }); + + // setting up for editing and suchlike + const [title, setTitle] = useState(null); + useEffect(() => { + setTitle(fetchPageQuery.data?.title); + }, [fetchPageQuery.data?.title]); + + const [text, setText] = useState(null); + useEffect(() => { + setText(fetchPageQuery.data?.description); + }, [fetchPageQuery.data?.description]); + + const [type, setType] = useState(false); + useEffect(() => { + setType(fetchPageQuery.data?.type == 1); + }, [fetchPageQuery.data?.type]); + + // extended attribute store! + const fetchAttributesQuery = useQuery({ + queryKey: ['attributes', currentNumber], + queryFn: () => fetchPageAttributes(currentNumber) + }); + + // verbs available to us + const fetchVerbsQuery = useQuery({ + queryKey: ['my verbs'], + queryFn: fetchCurrentVerbs, + }); + + let verbs = fetchVerbsQuery.data || []; + if (!editing) verbs = ["edit this"].concat(verbs); + else verbs = ["save changes"].concat(verbs); + + const postMutation = useMutation({ // for changing the value when we're done with it + mutationFn: postPage, + onSettled: async (data, error, variables) => { + queryClient.invalidateQueries(['page', variables.number, null]) + }, + }); + + function saveChanges() { + postMutation.mutate({ + number: currentNumber, + title: title, + description: text, + type: type ? 1 : 0, + }); + } + + function handleSubmitCommand() { + const c = command.trim(); + if (c == "edit this") setEditing(true); + else if (c == "save changes") { + saveChanges(); + setEditing(false); + } + else sendMessage(c); + + setCommand(""); + } // spread this return value on elements in order to make them navigate const commandLinkClick = (e) => { - if (e.target.tagName != "A") { return; } - if (!e.target.href.includes(window.location.origin)) { return; } + if (e.target.tagName != "A") return; + if (!e.target.href.includes(window.location.origin)) return; + if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return; + + // if it was an tag, if it's to a subdomain of this website, and a modifier wasn't held + // then we should prevent default behavior and execute our own. e.preventDefault(); const localLink = e.target.href.replace(window.location.origin, ""); const targetString = localLink.replace("/", ""); const targetNumber = Number(targetString); - if (targetNumber != 0 && targetNumber != NaN) { - const warpMessage = `warp #${targetString}`; - console.log(`clicked a link, executing "warp #${targetString}"`); + if (targetNumber != 0 && !isNaN(targetNumber)) { + const warpMessage = `warp #${targetNumber}`; + console.log(`clicked a link, executing "warp #${targetNumber}"`); sendMessage(warpMessage); return; } - + navigate(e.target.href.replace(window.location.origin, "")); }; + useEffect(() => { + window.history.replaceState(null, "", window.location.origin + "/" + currentNumber); + }, [currentNumber]); + return ( <> - +
+ setTitle(e.target.value)} + onChangeText={setText} + onChangeType={() => setType(!type)} + {...props} /> + +
- + -