113 lines
3.1 KiB
TypeScript
113 lines
3.1 KiB
TypeScript
import styled from "@emotion/styled"
|
|
import { useEffect, useMemo, useState } from "react"
|
|
import type { Vault, Item } from "opvault.js"
|
|
import { AiOutlineStar } from "react-icons/ai"
|
|
import { FiLock } from "react-icons/fi"
|
|
import { Si1Password } from "react-icons/si"
|
|
import { BsGear } from "react-icons/bs"
|
|
import { useTranslate } from "../i18n/index"
|
|
import { Settings } from "../settings"
|
|
import { FilteredVaultView } from "../components/FilteredVaultView"
|
|
|
|
const Container = styled.div`
|
|
display: flex;
|
|
height: calc(100vh - var(--titlebar-height));
|
|
`
|
|
const TabContainer = styled.div`
|
|
border-right: 1px solid var(--border-color);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
padding-bottom: 5px;
|
|
width: 54px;
|
|
@media (prefers-color-scheme: dark) {
|
|
background: #222;
|
|
border-right-color: transparent;
|
|
}
|
|
&&::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
`
|
|
const TabButton = styled.button<{ active?: boolean }>`
|
|
align-items: center;
|
|
background: ${p => (p.active ? "var(--selected-background)" : "transparent")};
|
|
border-radius: ${p => (p.active ? 0 : 3)}px;
|
|
border: transparent;
|
|
box-shadow: none;
|
|
display: inline-flex;
|
|
margin-bottom: 5px;
|
|
font-size: 22px;
|
|
padding: 10px 14px;
|
|
${p => p.active && "&:hover { background: var(--selected-background); }"}
|
|
@media (prefers-color-scheme: dark) {
|
|
--selected-background: #1c1c1c;
|
|
}
|
|
`
|
|
const TabContainerMain = styled.div`
|
|
flex-grow: 1;
|
|
`
|
|
|
|
export const VaultView: React.FC<{ vault: Vault; onLock(): void }> = ({
|
|
vault,
|
|
onLock,
|
|
}) => {
|
|
const [tab, setTab] = useState(Tab.All)
|
|
const [items, setItems] = useState<Item[]>(() => [])
|
|
const [showSettings, setShowSettings] = useState(false)
|
|
const t = useTranslate()
|
|
|
|
useEffect(() => {
|
|
arrayFrom(vault.values()).then(setItems)
|
|
}, [vault])
|
|
|
|
return (
|
|
<Container>
|
|
<TabContainer>
|
|
<TabContainerMain>
|
|
<TabButton active={tab === Tab.All} onClick={() => setTab(Tab.All)}>
|
|
<Si1Password />
|
|
</TabButton>
|
|
<TabButton active={tab === Tab.Favorites} onClick={() => setTab(Tab.Favorites)}>
|
|
<AiOutlineStar />
|
|
</TabButton>
|
|
</TabContainerMain>
|
|
<TabButton onClick={onLock} title={t.action.lock}>
|
|
<FiLock />
|
|
</TabButton>
|
|
<TabButton onClick={() => setShowSettings(true)} title={t.label.settings}>
|
|
<BsGear />
|
|
</TabButton>
|
|
</TabContainer>
|
|
|
|
{tab === Tab.All ? (
|
|
<FilteredVaultView items={items} />
|
|
) : tab === Tab.Favorites ? (
|
|
<FavoriteItemsView items={items} />
|
|
) : null}
|
|
|
|
<Settings show={showSettings} onHide={() => setShowSettings(false)} />
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
const FavoriteItemsView: React.FC<{ items: Item[] }> = ({ items }) => {
|
|
const favorites = useMemo(
|
|
() => items.filter(x => x.fave).sort((a, b) => a.fave - b.fave),
|
|
[items]
|
|
)
|
|
return <FilteredVaultView items={favorites} />
|
|
}
|
|
|
|
async function arrayFrom<T>(generator: AsyncGenerator<T, void, unknown>) {
|
|
const list: T[] = []
|
|
for await (const value of generator) {
|
|
list.push(value)
|
|
}
|
|
return list
|
|
}
|
|
|
|
enum Tab {
|
|
All,
|
|
Favorites,
|
|
}
|