Compare commits

..

No commits in common. "30ebc7d22dfaf402ebd0486ce3cd507924b66900" and "4b656d31e1f0c1e5581e35b038c52926a121ce11" have entirely different histories.

15 changed files with 124 additions and 135 deletions

View File

@ -4,7 +4,6 @@ import { Helmet } from 'react-helmet';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { ToastContainer, toast } from 'react-toastify'; import { ToastContainer, toast } from 'react-toastify';
import { Grid, Divider, Icon, Container } from 'semantic-ui-react'; import { Grid, Divider, Icon, Container } from 'semantic-ui-react';
import styled, { createGlobalStyle } from 'styled-components';
import api from '../api'; import api from '../api';
import actions from '../actions'; import actions from '../actions';
@ -51,17 +50,6 @@ import DocsDoc from './docs/Doc';
import Root from './main/root'; import Root from './main/root';
const StyledContent = styled.div`
display: flex;
flex-direction: column;
min-height: 100vh;
`;
const GlobalStyle = createGlobalStyle`
body {
background-color: rgb(255, 251, 248);
}
`;
function App() { function App() {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -104,11 +92,10 @@ function App() {
}, [dispatch, user, driftReady, syncedToDrift]); }, [dispatch, user, driftReady, syncedToDrift]);
return ( return (
<StyledContent> <div style={{display: 'flex', flexDirection: 'column', minHeight: '100vh'}}>
<GlobalStyle whiteColor />
<Helmet defaultTitle={utils.appName()} titleTemplate={`%s | ${utils.appName()}`} /> <Helmet defaultTitle={utils.appName()} titleTemplate={`%s | ${utils.appName()}`} />
<NavBar /> <NavBar />
<div style={{ flex: '1' }}> <div style={{ flex: '1 0 0' }}>
<Routes> <Routes>
<Route end path="/" element={isAuthenticated <Route end path="/" element={isAuthenticated
? <Home /> ? <Home />
@ -154,19 +141,20 @@ function App() {
<Divider hidden section /> <Divider hidden section />
</div> </div>
<div style={{ background: 'rgba(0,0,0,0.02)', padding: 10}}> <div style={{ background: 'rgb(240,240,240)', padding: '30px 0px' }}>
<Container> <Container>
<Grid verticalAlign='middle' stackable columns={2}> <Grid>
<Grid.Column> <Grid.Column computer={8}>
<Link to="/"><img alt={`${utils.appName()} logo`} src={logo} style={{ width: '100px', opacity: 0.5 }} /></Link> <Link to="/"><img alt={`${utils.appName()} logo`} src={logo} style={{ width: '100px', paddingTop: 20, paddingBottom: 20 }} /></Link>
{import.meta.env.VITE_SOURCE_REPO_URL && {import.meta.env.VITE_SOURCE_REPO_URL &&
<p style={{marginTop: 5 }}><small>{utils.appName()} software is free and open-source. <p style={{marginTop: 10}}><small>{utils.appName()} software is free and open-source. Contributions to the project are always welcome.
<br /> <br />
<Icon name="code" /> <a href={import.meta.env.VITE_SOURCE_REPO_URL} target="_blank" rel="noopener noreferrer" className='umami--click--source-footer'>Project source homepage</a> <Icon name="code" /> <a href={import.meta.env.VITE_SOURCE_REPO_URL} target="_blank" rel="noopener noreferrer" className='umami--click--source-footer'>Project source homepage</a>
</small></p> </small></p>
} }
</Grid.Column> </Grid.Column>
<Grid.Column textAlign="right" style={{ fontSize: 13 }}> <Grid.Column computer={8} textAlign="right">
<div style={{ paddingTop: 40 }}>
{import.meta.env.VITE_PATREON_URL && {import.meta.env.VITE_PATREON_URL &&
<p> <p>
<Icon name='trophy' /> <Icon name='trophy' />
@ -177,6 +165,8 @@ function App() {
<Icon name='book' /> <Icon name='book' />
<a href='/docs' target='_blank' rel='noopener noreferrer'>Documentation</a> <a href='/docs' target='_blank' rel='noopener noreferrer'>Documentation</a>
</p> </p>
<Divider />
<p> <p>
<Icon name="file alternate outline" /> <Icon name="file alternate outline" />
<Link to="/privacy">Privacy Policy</Link> <Link to="/privacy">Privacy Policy</Link>
@ -185,12 +175,13 @@ function App() {
<Icon name="file alternate outline" /> <Icon name="file alternate outline" />
<Link to="terms-of-use">Terms of Use</Link> <Link to="terms-of-use">Terms of Use</Link>
</p> </p>
</div>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
</Container> </Container>
</div> </div>
</StyledContent> </div>
); );
} }

View File

@ -155,11 +155,10 @@ Welcome back <Button floated="right" onClick={onClose} basic content="Close" />
<Input autoFocus size="large" fluid name="email" type="text" value={email} onChange={e => setEmail(e.target.value)} placeholder='Email or username' /> <Input autoFocus size="large" fluid name="email" type="text" value={email} onChange={e => setEmail(e.target.value)} placeholder='Email or username' />
</Form.Field> </Form.Field>
<Form.Field> <Form.Field>
<label>Password</label> <label>Password
<Link to="/password/forgotten" style={{ float: 'right' }} onClick={onClose}>Forgotten your password?</Link>
</label>
<Input size="large" fluid name="password" type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder='Password' /> <Input size="large" fluid name="password" type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder='Password' />
<div style={{ display: 'flex', justifyContent: 'end' }}>
<Link to="/password/forgotten" onClick={onClose}>Forgotten your password?</Link>
</div>
</Form.Field> </Form.Field>
<div className="ui hidden divider" /> <div className="ui hidden divider" />
<Form.Button type='submit' size="large" color="teal" fluid loading={loading}>Login</Form.Button> <Form.Button type='submit' size="large" color="teal" fluid loading={loading}>Login</Form.Button>

View File

@ -77,8 +77,7 @@ const NewFeedMessage = connect(
)} )}
</div> </div>
} }
<div style={{ display: 'flex', justifyContent: 'end', marginTop: 10 }}> <Button.Group style={{marginTop: 10, float:'right'}}>
<Button.Group>
{!noAttachments && {!noAttachments &&
<Dropdown <Dropdown
trigger={<Button size='small' icon='paperclip' content='Attach something' loading={attachmentUploading}/>} trigger={<Button size='small' icon='paperclip' content='Attach something' loading={attachmentUploading}/>}
@ -108,7 +107,7 @@ const NewFeedMessage = connect(
} }
<Button disabled={posting} loading={posting} size='small' color='teal' icon='send' content={inReplyTo ? 'Post reply' : 'Post message'} onClick={createEntry}/> <Button disabled={posting} loading={posting} size='small' color='teal' icon='send' content={inReplyTo ? 'Post reply' : 'Post message'} onClick={createEntry}/>
</Button.Group> </Button.Group>
</div> <div style={{clear:'both'}} />
</> </>
); );
}); });

