feat: sidebar
|
@ -2,36 +2,23 @@ import {
|
|||
React,
|
||||
hot,
|
||||
} from '@flecks/react';
|
||||
// import {
|
||||
// Navigate,
|
||||
// Route,
|
||||
// Routes,
|
||||
// } from '@flecks/react/router';
|
||||
// import {useSelector} from '@flecks/redux';
|
||||
// import {userIdSelector} from '@flecks/user';
|
||||
|
||||
import './global.scss';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
// import Dashboard from '../dashboard';
|
||||
import Login from './login';
|
||||
// import ProjectRoute from '../project/route';
|
||||
import useLocalStorage from '../../hooks/use-local-storage';
|
||||
import Resource from './resource';
|
||||
import Sidebar from './sidebar';
|
||||
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
const Persea = () => {
|
||||
// const isLoggedIn = useSelector(userIdSelector);
|
||||
function Persea() {
|
||||
const [expanded, setExpanded] = useLocalStorage('@persea/core/expanded', true);
|
||||
return (
|
||||
<div className={styles.persea}>
|
||||
<Login />
|
||||
{/* <Routes>
|
||||
<Route exact path="/" element={<Navigate to={isLoggedIn ? '/dashboard' : '/login'} />} />
|
||||
<Route path="/login" element={isLoggedIn ? <Navigate to="/" /> : <UserLocalLogin />} />
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/project/*" element={<ProjectRoute />} />
|
||||
</Routes> */}
|
||||
<Sidebar expanded={expanded} setExpanded={setExpanded} />
|
||||
<Resource expanded={expanded} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Persea.displayName = 'Persea';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
.persea {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
|
36
packages/core/src/components/persea/resource/index.jsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import {classnames, PropTypes, React} from '@flecks/react';
|
||||
import {useSelector} from '@flecks/redux';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
import {projectsSelector} from '../../../state/projects';
|
||||
import Login from './login';
|
||||
|
||||
function Resource({expanded}) {
|
||||
const {structure} = useSelector(projectsSelector);
|
||||
const projects = Object.entries(structure);
|
||||
return (
|
||||
<div className={classnames(styles.resource, expanded && styles.expanded)}>
|
||||
{
|
||||
projects.length > 0
|
||||
? <p>{projects.length}</p>
|
||||
: (
|
||||
<div className={styles.login}>
|
||||
<p>You must log in to access your projects.</p>
|
||||
<Login />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Resource.defaultProps = {};
|
||||
|
||||
Resource.displayName = 'Resource';
|
||||
|
||||
Resource.propTypes = {
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default Resource;
|
|
@ -0,0 +1,16 @@
|
|||
.resource {
|
||||
background-color: #1a1a1a;
|
||||
height: 100%;
|
||||
width: calc(100% - 3rem);
|
||||
|
||||
&.expanded {
|
||||
width: calc(100% - 25rem);
|
||||
}
|
||||
|
||||
.login {
|
||||
p {
|
||||
margin: 3em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,10 +6,6 @@ import styles from './index.module.scss';
|
|||
function Login() {
|
||||
return (
|
||||
<div className={styles.login}>
|
||||
<div>
|
||||
<h2>Sign in</h2>
|
||||
<p>You must sign in to continue</p>
|
||||
</div>
|
||||
<UserLocalLogin />
|
||||
</div>
|
||||
);
|
|
@ -1,16 +1,6 @@
|
|||
.login {
|
||||
margin: auto;
|
||||
max-width: 60%;
|
||||
padding-top: 5%;
|
||||
|
||||
> div {
|
||||
text-align: center;
|
||||
padding-bottom: 2em;
|
||||
|
||||
h2 {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
background-color: #272727;
|
||||
|
@ -38,6 +28,7 @@
|
|||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
[type="checkbox"] {
|
||||
|
@ -48,8 +39,9 @@
|
|||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
display: inline-block;
|
||||
flex-direction: row;
|
||||
width: auto;
|
||||
|
||||
input, span {
|
||||
margin: 0;
|
||||
|
@ -57,7 +49,7 @@
|
|||
}
|
||||
|
||||
a {
|
||||
padding: 1em 1em 1em 0;
|
||||
padding: 1em;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import {PropTypes, React} from '@flecks/react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
function Content({Content, name}) {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
{Content && (
|
||||
<>
|
||||
<p className={styles.name}>{name}</p>
|
||||
{Content}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Content.defaultProps = {};
|
||||
|
||||
Content.displayName = 'Content';
|
||||
|
||||
Content.propTypes = {
|
||||
Content: PropTypes.node.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Content;
|
|
@ -0,0 +1,14 @@
|
|||
.content {
|
||||
background-color: #272727;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: calc(100% - 3rem);
|
||||
}
|
||||
|
||||
.name {
|
||||
color: #ccc;
|
||||
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
|
||||
font-size: 0.8em;
|
||||
padding: 1em;
|
||||
text-transform: uppercase;
|
||||
}
|
41
packages/core/src/components/persea/sidebar/icon/index.jsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import {classnames, PropTypes, React} from '@flecks/react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
function Icon({
|
||||
className,
|
||||
isActive,
|
||||
name,
|
||||
onClick,
|
||||
}) {
|
||||
return (
|
||||
<label
|
||||
className={styles.label}
|
||||
key={name}
|
||||
>
|
||||
<span>{name}</span>
|
||||
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
|
||||
<button
|
||||
className={classnames(styles.icon, className, isActive && styles.active)}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
Icon.defaultProps = {
|
||||
className: '',
|
||||
onClick: () => {},
|
||||
};
|
||||
|
||||
Icon.displayName = 'Icon';
|
||||
|
||||
Icon.propTypes = {
|
||||
className: PropTypes.string,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Icon;
|
|
@ -0,0 +1,32 @@
|
|||
.icon {
|
||||
background-color: #333;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 70%;
|
||||
border: 0;
|
||||
border-left: 2px solid transparent;
|
||||
height: 3rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 3rem;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-left: 2px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 0;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
21
packages/core/src/components/persea/sidebar/icons/index.jsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import {PropTypes, React} from '@flecks/react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
function Icons({Icons}) {
|
||||
return (
|
||||
<div className={styles.icons}>
|
||||
{Icons}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Icons.defaultProps = {};
|
||||
|
||||
Icons.displayName = 'Icons';
|
||||
|
||||
Icons.propTypes = {
|
||||
Icons: PropTypes.arrayOf(PropTypes.node).isRequired,
|
||||
};
|
||||
|
||||
export default Icons;
|
|
@ -0,0 +1,5 @@
|
|||
.icons {
|
||||
background-color: #333;
|
||||
width: 3rem;
|
||||
height: 100%;
|
||||
}
|
58
packages/core/src/components/persea/sidebar/index.jsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import {
|
||||
classnames,
|
||||
PropTypes,
|
||||
React,
|
||||
useFlecks,
|
||||
} from '@flecks/react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
import useLocalStorage from '../../../hooks/use-local-storage';
|
||||
import ContentComponent from './content';
|
||||
import IconComponent from './icon';
|
||||
import IconsComponent from './icons';
|
||||
|
||||
function Sidebar({expanded, setExpanded}) {
|
||||
const flecks = useFlecks();
|
||||
const sidebarImplementations = flecks.invokeMerge('@persea/core.sidebar');
|
||||
const [sidebar, setSidebar] = useLocalStorage(
|
||||
'@persea/core/sidebar',
|
||||
Object.keys(sidebarImplementations).shift(),
|
||||
);
|
||||
const Icons = Object.entries(sidebarImplementations)
|
||||
.map(([key, {Icon}]) => (
|
||||
<IconComponent
|
||||
className={Icon}
|
||||
isActive={expanded && sidebar === key}
|
||||
key={key}
|
||||
name={key}
|
||||
onClick={() => {
|
||||
if (key === sidebar) {
|
||||
setExpanded(!expanded);
|
||||
}
|
||||
else if (!expanded) {
|
||||
setExpanded(true);
|
||||
}
|
||||
setSidebar(key);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
const {Content} = sidebarImplementations[sidebar];
|
||||
return (
|
||||
<div className={classnames(styles.sidebar, expanded && styles.expanded)}>
|
||||
<IconsComponent Icons={Icons} />
|
||||
<ContentComponent Content={expanded && <Content />} name={sidebar} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Sidebar.defaultProps = {};
|
||||
|
||||
Sidebar.displayName = 'Sidebar';
|
||||
|
||||
Sidebar.propTypes = {
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
setExpanded: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Sidebar;
|
|
@ -0,0 +1,10 @@
|
|||
.sidebar {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 3rem;
|
||||
|
||||
&.expanded {
|
||||
max-width: 100%;
|
||||
width: 25rem;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import {
|
||||
classnames,
|
||||
React,
|
||||
} from '@flecks/react';
|
||||
|
||||
import locals from './index.module.scss';
|
||||
|
||||
function Bottom() {
|
||||
const buttons = [
|
||||
<button className={classnames(locals.organization)} type="button">
|
||||
<svg height="512pt" viewBox="0 0 512 512" width="512pt" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m512 312v-160h-230v62h-152v-54h100v-160h-230v160h90v294h192v58h230v-160h-230v62h-152v-160h152v58zm-472-272h150v80h-150zm282 352h150v80h-150zm0-200h150v80h-150zm0 0" />
|
||||
</svg>
|
||||
</button>,
|
||||
// eslint-disable-next-line jsx-a11y/control-has-associated-label
|
||||
<button className={classnames(locals.resource, 'active')} type="button" />,
|
||||
];
|
||||
return (
|
||||
<div className={locals.bottom}>
|
||||
<div className={locals.buttons}>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Bottom.defaultProps = {};
|
||||
|
||||
Bottom.displayName = 'Bottom';
|
||||
|
||||
Bottom.propTypes = {};
|
||||
|
||||
export default Bottom;
|
|
@ -1,46 +0,0 @@
|
|||
.bottom {
|
||||
height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: space-around;
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: 2px transparent;
|
||||
height: 2em;
|
||||
padding: 0.5rem;
|
||||
width: 2em;
|
||||
|
||||
&:focus, &:hover {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:global(.active) {
|
||||
border-bottom: 1px solid #009fb4;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #999;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.side {
|
||||
padding: 0.35rem;
|
||||
}
|
||||
|
||||
button.resource {
|
||||
background-image: url('./map.png');
|
||||
background-origin: content-box;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
padding: 0.25rem;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<svg id="Layer_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g><path d="m113.147 53.166h-2.175v-22.456c0-16.934-13.777-30.71-30.788-30.71-16.934 0-30.71 13.776-30.71 30.71v22.456c-22.985-1.145-43.857 15.46-43.857 39.432v123.282c0 24.002 20.867 40.512 43.856 39.431v225.979c0 16.934 13.776 30.71 30.788 30.71 16.934 0 30.71-13.776 30.71-30.71v-225.979c22.21.781 41.606-16.895 41.606-39.431v-42.882c0-9.434-15.078-9.883-15.078.002 0 14.287.078 28.588.078 42.88 0 13.471-10.959 24.431-24.431 24.431h-68.098c-13.471 0-24.431-10.96-24.431-24.431v-123.282c0-13.472 10.959-24.432 24.431-24.432h68.099c13.471 0 24.431 10.96 24.431 24.432 0 13.466-.078 26.941-.078 40.402 0 9.459 15.078 9.869 15.078.004v-40.406c0-21.743-17.689-39.432-39.431-39.432zm-17.175 428.124c0 8.662-7.047 15.71-15.788 15.71-8.663 0-15.71-7.048-15.71-15.71v-225.979h31.498zm-31.498-450.58c0-8.662 7.047-15.71 15.788-15.71 8.663 0 15.71 7.048 15.71 15.71v22.456h-31.498z"/><path d="m290.612 255.094h-3.3v-224.384c0-16.934-13.776-30.71-30.788-30.71-16.934 0-30.71 13.776-30.71 30.71v224.384c-22.604-.988-42.731 16.189-42.731 39.431v123.283c0 23.349 20.118 40.263 42.731 39.432v24.051c0 16.934 13.777 30.71 30.788 30.71 16.934 0 30.71-13.776 30.71-30.71v-24.051c22.604.988 42.731-16.189 42.731-39.432v-123.284c0-21.742-17.689-39.43-39.431-39.43zm-49.799-224.384c0-8.662 7.048-15.71 15.788-15.71 8.663 0 15.71 7.048 15.71 15.71v224.384h-31.498zm31.499 450.58c0 8.662-7.047 15.71-15.788 15.71-8.663 0-15.71-7.048-15.71-15.71v-24.051h31.498zm42.731-63.482c0 13.472-10.96 24.432-24.431 24.432h-68.099c-13.471 0-24.431-10.96-24.431-24.432v-123.284c0-13.471 10.959-24.431 24.431-24.431h68.099c13.471 0 24.431 10.96 24.431 24.431z"/><path d="m466.952 139.191h-3.3v-108.481c0-16.934-13.776-30.71-30.788-30.71-16.934 0-30.71 13.776-30.71 30.71v108.481c-22.604-.988-42.731 16.19-42.731 39.432v123.282c0 23.349 20.118 40.263 42.731 39.432v139.953c0 16.934 13.777 30.71 30.788 30.71 16.934 0 30.71-13.776 30.71-30.71v-44.29c0-9.697-15-9.697-15 0v44.29c0 8.662-7.047 15.71-15.788 15.71-8.663 0-15.71-7.048-15.71-15.71v-139.953h31.498v52.663c0 9.697 15 9.697 15 0v-52.663c22.604.988 42.731-16.189 42.731-39.432v-123.282c0-21.742-17.689-39.432-39.431-39.432zm-49.799-108.481c0-8.662 7.048-15.71 15.788-15.71 8.663 0 15.71 7.048 15.71 15.71v108.481h-31.498zm74.23 271.195c0 13.472-10.96 24.432-24.431 24.432h-68.099c-13.471 0-24.431-10.96-24.431-24.432v-123.282c0-13.472 10.959-24.432 24.431-24.432h68.099c13.471 0 24.431 10.96 24.431 24.432z"/><path d="m102.347 100.484h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m102.347 146.738h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m102.347 192.993h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m279.812 302.412h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m279.812 348.666h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m279.812 394.921h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m456.151 186.51h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m456.151 232.764h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/><path d="m456.151 279.019h-46.498c-4.142 0-7.5 3.357-7.5 7.5s3.358 7.5 7.5 7.5h46.498c4.142 0 7.5-3.357 7.5-7.5s-3.358-7.5-7.5-7.5z"/></g></svg>
|
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 5.5 KiB |
18
packages/core/src/hooks/use-local-storage.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from '@flecks/react';
|
||||
|
||||
const useLocalStorage = (key, defaultValue) => {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
useLayoutEffect(() => {
|
||||
setValue(JSON.parse(window.localStorage.getItem(key) || JSON.stringify(defaultValue)));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [key]);
|
||||
useLayoutEffect(() => {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
}, [key, value]);
|
||||
return [value, setValue];
|
||||
};
|
||||
|
||||
export default useLocalStorage;
|
|
@ -2,7 +2,9 @@ import {patchJsonResource} from '@avocado/resource-persea';
|
|||
import {Hooks} from '@flecks/core';
|
||||
|
||||
import Persea from './components/persea';
|
||||
import {projects} from './state';
|
||||
import organization from './sidebar/organization';
|
||||
import projectsSidebar from './sidebar/projects';
|
||||
import {project, projects} from './state';
|
||||
|
||||
export * from './state';
|
||||
|
||||
|
@ -18,7 +20,12 @@ export default {
|
|||
};
|
||||
},
|
||||
'@flecks/redux.slices': () => ({
|
||||
project,
|
||||
projects,
|
||||
}),
|
||||
'@persea/core.sidebar': () => ({
|
||||
projects: projectsSidebar,
|
||||
organization,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
29
packages/core/src/sidebar/organization/content.jsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import {React} from '@flecks/react';
|
||||
import {useSelector} from '@flecks/redux';
|
||||
|
||||
import styles from './content.module.scss';
|
||||
|
||||
import {projectSelector, structureSelector} from '../../state';
|
||||
import Organization from './organization';
|
||||
|
||||
function Content() {
|
||||
const project = useSelector(projectSelector) || 'c41ddaac-89c2-46a4-b3e5-1d634a1a7c36';
|
||||
const {label, resourcePaths} = useSelector((state) => structureSelector(state, project));
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<Organization
|
||||
label={label}
|
||||
uuid={project}
|
||||
resourcePaths={resourcePaths}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Content.defaultProps = {};
|
||||
|
||||
Content.displayName = 'Content';
|
||||
|
||||
Content.propTypes = {};
|
||||
|
||||
export default Content;
|
|
@ -0,0 +1,6 @@
|
|||
.content {
|
||||
}
|
||||
|
||||
.icon {
|
||||
background-image: url('./icon_document.svg');
|
||||
}
|
48
packages/core/src/sidebar/organization/icon_document.svg
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
|
||||
<polygon fill="#5981C1" stroke="#4766B0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
36.766,12.169 9.352,12.169 9.352,60.169 48.504,60.169 48.504,23.907 "/>
|
||||
|
||||
<polygon fill="#5981C1" stroke="#4766B0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
36.766,23.907 48.504,23.907 36.766,12.169 "/>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<polygon fill="#8CAEDC" stroke="#4766B0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
45.103,3.831 17.689,3.831 17.689,51.831 56.841,51.831 56.841,15.569 "/>
|
||||
|
||||
<polygon fill="#5981C1" stroke="#4766B0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
45.103,15.569 56.841,15.569 45.103,3.831 "/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="33.161" y1="15.787" x2="37.931" y2="15.787"/>
|
||||
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="33.161" y1="20.288" x2="50.919" y2="20.288"/>
|
||||
|
||||
<rect x="23.644" y="15.787" fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="4.5" height="4.5"/>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="33.161" y1="25.581" x2="37.931" y2="25.581"/>
|
||||
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="33.161" y1="30.082" x2="50.919" y2="30.082"/>
|
||||
|
||||
<rect x="23.644" y="25.581" fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="4.5" height="4.5"/>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="33.161" y1="36.515" x2="37.931" y2="36.515"/>
|
||||
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="33.161" y1="41.015" x2="50.919" y2="41.015"/>
|
||||
|
||||
<rect x="23.644" y="36.515" fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="4.5" height="4.5"/>
|
||||
</g>
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="23.644" y1="9.7" x2="37.931" y2="9.7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
8
packages/core/src/sidebar/organization/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import styles from './content.module.scss';
|
||||
|
||||
import Content from './content';
|
||||
|
||||
export default {
|
||||
Content,
|
||||
Icon: styles?.icon,
|
||||
};
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 309 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 312 B |
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 318 B |
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 832 B |
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -6,7 +6,6 @@ import {
|
|||
PropTypes,
|
||||
React,
|
||||
useFlecks,
|
||||
useState,
|
||||
} from '@flecks/react';
|
||||
import {
|
||||
push,
|
||||
|
@ -17,9 +16,11 @@ import {
|
|||
useDispatch,
|
||||
} from '@flecks/redux';
|
||||
|
||||
import OrganizationActions from './actions';
|
||||
import './index.scss';
|
||||
|
||||
import useLocalStorage from '../../../hooks/use-local-storage';
|
||||
import OrganizationActions from './actions';
|
||||
|
||||
const normalizeDisplayName = (displayName) => (
|
||||
displayName.startsWith('HotExported') ? displayName.slice(11) : displayName
|
||||
);
|
||||
|
@ -33,7 +34,7 @@ const Organization = ({
|
|||
const dispatch = useDispatch();
|
||||
const flecks = useFlecks();
|
||||
const location = useLocation();
|
||||
const [collapsed, setCollapsed] = useState({});
|
||||
const [collapsed, setCollapsed] = useLocalStorage('@persea/core/collapsed', {});
|
||||
const treeFromResourcePath = (tree, uuid, [isFile, resourcePath]) => {
|
||||
const parts = resourcePath.split('/');
|
||||
parts.shift();
|
|
@ -1,5 +1,5 @@
|
|||
.organization {
|
||||
background-color: #222;
|
||||
// background-color: #222;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
@ -10,7 +10,10 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.label.inline {
|
||||
.label {
|
||||
background-color: transparent;
|
||||
&.inline {
|
||||
font-size: 0.85em;
|
||||
justify-content: flex-start;
|
||||
> div:first-child.icon {
|
||||
background-size: cover;
|
||||
|
@ -44,6 +47,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.node {
|
||||
&.parent > .item .label:before {
|
||||
color: #999;
|
49
packages/core/src/sidebar/projects/content.jsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import {React} from '@flecks/react';
|
||||
import {useSelector} from '@flecks/redux';
|
||||
|
||||
import styles from './content.module.scss';
|
||||
|
||||
import {projectSelector, projectsSelector} from '../../state';
|
||||
|
||||
function Content() {
|
||||
const project = useSelector(projectSelector) || 'c41ddaac-89c2-46a4-b3e5-1d634a1a7c36';
|
||||
const {structure} = useSelector(projectsSelector);
|
||||
const projects = Object.entries(structure);
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
{
|
||||
projects.length > 0
|
||||
? (
|
||||
<ul>
|
||||
{
|
||||
projects
|
||||
.map(([uuid, {label}]) => (
|
||||
<li className={project === uuid && styles.active} key={uuid}>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<div className={styles.uuid}>{uuid}</div>
|
||||
</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
: (
|
||||
<div className={styles.empty}>
|
||||
<p>You don't have any projects.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Content.defaultProps = {};
|
||||
|
||||
Content.displayName = 'Content';
|
||||
|
||||
Content.propTypes = {};
|
||||
|
||||
export default Content;
|
44
packages/core/src/sidebar/projects/content.module.scss
Normal file
|
@ -0,0 +1,44 @@
|
|||
.content {
|
||||
|
||||
ul {
|
||||
li {
|
||||
border: 1px solid transparent;
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
&.active {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--color-active);
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.10);
|
||||
}
|
||||
}
|
||||
label {
|
||||
padding: 0.5em;
|
||||
}
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
width: 100%;
|
||||
.uuid {
|
||||
font-size: 0.7em;
|
||||
margin: 0.5rem auto 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
p {
|
||||
font-family: var(--message-font-family);
|
||||
margin-top: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
background-image: url('./icon_books.svg');
|
||||
}
|
49
packages/core/src/sidebar/projects/icon_books.svg
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
|
||||
<rect x="6.125" y="8.75" fill="#8CAEDC" stroke="#4766B0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="17.25" height="46.5"/>
|
||||
|
||||
<path fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M14.75,42.396L14.75,42.396c-2.131,0-3.875,1.744-3.875,3.875v0c0,2.131,1.744,3.875,3.875,3.875h0
|
||||
c2.131,0,3.875-1.744,3.875-3.875v0C18.625,44.14,16.881,42.396,14.75,42.396z"/>
|
||||
<g>
|
||||
|
||||
<rect x="10.875" y="14.417" fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="7.75" height="20.75"/>
|
||||
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="14.75" y1="18.46" x2="14.75" y2="31.124"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<rect x="23.375" y="8.75" fill="#5981C1" stroke="#4766B0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="17.25" height="46.5"/>
|
||||
|
||||
<path fill="#8CAEDC" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M32,42.396L32,42.396c-2.131,0-3.875,1.744-3.875,3.875v0c0,2.131,1.744,3.875,3.875,3.875h0c2.131,0,3.875-1.744,3.875-3.875v0
|
||||
C35.875,44.14,34.131,42.396,32,42.396z"/>
|
||||
<g>
|
||||
|
||||
<rect x="28.125" y="14.417" fill="#8CAEDC" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="7.75" height="20.75"/>
|
||||
|
||||
<line fill="#8CAEDC" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="32" y1="18.46" x2="32" y2="31.124"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<rect x="40.625" y="8.75" fill="#8CAEDC" stroke="#4766B0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="17.25" height="46.5"/>
|
||||
|
||||
<path fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M49.25,42.396L49.25,42.396c-2.131,0-3.875,1.744-3.875,3.875v0c0,2.131,1.744,3.875,3.875,3.875h0
|
||||
c2.131,0,3.875-1.744,3.875-3.875v0C53.125,44.14,51.381,42.396,49.25,42.396z"/>
|
||||
<g>
|
||||
|
||||
<rect x="45.375" y="14.417" fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" width="7.75" height="20.75"/>
|
||||
|
||||
<line fill="#5981C1" stroke="#4766B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="49.25" y1="18.46" x2="49.25" y2="31.124"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
8
packages/core/src/sidebar/projects/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import styles from './content.module.scss';
|
||||
|
||||
import Content from './content';
|
||||
|
||||
export default {
|
||||
Content,
|
||||
Icon: styles?.icon,
|
||||
};
|
|
@ -1,2 +1,5 @@
|
|||
export * from './project';
|
||||
export {default as project} from './project';
|
||||
|
||||
export * from './projects';
|
||||
export {default as projects} from './projects';
|
||||
|
|
29
packages/core/src/state/project.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import {
|
||||
createSlice,
|
||||
} from '@flecks/redux';
|
||||
|
||||
export const projectSelector = (state) => state.project;
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'persea/project',
|
||||
initialState: null,
|
||||
/* eslint-disable no-param-reassign */
|
||||
reducers: {
|
||||
setProject: (state, {payload}) => payload,
|
||||
},
|
||||
/* eslint-enable no-param-reassign */
|
||||
});
|
||||
|
||||
slice.reducer.hydrateServer = async (req) => {
|
||||
const {user} = req;
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
const projects = await user.getProjects();
|
||||
if (0 === projects.length) {
|
||||
return null;
|
||||
}
|
||||
return projects[0].uuid;
|
||||
};
|
||||
|
||||
export default slice.reducer;
|
|
@ -100,9 +100,9 @@ slice.reducer.hydrateServer = async (req) => {
|
|||
.map(async ([uuid, resourcePaths]) => {
|
||||
let label;
|
||||
try {
|
||||
const buffer = await readFile(join(process.cwd(), 'projects', `${uuid}.json`));
|
||||
const buffer = await readFile(join(process.cwd(), 'projects', uuid, 'package.json'));
|
||||
const config = JSON.parse(buffer.toString());
|
||||
label = config.name;
|
||||
label = config.label || config.name;
|
||||
}
|
||||
catch (error) {
|
||||
label = uuid;
|
||||
|
|