Skip to content

Commit

Permalink
Merge pull request #40 from takahiroanno2024/feature/20-fullscreen-tools
Browse files Browse the repository at this point in the history
Fixes #20 - Optimize Fullscreen Menu
  • Loading branch information
nanocloudx authored Dec 26, 2024
2 parents cf6e751 + ef8ed2e commit dc58494
Show file tree
Hide file tree
Showing 14 changed files with 422 additions and 402 deletions.
1 change: 1 addition & 0 deletions scatter/next-app/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"import"
],
"rules": {
"indent": ["error", 2, { "SwitchCase": 1 }],
"quotes": ["error", "single"],
"semi": ["error", "never"],
"no-extra-semi": ["error"],
Expand Down
86 changes: 86 additions & 0 deletions scatter/next-app/components/DesktopFullscreenFavorites.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {BookmarkCheckIcon, XIcon} from 'lucide-react'
import React from 'react'
import {ColorFunc} from '@/hooks/useClusterColor'
import {Translator} from '@/hooks/useTranslatorAndReplacements'
import {Cluster, FavoritePoint} from '@/types'

type Props = {
favorites: FavoritePoint[]
removeFavorite: (fav: FavoritePoint) => void
clusters: Cluster[]
translator: Translator
color: ColorFunc
onlyCluster?: string
onClose: () => void
}

export function DesktopFullscreenFavorites({
favorites,
removeFavorite,
clusters,
translator,
color,
onlyCluster,
onClose
}: Props) {
return (
<div
className="absolute top-0 right-0 w-[400px] p-4 bg-gray-100 overflow-y-auto z-10 h-full shadow-md"
>
<div className="flex justify-between items-center mb-4 h-[32px]">
<h2 className="text-md font-bold">
{translator.t('お気に入り一覧')}
</h2>
<button onClick={onClose}>
<XIcon/>
</button>
</div>
{favorites.length === 0 ? (
<p>{translator.t('お気に入りがありません')}</p>
) : (
<ul>
{favorites.map((fav) => {
// クラスタ情報を取得
const cluster = clusters.find((c) => c.cluster_id === fav.cluster_id)
return (
<li
key={fav.arg_id}
className="mb-2 p-2 bg-white rounded shadow flex flex-col"
>
<div className="flex items-center justify-between">
{/* クラスタラベル */}
<h3
className="font-semibold text-md"
style={{
color: cluster ? color(cluster.cluster_id, onlyCluster) : '#000',
}}
>
{translator.t(cluster?.cluster || 'クラスタ')}
</h3>

{/* お気に入りボタン */}
<button
onClick={() => removeFavorite(fav)}
className="text-amber-500 text-lg focus:outline-none ml-2"
>
<BookmarkCheckIcon/>
</button>
</div>

{/* 引用部分を100文字に制限 */}
<p className="text-md text-gray-700 mt-1">
{truncateText(fav.argument, 100)}
</p>
</li>
)
})}
</ul>
)}
</div>
)
}

function truncateText(text: string, maxLength: number) {
if (text.length <= maxLength) return text
return text.slice(0, maxLength) + '...'
}
135 changes: 135 additions & 0 deletions scatter/next-app/components/DesktopFullscreenFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {XIcon} from 'lucide-react'
import React from 'react'
import {Translator} from '@/hooks/useTranslatorAndReplacements'
import {Argument, PropertyMap} from '@/types'

type Props = {
translator: Translator
onClose: () => void
highlightText: string
setHighlightText: React.Dispatch<React.SetStateAction<string>>
propertyMap: PropertyMap
propertyFilter: { [key: string]: string }
setPropertyFilter: React.Dispatch<React.SetStateAction<{ [key: string]: string }>>
dataHasVotes: boolean
minVotes: number
setMinVotes: React.Dispatch<React.SetStateAction<number>>
minConsensus: number
setMinConsensus: React.Dispatch<React.SetStateAction<number>>
voteFilter: {
total: number
filtered: number
filter: (arg: Argument) => boolean
}
}

export function DesktopFullscreenFilter(props: Props) {
const {t} = props.translator
return (
<div
className="absolute top-0 left-0 w-[400px] p-4 bg-gray-100 overflow-y-auto z-10 h-full shadow-md"
>
{/* Header */}
<div className="flex justify-between items-center mb-4 h-[32px]">
<h2 className="text-md font-bold">
{t('toolsFilterSettings')}
</h2>
<button onClick={props.onClose}>
<XIcon/>
</button>
</div>
{/* Search */}
<input
type="text"
placeholder={t('検索')}
value={props.highlightText}
onChange={(e) => props.setHighlightText(e.target.value)}
className="w-full mb-2 p-2 border rounded"
/>
<dl className={'mb-2'}>
{Object.entries(props.propertyMap).map(([propKey, propValues]) => {
const uniqueValues = Array.from(new Set(Object.values(propValues)))
uniqueValues.sort() // ABC順にソート
return (
<div key={propKey}>
<dt>{propKey}</dt>
<dd>
<select
value={props.propertyFilter[propKey] ?? ''}
onChange={(e) => {
// propKeyごとの選択値を状態管理する必要あり
props.setPropertyFilter((prev) => ({
...prev,
[propKey]: e.target.value,
}))
}}
className="border p-1 rounded w-full"
>
<option value="">{t('(すべて)')}</option>
{uniqueValues.map((val) => (
<option key={val} value={val}>
{val}
</option>
))}
</select>
</dd>
</div>
)
})}
</dl>

{/* Votes */}
{props.dataHasVotes && (
<div>
<div className="flex justify-between">
<button className="inline-block text-left">
{t('Votes')} {'>'}{' '}
<span className="inline-block w-10">{props.minVotes}</span>
</button>
<input
className="inline-block w-[200px] mr-2"
id="min-votes-slider"
type="range"
min="0"
max="50"
value={props.minVotes}
onInput={(e) => {
props.setMinVotes(
parseInt(
(e.target as HTMLInputElement).value
)
)
}}
/>
</div>
<div className="flex justify-between">
<button className="inline-block text-left">
{t('Consensus')} {'>'}{' '}
<span className="inline-block w-10">{props.minConsensus}%</span>
</button>
<input
className="inline-block w-[200px] mr-2"
id="min-consensus-slider"
type="range"
min="50"
max="100"
value={props.minConsensus}
onInput={(e) => {
props.setMinConsensus(
parseInt(
(e.target as HTMLInputElement).value
)
)
}}
/>
</div>
<div className="text-sm opacity-70">
{t('Showing')} {props.voteFilter.filtered}/
{props.voteFilter.total} {t('arguments')}
</div>
</div>
)}

</div>
)
}
73 changes: 73 additions & 0 deletions scatter/next-app/components/DesktopFullscreenTools.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
AtSignIcon,
BookMarkedIcon,
ChartPieIcon,
Minimize2Icon,
ScanSearchIcon,
SlidersHorizontalIcon,
TagIcon
} from 'lucide-react'
import React from 'react'
import {Translator} from '@/hooks/useTranslatorAndReplacements'

