Skip to content

Commit eaa5161

Browse files
committed
add MyTagsContainer to keep state in sync
1 parent 70efbac commit eaa5161

File tree

7 files changed

+111
-86
lines changed

7 files changed

+111
-86
lines changed

explorer/__fixtures__/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ import { TagDrawingSets } from '../types'
44
import tagDrawingSetsJson from './tagDrawingSets.json'
55

66
export const tagDrawingSets: TagDrawingSets = tagDrawingSetsJson as TagDrawingSets
7+
export const tags: string[] = Object.keys(tagDrawingSets).sort()

explorer/components/DrawingPage/AddTagModal.tsx

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
import React from 'react'
22
import styled from 'styled-components'
3-
import { Drawing, TagDrawingSets } from '../../types'
3+
import { Drawing } from '../../types'
44
import Modal from 'react-modal'
5-
import {
6-
addMyTagToDrawing,
7-
drawingHasMyTag,
8-
loadMyTagDrawingSets,
9-
removeMyTagFromDrawing,
10-
} from '../../lib/hashtags'
5+
import { MyTagsContainer } from '../../containers/MyTags'
116

127
export const AddTagModal: React.FC<{ drawing: Drawing; isOpen: boolean; closeModal: () => void }> = ({
138
drawing,
149
isOpen,
1510
closeModal,
1611
}) => {
17-
const [newTag, setNewTag] = React.useState('')
18-
const [tagDrawingSets, setTagDrawingSets] = React.useState<TagDrawingSets>({})
12+
const {
13+
myTags,
14+
drawingHasMyTag,
15+
addMyTagToDrawing,
16+
removeMyTagFromDrawing,
17+
} = MyTagsContainer.useContainer()
1918

20-
React.useEffect(() => setTagDrawingSets(loadMyTagDrawingSets()), [isOpen])
19+
const [newTag, setNewTag] = React.useState('')
2120

2221
function onNewTagSubmit(e: React.FormEvent<HTMLFormElement>) {
2322
addMyTagToDrawing(newTag, drawing)
2423
setNewTag('')
25-
setTagDrawingSets(loadMyTagDrawingSets())
2624
e.preventDefault()
2725
}
28-
const tags = Object.keys(tagDrawingSets)
2926

3027
return (
3128
<Modal
@@ -41,7 +38,7 @@ export const AddTagModal: React.FC<{ drawing: Drawing; isOpen: boolean; closeMod
4138
</form>
4239
<br />
4340
<TagList>
44-
{tags.sort().map(tag => (
41+
{myTags.map(tag => (
4542
<Tag key={tag}>
4643
<label>
4744
<input
@@ -51,7 +48,6 @@ export const AddTagModal: React.FC<{ drawing: Drawing; isOpen: boolean; closeMod
5148
e.currentTarget.checked
5249
? addMyTagToDrawing(tag, drawing)
5350
: removeMyTagFromDrawing(tag, drawing)
54-
setTagDrawingSets(loadMyTagDrawingSets())
5551
}}
5652
/> {tag}
5753
</label>

explorer/components/DrawingPage/DrawingPage.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ import { useRouter } from 'next/router'
2222
import Div100vh from 'react-div-100vh'
2323
import { PageLayout } from '../PageLayout'
2424
import { ActionsMenu } from './ActionsMenu'
25-
import { getFixtureTagsForDrawing, getMyTagsForDrawing } from '../../lib/hashtags'
25+
import { getTagsForDrawing } from '../../lib/hashtags'
26+
import { MyTagsContainer } from '../../containers/MyTags'
2627

2728
export const DrawingPage: React.FC<{ drawing: Drawing, year: number }> = ({ drawing, year }) => {
29+
const { getMyTagsForDrawing } = MyTagsContainer.useContainer()
2830
const router = useRouter()
31+
2932
const goToPrevious = () => router.push(`/drawing/${getPreviousSlug(drawing.slug)}`)
3033
const goToNext = () => router.push(`/drawing/${getNextSlug(drawing.slug)}`)
3134

@@ -39,9 +42,8 @@ export const DrawingPage: React.FC<{ drawing: Drawing, year: number }> = ({ draw
3942
handler?.()
4043
}
4144

42-
const fixtureTags = getFixtureTagsForDrawing(drawing)
43-
44-
const myTags = getMyTagsForDrawing(drawing) // needs to come from context in order to update
45+
const tags = getTagsForDrawing(drawing)
46+
const myTags = getMyTagsForDrawing(drawing)
4547

4648
return (
4749
<PageLayout title={drawing.title} showHeader={false} showFooter={false}>
@@ -51,8 +53,8 @@ export const DrawingPage: React.FC<{ drawing: Drawing, year: number }> = ({ draw
5153
<ImageWrap>
5254
<Image src={`${assetPrefix}/images/${drawing.image}`} alt={drawing.title} />
5355
</ImageWrap>
54-
{fixtureTags.length > 0 && <TagList>{fixtureTags.sort().map(tag => <Tag key={tag}>{tag}</Tag>)}</TagList>}
55-
{myTags.length > 0 && <TagList>{myTags.sort().map(tag => <Tag key={tag}>{tag}</Tag>)}</TagList>}
56+
{tags.length > 0 && <TagList>{tags.map(tag => <Tag key={tag}>{tag}</Tag>)}</TagList>}
57+
{myTags.length > 0 && <TagList>{myTags.map(tag => <Tag key={tag}>{tag}</Tag>)}</TagList>}
5658
<NavBar>
5759
<Link href="/drawing/[id]" as={`/drawing/${getPreviousSlug(drawing.slug)}`}>
5860
<ArrowButton title='Previous'><LeftArrow /></ArrowButton>

explorer/components/DrawingPage/ExportTagsModal.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
import React from 'react'
22
import styled, { css } from 'styled-components'
33
import Modal from 'react-modal'
4-
import { loadMyTagDrawingSets, saveMyTagDrawingSetsString } from '../../lib/hashtags'
4+
import { MyTagsContainer } from '../../containers/MyTags'
5+
import { TagDrawingSets } from '../../types'
56

67
export const ExportTagsModal: React.FC<{ isOpen: boolean; closeModal: () => void }> = ({
78
isOpen,
89
closeModal,
910
}) => {
10-
const tagDrawingSets = loadMyTagDrawingSets()
11-
const initialExportString = JSON.stringify(tagDrawingSets, null, 2)
12-
13-
// exportString state powers selectAll
14-
const [exportString, setExportString] = React.useState(initialExportString)
11+
const { myTagDrawingSets, saveMyTagDrawingSets } = MyTagsContainer.useContainer()
12+
const storedValue: string = JSON.stringify(myTagDrawingSets, null, 2)
1513
const [showCopySuccess, setShowCopySuccess] = React.useState(false)
1614

1715
function selectAll() {
18-
navigator.clipboard.writeText(exportString)
16+
navigator.clipboard.writeText(storedValue)
1917
setShowCopySuccess(true)
2018
setTimeout(() => setShowCopySuccess(false), 2000)
2119
}
2220

23-
// Edits in textarea are written to localStorage
21+
// Valid edits in textarea are written to localStorage
2422
function onChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
25-
setExportString(e.currentTarget.value)
26-
saveMyTagDrawingSetsString(e.currentTarget.value)
23+
try {
24+
saveMyTagDrawingSets(JSON.parse(e.currentTarget.value) as TagDrawingSets)
25+
} catch (e) {}
2726
}
2827

2928
return (
@@ -34,7 +33,7 @@ export const ExportTagsModal: React.FC<{ isOpen: boolean; closeModal: () => void
3433
<SelectAllButton onClick={selectAll} showSuccess={showCopySuccess}>select all</SelectAllButton>
3534
<CloseButton onClick={closeModal}>close</CloseButton>
3635
</ButtonBar>
37-
<ExportTextarea value={exportString} onChange={onChange}>{exportString}</ExportTextarea>
36+
<ExportTextarea value={storedValue} onChange={onChange} />
3837
</Wrap>
3938
</Modal>
4039
)

explorer/containers/MyTags.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from 'react'
2+
import { createContainer } from 'unstated-next'
3+
import { duplicateTagDrawingSets, loadMyTagDrawingSets } from '../lib/hashtags'
4+
import { Drawing, TagDrawingSets } from '../types'
5+
6+
export function useMyTags() {
7+
const [myTagDrawingSets, setMyTagDrawingSets] = React.useState<TagDrawingSets>({})
8+
9+
// Init client-side from localStorage
10+
React.useEffect(() => setMyTagDrawingSets(loadMyTagDrawingSets()), [])
11+
12+
function saveMyTagDrawingSets(tagDrawingSets: TagDrawingSets): void {
13+
localStorage.setItem('tagDrawingSets', JSON.stringify(tagDrawingSets))
14+
setMyTagDrawingSets(tagDrawingSets)
15+
}
16+
17+
function drawingHasMyTag(drawing: Drawing, tag: string): boolean {
18+
const drawings: Drawing[] = myTagDrawingSets[tag] ?? []
19+
return drawings.some(o => o.id === drawing.id)
20+
}
21+
22+
const myTags: string[] = Object.keys(myTagDrawingSets).sort()
23+
24+
function addMyTagToDrawing(tag: string, drawing: Drawing): void {
25+
const workingCopy: TagDrawingSets = duplicateTagDrawingSets(myTagDrawingSets)
26+
const drawings: Drawing[] = workingCopy[tag] ?? []
27+
if (!drawings.some(o => o.id === drawing.id)) {
28+
drawings.push(drawing)
29+
workingCopy[tag] = drawings
30+
saveMyTagDrawingSets(workingCopy)
31+
setMyTagDrawingSets(workingCopy)
32+
}
33+
}
34+
35+
function removeMyTagFromDrawing(tag: string, drawing: Drawing): void {
36+
const workingCopy: TagDrawingSets = duplicateTagDrawingSets(myTagDrawingSets)
37+
const drawings: Drawing[] | undefined = workingCopy[tag]
38+
if (!drawings) return
39+
workingCopy[tag] = drawings.filter(o => o.id !== drawing.id)
40+
if (!workingCopy[tag].length) delete workingCopy[tag]
41+
saveMyTagDrawingSets(workingCopy)
42+
setMyTagDrawingSets(workingCopy)
43+
}
44+
45+
function getMyTagsForDrawing(drawing: Drawing): string[] {
46+
return myTags.filter(tag => drawingHasMyTag(drawing, tag))
47+
}
48+
49+
return {
50+
myTagDrawingSets,
51+
saveMyTagDrawingSets,
52+
myTags,
53+
drawingHasMyTag,
54+
addMyTagToDrawing,
55+
removeMyTagFromDrawing,
56+
getMyTagsForDrawing,
57+
}
58+
}
59+
60+
export const MyTagsContainer = createContainer(useMyTags)

explorer/lib/hashtags.ts

Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,26 @@
11
import { Drawing, TagDrawingSets } from '../types'
2-
import { tagDrawingSets as fixtureTagDrawingSets } from '../__fixtures__'
2+
import {
3+
tagDrawingSets as fixtureTagDrawingSets,
4+
tags as fixtureTags,
5+
} from '../__fixtures__'
36

4-
export function loadMyTagDrawingSets(): TagDrawingSets {
5-
if (typeof window === 'undefined') return {} as TagDrawingSets
6-
return JSON.parse(localStorage.getItem('tagDrawingSets') || '{}') as TagDrawingSets
7-
}
8-
9-
export function saveMyTagDrawingSets(tagDrawingSets: TagDrawingSets): void {
10-
saveMyTagDrawingSetsString(JSON.stringify(tagDrawingSets || {} as TagDrawingSets))
11-
}
12-
13-
export function saveMyTagDrawingSetsString(value: string): void {
14-
localStorage.setItem('tagDrawingSets', value)
7+
export function getTagsForDrawing(drawing: Drawing): string[] {
8+
return fixtureTags.filter(tag => drawingHasTag(drawing, tag))
159
}
1610

17-
export function drawingHasMyTag(drawing: Drawing, tag: string): boolean {
18-
const tagDrawingSets: TagDrawingSets = loadMyTagDrawingSets()
19-
const drawings: Drawing[] = tagDrawingSets[tag] ?? []
11+
export function drawingHasTag(drawing: Drawing, tag: string): boolean {
12+
const drawings: Drawing[] = fixtureTagDrawingSets[tag] ?? []
2013
return drawings.some(o => o.id === drawing.id)
2114
}
2215

23-
export function addMyTagToDrawing(tag: string, drawing: Drawing): void {
24-
const tagDrawingSets: TagDrawingSets = loadMyTagDrawingSets()
25-
const drawings: Drawing[] = tagDrawingSets[tag] ?? []
26-
if (!drawings.some(o => o.id === drawing.id)) {
27-
drawings.push(drawing)
28-
tagDrawingSets[tag] = drawings
29-
saveMyTagDrawingSets(tagDrawingSets)
30-
}
31-
}
32-
33-
export function removeMyTagFromDrawing(tag: string, drawing: Drawing): void {
34-
const tagDrawingSets: TagDrawingSets = loadMyTagDrawingSets()
35-
const drawings: Drawing[] | undefined = tagDrawingSets[tag]
36-
if (!drawings) return
37-
tagDrawingSets[tag] = drawings.filter(o => o.id !== drawing.id)
38-
if (!tagDrawingSets[tag].length) delete tagDrawingSets[tag]
39-
saveMyTagDrawingSets(tagDrawingSets)
40-
}
41-
42-
export function getMyTags(): string[] {
43-
const tagDrawingSets: TagDrawingSets = loadMyTagDrawingSets()
44-
return Object.keys(tagDrawingSets)
45-
}
46-
47-
export function getMyTagsForDrawing(drawing: Drawing): string[] {
48-
return getMyTags().filter(tag => drawingHasMyTag(drawing, tag))
49-
}
50-
51-
export function getFixtureTagsForDrawing(drawing: Drawing): string[] {
52-
return getFixtureTags().filter(tag => drawingHasFixtureTag(drawing, tag))
53-
}
54-
55-
export function getFixtureTags(): string[] {
56-
return Object.keys(fixtureTagDrawingSets)
16+
export function loadMyTagDrawingSets(): TagDrawingSets {
17+
if (typeof window === 'undefined') return {} as TagDrawingSets
18+
return JSON.parse(localStorage.getItem('tagDrawingSets') || '{}') as TagDrawingSets
5719
}
5820

59-
export function drawingHasFixtureTag(drawing: Drawing, tag: string): boolean {
60-
const drawings: Drawing[] = fixtureTagDrawingSets[tag] ?? []
61-
return drawings.some(o => o.id === drawing.id)
21+
export function duplicateTagDrawingSets(original: TagDrawingSets): TagDrawingSets {
22+
return Object.keys(original).reduce<TagDrawingSets>((accumulator, tag) => {
23+
accumulator[tag] = [...original[tag]]
24+
return accumulator
25+
}, {})
6226
}

explorer/pages/_app.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import { AppFlags } from '../components/AppFlags'
22
import { AppStyle } from '../components/AppStyle'
33
import { CookiesProvider } from 'react-cookie'
44
import Modal from 'react-modal'
5+
import { MyTagsContainer } from '../containers/MyTags'
56

67
Modal.setAppElement('#__next')
78

89
export default function App({ Component, pageProps }) {
910
return (
1011
<CookiesProvider>
11-
<AppFlags />
12-
<AppStyle />
13-
<Component {...pageProps} />
12+
<MyTagsContainer.Provider>
13+
<AppFlags />
14+
<AppStyle />
15+
<Component {...pageProps} />
16+
</MyTagsContainer.Provider>
1417
</CookiesProvider>
1518
)
1619
}

0 commit comments

Comments
 (0)