Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflect board selection in the titlebar #365

Merged
merged 1 commit into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/renderer/Hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,68 @@ export function useStaticCallback<T extends (...args: any[]) => any>(callback: T

return useCallback((...args: any[]) => cb.current(...args), []) as T
}

type Setter<T> = ((state: T) => T) | T
/**
* Provides access to shared mutable state via hook interface. Provided
* handle object used as key to which shared state is associated with
* there for thing wishing to share the state will need to share the handle.
*/
export function useSharedState<T>(id: string, defaultValue: T): [T, (v: Setter<T>) => void] {
const atom = Atom.new(id, defaultValue)
const [state, setState] = useState(atom.value)
useEffect((): (() => void) => {
atom.watch(setState)
return () => atom.unwatch(setState)
}, [])

const setSharedState: any = (t: any) => {
if (typeof t === 'function') {
atom.transact(t as any)
} else {
atom.swap(t)
}
}

return [state, setSharedState]
}

class Atom<T> {
value: T
watchers: ((state: T) => any)[]
static new<T>(id: string, value: T): Atom<T> {
if (atoms.has(id)) {
return atoms.get(id)
}
const atom = new Atom(value)
atoms.set(id, atom)
return atom
}
constructor(value: T) {
this.value = value
this.watchers = []
}
transact(f: (inn: T) => T) {
this.value = f(this.value)
this.notify()
}
swap(value: T) {
this.value = value
this.notify()
}
notify() {
const { value, watchers } = this
watchers.forEach((watcher) => watcher(value))
}
watch(watcher: (state: T) => any) {
this.watchers.push(watcher)
}
unwatch(watcher: (state: T) => any) {
const index = this.watchers.indexOf(watcher)
if (index >= 0) {
this.watchers.splice(index, index + 1)
}
}
}

const atoms = new Map()
12 changes: 9 additions & 3 deletions src/renderer/components/TitleEditor.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
.TitleEditor {
font-size: 14px;
line-height: 20px;
width: 100%;
font-size: 1.25em;
padding: 0px; /* someone (me?) put padding on all inputs */
}

/* use hidden text to cause title editor
input expand with typed text */
span.TitleEditor {
visibility: hidden;
z-index: -1;
height: 0px;
}
27 changes: 16 additions & 11 deletions src/renderer/components/TitleEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,22 @@ export default function TitleEditor(props: Props) {
return null
}

// span below input ensures that outer element is resized according
// to the content causing input field also grow with it.
return (
<input
ref={input}
draggable={preventDrag}
onDragStart={onDragStart}
type="text"
className="TitleEditor"
value={doc[field]}
placeholder={placeholder}
onKeyDown={onKeyDown}
onChange={onChange}
/>
<>
<input
ref={input}
draggable={preventDrag}
onDragStart={onDragStart}
type="text"
className="TitleEditor"
value={doc[field]}
placeholder={placeholder}
onKeyDown={onKeyDown}
onChange={onChange}
/>
<span className="TitleEditor">{doc[field]}</span>
</>
)
}
1 change: 1 addition & 0 deletions src/renderer/components/content-types/TextContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ ContentTypes.register({
board: TextContent,
workspace: TextContent,
list: TextInList,
'title-bar': TextInList,
},
create,
createFrom,
Expand Down
36 changes: 35 additions & 1 deletion src/renderer/components/content-types/ThreadContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import * as ContentTypes from '../../ContentTypes'
import Content, { ContentProps } from '../Content'
import { createDocumentLink, HypermergeUrl } from '../../ShareLink'
import { useDocument } from '../../Hooks'
import ListItem from '../ui/ListItem'
import Badge from '../ui/Badge'
import ContentDragHandle from '../ui/ContentDragHandle'
import TitleWithSubtitle from '../ui/TitleWithSubtitle'
import './ThreadContent.css'

interface Message {
Expand All @@ -13,6 +17,7 @@ interface Message {
}

interface Doc {
title?: string
messages: Message[]
}

Expand Down Expand Up @@ -88,6 +93,31 @@ export default function ThreadContent(props: ContentProps) {
)
}

export function ThreadInList(props: ContentProps) {
const { hypermergeUrl, url } = props
const [doc] = useDocument<Doc>(hypermergeUrl)
if (!doc) return null

const title = doc.title != null && doc.title !== '' ? doc.title : 'Untitled conversation'
const subtitle = (doc.messages[doc.messages.length - 1] || { content: '' }).content
const editable = true

return (
<ListItem>
<ContentDragHandle url={url}>
<Badge icon={icon} />
</ContentDragHandle>
<TitleWithSubtitle
titleEditorField="title"
title={title}
subtitle={subtitle}
hypermergeUrl={hypermergeUrl}
editable={editable}
/>
</ListItem>
)
}

function stopPropagation(e: React.SyntheticEvent) {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
Expand Down Expand Up @@ -136,13 +166,17 @@ function create(unusedAttrs, handle) {
})
}

