153 lines
4.0 KiB
TypeScript
153 lines
4.0 KiB
TypeScript
import styled from "@emotion/styled"
|
|
import type { ItemSection, ItemField } from "opvault.js"
|
|
import { FieldType } from "opvault.js"
|
|
import { useCallback, useMemo, useState } from "react"
|
|
import { useTranslate } from "../i18n"
|
|
import { parseMonthYear } from "../utils"
|
|
import { BigTextView } from "./BigTextView"
|
|
import { ErrorBoundary } from "./ErrorBoundary"
|
|
import { useItemFieldContextMenu } from "./ItemFieldContextMenu"
|
|
import { toast, ToastType } from "./Toast"
|
|
|
|
const Container = styled.div`
|
|
cursor: pointer;
|
|
&:hover {
|
|
color: #6fa9ff;
|
|
text-decoration: underline;
|
|
}
|
|
`
|
|
|
|
export { Container as ClickableContainer }
|
|
|
|
function useCopy(text: string) {
|
|
const t = useTranslate()
|
|
return useCallback(() => {
|
|
navigator.clipboard.writeText(text)
|
|
toast({
|
|
type: ToastType.Secondary,
|
|
message: t.tips.copied_to_clipboard,
|
|
})
|
|
}, [text, t])
|
|
}
|
|
|
|
export { Password as PasswordFieldView }
|
|
|
|
const Password: React.FC<{
|
|
field: Pick<ItemSection.Concealed, "v">
|
|
}> = ({ field }) => {
|
|
const t = useTranslate()
|
|
const [show, setShow] = useState(false)
|
|
const [bigText, showBigText] = useState(false)
|
|
|
|
const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
|
|
const onToggle = useCallback(() => setShow(x => !x), [])
|
|
const onCopy = useCopy(field.v)
|
|
const onOpenBigText = useCallback(() => {
|
|
showBigText(true)
|
|
}, [])
|
|
const onCloseBigText = useCallback(() => {
|
|
showBigText(false)
|
|
}, [])
|
|
|
|
return (
|
|
<>
|
|
<Container
|
|
onContextMenu={onRightClick}
|
|
onDoubleClick={() => setShow(x => !x)}
|
|
onClick={onCopy}
|
|
style={{
|
|
fontFamily: "var(--monospace)",
|
|
...(!show && { userSelect: "none" }),
|
|
}}
|
|
>
|
|
{show ? field.v : "·".repeat(10)}
|
|
</Container>
|
|
{bigText && <BigTextView onClose={onCloseBigText}>{field.v}</BigTextView>}
|
|
<ContextMenuContainer>
|
|
<Item onClick={onCopy}>{t.action.copy}</Item>
|
|
<Item onClick={onToggle}>{show ? t.action.hide : t.action.show}</Item>
|
|
{!bigText && (
|
|
<Item onClick={onOpenBigText}>{t.action.show_in_big_characters}</Item>
|
|
)}
|
|
</ContextMenuContainer>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const MonthYear: React.FC<{ field: ItemSection.MonthYear }> = ({ field }) => {
|
|
const { year, month } = parseMonthYear(field.v)
|
|
return (
|
|
<Container>
|
|
{month.toString().padStart(2, "0")}/{year.toString().padStart(4, "0")}
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
const DateView: React.FC<{ field: ItemSection.Date }> = ({ field }) => {
|
|
const date = useMemo(() => new Date(field.v * 1000), [field.v])
|
|
return <Container>{date.toLocaleDateString()}</Container>
|
|
}
|
|
|
|
const TextView: React.FC<{ value: string }> = ({ value }) => {
|
|
const { onRightClick, ContextMenuContainer, Item } = useItemFieldContextMenu()
|
|
const onCopy = useCopy(value)
|
|
|
|
return (
|
|
<>
|
|
<Container onContextMenu={onRightClick} onClick={onCopy}>
|
|
{value}
|
|
</Container>
|
|
<ContextMenuContainer>
|
|
<Item onClick={onCopy}>Copier</Item>
|
|
</ContextMenuContainer>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export const ItemFieldValue: React.FC<{
|
|
field: ItemSection.Any
|
|
}> = ({ field }) => {
|
|
if (field.v == null) {
|
|
return null
|
|
}
|
|
|
|
switch (field.k) {
|
|
case "concealed":
|
|
return <Password field={field} />
|
|
case "monthYear":
|
|
return <MonthYear field={field} />
|
|
case "date":
|
|
return <DateView field={field} />
|
|
case "address":
|
|
return (
|
|
<Container style={{ whiteSpace: "pre" }}>
|
|
<div>{field.v.street}</div>
|
|
<div>
|
|
{field.v.city}, {field.v.state} ({field.v.zip})
|
|
</div>
|
|
<div>{field.v.country}</div>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<ErrorBoundary>
|
|
<TextView value={field.v} />
|
|
</ErrorBoundary>
|
|
)
|
|
}
|
|
|
|
export const ItemDetailsFieldValue: React.FC<{
|
|
field: ItemField
|
|
}> = ({ field }) => {
|
|
if (field.type === FieldType.Password || field.designation === "password") {
|
|
return <Password field={{ v: field.value } as any} />
|
|
}
|
|
|
|
return (
|
|
<ErrorBoundary>
|
|
<TextView value={field.value!} />
|
|
</ErrorBoundary>
|
|
)
|
|
}
|