type Props = {
canFilter: boolean // !!propertyMap
zoomReset: boolean | (() => void)
translator: Translator
exitFullScreen: () => void // back
showSettings: boolean
setShowSettings: React.Dispatch<React.SetStateAction<boolean>>
showLabels: boolean
setShowLabels: React.Dispatch<React.SetStateAction<boolean>>
showTitle: boolean
setShowTitle: React.Dispatch<React.SetStateAction<boolean>>
showRatio: boolean
setShowRatio: React.Dispatch<React.SetStateAction<boolean>>
showFavorites: boolean
setShowFavorites: React.Dispatch<React.SetStateAction<boolean>>
}

export function DesktopFullscreenTools(props: Props) {
const {t} = props.translator
return (
<div className="absolute top-0 w-full p-2" style={{backgroundColor: 'rgba(255, 255, 255, 0.5)'}}>
<div className={'flex justify-between items-center'}>
<div className={'flex'}>
{props.canFilter && (
<button className="w-[80px] m-1 flex flex-col items-center" onClick={() => props.setShowSettings(x => !x)}>
<SlidersHorizontalIcon className={props.showSettings ? 'text-blue-500' : 'text-gray-500'}/>
<p className={'text-xs text-gray-700'}>{t('toolsFilterSettings')}</p>
</button>
)}
<button className="w-[80px] m-1 flex flex-col items-center" onClick={() => props.setShowLabels(x => !x)}>
<TagIcon className={props.showLabels ? 'text-blue-500' : 'text-gray-500'}/>
<p className={'text-xs text-gray-700'}>{t('toolsDisplayLabels')}</p>
</button>
<button className="w-[80px] m-1 flex flex-col items-center" onClick={() => props.setShowTitle(x => !x)}>
<AtSignIcon className={props.showTitle ? 'text-blue-500' : 'text-gray-500'}/>
<p className={'text-xs text-gray-700'}>{t('toolsDisplayTitle')}</p>
</button>
<button className="w-[80px] m-1 flex flex-col items-center" onClick={() => props.setShowRatio(x => !x)}>
<ChartPieIcon className={props.showRatio ? 'text-blue-500' : 'text-gray-500'}/>
<p className={'text-xs text-gray-700'}>{t('toolsDisplayPercentage')}</p>
</button>
<button className="w-[80px] m-1 flex flex-col items-center"
onClick={() => typeof props.zoomReset === 'function' && props.zoomReset()}>
<ScanSearchIcon className={props.zoomReset ? 'text-blue-500' : 'text-gray-500'}/>
<p className={'text-xs text-gray-700'}>{t('toolsResetPosition')}</p>
</button>
</div>
<div className={'flex justify-between items-center'}>
<button className="w-[80px] m-1 flex flex-col items-center" onClick={() => props.setShowFavorites(x => !x)}>
<BookMarkedIcon className={'text-gray-700'}/>
<p className={'text-xs text-gray-700'}>{t('toolsDisplayFavorites')}</p>
</button>
<button className="w-[80px] m-1 flex flex-col items-center" onClick={props.exitFullScreen}>
<Minimize2Icon className={'text-gray-700'}/>
<p className={'text-xs text-gray-700'}>{t('toolsExitFullScreen')}</p>
</button>
</div>
</div>
</div>
)
}
Loading

0 comments on commit dc58494

Please sign in to comment.