Skip to content

Commit

Permalink
Merge pull request #79 from s12chung/text_note
Browse files Browse the repository at this point in the history
createNoteData flow changes and minor polishes
  • Loading branch information
s12chung authored Nov 22, 2023
2 parents 409cf75 + ad95969 commit 7f5aeb1
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 118 deletions.
15 changes: 4 additions & 11 deletions pkg/extractor/instagram/instagram_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,19 @@ func TestSource_ID(t *testing.T) {
}
}

var extractToDirSuffixes = []int{0, 1, 2, 9, 10, 11, 99, 100, 101, 110}

const extractToDirPrefix = "2023-11-21_10-42-44_UTC_"

func init() {
extractToDirSuffixes := []int{0, 1, 2, 9, 10, 11, 99, 100, 101, 110}
args := make([]string, len(extractToDirSuffixes)+1)
args[0] = "touch"
for i, suffix := range extractToDirSuffixes {
args[i+1] = extractToDirPrefix + strconv.Itoa(suffix) + extensions[0]
args[i+1] = "2023-11-21_10-42-44_UTC_" + strconv.Itoa(suffix) + extensions[0]
}

extractToDirArgs = func(login, id string) []string { return args }
}

func TestPost_ExtractToDir(t *testing.T) {
testName := "TestPost_ExtractToDir"
cacheDir := path.Join(os.TempDir(), test.GenerateName("Instagram"))
require.NoError(t, os.MkdirAll(cacheDir, ioutil.OwnerRWXGroupRX))

Expand Down Expand Up @@ -108,12 +106,7 @@ func TestPost_ExtractToDir(t *testing.T) {
for i, entry := range entries {
entryNames[i] = entry.Name()
}

filenames := make([]string, len(extractToDirSuffixes))
for i, suffix := range extractToDirSuffixes {
filenames[i] = extractToDirPrefix + fmt.Sprintf("%03d", suffix) + extensions[0]
}
require.Equal(filenames, entryNames)
fixture.CompareReadOrUpdate(t, filepath.Join(testName, tc.name+".json"), fixture.JSON(t, entryNames))
})
}
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/extractor/instagram/testdata/TestPost_ExtractToDir/fake.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
"2023-11-21_10-42-44_UTC_000.jpg",
"2023-11-21_10-42-44_UTC_001.jpg",
"2023-11-21_10-42-44_UTC_002.jpg",
"2023-11-21_10-42-44_UTC_009.jpg",
"2023-11-21_10-42-44_UTC_010.jpg",
"2023-11-21_10-42-44_UTC_011.jpg",
"2023-11-21_10-42-44_UTC_099.jpg",
"2023-11-21_10-42-44_UTC_100.jpg",
"2023-11-21_10-42-44_UTC_101.jpg",
"2023-11-21_10-42-44_UTC_110.jpg"
]
13 changes: 7 additions & 6 deletions ui/src/components/notes/NoteCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,20 @@ const otherKeys = filterKeys(
[commonLevelKey],
)

const NoteCreate: React.FC<{ readonly data: CreateNoteData; readonly onClose: () => void }> = ({
data,
onClose,
}) => {
const NoteCreate: React.FC<{
readonly data: CreateNoteData
readonly onCreate: () => void
readonly onClose: () => void
}> = ({ data, onCreate, onClose }) => {
const fetcher = useFetcher<INoteFormData>()
const { error, success } = useContext(NotificationsContext)
useEffect(() => {
if (!fetcher.data) return
fetcher.data.note
.then((note) => success(`Created new Note: ${note.text}`))
.catch(() => error("Failed to create Note"))
.finally(() => onClose())
}, [fetcher, onClose, success, error])
.finally(() => onCreate())
}, [fetcher, onClose, success, error, onCreate])

