Compare commits
No commits in common. "958edd755626e94ce5a0e18c73a05b35163860fa" and "9e9491e064f20916fb075b9f67707126b986d176" have entirely different histories.
958edd7556
...
9e9491e064
Binary file not shown.
Binary file not shown.
@ -17,7 +17,6 @@
|
|||||||
"react-app-polyfill": "^0.2.0",
|
"react-app-polyfill": "^0.2.0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-confirm": "^0.1.18",
|
"react-confirm": "^0.1.18",
|
||||||
"react-content-loader": "^6.2.1",
|
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-helmet": "^6.0.0",
|
"react-helmet": "^6.0.0",
|
||||||
"react-joyride": "^2.4.0",
|
"react-joyride": "^2.4.0",
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import api from '.';
|
import api from '.';
|
||||||
import actions from '../actions';
|
|
||||||
import { store } from '..';
|
|
||||||
|
|
||||||
export const groups = {
|
export const groups = {
|
||||||
create(data, success, fail) {
|
create(data, success, fail) {
|
||||||
@ -10,7 +8,6 @@ export const groups = {
|
|||||||
api.authenticatedRequest('DELETE', `/groups/${id}`, null, success, fail);
|
api.authenticatedRequest('DELETE', `/groups/${id}`, null, success, fail);
|
||||||
},
|
},
|
||||||
getMine(success, fail) {
|
getMine(success, fail) {
|
||||||
store.dispatch(actions.groups.request());
|
|
||||||
api.authenticatedRequest('GET', '/groups', null, data => success && success(data.groups), fail);
|
api.authenticatedRequest('GET', '/groups', null, data => success && success(data.groups), fail);
|
||||||
},
|
},
|
||||||
get(id, success, fail) {
|
get(id, success, fail) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Card, List, Dimmer } from 'semantic-ui-react';
|
import { Card, List } from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { BulletList } from 'react-content-loader'
|
|
||||||
import UserChip from './UserChip';
|
import UserChip from './UserChip';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import utils from '../../utils/utils.js';
|
import utils from '../../utils/utils.js';
|
||||||
@ -9,25 +8,23 @@ import utils from '../../utils/utils.js';
|
|||||||
export default function ExploreCard({ count }) {
|
export default function ExploreCard({ count }) {
|
||||||
const [highlightProjects, setHighlightProjects] = useState([]);
|
const [highlightProjects, setHighlightProjects] = useState([]);
|
||||||
const [highlightUsers, setHighlightUsers] = useState([]);
|
const [highlightUsers, setHighlightUsers] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
|
||||||
api.search.discover(count || 3, ({ highlightProjects, highlightUsers }) => {
|
api.search.discover(count || 3, ({ highlightProjects, highlightUsers }) => {
|
||||||
setHighlightProjects(highlightProjects);
|
setHighlightProjects(highlightProjects);
|
||||||
setHighlightUsers(highlightUsers);
|
setHighlightUsers(highlightUsers);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if ((highlightProjects?.length === 0 || highlightUsers?.length === 0)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card fluid>
|
<Card fluid>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<h4>Discover a project</h4>
|
|
||||||
{loading && <BulletList />}
|
|
||||||
{highlightProjects?.length > 0 && <>
|
{highlightProjects?.length > 0 && <>
|
||||||
|
<h4>Discover a project</h4>
|
||||||
<List relaxed>
|
<List relaxed>
|
||||||
{highlightProjects?.map(p =>
|
{highlightProjects.map(p =>
|
||||||
<List.Item key={p._id}>
|
<List.Item key={p._id}>
|
||||||
<List.Icon name='book' size='large' verticalAlign='middle' />
|
<List.Icon name='book' size='large' verticalAlign='middle' />
|
||||||
<List.Content>
|
<List.Content>
|
||||||
@ -38,11 +35,10 @@ export default function ExploreCard({ count }) {
|
|||||||
</List>
|
</List>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
<h4>Find others on {utils.appName()}</h4>
|
|
||||||
{loading && <BulletList />}
|
|
||||||
{highlightUsers?.length > 0 && <>
|
{highlightUsers?.length > 0 && <>
|
||||||
|
<h4>Find others on {utils.appName()}</h4>
|
||||||
<List relaxed>
|
<List relaxed>
|
||||||
{highlightUsers?.map(u =>
|
{highlightUsers.map(u =>
|
||||||
<List.Item key={u._id}>
|
<List.Item key={u._id}>
|
||||||
<List.Content>
|
<List.Content>
|
||||||
<UserChip user={u} className='umami--click--discover-user'/>
|
<UserChip user={u} className='umami--click--discover-user'/>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Modal, Menu, Button, Container, Dropdown } from 'semantic-ui-react';
|
import { Loader, List, Popup, Modal, Grid, Icon, Button, Container, Dropdown } from 'semantic-ui-react';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import actions from '../../actions';
|
import actions from '../../actions';
|
||||||
import utils from '../../utils/utils.js';
|
import utils from '../../utils/utils.js';
|
||||||
@ -10,7 +10,6 @@ import utils from '../../utils/utils.js';
|
|||||||
import logo from '../../images/logo/main.png';
|
import logo from '../../images/logo/main.png';
|
||||||
import UserChip from './UserChip';
|
import UserChip from './UserChip';
|
||||||
import SupporterBadge from './SupporterBadge';
|
import SupporterBadge from './SupporterBadge';
|
||||||
import SearchBar from './SearchBar';
|
|
||||||
|
|
||||||
const StyledNavBar = styled.div`
|
const StyledNavBar = styled.div`
|
||||||
height:60px;
|
height:60px;
|
||||||
@ -19,10 +18,12 @@ const StyledNavBar = styled.div`
|
|||||||
.logo{
|
.logo{
|
||||||
height:40px;
|
height:40px;
|
||||||
margin-top:5px;
|
margin-top:5px;
|
||||||
margin-right: 50px;
|
}
|
||||||
transition: opacity 0.3s;
|
.nav-links{
|
||||||
&:hover{
|
display: flex;
|
||||||
opacity: 0.5;
|
align-items: center;
|
||||||
|
.ui.button{
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.only-mobile{
|
.only-mobile{
|
||||||
@ -32,23 +33,56 @@ const StyledNavBar = styled.div`
|
|||||||
}
|
}
|
||||||
.above-mobile{
|
.above-mobile{
|
||||||
@media only screen and (max-width: 767px) {
|
@media only screen and (max-width: 767px) {
|
||||||
display:none !important;
|
display:none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function NavBar() {
|
const SearchBar = styled.div`
|
||||||
const navigate = useNavigate();
|
background-color:rgba(0,0,0,0.1);
|
||||||
const location = useLocation();
|
padding-left:5px;
|
||||||
|
padding-top: 3px;
|
||||||
|
border: none;
|
||||||
|
border-radius:5px;
|
||||||
|
transition: background-color 0.5s;
|
||||||
|
margin-right:8px;
|
||||||
|
display:inline-block;
|
||||||
|
&:before{
|
||||||
|
display:inline-block;
|
||||||
|
content: '🔍';
|
||||||
|
}
|
||||||
|
&:focus-within{
|
||||||
|
background-color:rgba(250,250,250,0.5);
|
||||||
|
color:rgb(50,50,50);
|
||||||
|
input {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input::placeholder {
|
||||||
|
color:black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input{
|
||||||
|
border:none;
|
||||||
|
background:none;
|
||||||
|
padding:8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function NavBar() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { isAuthenticated, user, groups, helpModalOpen } = useSelector(state => {
|
const { isAuthenticated, user, groups, helpModalOpen, searchPopupOpen, searchTerm, searchResults, searching } = useSelector(state => {
|
||||||
const user = state.users.users.filter(u => state.auth.currentUserId === u._id)[0];
|
const user = state.users.users.filter(u => state.auth.currentUserId === u._id)[0];
|
||||||
const groups = state.groups.groups.filter(g => utils.isInGroup(user, g._id));
|
const groups = state.groups.groups.filter(g => utils.isInGroup(user, g._id));
|
||||||
const { isAuthenticated } = state.auth;
|
const { isAuthenticated } = state.auth;
|
||||||
const { helpModalOpen } = state.app;
|
const { helpModalOpen, searchPopupOpen, searchTerm, searchResults, searching } = state.app;
|
||||||
return { isAuthenticated, user, groups, helpModalOpen };
|
return { isAuthenticated, user, groups, helpModalOpen, searchPopupOpen, searchTerm, searchResults, searching };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(actions.app.openSearchPopup(false));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
const logout = () => api.auth.logout(() => {
|
const logout = () => api.auth.logout(() => {
|
||||||
dispatch(actions.auth.logout());
|
dispatch(actions.auth.logout());
|
||||||
dispatch(actions.users.syncDrift(false))
|
dispatch(actions.users.syncDrift(false))
|
||||||
@ -56,17 +90,77 @@ export default function NavBar() {
|
|||||||
navigate('/');
|
navigate('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
dispatch(actions.app.updateSearching(true));
|
||||||
|
api.search.all(searchTerm, r => dispatch(actions.app.updateSearchResults(r)));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledNavBar>
|
<StyledNavBar>
|
||||||
<Container style={{display:'flex', justifyContent: 'space-between', alignItems: 'center'}}>
|
<Container style={{display:'flex', justifyContent: 'space-between', alignItems: 'center'}}>
|
||||||
<Link to="/"><img alt={`${utils.appName()} logo`} src={logo} className="logo" /></Link>
|
<Link to="/"><img alt={`${utils.appName()} logo`} src={logo} className="logo" /></Link>
|
||||||
<div style={{flex: 1}}>
|
{isAuthenticated
|
||||||
<Menu secondary>
|
? (
|
||||||
<Menu.Item className='above-mobile' as={Link} to='/' name='home' active={location.pathname === '/'} />
|
<div className='nav-links'>
|
||||||
<Menu.Item className='above-mobile' as={Link} to='/explore' name='explore' active={location.pathname === '/explore'} />
|
<Popup basic on='focus' open={searchPopupOpen}
|
||||||
<Menu.Item className='above-mobile' active={location.pathname.startsWith('/groups')} name='Groups'>
|
onOpen={e => dispatch(actions.app.openSearchPopup(true))} onClose={e => dispatch(actions.app.openSearchPopup(false))}
|
||||||
<Dropdown pointing='top left'
|
trigger={<SearchBar><input placeholder='Click to search...' value={searchTerm} onChange={e => dispatch(actions.app.updateSearchTerm(e.target.value))} onKeyDown={e => e.keyCode === 13 && search()} /></SearchBar>}
|
||||||
trigger={<span>Groups</span>}
|
content={<div style={{width: 300}} className='joyride-search'>
|
||||||
|
{!searchResults?.users && !searchResults?.groups ?
|
||||||
|
<small>
|
||||||
|
{searching
|
||||||
|
? <span><Loader size='tiny' inline active style={{marginRight: 10}}/> Searching...</span>
|
||||||
|
: <span>Type something and press enter to search</span>
|
||||||
|
}
|
||||||
|
</small>
|
||||||
|
: <>
|
||||||
|
{(!searchResults.users?.length && !searchResults?.groups?.length && !searchResults?.projects?.length) ?
|
||||||
|
<span><small>No results found</small></span>
|
||||||
|
:
|
||||||
|
<Grid stackable>
|
||||||
|
{searchResults?.users?.length > 0 &&
|
||||||
|
<Grid.Column width={6}>
|
||||||
|
{searchResults?.users?.map(u =>
|
||||||
|
<div style={{marginBottom: 5}}><UserChip user={u} key={u._id} /></div>
|
||||||
|
)}
|
||||||
|
</Grid.Column>
|
||||||
|
}
|
||||||
|
{(searchResults?.projects.length > 0 || searchResults.groups.length > 0) &&
|
||||||
|
<Grid.Column width={10}>
|
||||||
|
<List>
|
||||||
|
{searchResults?.projects?.map(p =>
|
||||||
|
<List.Item key={p._id}>
|
||||||
|
<List.Icon name='book' size='large' verticalAlign='middle' />
|
||||||
|
<List.Content>
|
||||||
|
<List.Header as={Link} to={'/' + p.fullName}>{p.name}</List.Header>
|
||||||
|
<List.Description><UserChip compact user={p.owner} /></List.Description>
|
||||||
|
</List.Content>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
{searchResults?.groups?.map(g =>
|
||||||
|
<List.Item key={g._id}>
|
||||||
|
<List.Icon name='users' size='large' verticalAlign='middle' />
|
||||||
|
<List.Content>
|
||||||
|
<List.Header as={Link} to={`/groups/${g._id}`}>{g.name}</List.Header>
|
||||||
|
<List.Description><small>{g.closed ? <span><Icon name='lock' /> Closed group</span> : <span>Open group</span>}</small></List.Description>
|
||||||
|
</List.Content>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Grid.Column>
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
}</>}
|
||||||
|
</div>} />
|
||||||
|
|
||||||
|
<span className='above-mobile'>
|
||||||
|
<Button as={Link} to="/" size="small" icon='home' basic content='Home' />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{groups.length > 0 &&
|
||||||
|
<span className='above-mobile'>
|
||||||
|
<Dropdown icon={null} direction='left' pointing='top right'
|
||||||
|
trigger={<Button size='small' icon='users' basic content='Groups'/>}
|
||||||
>
|
>
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
<Dropdown.Header icon='users' content='Your groups' />
|
<Dropdown.Header icon='users' content='Your groups' />
|
||||||
@ -77,79 +171,86 @@ export default function NavBar() {
|
|||||||
<Dropdown.Item as={Link} to='/groups/new' icon='plus' content='Create a new group' />
|
<Dropdown.Item as={Link} to='/groups/new' icon='plus' content='Create a new group' />
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Menu.Item>
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
<Menu.Menu position='right'>
|
<span className='above-mobile'>
|
||||||
{isAuthenticated && <>
|
<Button size='small' icon='help' basic onClick={e => dispatch(actions.app.openHelpModal(true))}/>
|
||||||
<Menu.Item className='above-mobile'><SearchBar /></Menu.Item>
|
</span>
|
||||||
<Dropdown direction="left" pointing="top right" icon={null} style={{ marginTop: 10}}
|
|
||||||
trigger={<UserChip user={user} withoutLink avatarOnly />}
|
|
||||||
>
|
|
||||||
<Dropdown.Menu style={{ minWidth: '200px', paddingTop: 10 }}>
|
|
||||||
{user &&
|
|
||||||
<Dropdown.Header as={Link} to={`/${user.username}`}>
|
|
||||||
<UserChip user={user} />
|
|
||||||
</Dropdown.Header>
|
|
||||||
}
|
|
||||||
{user?.isGoldSupporter && <Dropdown.Header><SupporterBadge type='gold' /></Dropdown.Header>}
|
|
||||||
{user?.isSilverSupporter && !user?.isGoldSupporter && <Dropdown.Header><SupporterBadge type='silver' /></Dropdown.Header>}
|
|
||||||
<Dropdown.Divider />
|
|
||||||
<Link to="/" className="item">Projects</Link>
|
|
||||||
{user &&<Link to={`/${user.username}`} className="item">Profile</Link>}
|
|
||||||
<Link to="/settings" className="item">Settings</Link>
|
|
||||||
<Dropdown.Divider />
|
|
||||||
{user?.roles?.indexOf('root') > -1 &&
|
|
||||||
<Dropdown.Item as={Link} to='/root'>Root</Dropdown.Item>
|
|
||||||
}
|
|
||||||
<Dropdown.Item as={Link} to='/docs'>Help</Dropdown.Item>
|
|
||||||
<Dropdown.Item onClick={e => dispatch(actions.app.openHelpModal(true))}>About {utils.appName()}</Dropdown.Item>
|
|
||||||
<Dropdown.Item onClick={logout}>Logout</Dropdown.Item>
|
|
||||||
</Dropdown.Menu>
|
|
||||||
</Dropdown>
|
|
||||||
</>}
|
|
||||||
|
|
||||||
{!isAuthenticated && <>
|
<Dropdown direction="left" pointing="top right" icon={null} style={{marginLeft: 10, marginTop: 5}}
|
||||||
<Menu.Item name='Login' onClick={() => dispatch(actions.auth.openLogin())} />
|
trigger={<UserChip user={user} withoutLink avatarOnly />}
|
||||||
<Menu.Item>
|
>
|
||||||
<Button size='small' color="teal" onClick={() => dispatch(actions.auth.openRegister())}>
|
<Dropdown.Menu style={{ minWidth: '200px', paddingTop: 10 }}>
|
||||||
<span role="img" aria-label="wave">👋</span> Sign-up
|
{user &&
|
||||||
</Button>
|
<Dropdown.Header as={Link} to={`/${user.username}`}>
|
||||||
</Menu.Item>
|
<UserChip user={user} />
|
||||||
</>}
|
</Dropdown.Header>
|
||||||
</Menu.Menu>
|
}
|
||||||
</Menu>
|
{user?.isGoldSupporter && <Dropdown.Header><SupporterBadge type='gold' /></Dropdown.Header>}
|
||||||
</div>
|
{user?.isSilverSupporter && !user?.isGoldSupporter && <Dropdown.Header><SupporterBadge type='silver' /></Dropdown.Header>}
|
||||||
</Container>
|
<Dropdown.Divider />
|
||||||
<AboutModal open={helpModalOpen} onClose={e => dispatch(actions.app.openHelpModal(false))} />
|
<Link to="/" className="item">Projects</Link>
|
||||||
</StyledNavBar>
|
{user &&<Link to={`/${user.username}`} className="item">Profile</Link>}
|
||||||
);
|
<Link to="/settings" className="item">Settings</Link>
|
||||||
}
|
<Dropdown.Divider />
|
||||||
|
{user?.roles?.indexOf('root') > -1 &&
|
||||||
|
<Dropdown.Item as={Link} to='/root'>Root</Dropdown.Item>
|
||||||
|
}
|
||||||
|
<Dropdown.Item as={Link} to='/docs'>Help</Dropdown.Item>
|
||||||
|
<Dropdown.Item onClick={logout}>Logout</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div className='nav-links'>
|
||||||
|
<span className="only-mobile">
|
||||||
|
<Dropdown
|
||||||
|
icon={null}
|
||||||
|
trigger={<Button basic icon="bars" />}
|
||||||
|
>
|
||||||
|
<Dropdown.Menu direction="left">
|
||||||
|
<Dropdown.Item onClick={() => dispatch(actions.auth.openLogin())}>Login</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
</span>
|
||||||
|
<span className="above-mobile">
|
||||||
|
<Button basic color='teal' onClick={() => dispatch(actions.auth.openLogin())}>Login</Button>
|
||||||
|
</span>
|
||||||
|
<Button color="teal" onClick={() => dispatch(actions.auth.openRegister())}>
|
||||||
|
<span role="img" aria-label="wave">👋</span> Sign-up
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Modal open={helpModalOpen} onClose={e => dispatch(actions.app.openHelpModal(false))}>
|
||||||
|
<Modal.Header>Welcome to {utils.appName()}!</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<h3>Introduction</h3>
|
||||||
|
<p>{utils.appName()} has been designed as a resource for weavers – not only for those working alone as individuals, but also for groups who wish to share ideas, design inspirations and weaving patterns. It is ideal for those looking for a depository to store their individual work, and also for groups such as guilds, teaching groups, or any other collaborative working partnerships.</p>
|
||||||
|
<p>Projects can be created within {utils.appName()} using the integral WIF-compatible draft editor, or alternatively files can be imported from other design software along with supporting images and other information you may wish to be saved within the project file. Once complete, projects may be stored privately, shared within a closed group, or made public for other {utils.appName()} users to see. The choice is yours!</p>
|
||||||
|
|
||||||
function AboutModal({ open, onClose }) {
|
<h3>Getting started</h3>
|
||||||
return (
|
<p><strong>Creating a profile:</strong> You can add a picture, links to a personal website, and other social media accounts to tell others more about yourself.</p>
|
||||||
<Modal open={open} onClose={e => onClose()}>
|
<p><strong>Creating a group:</strong> You have the option to do things alone, or create a group. By clicking on the ‘Create a group’ button, you can name your group, and then invite members via email or directly through {utils.appName()} if they are existing {utils.appName()} users.</p>
|
||||||
<Modal.Header>Welcome to {utils.appName()}!</Modal.Header>
|
<p><strong>Creating a new project:</strong> When you are ready to create/store a project on the system, you are invited to give the project a name, and a brief description. You will then be taken to a ‘Welcome to your project’ screen, where if you click on ‘add something’, you have the option of creating a new weaving pattern directly inside {utils.appName()} or you can simply import a WIF file from your preferred weaving software. Once imported, you can perform further editing within {utils.appName()}, or you can add supporting picture files and any other additional information you wish to keep (eg weaving notes, yarn details etc).</p>
|
||||||
<Modal.Content>
|
<p>Once complete you then have the option of saving the file privately, shared within a group, or made public for other {utils.appName()} users to see.</p>
|
||||||
<h3>Introduction</h3>
|
|
||||||
<p>{utils.appName()} has been designed as a resource for weavers – not only for those working alone as individuals, but also for groups who wish to share ideas, design inspirations and weaving patterns. It is ideal for those looking for a depository to store their individual work, and also for groups such as guilds, teaching groups, or any other collaborative working partnerships.</p>
|
|
||||||
<p>Projects can be created within {utils.appName()} using the integral WIF-compatible draft editor, or alternatively files can be imported from other design software along with supporting images and other information you may wish to be saved within the project file. Once complete, projects may be stored privately, shared within a closed group, or made public for other {utils.appName()} users to see. The choice is yours!</p>
|
|
||||||
|
|
||||||
<h3>Getting started</h3>
|
<h3>Help and support</h3>
|
||||||
<p><strong>Creating a profile:</strong> You can add a picture, links to a personal website, and other social media accounts to tell others more about yourself.</p>
|
<p>The documentation provides useful information that might help you if you get stuck.</p>
|
||||||
<p><strong>Creating a group:</strong> You have the option to do things alone, or create a group. By clicking on the ‘Create a group’ button, you can name your group, and then invite members via email or directly through {utils.appName()} if they are existing {utils.appName()} users.</p>
|
<Button as='a' href='/docs' target='_blank' rel='noopener noreferrer'>View the docs</Button>
|
||||||
<p><strong>Creating a new project:</strong> When you are ready to create/store a project on the system, you are invited to give the project a name, and a brief description. You will then be taken to a ‘Welcome to your project’ screen, where if you click on ‘add something’, you have the option of creating a new weaving pattern directly inside {utils.appName()} or you can simply import a WIF file from your preferred weaving software. Once imported, you can perform further editing within {utils.appName()}, or you can add supporting picture files and any other additional information you wish to keep (eg weaving notes, yarn details etc).</p>
|
|
||||||
<p>Once complete you then have the option of saving the file privately, shared within a group, or made public for other {utils.appName()} users to see.</p>
|
|
||||||
|
|
||||||
<h3>Help and support</h3>
|
<h3>We hope you enjoy using {utils.appName()}</h3>
|
||||||
<p>The documentation provides useful information that might help you if you get stuck.</p>
|
<p>If you have any comments or feedback please tell us by emailing <a href={`mailTo:${import.meta.env.VITE_CONTACT_EMAIL}`}>{import.meta.env.VITE_CONTACT_EMAIL}</a>!</p>
|
||||||
<Button as='a' href='/docs' target='_blank' rel='noopener noreferrer'>View the docs</Button>
|
</Modal.Content>
|
||||||
|
<Modal.Actions>
|
||||||
|
<Button onClick={e => dispatch(actions.app.openHelpModal(false))} color='teal' icon='check' content='OK' />
|
||||||
|
</Modal.Actions>
|
||||||
|
</Modal>
|
||||||
|
</Container>
|
||||||
|
</StyledNavBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
<h3>We hope you enjoy using {utils.appName()}</h3>
|
export default NavBar;
|
||||||
<p>If you have any comments or feedback please tell us by emailing <a href={`mailTo:${import.meta.env.VITE_CONTACT_EMAIL}`}>{import.meta.env.VITE_CONTACT_EMAIL}</a>!</p>
|
|
||||||
</Modal.Content>
|
|
||||||
<Modal.Actions>
|
|
||||||
<Button onClick={e => onClose()} color='teal' icon='check' content='OK' />
|
|
||||||
</Modal.Actions>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card } from 'semantic-ui-react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import UserChip from './UserChip';
|
|
||||||
|
|
||||||
export default function PatternCard({ object, project, user }) {
|
|
||||||
if (!object) return null;
|
|
||||||
return (
|
|
||||||
<Card raised key={object._id} style={{ cursor: 'pointer' }} as={Link} to={`/${user?.username}/${project?.path}/${object._id}`}>
|
|
||||||
<div style={{ height: 200, backgroundImage: `url(${object.preview})`, backgroundSize: 'cover', backgroundPosition: 'top right', position: 'relative' }}>
|
|
||||||
{user &&
|
|
||||||
<div style={{position: 'absolute', top: 5, left: 5, padding: '3px 6px', background: 'rgba(250,250,250,0.8)', borderRadius: 5}}>
|
|
||||||
<UserChip user={user} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<Card.Content>
|
|
||||||
<p style={{ wordBreak: 'break-all' }}>{object.name}</p>
|
|
||||||
{project?.path &&
|
|
||||||
<p style={{fontSize: 11, color: 'black'}}>{project.path}</p>
|
|
||||||
}
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Instagram, List } from 'react-content-loader';
|
|
||||||
import { Card } from 'semantic-ui-react';
|
|
||||||
|
|
||||||
export default function PatternLoader({ count, isCompact }) {
|
|
||||||
if (!count) count = 1;
|
|
||||||
return (<>
|
|
||||||
{[...new Array(count)].map((item, index) =>
|
|
||||||
<Card key={index}>{isCompact ? <Card.Content><List /></Card.Content> : <Instagram />}</Card>
|
|
||||||
)}
|
|
||||||
</>);
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Popup, Loader, Grid, List, Input } from 'semantic-ui-react';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import actions from '../../actions';
|
|
||||||
import api from '../../api';
|
|
||||||
|
|
||||||
import UserChip from './UserChip';
|
|
||||||
|
|
||||||
const StyledSearchBar = styled.div`
|
|
||||||
background-color:rgba(0,0,0,0.1);
|
|
||||||
padding:5px;
|
|
||||||
border: none;
|
|
||||||
border-radius:5px;
|
|
||||||
transition: background-color 0.5s;
|
|
||||||
&:focus-within{
|
|
||||||
background-color:rgba(250,250,250,0.5);
|
|
||||||
}
|
|
||||||
input::placeholder {
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function SearchBar() {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { searchPopupOpen, searchTerm, searchResults, searching } = useSelector(state => {
|
|
||||||
const { searchPopupOpen, searchTerm, searchResults, searching } = state.app;
|
|
||||||
return { searchPopupOpen, searchTerm, searchResults, searching };
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(actions.app.openSearchPopup(false));
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
const search = () => {
|
|
||||||
dispatch(actions.app.updateSearching(true));
|
|
||||||
api.search.all(searchTerm, r => dispatch(actions.app.updateSearchResults(r)));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popup basic on='focus' open={searchPopupOpen}
|
|
||||||
onOpen={e => dispatch(actions.app.openSearchPopup(true))} onClose={e => dispatch(actions.app.openSearchPopup(false))}
|
|
||||||
trigger={
|
|
||||||
<StyledSearchBar><Input transparent size='small' placeholder='Search...' icon='search' iconPosition='left' value={searchTerm} onChange={e => dispatch(actions.app.updateSearchTerm(e.target.value))} onKeyDown={e => e.keyCode === 13 && search()} /></StyledSearchBar>
|
|
||||||
}
|
|
||||||
content={<div style={{width: 300}} className='joyride-search'>
|
|
||||||
{!searchResults?.users && !searchResults?.groups ?
|
|
||||||
<small>
|
|
||||||
{searching
|
|
||||||
? <span><Loader size='tiny' inline active style={{marginRight: 10}}/> Searching...</span>
|
|
||||||
: <span>Type something and press enter to search</span>
|
|
||||||
}
|
|
||||||
</small>
|
|
||||||
: <>
|
|
||||||
{(!searchResults.users?.length && !searchResults?.groups?.length && !searchResults?.projects?.length) ?
|
|
||||||
<span><small>No results found</small></span>
|
|
||||||
:
|
|
||||||
<Grid stackable>
|
|
||||||
{searchResults?.users?.length > 0 &&
|
|
||||||
<Grid.Column width={6}>
|
|
||||||
{searchResults?.users?.map(u =>
|
|
||||||
<div style={{marginBottom: 5}}><UserChip user={u} key={u._id} /></div>
|
|
||||||
)}
|
|
||||||
</Grid.Column>
|
|
||||||
}
|
|
||||||
{(searchResults?.projects.length > 0 || searchResults.groups.length > 0) &&
|
|
||||||
<Grid.Column width={10}>
|
|
||||||
<List>
|
|
||||||
{searchResults?.projects?.map(p =>
|
|
||||||
<List.Item key={p._id}>
|
|
||||||
<List.Icon name='book' size='large' verticalAlign='middle' />
|
|
||||||
<List.Content>
|
|
||||||
<List.Header as={Link} to={'/' + p.fullName}>{p.name}</List.Header>
|
|
||||||
<List.Description><UserChip compact user={p.owner} /></List.Description>
|
|
||||||
</List.Content>
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
{searchResults?.groups?.map(g =>
|
|
||||||
<List.Item key={g._id}>
|
|
||||||
<List.Icon name='users' size='large' verticalAlign='middle' />
|
|
||||||
<List.Content>
|
|
||||||
<List.Header as={Link} to={`/groups/${g._id}`}>{g.name}</List.Header>
|
|
||||||
<List.Description><small>{g.closed ? <span><Icon name='lock' /> Closed group</span> : <span>Open group</span>}</small></List.Description>
|
|
||||||
</List.Content>
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Grid.Column>
|
|
||||||
}
|
|
||||||
</Grid>
|
|
||||||
}</>}
|
|
||||||
</div>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -3,7 +3,6 @@ import { Helmet } from 'react-helmet';
|
|||||||
import { Loader, Divider, Button, Message, Container, Segment, Grid, Card, Icon, List } from 'semantic-ui-react';
|
import { Loader, Divider, Button, Message, Container, Segment, Grid, Card, Icon, List } from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { BulletList } from 'react-content-loader'
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import actions from '../../actions';
|
import actions from '../../actions';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
@ -12,19 +11,18 @@ import utils from '../../utils/utils.js';
|
|||||||
import UserChip from '../includes/UserChip';
|
import UserChip from '../includes/UserChip';
|
||||||
import HelpLink from '../includes/HelpLink';
|
import HelpLink from '../includes/HelpLink';
|
||||||
import ProjectCard from '../includes/ProjectCard';
|
import ProjectCard from '../includes/ProjectCard';
|
||||||
import PatternLoader from '../includes/PatternLoader';
|
|
||||||
import Tour from '../includes/Tour';
|
import Tour from '../includes/Tour';
|
||||||
import DiscoverCard from '../includes/DiscoverCard';
|
import DiscoverCard from '../includes/DiscoverCard';
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const [runJoyride, setRunJoyride] = useState(false);
|
const [runJoyride, setRunJoyride] = useState(false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { user, projects, groups, invitations, loadingProjects, loadingGroups } = useSelector(state => {
|
const { user, projects, groups, invitations, loadingProjects } = useSelector(state => {
|
||||||
const user = state.users.users.filter(u => state.auth.currentUserId === u._id)[0];
|
const user = state.users.users.filter(u => state.auth.currentUserId === u._id)[0];
|
||||||
const groups = state.groups.groups.filter(g => utils.isInGroup(user, g._id));
|
const groups = state.groups.groups.filter(g => utils.isInGroup(user, g._id));
|
||||||
const invitations = state.invitations.invitations.filter(i => i.recipient === user?._id);
|
const invitations = state.invitations.invitations.filter(i => i.recipient === user?._id);
|
||||||
const projects = state.projects.projects.filter(p => p.user === user?._id);
|
const projects = state.projects.projects.filter(p => p.user === user?._id);
|
||||||
return { user, projects, groups, invitations, loadingProjects: state.projects.loading, loadingGroups: state.groups.loading };
|
return { user, projects, groups, invitations, loadingProjects: state.projects.loading };
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -91,48 +89,41 @@ function Home() {
|
|||||||
|
|
||||||
<DiscoverCard count={3} />
|
<DiscoverCard count={3} />
|
||||||
|
|
||||||
<Card fluid className='joyride-groups' style={{opacity: 0.8}}>
|
{(groups && groups.length) ?
|
||||||
<Card.Content>
|
<Card fluid className='joyride-groups' style={{opacity: 0.8}}>
|
||||||
<Card.Header>Your groups</Card.Header>
|
<Card.Content>
|
||||||
|
<Card.Header>Your groups</Card.Header>
|
||||||
|
|
||||||
{(loadingGroups && !groups?.length) ?
|
<List relaxed>
|
||||||
<div>
|
{groups.map(g =>
|
||||||
<BulletList />
|
<List.Item key={g._id}>
|
||||||
<BulletList />
|
<List.Icon name='users' size='large' verticalAlign='middle' />
|
||||||
</div>
|
<List.Content>
|
||||||
:
|
<List.Header as={Link} to={`/groups/${g._id}`}>{g.name}</List.Header>
|
||||||
(groups?.length > 0 ?
|
<List.Description>{utils.isGroupAdmin(user, g) ? 'Administrator' : 'Member'}</List.Description>
|
||||||
<List relaxed>
|
</List.Content>
|
||||||
{groups.map(g =>
|
</List.Item>
|
||||||
<List.Item key={g._id}>
|
)}
|
||||||
<List.Icon name='users' size='large' verticalAlign='middle' />
|
</List>
|
||||||
<List.Content>
|
<Button className='joyride-createGroup' fluid size='small' icon='plus' content='Create a new group' as={Link} to='/groups/new' />
|
||||||
<List.Header as={Link} to={`/groups/${g._id}`}>{g.name}</List.Header>
|
<HelpLink link={`/docs/groups`} text='Learn more about groups' marginTop/>
|
||||||
<List.Description>{utils.isGroupAdmin(user, g) ? 'Administrator' : 'Member'}</List.Description>
|
</Card.Content>
|
||||||
</List.Content>
|
</Card>
|
||||||
</List.Item>
|
:
|
||||||
)}
|
<Message>
|
||||||
</List>
|
<Message.Header>Groups</Message.Header>
|
||||||
:
|
<p>Groups enable you to build communities of weavers and makers with similar interests. Create one for your weaving group or class today.</p>
|
||||||
<Card.Description>
|
<Button className='joyride-createGroup' as={Link} to='/groups/new' size='small' color='purple' icon='plus' content='Create a group' />
|
||||||
Groups enable you to join or build communities of weavers and makers with similar interests.
|
</Message>
|
||||||
</Card.Description>
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
<Divider hidden />
|
|
||||||
<Button className='joyride-createGroup' fluid size='small' icon='plus' content='Create a new group' as={Link} to='/groups/new' />
|
|
||||||
<HelpLink link={`/docs/groups`} text='Learn more about groups' marginTop/>
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{(import.meta.env.VITE_PATREON_URL || import.meta.env.VITE_KOFI_URL) &&
|
{(import.meta.env.VITE_PATREON_URL || import.meta.env.VITE_KOFI_URL) &&
|
||||||
<Card fluid style={{opacity: 0.8}}>
|
<Card fluid style={{opacity: 0.8}}>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<Card.Header><span role="img" aria-label="Dancer">🕺</span> Support {utils.appName()}</Card.Header>
|
<Card.Header><span role="img" aria-label="Dancer">🕺</span> Support {utils.appName()}</Card.Header>
|
||||||
<Card.Description>{utils.appName()} is offered free of charge, but costs money to run and build. If you get value out of {utils.appName()} you may like to consider supporting it.</Card.Description>
|
<Card.Description>{utils.appName()} is offered free of charge, but costs money to run and build. If you get value out of {utils.appName()} you may like to consider supporting it.</Card.Description>
|
||||||
<Divider hidden />
|
|
||||||
{import.meta.env.VITE_KOFI_URL &&
|
{import.meta.env.VITE_KOFI_URL &&
|
||||||
<Button size='small' fluid as='a' href={import.meta.env.VITE_KOFI_URL} target='_blank' rel='noopener noreferrer' className='umami--click--kofi-button'><span role='img' aria-label='Coffee' style={{marginRight: 5}}>☕️</span> Buy me a coffee</Button>
|
<Button style={{marginTop: 10}} size='small' fluid as='a' href={import.meta.env.VITE_KOFI_URL} target='_blank' rel='noopener noreferrer' className='umami--click--kofi-button'><span role='img' aria-label='Coffee' style={{marginRight: 5}}>☕️</span> Buy me a coffee</Button>
|
||||||
}
|
}
|
||||||
{import.meta.env.VITE_PATREON_URL &&
|
{import.meta.env.VITE_PATREON_URL &&
|
||||||
<Button style={{marginTop: 10}} size='small' fluid as='a' href={import.meta.env.VITE_PATREON_URL} target='_blank' rel='noopener noreferrer' className='umami--click--patreon-button'><span role='img' aria-label='Party' style={{marginRight: 5}}>🥳</span> Become a patron</Button>
|
<Button style={{marginTop: 10}} size='small' fluid as='a' href={import.meta.env.VITE_PATREON_URL} target='_blank' rel='noopener noreferrer' className='umami--click--patreon-button'><span role='img' aria-label='Party' style={{marginRight: 5}}>🥳</span> Become a patron</Button>
|
||||||
@ -144,39 +135,42 @@ function Home() {
|
|||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
|
|
||||||
<Grid.Column computer={11} className='joyride-projects'>
|
<Grid.Column computer={11} className='joyride-projects'>
|
||||||
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
|
{loadingProjects && !projects.length &&
|
||||||
<h2><Icon name='book' /> Your projects</h2>
|
|
||||||
<div><Button className='joyride-createProject' as={Link} to="/projects/new" color='teal' content='Create a project' icon='plus' /></div>
|
|
||||||
</div>
|
|
||||||
<p>Projects contain the patterns and files that make up your creations.
|
|
||||||
<HelpLink className='joyride-help' link={`/docs/projects`} text='Learn more about projects' marginLeft/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Divider hidden />
|
|
||||||
|
|
||||||
{loadingProjects && !projects?.length &&
|
|
||||||
<Card.Group itemsPerRow={2} stackable>
|
|
||||||
<PatternLoader isCompact count={3} />
|
|
||||||
</Card.Group>
|
|
||||||
}
|
|
||||||
|
|
||||||
{user && !loadingProjects && !projects?.length &&
|
|
||||||
<div style={{textAlign: 'center'}}>
|
<div style={{textAlign: 'center'}}>
|
||||||
<Segment placeholder textAlign='center'>
|
<h4>Loading your projects...</h4>
|
||||||
<h3>On {utils.appName()}, your patterns and files are stored in <strong><span role="img" aria-label="box">📦</span> projects</strong></h3>
|
<Loader active inline="centered" />
|
||||||
<p>Projects can contain anything: from rough ideas or design experiments through to commissions and exhibitions. Treat them as if they were just <span role="img" aria-label="folder">📁</span> folders on your computer.</p>
|
|
||||||
<Divider section hidden />
|
|
||||||
<h4>Start by creating your first project. You can keep it private if you prefer.</h4>
|
|
||||||
|
|
||||||
<Button className='joyride-createProject' as={Link} to="/projects/new" color="teal" icon="plus" content="Create a project" />
|
|
||||||
</Segment>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{projects?.length > 0 &&
|
{user && !loadingProjects && (!projects || !projects.length) &&
|
||||||
|
<div style={{textAlign: 'center'}}>
|
||||||
|
<h1>
|
||||||
|
<span role="img" aria-label="chequered flag">🚀</span> Let's get started
|
||||||
|
</h1>
|
||||||
|
<Divider hidden/>
|
||||||
|
<Segment placeholder textAlign='center'>
|
||||||
|
<h3>On {utils.appName()}, your patterns and files are stored in <strong><span role="img" aria-label="box">📦</span> projects</strong></h3>
|
||||||
|
<p>Projects can contain anything: from rough ideas or design experiments through to commissions and exhibitions. Treat them as if they were just <span role="img" aria-label="folder">📁</span> folders on your computer.</p>
|
||||||
|
<p><HelpLink className='joyride-help' link={`/docs/projects`} text='Learn more about projects' marginTop/></p>
|
||||||
|
<Divider />
|
||||||
|
<h4>Start by creating a new project. Don't worry, you can keep it private.</h4>
|
||||||
|
|
||||||
|
<Button className='joyride-createProject' as={Link} to="/projects/new" color="teal" icon="plus" content="Create a project" />
|
||||||
|
</Segment>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{projects && projects.length > 0 &&
|
||||||
<div>
|
<div>
|
||||||
|
<Button className='joyride-createProject' as={Link} to="/projects/new" color='teal' content='Create a project' icon='plus' floated='right'/>
|
||||||
|
<h2><Icon name='book' /> Your projects</h2>
|
||||||
|
<p>Projects contain the patterns and files that make up your creations.
|
||||||
|
<HelpLink className='joyride-help' link={`/docs/projects`} text='Learn more about projects' marginLeft/>
|
||||||
|
</p>
|
||||||
|
<Divider clearing hidden />
|
||||||
<Card.Group itemsPerRow={2} stackable>
|
<Card.Group itemsPerRow={2} stackable>
|
||||||
{projects.map(proj => (
|
{projects && projects.map(proj => (
|
||||||
<ProjectCard key={proj._id} project={proj} />
|
<ProjectCard key={proj._id} project={proj} />
|
||||||
))}
|
))}
|
||||||
</Card.Group>
|
</Card.Group>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Container, Card, Grid, Button } from 'semantic-ui-react';
|
import { Container, Card, Grid, Button } from 'semantic-ui-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import actions from '../../../actions';
|
import actions from '../../../actions';
|
||||||
import api from '../../../api';
|
import api from '../../../api';
|
||||||
import utils from '../../../utils/utils.js';
|
import utils from '../../../utils/utils.js';
|
||||||
|
|
||||||
|
import UserChip from '../../includes/UserChip';
|
||||||
import DiscoverCard from '../../includes/DiscoverCard';
|
import DiscoverCard from '../../includes/DiscoverCard';
|
||||||
import PatternCard from '../../includes/PatternCard';
|
|
||||||
import PatternLoader from '../../includes/PatternLoader';
|
|
||||||
import DraftPreview from '../projects/objects/DraftPreview';
|
import DraftPreview from '../projects/objects/DraftPreview';
|
||||||
|
|
||||||
export default function Explore() {
|
export default function Explore() {
|
||||||
@ -18,9 +18,9 @@ export default function Explore() {
|
|||||||
return { objects: state.objects.exploreObjects, page: state.objects.explorePage };
|
return { objects: state.objects.exploreObjects, page: state.objects.explorePage };
|
||||||
});
|
});
|
||||||
|
|
||||||
/*useEffect(() => {
|
useEffect(() => {
|
||||||
if (page < 2) loadMoreExplore();
|
if (page < 2) loadMoreExplore();
|
||||||
}, []);*/
|
}, []);
|
||||||
|
|
||||||
function loadMoreExplore() {
|
function loadMoreExplore() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -41,14 +41,25 @@ export default function Explore() {
|
|||||||
|
|
||||||
<Card.Group stackable doubling itemsPerRow={3} style={{marginTop: 30}}>
|
<Card.Group stackable doubling itemsPerRow={3} style={{marginTop: 30}}>
|
||||||
{objects?.filter(o => o.projectObject && o.userObject).map(object =>
|
{objects?.filter(o => o.projectObject && o.userObject).map(object =>
|
||||||
<PatternCard key={object._id} object={object} project={object.projectObject} user={object.userObject} />
|
<Card raised key={object._id} style={{ cursor: 'pointer' }} as={Link} to={`/${object.userObject?.username}/${object.projectObject?.path}/${object._id}`}>
|
||||||
|
<div style={{ height: 200, backgroundImage: `url(${object.preview})`, backgroundSize: 'cover', backgroundPosition: 'top right', position: 'relative' }}>
|
||||||
|
{object.userObject &&
|
||||||
|
<div style={{position: 'absolute', top: 5, left: 5, padding: 3, background: 'rgba(250,250,250,0.5)', borderRadius: 5}}>
|
||||||
|
<UserChip user={object.userObject} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Card.Content>
|
||||||
|
<p style={{ wordBreak: 'break-all' }}>{object.name}</p>
|
||||||
|
{object.projectObject?.path &&
|
||||||
|
<p style={{fontSize: 11, color: 'black'}}>{object.projectObject.path}</p>
|
||||||
|
}
|
||||||
|
</Card.Content>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
{objects?.length === 0 && <>
|
|
||||||
<PatternLoader count={6} />
|
|
||||||
</>}
|
|
||||||
</Card.Group>
|
</Card.Group>
|
||||||
<div style={{display: 'flex', justifyContent: 'center', marginTop: 30}}>
|
<div style={{display: 'flex', justifyContent: 'center', marginTop: 30}}>
|
||||||
<Button loading={loading} onClick={loadMoreExplore}>View more</Button>
|
<Button loading={loading} onClick={loadMoreExplore}>Load more</Button>
|
||||||
</div>
|
</div>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -137,11 +137,10 @@ function ObjectViewer() {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
{user &&
|
{user &&
|
||||||
<Dropdown direction='left' icon={null} trigger={<Button size="small" icon="copy" secondary content="Copy to.." />}>
|
<Dropdown icon={null} trigger={<Button size="small" icon="copy" secondary content="Copy to.." />}>
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
<Dropdown.Header>Select a project to copy this pattern to</Dropdown.Header>
|
<Dropdown.Header>Select a project to copy this pattern to</Dropdown.Header>
|
||||||
{myProjects?.map(myProject => <Dropdown.Item content={myProject.name} onClick={e => copyPattern(myProject)} />)}
|
{myProjects?.map(myProject => <Dropdown.Item content={myProject.name} onClick={e => copyPattern(myProject)} />)}
|
||||||
{myProjects?.length === 0 && <Dropdown.Item>You don't have any projects.</Dropdown.Item>}
|
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { Divider, Grid, Button, Container, Card } from 'semantic-ui-react';
|
import { Divider, Grid, Button, Container } from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import utils from '../../utils/utils.js';
|
import utils from '../../utils/utils.js';
|
||||||
|
|
||||||
import PatternCard from '../includes/PatternCard';
|
|
||||||
import PatternLoader from '../includes/PatternLoader';
|
|
||||||
import projectImage from '../../images/project.png';
|
import projectImage from '../../images/project.png';
|
||||||
import toolsImage from '../../images/tools.png';
|
import toolsImage from '../../images/tools.png';
|
||||||
import filesImage from '../../images/files.png';
|
import filesImage from '../../images/files.png';
|
||||||
@ -15,14 +11,10 @@ import filesImage from '../../images/files.png';
|
|||||||
const StyledHero = styled.div`
|
const StyledHero = styled.div`
|
||||||
background: linen;
|
background: linen;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
padding-top: 50px;
|
margin-top: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function MarketingHome({ onRegisterClicked }) {
|
function MarketingHome({ onRegisterClicked }) {
|
||||||
const { objects } = useSelector(state => {
|
|
||||||
return { objects: state.objects.exploreObjects };
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet title='Home' />
|
<Helmet title='Home' />
|
||||||
@ -63,25 +55,6 @@ function MarketingHome({ onRegisterClicked }) {
|
|||||||
</Container>
|
</Container>
|
||||||
</StyledHero>
|
</StyledHero>
|
||||||
|
|
||||||
<div style={{paddingTop: 75, marginBottom: 75, background: 'linear-gradient(linen, rgb(255,251,248)'}}>
|
|
||||||
<Container>
|
|
||||||
<div style={{display: 'flex', justifyContent:'space-between', alignItems: 'center'}}>
|
|
||||||
<h2>See what people have been creating</h2>
|
|
||||||
<Button size='large' as={Link} to='/explore' primary>Explore {utils.appName()}</Button>
|
|
||||||
</div>
|
|
||||||
<Card.Group stackable doubling itemsPerRow={3} style={{marginTop: 30}}>
|
|
||||||
{objects?.length > 0 &&
|
|
||||||
(objects?.filter(o => o.projectObject && o.userObject).slice(0, 2).map(object =>
|
|
||||||
<PatternCard object={object} project={object.projectObject} user={object.userObject} />
|
|
||||||
))
|
|
||||||
}
|
|
||||||
{objects?.length === 0 && <>
|
|
||||||
<PatternLoader count={3} />
|
|
||||||
</>}
|
|
||||||
</Card.Group>
|
|
||||||
</Container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Container style={{ marginTop: 50 }}>
|
<Container style={{ marginTop: 50 }}>
|
||||||
<Grid stackable centered>
|
<Grid stackable centered>
|
||||||
<Grid.Column computer={6} textAlign="right">
|
<Grid.Column computer={6} textAlign="right">
|
||||||
|
@ -2881,15 +2881,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-content-loader@npm:^6.2.1":
|
|
||||||
version: 6.2.1
|
|
||||||
resolution: "react-content-loader@npm:6.2.1"
|
|
||||||
peerDependencies:
|
|
||||||
react: ">=16.0.0"
|
|
||||||
checksum: f777d408256a4218677e47f4cf3988d9fd8e556450e9b85ee1eb3952a5d5802573cea0df5eaf4dbc936c9522f355657de6f8ab0ecdf035d7dccdef15b45c9dae
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"react-dom@npm:^18.2.0":
|
"react-dom@npm:^18.2.0":
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
resolution: "react-dom@npm:18.2.0"
|
resolution: "react-dom@npm:18.2.0"
|
||||||
@ -3573,7 +3564,6 @@ __metadata:
|
|||||||
react-app-polyfill: ^0.2.0
|
react-app-polyfill: ^0.2.0
|
||||||
react-color: ^2.19.3
|
react-color: ^2.19.3
|
||||||
react-confirm: ^0.1.18
|
react-confirm: ^0.1.18
|
||||||
react-content-loader: ^6.2.1
|
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
react-helmet: ^6.0.0
|
react-helmet: ^6.0.0
|
||||||
react-joyride: ^2.4.0
|
react-joyride: ^2.4.0
|
||||||
|
Loading…
Reference in New Issue
Block a user