const icon = 'comments'

ContentTypes.register({
type: 'thread',
name: 'Thread',
icon: 'comments',
icon,
contexts: {
workspace: ThreadContent,
board: ThreadContent,
list: ThreadInList,
'title-bar': ThreadInList,
},
create,
})
1 change: 1 addition & 0 deletions src/renderer/components/content-types/UrlContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ ContentTypes.register({
workspace: UrlContent,
board: UrlContent,
list: UrlContent,
'title-bar': UrlContent,
},
create,
createFrom,
Expand Down
27 changes: 27 additions & 0 deletions src/renderer/components/content-types/board/Board.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,30 @@
background-image: url('../../../images/grid.svg');
background-blend-mode: exclusion;
}

.ActiveCard {
overflow: hidden;
margin: 0 3px;
border-radius: 16px;
box-shadow: -6px 1px 3px -3px rgba(0, 0, 0, 0.3);
background: inherit;
flex: 1;
border-radius: 25px;
margin-left: -6px;
padding: 2px;
background: inherit;
}

.BoardSelection {
display: flex;
flex-flow: row;
background: inherit;
}

.BoardSelection .ActiveCard:not(:first-child) {
margin-left: -16px;
}

.BoardSelection .Badge {
--size: 22px;
}
4 changes: 3 additions & 1 deletion src/renderer/components/content-types/board/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ const Board: FunctionComponent<ContentProps> = (props: ContentProps) => {
}))

const boardRef = useRef<HTMLDivElement>(null)
const { selection, selectOnly, selectToggle, selectNone } = useSelection<CardId>()
const { selection, selectOnly, selectToggle, selectNone } = useSelection<CardId>(
props.hypermergeUrl
)

const [doc, dispatch] = useDocumentReducer<BoardDoc, BoardAction>(
props.hypermergeUrl,
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/components/content-types/board/BoardCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
background-origin: content-box;
cursor: se-resize;
visibility: hidden;

/* Card content might use z-index as well so use high
index here to avoid resizer being covered.*/
z-index: 999;
}

.BoardCard:hover .BoardCard-resizeHandle {
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/content-types/board/BoardInBoard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'

import { ContentProps } from '../../Content'
import { BoardDoc } from '.'
import { BoardDoc, icon } from '.'
import { useDocument } from '../../../Hooks'
import './BoardInBoard.css'
import Badge from '../../ui/Badge'
Expand Down Expand Up @@ -30,7 +30,7 @@ export default function BoardInBoard(props: ContentProps) {

return (
<CenteredStack>
<Badge size="huge" icon="files-o" backgroundColor={backgroundColor} />
<Badge size="huge" icon={icon} backgroundColor={backgroundColor} />
<Heading wrap>{title}</Heading>
<SecondaryText>{subTitle}</SecondaryText>
</CenteredStack>
Expand Down
3 changes: 1 addition & 2 deletions src/renderer/components/content-types/board/BoardInList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { BoardDoc } from '.'
import { BoardDoc, icon } from '.'
import { ContentProps } from '../../Content'
import { useDocument } from '../../../Hooks'
import Badge, { Props as BadgeProps } from '../../ui/Badge'
Expand All @@ -20,7 +20,6 @@ export default function BoardInList(props: Props) {
}

const { title, backgroundColor, cards } = doc
const icon = 'files-o'

const cardLength = Object.keys(cards).length
const subtitle = `${cardLength} item${cardLength !== 1 ? 's' : ''}`
Expand Down
Loading