dev.redesign #18

Merged
Siklos merged 3 commits from dev.redesign into dev 2022-08-08 05:39:05 -04:00
14 changed files with 147 additions and 79 deletions

View file

@ -0,0 +1,39 @@
import { ClockIcon, CubeIcon, MapIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { BarIcon } from './BarIcon';
interface IBarProps {
isSidebarOpen: boolean
isElementsSidebarOpen: boolean
isHistoryOpen: boolean
ToggleSidebar: () => void
ToggleElementsSidebar: () => void
ToggleTimeline: () => void
}
export const BAR_WIDTH = 64; // 4rem
export const Bar: React.FC<IBarProps> = (props) => {
return (
<div className='fixed z-20 flex flex-col top-0 left-0 h-screen w-16 bg-slate-100'>
<BarIcon
isActive={props.isSidebarOpen}
title='Components'
onClick={() => props.ToggleSidebar()}>
<CubeIcon className='heroicon'/>
</BarIcon>
<BarIcon
isActive={props.isElementsSidebarOpen}
title='Map'
onClick={() => props.ToggleElementsSidebar()}>
<MapIcon className='heroicon'/>
</BarIcon>
<BarIcon
isActive={props.isHistoryOpen}
title='Timeline'
onClick={() => props.ToggleTimeline()}>
<ClockIcon className='heroicon'/>
</BarIcon>
</div>
);
};

View file

@ -0,0 +1,22 @@
import * as React from 'react';
interface IBarIconProps {
title: string
children: React.ReactElement
isActive: boolean
onClick: () => void
}
export const BarIcon: React.FC<IBarIconProps> = (props) => {
const isActiveClasses = props.isActive ? 'border-l-4 border-blue-500 bg-slate-200' : '';
return (
<button
className={`bar-btn group ${isActiveClasses}`}
title={props.title}
onClick={() => props.onClick()}
>
<span className='sidebar-tooltip group-hover:scale-100'>{props.title}</span>
{ props.children }
</button>
);
};

View file

@ -1,4 +1,4 @@
import { describe, test, expect, vi } from 'vitest';
import { describe, expect, vi } from 'vitest';
import * as React from 'react';
import { fireEvent, render, screen } from '../../utils/test-utils';
import { ElementsSidebar } from './ElementsSidebar';
@ -11,7 +11,6 @@ describe.concurrent('Elements sidebar', () => {
isOpen={true}
isHistoryOpen={false}
SelectedContainer={null}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={() => {}}
/>);
@ -39,7 +38,6 @@ describe.concurrent('Elements sidebar', () => {
isOpen={true}
isHistoryOpen={false}
SelectedContainer={null}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={() => {}}
/>);
@ -69,7 +67,6 @@ describe.concurrent('Elements sidebar', () => {
isOpen={true}
isHistoryOpen={false}
SelectedContainer={MainContainer}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={() => {}}
/>);
@ -154,7 +151,6 @@ describe.concurrent('Elements sidebar', () => {
isOpen={true}
isHistoryOpen={false}
SelectedContainer={MainContainer}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={() => {}}
/>);
@ -207,7 +203,6 @@ describe.concurrent('Elements sidebar', () => {
isOpen={true}
isHistoryOpen={false}
SelectedContainer={SelectedContainer}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={selectContainer}
/>);
@ -229,7 +224,6 @@ describe.concurrent('Elements sidebar', () => {
isOpen={true}
isHistoryOpen={false}
SelectedContainer={SelectedContainer}
onClick={() => {}}
onPropertyChange={() => {}}
selectContainer={selectContainer}
/>);

View file

@ -9,7 +9,6 @@ interface IElementsSidebarProps {
isOpen: boolean
isHistoryOpen: boolean
SelectedContainer: IContainerModel | null
onClick: () => void
onPropertyChange: (key: string, value: string) => void
selectContainer: (container: IContainerModel) => void
}
@ -42,8 +41,8 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
const selectedClass: string = this.props.SelectedContainer !== undefined &&
this.props.SelectedContainer !== null &&
this.props.SelectedContainer.properties.id === container.properties.id
? 'bg-blue-500 hover:bg-blue-600'
: 'bg-slate-400 hover:bg-slate-600';
? 'border-l-4 border-blue-500 bg-slate-400/60 hover:bg-slate-400'
: 'bg-slate-300/60 hover:bg-slate-300';
containerRows.push(
<motion.button
whileHover={{ scale: 1.05 }}
@ -65,14 +64,11 @@ export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps>
});
return (
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<button className='close-button bg-slate-400 hover:bg-slate-600 justify-start' onClick={this.props.onClick}>
&times; Close
</button>
<div className='bg-slate-500 sidebar-row'>
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<div className='bg-slate-100 font-bold sidebar-title'>
Elements
</div>
<div className='overflow-y-auto overflow-x-hidden text-slate-200 flex-grow divide-y divide-solid divide-slate-500'>
<div className='overflow-y-auto overflow-x-hidden text-gray-800 flex-grow'>
{ containerRows }
</div>
<Properties properties={this.props.SelectedContainer?.properties} onChange={this.props.onPropertyChange}></Properties>

View file

@ -21,7 +21,7 @@ const FloatingButton: React.FC<IFloatingButtonProps> = (props: IFloatingButtonPr
: <XIcon className="floating-btn" />;
return (
<div className={props.className}>
<div className={`transition-all ${props.className}`}>
<div className={`transition-all flex flex-col gap-2 items-center ${buttonListClasses}`}>
{ props.children }
</div>

View file

@ -5,7 +5,6 @@ interface IHistoryProps {
history: IHistoryState[]
historyCurrentStep: number
isOpen: boolean
onClick: () => void
jumpTo: (move: number) => void
}
@ -46,12 +45,9 @@ export class History extends React.PureComponent<IHistoryProps> {
states.reverse();
return (
<div className={`fixed flex flex-col bg-slate-400 text-white transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<button className='close-button bg-slate-500 hover:bg-slate-700 justify-start' onClick={this.props.onClick}>
&times; Close
</button>
<div className='bg-slate-600 sidebar-row'>
History
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<div className='bg-slate-600 font-bold sidebar-title'>
Timeline
</div>
<div className='overflow-y-auto overflow-x-hidden text-slate-300 flex-grow divide-y divide-solid divide-slate-600'>
{ states }

View file

@ -18,7 +18,7 @@ export class Properties extends React.PureComponent<IPropertiesProps> {
.forEach((pair) => this.handleProperties(pair, groupInput));
return (
<div className='p-3 bg-slate-500 h-3/5 overflow-y-auto'>
<div className='p-3 bg-slate-200 h-3/5 overflow-y-auto'>
{ groupInput }
</div>
);
@ -33,12 +33,12 @@ export class Properties extends React.PureComponent<IPropertiesProps> {
const isDisabled = key === 'id' || key === 'parentId'; // hardcoded
groupInput.push(
<div key={id} className='mt-4'>
<label className='text-sm font-medium text-slate-200' htmlFor={id}>{key}</label>
<label className='text-sm font-medium text-gray-800' htmlFor={id}>{key}</label>
<input
className='text-base font-medium transition-all text-slate-200 mt-1 block w-full px-3 py-2
bg-slate-600 border-2 border-slate-600 rounded-lg shadow-sm placeholder-slate-400
className='text-base font-medium transition-all text-gray-800 mt-1 block w-full px-3 py-2
bg-white border-2 border-white rounded-lg placeholder-gray-800
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
disabled:bg-slate-700 disabled:text-slate-400 disabled:border-slate-700 disabled:shadow-none
disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none
'
type={type}
id={id}

View file

@ -3,6 +3,7 @@ import { UncontrolledReactSVGPanZoom } from 'react-svg-pan-zoom';
import { Container } from './Elements/Container';
import { ContainerModel } from '../../Interfaces/ContainerModel';
import { Selector } from './Elements/Selector';
import { BAR_WIDTH } from '../Bar/Bar';
interface ISVGProps {
width: number
@ -12,8 +13,8 @@ interface ISVGProps {
}
interface ISVGState {
viewerWidth: number,
viewerHeight: number,
viewerWidth: number
viewerHeight: number
}
export class SVG extends React.PureComponent<ISVGProps> {
@ -23,14 +24,14 @@ export class SVG extends React.PureComponent<ISVGProps> {
constructor(props: ISVGProps) {
super(props);
this.state = {
viewerWidth: window.innerWidth,
viewerWidth: window.innerWidth - BAR_WIDTH,
viewerHeight: window.innerHeight
};
}
resizeViewBox(): void {
this.setState({
viewerWidth: window.innerWidth,
viewerWidth: window.innerWidth - BAR_WIDTH,
viewerHeight: window.innerHeight
});
}
@ -60,7 +61,7 @@ export class SVG extends React.PureComponent<ISVGProps> {
}
return (
<div id={SVG.ID}>
<div id={SVG.ID} className='ml-16'>
<UncontrolledReactSVGPanZoom
width={this.state.viewerWidth}
height={this.state.viewerHeight}
@ -69,7 +70,7 @@ export class SVG extends React.PureComponent<ISVGProps> {
miniatureProps={{
position: 'left',
background: '#616264',
width: window.innerWidth - 12,
width: window.innerWidth - 12 - BAR_WIDTH,
height: 120
}}
>

View file

@ -5,29 +5,23 @@ import Sidebar from './Sidebar';
describe.concurrent('Sidebar', () => {
it('Start default', () => {
const handleClick = vi.fn();
render(
<Sidebar
componentOptions={[]}
isOpen={true}
onClick={handleClick}
buttonOnClick={() => {}}
/>
);
const stuff = screen.queryByText(/stuff/i);
const close = screen.getByText(/close/i);
expect(screen.getByText(/Components/i).classList.contains('left-0')).toBeDefined();
expect(stuff).toBeNull();
fireEvent.click(close);
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('Start close', () => {
render(<Sidebar
componentOptions={[]}
isOpen={false}
onClick={() => {}}
buttonOnClick={() => {}}
/>);
@ -49,7 +43,6 @@ describe.concurrent('Sidebar', () => {
}
]}
isOpen={true}
onClick={() => {}}
buttonOnClick={handleButtonClick}
/>);
const stuff = screen.getByText(/stuff/i);

View file

@ -1,32 +1,39 @@
import * as React from 'react';
import { AvailableContainer } from '../../Interfaces/AvailableContainer';
import { truncateString } from '../../utils/stringtools';
interface ISidebarProps {
componentOptions: AvailableContainer[]
isOpen: boolean
onClick: () => void
buttonOnClick: (type: string) => void
}
export default class Sidebar extends React.PureComponent<ISidebarProps> {
public render(): JSX.Element {
const listElements = this.props.componentOptions.map(componentOption =>
<button className='hover:bg-blue-600 transition-all sidebar-row' key={componentOption.Type} onClick={() => this.props.buttonOnClick(componentOption.Type)}>
{componentOption.Type}
<button
className='justify-center transition-all sidebar-component'
key={componentOption.Type}
title={componentOption.Type}
onClick={() => this.props.buttonOnClick(componentOption.Type)}
>
{truncateString(componentOption.Type, 5)}
</button>
);
const isOpenClasses = this.props.isOpen ? 'left-0' : '-left-64';
const isOpenClasses = this.props.isOpen ? 'left-16' : '-left-64';
return (
<div className={`fixed bg-blue-500 dark:bg-blue-500 text-white transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
<button className='close-button hover:bg-blue-600 justify-end' onClick={this.props.onClick}>
Close &times;
</button>
<div className='bg-blue-400 sidebar-row'>
<div className={`fixed z-10 bg-slate-200
text-gray-700 transition-all h-screen w-64
overflow-y-auto ${isOpenClasses}`}>
<div className='bg-slate-100 sidebar-title'>
Components
</div>
<div className='grid grid-cols-1 md:grid-cols-3 gap-2
m-2 md:text-xs font-bold'>
{listElements}
</div>
</div>
);
}
}

View file

@ -7,6 +7,7 @@ import { ContainerModel } from '../../Interfaces/ContainerModel';
import { IHistoryState } from '../../App';
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
import FloatingButton from '../FloatingButton/FloatingButton';
import { Bar } from '../Bar/Bar';
interface IUIProps {
current: IHistoryState
@ -58,7 +59,7 @@ export class UI extends React.PureComponent<IUIProps, IUIState> {
/**
* Toggle the elements
*/
public ToggleHistory(): void {
public ToggleTimeline(): void {
this.setState({
isHistoryOpen: !this.state.isHistoryOpen
});
@ -75,47 +76,34 @@ export class UI extends React.PureComponent<IUIProps, IUIState> {
return (
<>
<Bar
isSidebarOpen={this.state.isSidebarOpen}
isElementsSidebarOpen={this.state.isElementsSidebarOpen}
isHistoryOpen={this.state.isHistoryOpen}
ToggleElementsSidebar={() => this.ToggleElementsSidebar()}
ToggleSidebar={() => this.ToggleSidebar()}
ToggleTimeline={() => this.ToggleTimeline()}
/>
<Sidebar
componentOptions={this.props.AvailableContainers}
isOpen={this.state.isSidebarOpen}
onClick={() => this.ToggleSidebar()}
buttonOnClick={(type: string) => this.props.AddContainer(type)}
/>
<button
className='fixed z-10 top-4 left-4 text-lg bg-blue-200 hover:bg-blue-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleSidebar()}
>
&#9776; Components
</button>
<ElementsSidebar
MainContainer={this.props.current.MainContainer}
SelectedContainer={this.props.current.SelectedContainer}
isOpen={this.state.isElementsSidebarOpen}
isHistoryOpen={this.state.isHistoryOpen}
onClick={() => this.ToggleElementsSidebar()}
onPropertyChange={this.props.OnPropertyChange}
selectContainer={this.props.SelectContainer}
/>
<button
className='fixed z-10 top-4 right-12 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleElementsSidebar()}
>
&#9776; Elements
</button>
<History
history={this.props.history}
historyCurrentStep={this.props.historyCurrentStep}
isOpen={this.state.isHistoryOpen}
onClick={() => this.ToggleHistory()}
jumpTo={this.props.LoadState}
/>
<button
className='fixed z-10 top-4 right-72 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
onClick={() => this.ToggleHistory()}>
&#9776; History
</button>
<FloatingButton className={`fixed z-10 flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
<button
@ -123,14 +111,14 @@ export class UI extends React.PureComponent<IUIProps, IUIState> {
title='Export as JSON'
onClick={this.props.SaveEditorAsJSON}
>
<UploadIcon className="h-full w-full text-white align-middle items-center justify-center" />
<UploadIcon className="heroicon text-white" />
</button>
<button
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as SVG'
onClick={this.props.SaveEditorAsSVG}
>
<PhotographIcon className="h-full w-full text-white align-middle items-center justify-center" />
<PhotographIcon className="heroicon text-white" />
</button>
</FloatingButton>
</>

Binary file not shown.

View file

@ -3,19 +3,45 @@
@tailwind utilities;
@layer components {
.sidebar-row {
@apply p-6 w-full
.sidebar-title {
@apply p-6 font-bold
}
.sidebar-component {
@apply transition-all px-2 py-6 text-sm rounded-lg bg-slate-300/60 hover:bg-slate-300
}
.elements-sidebar-row {
@apply pl-6 pr-6 pt-2 pb-2 w-full
}
.close-button {
@apply transition-all w-full h-auto p-4 flex
}
.mainmenu-btn {
@apply transition-all bg-blue-100 hover:bg-blue-200 text-blue-700 text-lg font-semibold p-8 rounded-lg
}
.floating-btn {
@apply h-full w-full text-white align-middle items-center justify-center
}
.bar-btn {
@apply h-16 w-full p-3 bg-slate-100 hover:bg-slate-200
transition-all text-gray-700 hover:text-gray-600
}
.heroicon {
@apply h-full w-full align-middle items-center justify-center
}
.sidebar-tooltip {
@apply absolute w-auto p-2 m-2 min-w-max left-14
rounded-md shadow-md
text-gray-800 bg-slate-100
dark:text-white dark:bg-gray-800
text-xs font-bold
transition-all duration-100 scale-0 origin-left;
}
}

6
src/utils/stringtools.ts Normal file
View file

@ -0,0 +1,6 @@
export function truncateString(str: string, num: number): string {
if (str.length <= num) {
return str;
}
return `${str.slice(0, num)}...`;
}