const [submitted, setSubmitted] = useState<boolean>(false)
const submitButtonRef = useRef<HTMLButtonElement>(null)
Expand Down
155 changes: 92 additions & 63 deletions ui/src/components/sources/SourceShow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* eslint-disable max-lines */
import { CommonLevel } from "../../services/models/Lang.ts"
import { CreateNoteData, createNoteDataFromSourceTerm } from "../../services/models/Note.ts"
import {
CreateNoteData,
createNoteDataFromSourceTerm,
createNoteDataFromUsage,
} from "../../services/models/Note.ts"
import {
PosPunctuation,
Source,
Expand All @@ -18,11 +22,7 @@ import AwaitWithFallback from "../AwaitWithFallback.tsx"
import SlideOver from "../SlideOver.tsx"
import NoteCreate from "../notes/NoteCreate.tsx"
import { StopKeyboardContext, useStopKeyboard } from "./SourceShow_SourceComponent.ts"
import {
getTermProps,
ITermsComponentProps,
useFocusTextWithKeyboard,
} from "./SourceShow_SourceNavComponent.ts"
import { getUsage, useFocusTextWithKeyboard } from "./SourceShow_SourceNavComponent.ts"
import { otherTranslationTexts, useChangeTermWithKeyboard } from "./SourceShow_TermsComponent.ts"
import { useFocusTokenWithKeyboard } from "./SourceShow_TokensComponent.ts"
import {
Expand All @@ -32,7 +32,7 @@ import {
SourceEditHeader,
SourcePartDetailMenu,
} from "./SourceShow_Update.tsx"
import React, { useEffect, useMemo, useRef, useState } from "react"
import React, { useContext, useEffect, useMemo, useRef, useState } from "react"
import { useFetcher } from "react-router-dom"

export interface ISourceShowData {
Expand Down Expand Up @@ -237,75 +237,106 @@ const SourceShowComponent: React.FC<{ readonly source: Source }> = ({ source })
)
}

// eslint-disable-next-line max-lines-per-function
const SourceNavComponent: React.FC<{ readonly source: Source; readonly safeSet: SafeSet }> = ({
source,
safeSet,
}) => {
const [termProps, setTermProps] = useState<ITermsComponentProps | null>(null)
const [createNoteData, setCreateNoteData] = useState<CreateNoteData | null>(null)
const { setStopKeyboardEvents } = useContext(StopKeyboardContext)
useEffect(
() => setStopKeyboardEvents(createNoteData !== null),
[createNoteData, setStopKeyboardEvents],
)

const termsFocused = termProps !== null
const [selectedToken, setSelectedToken] = useState<Token | null>(null)
const isTokenSelected = selectedToken !== null
const [partFocusIndex, textFocusIndex, focusElement, setText] = useFocusTextWithKeyboard(
source.parts,
termsFocused,
() => setTermProps(null),
isTokenSelected,
() =>
setCreateNoteData(createNoteDataFromUsage(getUsage(source, partFocusIndex, textFocusIndex))),
() => setSelectedToken(null),
)

const textRefs = useRef<(HTMLDivElement | null)[][]>([])
useEffect(() => {
setTermProps(null)
setSelectedToken(null)
const textElement = textRefs.current[partFocusIndex][textFocusIndex]
if (!textElement) return
focusElement(textElement)
scrollTo(textElement)
}, [focusElement, partFocusIndex, textFocusIndex])

return (
<SourcePartsWrapper sourceId={source.id} parts={source.parts} safeSet={safeSet}>
{(tokenizedText, partIndex, textIndex) => {
const textFocused = partIndex === partFocusIndex && textIndex === textFocusIndex
return (
<div
ref={(ref) => {
if (!textRefs.current[partIndex]) textRefs.current[partIndex] = []
textRefs.current[partIndex][textIndex] = ref
}}
tabIndex={-1}
className={joinClasses(textFocused ? "py-4 bg-gray-std" : "", "group py-2")}
onClick={preventDefault(() => setText(partIndex, textIndex))}
>
<div className={joinClasses(textClassBase, textFocused ? "text-light" : "")}>
{tokenizedText.text}
</div>
{textFocused ? (
<TokensComponent
tokens={tokenizedText.tokens}
termsFocused={termsFocused}
onTokenSelect={(tokenIndex) =>
setTermProps(getTermProps(source, partFocusIndex, textFocusIndex, tokenIndex))
}
onTokenChange={(tokenElement) => focusElement(tokenElement)}
/>
) : null}
<div className={textFocused ? "text-2xl" : translationClassBase}>
{tokenizedText.translation}
<>
{createNoteData !== null && (
<CreateNoteDialog
data={createNoteData}
onCreate={() => {
setCreateNoteData(null)
setSelectedToken(null)
}}
onClose={() => {
setCreateNoteData(null)
}}
/>
)}
<SourcePartsWrapper sourceId={source.id} parts={source.parts} safeSet={safeSet}>
{(tokenizedText, partIndex, textIndex) => {
const textFocused = partIndex === partFocusIndex && textIndex === textFocusIndex
return (
<div
ref={(ref) => {
if (!textRefs.current[partIndex]) textRefs.current[partIndex] = []
textRefs.current[partIndex][textIndex] = ref
}}
tabIndex={-1}
className={joinClasses(textFocused ? "py-4 bg-gray-std" : "", "group py-2")}
onClick={preventDefault(() => setText(partIndex, textIndex))}
>
<div className={joinClasses(textClassBase, textFocused ? "text-light" : "")}>
{tokenizedText.text}
</div>
{textFocused ? (
<TokensComponent
tokens={tokenizedText.tokens}
isTokenSelected={isTokenSelected}
onTokenSelect={(tokenIndex) => setSelectedToken(tokenizedText.tokens[tokenIndex])}
onTokenChange={(tokenElement) => focusElement(tokenElement)}
/>
) : null}
<div className={textFocused ? "text-2xl" : translationClassBase}>
{tokenizedText.translation}
</div>
{textFocused && selectedToken ? (
<TermsComponent
token={selectedToken}
onTermSelect={(term: Term) => {
setCreateNoteData(
createNoteDataFromSourceTerm(
term,
getUsage(source, partFocusIndex, textFocusIndex),
),
)
}}
/>
) : null}
</div>
{textFocused && termsFocused ? (
<TermsComponent token={termProps.token} usage={termProps.usage} />
) : null}
</div>
)
}}
</SourcePartsWrapper>
)
}}
</SourcePartsWrapper>
</>
)
}

const TokensComponent: React.FC<{
readonly tokens: Token[]
readonly termsFocused: boolean
readonly isTokenSelected: boolean
readonly onTokenSelect: (tokenFocusIndex: number) => void
readonly onTokenChange: (tokenElement: HTMLDivElement) => void
}> = ({ tokens, termsFocused, onTokenSelect, onTokenChange }) => {
const [tokenFocusIndex] = useFocusTokenWithKeyboard(tokens, termsFocused, onTokenSelect)
}> = ({ tokens, isTokenSelected, onTokenSelect, onTokenChange }) => {
const [tokenFocusIndex] = useFocusTokenWithKeyboard(tokens, isTokenSelected, onTokenSelect)

const tokenRefs = useRef<(HTMLDivElement | null)[]>([])
useEffect(() => {
Expand Down Expand Up @@ -351,19 +382,20 @@ interface ITermsShowData {

const termsComponentClass = "grid-std text-left text-lg py-2 space-y-2"

const TermsComponent: React.FC<ITermsComponentProps> = ({ token, usage }) => {
const TermsComponent: React.FC<{
readonly token: Token
readonly onTermSelect: (term: Term) => void
}> = ({ token, onTermSelect }) => {
const fetcher = useFetcher<ITermsShowData>()
const terms = useMemo<Term[]>(() => (fetcher.data ? fetcher.data.terms : []), [fetcher.data])
useEffect(() => {
if (fetcher.state !== "idle" || fetcher.data) return
fetcher.load(`/terms/search?${queryString({ query: token.text, pos: token.partOfSpeech })}`)
}, [fetcher, token])

const [createNoteData, setCreateNoteData] = useState<CreateNoteData | null>(null)
const [termFocusIndex, pageIndex, pagesLen, maxPageSize, shake] = useChangeTermWithKeyboard(
terms,
(term: Term) => setCreateNoteData(createNoteDataFromSourceTerm(term, usage)),
() => createNoteData !== null,
onTermSelect,
)

const termRefs = useRef<(HTMLDivElement | null)[]>([])
Expand Down Expand Up @@ -407,22 +439,19 @@ const TermsComponent: React.FC<ITermsComponentProps> = ({ token, usage }) => {
</div>
</div>
)}

{createNoteData !== null && (
<NoteDialog data={createNoteData} onClose={() => setCreateNoteData(null)} />
)}
</div>
)
}

const NoteDialog: React.FC<{ readonly data: CreateNoteData; readonly onClose: () => void }> = ({
data,
onClose,
}) => {
const CreateNoteDialog: React.FC<{
readonly data: CreateNoteData
readonly onCreate: () => void
readonly onClose: () => void
}> = ({ data, onCreate, onClose }) => {
return (
<SlideOver.Dialog show onClose={onClose}>
<SlideOver.Header title="Create Note" onClose={onClose} />
<NoteCreate data={data} onClose={onClose} />
<NoteCreate data={data} onCreate={onCreate} onClose={onClose} />
</SlideOver.Dialog>
)
}
Expand Down
Loading

0 comments on commit 7f5aeb1

Please sign in to comment.