View File

@ -18,17 +18,21 @@ function ProjectCard({ project }) {
<Card.Meta>{shorten(project.description)}</Card.Meta> <Card.Meta>{shorten(project.description)}</Card.Meta>
</Card.Content> </Card.Content>
<Card.Content extra> <Card.Content extra>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<UserChip user={project.owner} /> <UserChip user={project.owner} />
<span style={{ float: 'right' }}>
{project.visibility === 'private' {project.visibility === 'private'
? ( ? (
<div><Icon name="lock" /> Private</div> <span>
<Icon name="lock" /> Private
</span>
) )
: ( : (
<div><Icon name="unlock" /> Public</div> <span>
<Icon name="unlock" /> Public
</span>
) )
} }
</div> </span>
</Card.Content> </Card.Content>
</Card> </Card>
); );

View File

@ -126,7 +126,7 @@ function Home() {
} }
{(groups && groups.length) ? {(groups && groups.length) ?
<Card fluid className='joyride-groups' style={{opacity: 0.8}}> <Card fluid className='joyride-groups'>
<Card.Content> <Card.Content>
<Card.Header>Your groups</Card.Header> <Card.Header>Your groups</Card.Header>
@ -154,7 +154,7 @@ function Home() {
} }
{(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 color='blue'>
<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>
@ -181,16 +181,15 @@ function Home() {
{user && !loadingProjects && (!projects || !projects.length) && {user && !loadingProjects && (!projects || !projects.length) &&
<div style={{textAlign: 'center'}}> <div style={{textAlign: 'center'}}>
<h1> <h1>
<span role="img" aria-label="chequered flag">🚀</span> Let's get started <span role="img" aria-label="chequered flag">🚀</span> Let's get started, {user?.username}!
</h1> </h1>
<Divider hidden/> <Divider hidden/>
<Segment placeholder textAlign='center'> <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> <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>Projects can contain anything - from rough ideas or design experiments through to commissions and exhibitions. Treat them as if they were just weaving-related <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 /> <Divider />
<h4>Start by creating a new project. Don't worry, you can keep it private.</h4> <h4>Start by creating a new project. Don't worry, you can keep it private.</h4>
<p><HelpLink className='joyride-help' link={`/docs/projects`} text='Learn more about projects' marginTop/></p>
<Button className='joyride-createProject' as={Link} to="/projects/new" color="teal" icon="plus" content="Create a project" /> <Button className='joyride-createProject' as={Link} to="/projects/new" color="teal" icon="plus" content="Create a project" />
</Segment> </Segment>

View File

@ -152,7 +152,6 @@ function Group() {
</Grid.Column> </Grid.Column>
<Grid.Column computer={12}> <Grid.Column computer={12}>
<Segment>
{user ? {user ?
<> <>
{!utils.isInGroup(user, group._id) && {!utils.isInGroup(user, group._id) &&
@ -183,7 +182,6 @@ function Group() {
: :
<Message>Please login to view or join this group.</Message> <Message>Please login to view or join this group.</Message>
} }
</Segment>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
</div> </div>

View File

@ -85,7 +85,7 @@ function Members() {
<div> <div>
{loading && (!members || !members.length) && <Loader active inline="centered" />} {loading && (!members || !members.length) && <Loader active inline="centered" />}
{!loading && utils.isGroupAdmin(user, group) && {!loading && utils.isGroupAdmin(user, group) &&
<Segment color='blue' style={{marginBottom: 30}}> <Segment raised color='blue' style={{marginBottom: 30}}>
{(members && members.length === 1 && members[0]._id === user._id) ? {(members && members.length === 1 && members[0]._id === user._id) ?
<Header>You're the only person in this group</Header> <Header>You're the only person in this group</Header>
: :
@ -109,7 +109,7 @@ function Members() {
} }
{requests?.length > 0 && {requests?.length > 0 &&
<Segment color='green' style={{marginBottom:30}}> <Segment raised color='green' style={{marginBottom:30}}>
<h3>You have membership requests</h3> <h3>You have membership requests</h3>
<p>The following users want to join {group.name}.</p> <p>The following users want to join {group.name}.</p>
<Table relaxed='very' basic='very'> <Table relaxed='very' basic='very'>
@ -136,16 +136,14 @@ function Members() {
<UserChip user={i.recipientUser} /> <UserChip user={i.recipientUser} />
</Card.Content> </Card.Content>
<Card.Content extra > <Card.Content extra >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Label size='tiny' color='yellow' content='Invited' /> <Label size='tiny' color='yellow' content='Invited' />
{utils.isGroupAdmin(user, group) && {utils.isGroupAdmin(user, group) &&
<Dropdown text='Options'> <Dropdown text='Options' style={{float:'right'}}>
<Dropdown.Menu> <Dropdown.Menu>
<Dropdown.Item icon='trash' content='Delete invitation' onClick={e => deleteInvitation(i)} /> <Dropdown.Item icon='trash' content='Delete invitation' onClick={e => deleteInvitation(i)} />
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
} }
</div>
</Card.Content> </Card.Content>
</Card> </Card>
)} )}
@ -156,18 +154,16 @@ function Members() {
<Card.Meta style={{marginTop: 10}}>{m.bio}</Card.Meta> <Card.Meta style={{marginTop: 10}}>{m.bio}</Card.Meta>
</Card.Content> </Card.Content>
<Card.Content extra > <Card.Content extra >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
{utils.isGroupAdmin(m, group) && {utils.isGroupAdmin(m, group) &&
<Label size='tiny' color='violet' icon='rocket' content='Admin' /> <Label size='tiny' color='violet' icon='rocket' content='Admin' />
} }
{utils.isGroupAdmin(user, group) && user._id !== m._id && {utils.isGroupAdmin(user, group) && user._id !== m._id &&
<Dropdown text='Options'> <Dropdown text='Options' style={{float:'right'}}>
<Dropdown.Menu> <Dropdown.Menu>
<Dropdown.Item icon='ban' content='Kick' onClick={e => kickUser(m._id)} /> <Dropdown.Item icon='ban' content='Kick' onClick={e => kickUser(m._id)} />
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
} }
</div>
</Card.Content> </Card.Content>
</Card> </Card>
)} )}

View File

@ -75,16 +75,13 @@ function Projects() {
<h2>Projects in this group</h2> <h2>Projects in this group</h2>
<p>This tab lists projects that members have made available to this group.</p> <p>This tab lists projects that members have made available to this group.</p>
{myProjects?.length > 0 && <div style={{ display: 'flex', justifyContent: 'end' }}> {myProjects?.length > 0 && <>
<AddProject /> <AddProject style={{float:'right'}} />
</div>} </>}
<Divider />
{projects?.length > 0 && {projects?.length > 0 &&
<div style={{ display: 'flex', justifyContent: 'end' }}> <Input autoFocus style={{float:'right', marginRight: 5}} size='small' icon='search' value={projectFilter} onChange={e => dispatch(actions.groups.updateProjectFilter(e.target.value))} placeholder='Filter projects...' />
<Input size='small' icon='search' value={projectFilter} onChange={e => dispatch(actions.groups.updateProjectFilter(e.target.value))} placeholder='Filter projects...' />
</div>
} }
<Divider hidden clearing />
{projects?.length > 0 ? {projects?.length > 0 ?
<Card.Group itemsPerRow={3}> <Card.Group itemsPerRow={3}>

View File

@ -112,7 +112,7 @@ function ObjectList({ compact }) {
: <div style={{ height: 40, width:40, backgroundImage: `url(${logoGreyShort})`, backgroundSize: '50px' }} /> : <div style={{ height: 40, width:40, backgroundImage: `url(${logoGreyShort})`, backgroundSize: '50px' }} />
)} )}
<div style={{flex: 1, marginLeft: 5}}> <div style={{flex: 1, marginLeft: 5}}>
<h3 style={{fontSize: 13, marginBottom: 0, wordBreak: 'break-all'}}>{object.name}</h3> <h3 style={{fontSize: 13, marginBottom: 0}}>{object.name}</h3>
<Label size='mini' rounded> <Label size='mini' rounded>
{object.type === 'pattern' && <><Icon name='pencil' /> WIF pattern</>} {object.type === 'pattern' && <><Icon name='pencil' /> WIF pattern</>}
{object.type === 'file' && {object.type === 'file' &&
@ -166,7 +166,7 @@ function ObjectList({ compact }) {
) )
} }
<Card.Content> <Card.Content>
<p style={{ wordBreak: 'break-all' }}>{object.name}</p> <Card.Header style={{ wordBreak: 'break-all' }}>{object.name}</Card.Header>
</Card.Content> </Card.Content>
</Card> </Card>
))} ))}

View File

@ -117,9 +117,9 @@ function ObjectViewer() {
<> <>
<Helmet title={`${object.name || 'Project Item'} | ${project?.name || 'Project'}`} /> <Helmet title={`${object.name || 'Project Item'} | ${project?.name || 'Project'}`} />
<div style={{ display: 'flex', justifyContent: 'end' }}> <span style={{float:'right'}}>
{object.type === 'pattern' && (project.user === (user && user._id) || project.openSource || object.preview) && <> {object.type === 'pattern' && (project.user === (user && user._id) || project.openSource || object.preview) && <>
<Dropdown icon={null} trigger={<Button size='small' secondary icon='download' content='Download pattern' loading={downloading} disabled={downloading}/>}> <Dropdown icon={null} trigger={<Button size='tiny' secondary icon='download' content='Download pattern' loading={downloading} disabled={downloading}/>}>
<Dropdown.Menu> <Dropdown.Menu>
{object.preview && {object.preview &&
<Dropdown.Item onClick={e => downloadDrawdownImage(object)} content='Download drawdown as an image' icon='file outline' /> <Dropdown.Item onClick={e => downloadDrawdownImage(object)} content='Download drawdown as an image' icon='file outline' />
@ -160,7 +160,7 @@ function ObjectViewer() {
</> </>
} }
</div> </span>
{editingName ? {editingName ?
<div style={{marginBottom: 5}}> <div style={{marginBottom: 5}}>

View File

@ -54,6 +54,12 @@ function Project() {
{project {project
&& ( && (
<div> <div>
{/*history.location?.state?.prevPath &&
<div style={{marginBottom:15}}>
<Button basic secondary onClick={e => history.goBack()} icon='arrow left' content='Go back' />
</div>
*/}
{wideBody() && project.owner && {wideBody() && project.owner &&
<> <>
<h3 style={{ marginBottom: 0 }}> <h3 style={{ marginBottom: 0 }}>
@ -70,15 +76,14 @@ function Project() {
<Card fluid> <Card fluid>
<Card.Content> <Card.Content>
<Card.Header style={{ marginBottom: 10 }}> <Card.Header style={{ marginBottom: 10 }}>
{project.visibility === 'private' && <span data-tooltip="This project is private" data-position="right center"><Icon name="lock" /></span>} {project.visibility === 'private' && <span data-tooltip="This project is private"><Icon name="lock" /></span>}
{project.name} {project.name}
</Card.Header> </Card.Header>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
{project.owner && <UserChip user={project.owner} />} {project.owner && <UserChip user={project.owner} />}
{utils.canEditProject(user, project) && {utils.canEditProject(user, project) &&
<Button basic size='mini' icon='cogs' content='Settings' as={Link} to={`/${fullName}/settings`} /> <Button style={{float:'right'}} basic size='mini' icon='cogs' content='Settings' as={Link} to={`/${fullName}/settings`} />
} }
</div>
</Card.Content> </Card.Content>
<Card.Content extra> <Card.Content extra>
{editingDescription {editingDescription

View File

@ -64,7 +64,7 @@ function ProjectSettings() {
<HelpLink link={`/docs/projects#changing-the-project-s-settings`} /> <HelpLink link={`/docs/projects#changing-the-project-s-settings`} />
<Divider hidden /> <Divider hidden section />
<Segment> <Segment>
<h3>General settings</h3> <h3>General settings</h3>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 29 KiB

0
web/src/index.css Normal file
View File

View File

@ -9,6 +9,7 @@ import { BrowserTracing } from '@sentry/tracing';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import 'pell/dist/pell.min.css'; import 'pell/dist/pell.min.css';
import './index.css';
import reducers from './reducers'; import reducers from './reducers';
import App from './components/App'; import App from './components/App';