Editor Basics (#43)

Add basic editor
This commit is contained in:
Philip Molares 2020-05-29 15:44:45 +02:00 committed by GitHub
parent 557386f78f
commit e2155e735d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 834 additions and 467 deletions

View file

@ -1,16 +1,16 @@
import React, { Fragment, useEffect, useState } from 'react'
import { useLocation } from 'react-router'
import { setUp } from '../../initializers'
import './application-loader.scss'
import { LoadingScreen } from './loading-screen'
interface ApplicationLoaderProps {
initTasks: Promise<void>[]
}
export const ApplicationLoader: React.FC<ApplicationLoaderProps> = ({ children, initTasks }) => {
export const ApplicationLoader: React.FC = ({ children }) => {
const [failed, setFailed] = useState<boolean>(false)
const [doneTasks, setDoneTasks] = useState<number>(0)
const [initTasks, setInitTasks] = useState<Promise<void>[]>([])
const { pathname } = useLocation()
const runTask:((task: Promise<void>) => (Promise<void>)) = async (task) => {
const runTask = async (task: Promise<void>): Promise<void> => {
await task
setDoneTasks(prevDoneTasks => {
return prevDoneTasks + 1
@ -18,7 +18,12 @@ export const ApplicationLoader: React.FC<ApplicationLoaderProps> = ({ children,
}
useEffect(() => {
setDoneTasks(0)
const baseUrl:string = window.location.pathname.replace(pathname, '') + '/'
console.debug('Base URL is', baseUrl)
setInitTasks(setUp(baseUrl))
}, [pathname])
useEffect(() => {
for (const task of initTasks) {
runTask(task).catch(reason => {
setFailed(true)

View file

@ -0,0 +1,11 @@
import React from 'react'
const EditorWindow: React.FC = () => {
return (
<div style={{ backgroundColor: 'green' }}>
Hello, EditorWindow!
</div>
)
}
export { EditorWindow }

View file

@ -0,0 +1,30 @@
import React from 'react'
import { useSelector } from 'react-redux'
import { useParams } from 'react-router'
import { ApplicationState } from '../../redux'
import { EditorMode } from '../../redux/editor/types'
import { EditorWindow } from './editor-window/editor-window'
import { MarkdownPreview } from './markdown-preview/markdown-preview'
import { TaskBar } from './task-bar/task-bar'
interface RouteParameters {
id: string
}
const Editor: React.FC = () => {
const editorMode: EditorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode)
const { id } = useParams<RouteParameters>()
return (
<div className={'d-flex flex-column vh-100'}>
<TaskBar/>
<h1>{id}</h1>
<div className={'flex-fill flex-row d-flex'}>
{ editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH ? <EditorWindow/> : null }
{ editorMode === EditorMode.PREVIEW || editorMode === EditorMode.BOTH ? <MarkdownPreview/> : null }
</div>
</div>
)
}
export { Editor }

View file

@ -0,0 +1,11 @@
import React from 'react'
const MarkdownPreview: React.FC = () => {
return (
<div style={{ backgroundColor: 'red' }}>
Hello, MarkdownPreview!
</div>
)
}
export { MarkdownPreview }

View file

@ -0,0 +1,14 @@
.activeIndicator {
$indicator-size: 12px;
border-radius: $indicator-size;
height: $indicator-size;
width: $indicator-size;
&.active {
background-color: #5cb85c;
}
&.inactive {
background-color: #d20000;
}
}

View file

@ -0,0 +1,17 @@
import React from 'react'
import './active-indicator.scss'
export enum ActiveIndicatorStatus {
ACTIVE ='active',
INACTIVE ='inactive'
}
export interface ActiveIndicatorProps {
status: ActiveIndicatorStatus;
}
export const ActiveIndicator: React.FC<ActiveIndicatorProps> = ({ status }) => {
return (
<span className={`activeIndicator ${status}`}/>
)
}

View file

@ -0,0 +1,3 @@
.upper-case {
text-transform: uppercase;
}

View file

@ -0,0 +1,27 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React from 'react'
import { Dropdown } from 'react-bootstrap'
import { ActiveIndicatorStatus } from './active-indicator'
import './connection-indicator.scss'
import { UserLine } from './user-line'
const ConnectionIndicator: React.FC = () => {
const userOnline = 2
return (
<Dropdown className="small" alignRight>
<Dropdown.Toggle id="connection-indicator" size="sm" variant="primary" className="upper-case">
<FontAwesomeIcon icon="users"/> {userOnline} Online
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item disabled={true} className="d-flex align-items-center p-0">
<UserLine name="Philip Molares" photo="https://1.gravatar.com/avatar/767fc9c115a1b989744c755db47feb60?s=200&r=pg&d=mp" color="red" status={ActiveIndicatorStatus.INACTIVE}/>
</Dropdown.Item>
<Dropdown.Item disabled={true} className="d-flex align-items-center p-0">
<UserLine name="Philip Molares" photo="https://1.gravatar.com/avatar/767fc9c115a1b989744c755db47feb60?s=200&r=pg&d=mp" color="blue" status={ActiveIndicatorStatus.ACTIVE}/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
)
}
export { ConnectionIndicator }

View file

@ -0,0 +1,15 @@
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
const DarkModeButton: React.FC = () => {
return (
<ToggleButtonGroup type="checkbox" defaultValue={[]} name="dark-mode" className="ml-2">
<ToggleButton value={1} variant="light" className="text-secondary">
<FontAwesomeIcon icon="moon"/>
</ToggleButton>
</ToggleButtonGroup>
)
}
export { DarkModeButton }

View file

@ -0,0 +1,72 @@
import React from 'react'
import { Dropdown } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
const EditorMenu: React.FC = () => {
useTranslation()
return (
<Dropdown className="small" alignRight={true}>
<Dropdown.Toggle variant="light" size="sm" id="editor-menu" className="text-secondary">
<Trans i18nKey="menu"/>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Header>
<Trans i18nKey="extra"/>
</Dropdown.Header>
<Dropdown.Item className="small">
<FontAwesomeIcon icon="history"/> <Trans i18nKey="revision"/>
</Dropdown.Item>
<Dropdown.Item className="small">
<FontAwesomeIcon icon="tv"/> <Trans i18nKey="slideMode"/>
</Dropdown.Item>
<Dropdown.Divider/>
<Dropdown.Header>
<Trans i18nKey="export"/>
</Dropdown.Header>
<Dropdown.Item className="small">
<FontAwesomeIcon icon={['fab', 'dropbox']}/> Dropbox
</Dropdown.Item>
<Dropdown.Item className="small">
<FontAwesomeIcon icon={['fab', 'github']}/> Gist
</Dropdown.Item>
<Dropdown.Divider/>
<Dropdown.Header>
<Trans i18nKey="import"/>
</Dropdown.Header>
<Dropdown.Item className="small">
<FontAwesomeIcon icon={['fab', 'dropbox']}/> Dropbox
</Dropdown.Item>
<Dropdown.Item className="small">
<FontAwesomeIcon icon={['fab', 'github']}/> Gist
</Dropdown.Item>
<Dropdown.Item className="small">
<FontAwesomeIcon icon="paste"/> <Trans i18nKey="clipboard"/>
</Dropdown.Item>
<Dropdown.Divider/>
<Dropdown.Header>
<Trans i18nKey="download"/>
</Dropdown.Header>
<Dropdown.Item className="small">
<FontAwesomeIcon icon="file-alt"/> Markdown
</Dropdown.Item>
<Dropdown.Item className="small">
<FontAwesomeIcon icon="file-code"/> HTML
</Dropdown.Item>
<Dropdown.Item className="small">
<FontAwesomeIcon icon="file-code"/> Raw HTML
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
)
}
export { EditorMenu }

View file

@ -0,0 +1,30 @@
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React from 'react'
import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../redux'
import { EditorMode } from '../../../redux/editor/types'
import { setEditorModeConfig } from '../../../redux/editor/methods'
const EditorViewMode: React.FC = () => {
const editorConfig = useSelector((state: ApplicationState) => state.editorConfig)
return (
<ToggleButtonGroup
type="radio"
name="options"
defaultValue={editorConfig.editorMode}
onChange={(value: EditorMode) => { setEditorModeConfig(value) }}>
<ToggleButton value={EditorMode.PREVIEW} variant="outline-secondary">
<FontAwesomeIcon icon="eye"/>
</ToggleButton>
<ToggleButton value={EditorMode.BOTH} variant="outline-secondary">
<FontAwesomeIcon icon="columns"/>
</ToggleButton>
<ToggleButton value={EditorMode.EDITOR} variant="outline-secondary">
<FontAwesomeIcon icon="pencil-alt"/>
</ToggleButton>
</ToggleButtonGroup>
)
}
export { EditorViewMode }

View file

@ -0,0 +1,46 @@
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Link } from 'react-router-dom'
import { Button, Nav, Navbar } from 'react-bootstrap'
import { DarkModeButton } from './dark-mode-button'
import { EditorViewMode } from './editor-view-mode'
import { Trans, useTranslation } from 'react-i18next'
import { EditorMenu } from './editor-menu'
import { ConnectionIndicator } from './connection-indicator'
const TaskBar: React.FC = () => {
useTranslation()
return (
<Navbar bg={'light'}>
<Nav className="mr-auto d-flex align-items-center">
<Navbar.Brand>
<Link to="/intro" className="text-secondary">
<FontAwesomeIcon icon="file-alt"/> CodiMD
</Link>
</Navbar.Brand>
<EditorViewMode/>
<DarkModeButton/>
<Button className="ml-2 text-secondary" size="sm"
variant="outline-light">
<FontAwesomeIcon icon="question-circle"/>
</Button>
</Nav>
<Nav className="d-flex align-items-center text-secondary">
<Button className="ml-2 text-secondary" size="sm" variant="outline-light">
<FontAwesomeIcon icon="plus"/> <Trans i18nKey="new"/>
</Button>
<Button className="ml-2 text-secondary" size="sm" variant="outline-light">
<FontAwesomeIcon icon="share-square"/> <Trans i18nKey="publish"/>
</Button>
<div className="text-secondary">
<EditorMenu/>
</div>
<div className="mr-2">
<ConnectionIndicator/>
</div>
</Nav>
</Navbar>
)
}
export { TaskBar }

View file

@ -0,0 +1,5 @@
.user-line-color-indicator {
border-left: 3px solid;
min-height: 30px;
height: 100%;
}

View file

@ -0,0 +1,21 @@
import React, { Fragment } from 'react'
import { UserAvatar } from '../../landing/layout/user-avatar/user-avatar'
import { ActiveIndicator, ActiveIndicatorStatus } from './active-indicator'
import './user-line.scss'
export interface UserLineProps {
name: string;
photo: string;
color: string;
status: ActiveIndicatorStatus;
}
export const UserLine: React.FC<UserLineProps> = ({ name, photo, color, status }) => {
return (
<Fragment>
<div className='d-inline-flex align-items-bottom user-line-color-indicator' style={{ borderLeftColor: color }}/>
<UserAvatar photo={photo} name={name} additionalClasses={'mx-2'}/>
<ActiveIndicator status={status} />
</Fragment>
)
}

View file

@ -0,0 +1,15 @@
import React from 'react'
import { Container } from 'react-bootstrap'
import { HeaderBar } from './layout/navigation/header-bar/header-bar'
import { Footer } from './layout/footer/footer'
import './layout/style/index.scss'
export const LandingLayout: React.FC = ({ children }) => {
return (
<Container>
<HeaderBar/>
{children}
<Footer/>
</Container>
)
}

View file

@ -1,30 +0,0 @@
import React from 'react'
import { Redirect, Route, Switch } from 'react-router-dom'
import { History } from '../pages/history/history'
import { Intro } from '../pages/intro/intro'
import { Container } from 'react-bootstrap'
import { HeaderBar } from './navigation/header-bar/header-bar'
import { Footer } from './footer/footer'
import './style/index.scss'
import { Login } from '../pages/login/login'
export const Landing: React.FC = () => {
return (<Container>
<HeaderBar/>
<Switch>
<Route path="/history">
<History/>
</Route>
<Route path="/intro">
<Intro/>
</Route>
<Route path="/login">
<Login/>
</Route>
<Route path="/">
<Redirect to="/intro"/>
</Route>
</Switch>
<Footer/>
</Container>)
}

View file

@ -1,4 +0,0 @@
.user-avatar {
width: 16px;
height: 16px;
}

View file

@ -5,22 +5,16 @@ import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../../../redux'
import { LinkContainer } from 'react-router-bootstrap'
import { clearUser } from '../../../../../redux/user/methods'
import './user-dropdown.scss'
import { Trans } from 'react-i18next'
import { UserAvatar } from '../../user-avatar/user-avatar'
export const UserDropdown: React.FC = () => {
const user = useSelector((state: ApplicationState) => state.user)
return (
<Dropdown alignRight>
<Dropdown.Toggle size="sm" variant="dark" id="dropdown-basic">
<div className='d-inline-flex align-items-baseline'>
<img
src={user.photo}
className="user-avatar"
alt={`Avatar of ${user.name}`}
/><span>{user.name}</span>
</div>
<Dropdown.Toggle size="sm" variant="dark" id="dropdown-user" className={'d-flex align-items-center'}>
<UserAvatar name={user.name} photo={user.photo}/>
</Dropdown.Toggle>
<Dropdown.Menu>

View file

@ -0,0 +1,8 @@
.user-avatar {
width: 20px;
height: 20px;
}
.user-name {
font-size: 1rem;
}

View file

@ -0,0 +1,23 @@
import React from 'react'
import './user-avatar.scss'
export interface UserAvatarProps {
name: string;
photo: string;
additionalClasses?: string;
}
const UserAvatar: React.FC<UserAvatarProps> = ({ name, photo, additionalClasses = '' }) => {
return (
<span className={'d-inline-flex align-items-center ' + additionalClasses}>
<img
src={photo}
className="user-avatar"
alt={`Avatar of ${name}`}
/>
<span className="ml-1 user-name">{name}</span>
</span>
)
}
export { UserAvatar }