Skip to content

Commit

Permalink
Merge pull request #71 from SolidLabResearch/master
Browse files Browse the repository at this point in the history
🚀
  • Loading branch information
eliasnijs authored May 8, 2024
2 parents 1ce3457 + d43c810 commit 9296f3b
Show file tree
Hide file tree
Showing 37 changed files with 7,495 additions and 2,874 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ This project is in active development. This in an overview of the progress:
- [x] Sending, retrieving and displaying messages
- [x] Watching a video stream
- [x] Video stream syncing and controls
- [ ] Privacy
- [x] Privacy
- [ ] Polishing
2. **Statistics dashboard:**
- *details to be determined*
Expand Down
3 changes: 2 additions & 1 deletion solid-watchparty/github-post-build-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import * as fs from "fs";

const routes = [
"/menu",
"/watch"
"/watch",
"/auth",
];
const dir = viteConfig.build.outDir;
for (const route of routes) {
Expand Down
8,457 changes: 6,026 additions & 2,431 deletions solid-watchparty/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions solid-watchparty/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@comunica/query-sparql-link-traversal": "^0.3.0",
"@comunica/query-sparql-link-traversal-solid": "^0.4.0",
"@comunica/query-sparql-solid": "^2.4.0",
"@incremunica/query-sparql-incremental": "^1.2.0",
"@inrupt/solid-client": "^1.30.2",
Expand All @@ -22,6 +23,7 @@
"clsx": "^2.1.0",
"dashjs": "^4.7.3",
"lucide-react": "^0.363.0",
"microtime": "^3.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-full-screen": "^1.1.1",
Expand Down
6 changes: 4 additions & 2 deletions solid-watchparty/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SessionProvider } from '@inrupt/solid-ui-react'
import LoginPage from './pages/LoginPage';
import MenuPage from './pages/MenuPage';
import WatchPage from './pages/WatchPage';
import LandingPage from './pages/LandingPage';

/* context imports */
import { MessageBoxContext } from './contexts';
Expand All @@ -15,7 +16,8 @@ import { MessageBoxContext } from './contexts';
import config from '../config';

const router = createBrowserRouter([
{path: (config.baseDir + "/"), element: <LoginPage/>},
{path: (config.baseDir + "/"), element: <LandingPage/>},
{path: (config.baseDir + "/auth"), element: <LoginPage/>},
{path: (config.baseDir + "/menu"), element: <MenuPage/>},
{path: (config.baseDir + "/watch"), element: <WatchPage/>},
]);
Expand All @@ -25,7 +27,7 @@ function App() {
return (
<SessionProvider>
<MessageBoxContext.Provider value={messageBox}>
<RouterProvider router={router}/>
<RouterProvider router={router}/>
</MessageBoxContext.Provider>
</SessionProvider>
);
Expand Down
Binary file added solid-watchparty/src/assets/bannerimg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 49 additions & 34 deletions solid-watchparty/src/components/SWChatComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* library imports */
import { useState, useEffect, } from 'react';
import { useState, useEffect, useContext } from 'react';
import { useSession, } from "@inrupt/solid-ui-react";
import PropTypes from 'prop-types';

Expand All @@ -8,6 +8,9 @@ import SWMessageComponent from '../components/SWMessageComponent'
import SWAutoScrollDiv from '../components/SWAutoScrollDiv';
import SWLoadingIcon from '../components/SWLoadingIcon';

/* context imports */
import { MessageBoxContext } from '../contexts';

/* service imports */
import MessageSolidService from '../services/message.solidservice.js'
import UserSolidService from '../services/user.solidservice.js'
Expand All @@ -21,39 +24,54 @@ function SWChatComponent({roomUrl, joined}) {
const [input, setInput] = useState('');
const [messages, setMessages] = useState([]);
const sessionContext = useSession();
const [messageBox,] = useContext(MessageBoxContext);
const [userNames, setUserNames] = useState({});

useEffect(() => {
let messageSeriesStreams = null;
let messageStreams = [];
const fetch = async () => {
messageSeriesStreams = await MessageSolidService.getMessageSeriesStream(sessionContext, roomUrl);
const messageSeriesStreams = await MessageSolidService.getMessageSeriesStream(sessionContext, roomUrl);
if (messageSeriesStreams.error) {
console.error(messageSeriesStreams.error)
messageSeriesStreams = null;
setState({isLoading: false, hasAccess: false});
return;
}
console.log('NOW LISTENING FOR MESSAGE STREAMS')
messageSeriesStreams.on('data', async (data) => {
console.log('NEW MESSAGESTREAM ACQUIRED')
let messageStream = await MessageSolidService.getMessageStream(sessionContext,
data.get('messageSeries').value);
messageStreams.push(messageStream);
if (messageStream.error) {
const messageSeries = data.get('messageSeries').value;

let senderName = "Unknown";
let creatorUrlStream = await MessageSolidService.getMessageSeriesCreatorStream(sessionContext, messageSeries);
creatorUrlStream.on('data', (data) => {
const creatorUrl = data?.get('creator')?.value;
UserSolidService.getName(sessionContext, creatorUrl).then((name) => {
if (!name.error) {
setUserNames((userNames) => {
userNames[messageSeries] = name;
return userNames;
});
}
});
});

// TODO(Elias): Switch out restart of stream when Incremunica has internal handling for this
let messageStreamAuthCheck = await MessageSolidService.getMessageStream(sessionContext, messageSeries);
messageStreamAuthCheck.on('data', async (data) => {
messageStreamAuthCheck.close();
});

let messageStream = await MessageSolidService.getMessageStream(sessionContext, messageSeries);
if (!messageStream || messageStream.error) {
messageStream = null;
return;
}
messageStream.on('data', async (data) => {
let name = await UserSolidService.getName(sessionContext, data.get('sender').value);
if (name.error) {
name = '[Anonymous]';
}
const message = {
text: data.get('text').value,
sender: name,
date: new Date(data.get('dateSent').value),
key: (name + data.get('dateSent').value),
text: data.get('text').value,
messageBoxUrl: messageSeries,
date: new Date(data.get('dateSent').value),
key: (name + data.get('dateSent').value),
};
// TODO: Make this more efficient
setMessages(messages => (
[...messages, message]
.sort((m1, m2) => (m1.date > m2.date) ? 1 : ((m1.date < m2.date) ? -1 : 0))
Expand All @@ -65,18 +83,6 @@ function SWChatComponent({roomUrl, joined}) {
setState({isLoading: false, hasAccess: true});
}
fetch();

return (() => {
if (messageSeriesStreams) {
messageSeriesStreams.close();
}
for (let i = 0; i < messageStreams.length; i++) {
if (messageStreams[i]) {
messageStreams[i].close()
}
}
setMessages([]);
});
}, [sessionContext.session, sessionContext.sessionRequestInProgress, roomUrl, joined])


Expand All @@ -85,7 +91,11 @@ function SWChatComponent({roomUrl, joined}) {
if (input.length === 0) {
return;
}
MessageSolidService.createMessage(sessionContext, input, roomUrl);
MessageSolidService.createMessage(sessionContext, input, roomUrl, messageBox).then((r) => {
if (r.error) {
console.error(r.error);
}
})
setInput('');
}

Expand All @@ -110,13 +120,18 @@ function SWChatComponent({roomUrl, joined}) {
pageContent = (
<>
<SWAutoScrollDiv className="overflow-y-auto overflow-x-auto mb-2 shrink">
{messages.map((message) => <SWMessageComponent message={message} key={message.key}/>)}
{messages.map((message) => {
const sender = userNames[message.messageBoxUrl];
return (
<SWMessageComponent message={{...message, sender}} key={message.key}/>
);
})}
</SWAutoScrollDiv>
<form autoComplete="off" className="grow-0 flex flex-between items-center" onSubmit={submitMessage}>
<input id="msgInput" className="px-2 h-10 rgb-bg-1 sw-border w-full"
<input id="msgInput" className="px-2 h-10 rgb-bg-1 sw-border w-full border-solid"
onChange={(e) => setInput(parseMessage(e.target.value))}
value={input} type='text'/>
<button className="sw-btn hidden"> P </button>
<button className="sw-btn hidden"></button>
</form>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion solid-watchparty/src/components/SWFooter.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function SWFooter()
{
return (
<div className="grow-0 p-8 flex flex-col justify-end text-center">
<div className="p-8 flex flex-col justify-end text-center">
<p className="rgb-2">(c) IDLab 2023</p>
</div>
);
Expand Down
6 changes: 2 additions & 4 deletions solid-watchparty/src/components/SWLoginButton.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
/* libary imports */
import { LoginButton } from '@inrupt/solid-ui-react';
import PropTypes from 'prop-types';
import { FaChevronRight } from 'react-icons/fa';

function SWLoginButton(props)
{
return (
<LoginButton authOptions={props.authOptions} oidcIssuer={props.oidcIssuer}
redirectUrl={props.redirectUrl} onError={console.error}>
<button className={"sw-btn rgb-bg-3 rgb-3" + ' ' + props.className}>Log In</button>
</LoginButton>
<></>
);
}

Expand Down
4 changes: 2 additions & 2 deletions solid-watchparty/src/components/SWMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ MenuBar.propTypes = {
}


export function MenuItem({children, onClick}) {
export function MenuItem({children, onClick, href}) {
return (
<button className="flex flex-row" onClick={onClick}>
<a>{children}</a>
<a href={href}>{children}</a>
</button>
);
}
Expand Down
13 changes: 7 additions & 6 deletions solid-watchparty/src/components/SWMessageComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ import { displayDate } from '../utils/general.js'

function MessageComponent({message})
{
const sender = message.sender ? message.sender : "Name not found";
return (
<div className="pb-2 flex">
<div className="w-6 h-6 m-2 rgb-bg-3 rounded-max">
<div className="pb-2 flex w-[90%]">
<div className="w-6 min-w-6 h-6 m-2 rgb-bg-3 rounded-max">
</div>
<div className="pb-2">
<div className="w-fit w-max flex items-baseline">
<div className="pb-2 w-full">
<div className="w-full flex items-baseline">
<p className="sw-fw-1 mr-2">{message.sender}</p>
<p className="rgb-2 text-sm">{displayDate(message.date)}</p>
</div>
<div className="rgb-1 w-fit w-max">
<p>{message.text}</p>
<div className="rgb-1 w">
<p className="break-words">{message.text}</p>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion solid-watchparty/src/components/SWModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function SWModal({children, className, setIsShown}) {
}
return (
<div className="fixed flex-col top-0 right-0 left-0 bottom-0 flex justify-center items-center z-50">
<div onClick={outsideClicked} className="fixed top-0 right-0 left-0 bottom-0 rgb-bg-1 opacity-80"/>
<div onClick={outsideClicked} className="fixed top-0 right-0 left-0 bottom-0 bg-[#000D]"/>
<div className={className}>
{children}
</div>
Expand Down
77 changes: 77 additions & 0 deletions solid-watchparty/src/components/SWModalInputBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* libary imports */
import { useSession, } from '@inrupt/solid-ui-react';
import { useState, useContext, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { FaMagnifyingGlass } from "react-icons/fa6";
import { FaChevronRight } from 'react-icons/fa';

/* component imports */
import SWPageWrapper from '../components/SWPageWrapper';
import SWLoadingIcon from '../components/SWLoadingIcon';
import SWModal from '../components/SWModal';
import SWRoomPoster from '../components/SWRoomPoster';

/* service imports */
import RoomSolidService from '../services/room.solidservice';
import MessageSolidService from '../services/message.solidservice';

/* context imports */
import { MessageBoxContext } from '../contexts';

/* util imports */
import { validateAll, validateRequired, validateIsUrl, validateLength } from '../utils/validationUtils';
import { displayDate } from '../utils/general';

/* config imports */
import config from '../../config';


function SWModalInputBar({setModalIsShown, title, f, args}) {
const inputRef = useRef(null);

const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");


useEffect(() => {
inputRef.current.focus();
}, [isLoading]);

const onSubmit = async (e) => {
e.preventDefault();
if (isLoading) {
return;
}
setIsLoading(true);
const result = await f({
input: inputRef.current.value,
setError: setError,
...args
});
if (result) {
setError(result);
}
setIsLoading(false);
}

const inputStyle =
(isLoading) ? "sw-input-disabled"
: (error) ? "sw-input-error"
: "sw-input";
return (
<SWModal className="p-12 z-10 w-1/2" setIsShown={setModalIsShown}>
<form onSubmit={onSubmit} className={`p-24 flex w-full items-center justify-between gap-6 border ${inputStyle}`}>
<label className="w-fit sw-fw-1 rgb-1">{title}:</label>
<input className="flex grow" ref={inputRef} onChange={() => setError("")} disabled={isLoading} />
<button className={`sw-btn w-fit`} type="submit">
{ isLoading ? <SWLoadingIcon className="w-4"/> : <FaChevronRight className="w-4 h-4"/> }
</button>
</form>
<div className="h-12 mt-3 rgb-alert sw-fw-1">
<label>{error}</label>
</div>
</SWModal>
);
}

export default SWModalInputBar;
Loading

0 comments on commit 9296f3b

Please sign in to comment.