Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions src/components/ContributorPRsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { createPortal } from 'react-dom'
import { motion, AnimatePresence } from 'framer-motion'
import { openUrl } from '@tauri-apps/plugin-opener'
Expand All @@ -19,24 +20,24 @@ interface Props {
onClose: () => void
}

function stateBadge(pr: PR) {
function stateBadge(pr: PR, t: (key: string) => string) {
if (pr.merged_at) {
return (
<span className="text-[9px] font-semibold px-1.5 py-0.5 rounded bg-accent/15 text-accent">
Merged
{t('contributor_pr_merged')}
</span>
)
}
if (pr.state === 'open') {
return (
<span className="text-[9px] font-semibold px-1.5 py-0.5 rounded bg-mint/15 text-mint">
Open
{t('contributor_pr_open')}
</span>
)
}
return (
<span className="text-[9px] font-semibold px-1.5 py-0.5 rounded bg-danger/15 text-danger">
Closed
{t('contributor_pr_closed')}
</span>
)
}
Expand All @@ -50,6 +51,7 @@ function formatDate(iso: string) {
}

export function ContributorPRsModal({ login, avatarUrl, onClose }: Props) {
const { t } = useTranslation('common')
const [prs, setPrs] = useState<PR[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
Expand Down Expand Up @@ -127,7 +129,9 @@ export function ContributorPRsModal({ login, avatarUrl, onClose }: Props) {
{login}
</button>
<p className="text-[11px] text-muted">
{loading ? 'Loading PRs...' : `${prs.length} pull request${prs.length !== 1 ? 's' : ''}`}
{loading
? t('contributor_pr_loading')
: t(prs.length === 1 ? 'contributor_pr_count_one' : 'contributor_pr_count_other', { count: prs.length })}
</p>
</div>
<button
Expand Down Expand Up @@ -162,12 +166,12 @@ export function ContributorPRsModal({ login, avatarUrl, onClose }: Props) {
</div>
) : error ? (
<div className="flex flex-col items-center justify-center gap-2 py-12 text-muted text-sm">
<p>Failed to load pull requests.</p>
<p className="text-xs text-muted/50">Check your internet connection.</p>
<p>{t('contributor_pr_load_failed')}</p>
<p className="text-xs text-muted/50">{t('contributor_pr_network_hint')}</p>
</div>
) : prs.length === 0 ? (
<div className="flex flex-col items-center justify-center gap-2 py-12 text-muted text-sm">
<p>No pull requests found.</p>
<p>{t('contributor_pr_empty')}</p>
</div>
) : (
<div className="flex flex-col">
Expand All @@ -185,7 +189,7 @@ export function ContributorPRsModal({ login, avatarUrl, onClose }: Props) {
<p className="text-xs font-medium text-ink truncate">{pr.title}</p>
<p className="text-[10px] text-muted mt-0.5">{formatDate(pr.created_at)}</p>
</div>
{stateBadge(pr)}
{stateBadge(pr, t)}
</button>
))}
</div>
Expand Down
5 changes: 4 additions & 1 deletion src/components/cards/ProjectCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ export function ProjectCardList({
(dndCategoryGroups || categoryGroups)!,
categories,
isDndEnabled ? cardForDnd : cardFor,
t('uncategorized'),
isDndEnabled,
)
: unpinnedProjects.map((p) => isDndEnabled ? cardForDnd(p) : cardFor(p))),
Expand All @@ -461,6 +462,7 @@ export function ProjectCardList({
(dndCategoryGroups || categoryGroups)!,
categories,
isDndEnabled ? cardForDnd : cardFor,
t('uncategorized'),
isDndEnabled,
)
: projects.map((p) => isDndEnabled ? cardForDnd(p) : cardFor(p))
Expand Down Expand Up @@ -520,6 +522,7 @@ function renderCategoryGroups(
groups: Map<string, Project[]>,
categories: Category[],
cardFor: (p: Project) => ReactNode,
uncategorizedLabel: string,
disableAnimation = false,
): ReactNode[] {
const result: ReactNode[] = []
Expand Down Expand Up @@ -547,7 +550,7 @@ function renderCategoryGroups(
result.push(
<CategorySection
key="cat-uncategorized"
title="Uncategorized"
title={uncategorizedLabel}
count={uncategorized.length}
defaultOpen={uncategorized.length > 0}
disableAnimation={disableAnimation}
Expand Down
53 changes: 27 additions & 26 deletions src/components/modals/BugReportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ function installErrorCapture() {

installErrorCapture()

async function getGPUInfo(): Promise<string> {
async function getGPUInfo(t: (key: string) => string): Promise<string> {
try {
const canvas = document.createElement('canvas')
const gl =
canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
if (!gl) return 'WebGL not available'
if (!gl) return t('gpu_webgl_unavailable')
const debugInfo = (
gl as WebGLRenderingContext
).getExtension('WEBGL_debug_renderer_info')
if (!debugInfo) return 'GPU info unavailable'
if (!debugInfo) return t('gpu_info_unavailable')
const renderer = (gl as WebGLRenderingContext).getParameter(
debugInfo.UNMASKED_RENDERER_WEBGL,
)
Expand All @@ -84,19 +84,19 @@ async function getGPUInfo(): Promise<string> {
canvas.remove()
return `${vendor}, ${renderer}`
} catch {
return 'GPU info unavailable'
return t('gpu_info_unavailable')
}
}

async function buildSystemReport(): Promise<{ specs: string; errors: string }> {
const gpu = await getGPUInfo()
async function buildSystemReport(t: (key: string) => string): Promise<{ specs: string; errors: string }> {
const gpu = await getGPUInfo(t)

const specs = [
'## System',
`## ${t('bug_report_system')}`,
'',
`- **Version**: ${version}`,
`- **Date**: ${new Date().toISOString().slice(0, 10)}`,
`- **OS**: ${
`- **${t('bug_report_version')}**: ${version}`,
`- **${t('bug_report_date')}**: ${new Date().toISOString().slice(0, 10)}`,
`- **${t('bug_report_os')}**: ${
navigator.userAgent.includes('Windows')
? 'Windows'
: navigator.userAgent.includes('Mac OS X') ||
Expand All @@ -106,28 +106,28 @@ async function buildSystemReport(): Promise<{ specs: string; errors: string }> {
? 'Linux'
: navigator.userAgent
}`,
`- **Platform**: ${navigator.platform}`,
`- **Language**: ${navigator.language}`,
`- **CPU Cores**: ${navigator.hardwareConcurrency ?? 'unknown'}`,
`- **RAM**: ${
`- **${t('bug_report_platform')}**: ${navigator.platform}`,
`- **${t('bug_report_language')}**: ${navigator.language}`,
`- **${t('bug_report_cpu_cores')}**: ${navigator.hardwareConcurrency ?? t('unknown')}`,
`- **${t('bug_report_ram')}**: ${
(
navigator as Navigator & { deviceMemory?: number }
).deviceMemory
? `${(navigator as Navigator & { deviceMemory?: number }).deviceMemory} GB`
: 'unknown'
: t('unknown')
}`,
`- **Screen**: ${screen.width}x${screen.height} @${screen.colorDepth}bit`,
`- **GPU**: ${gpu}`,
`- **User Agent**: ${navigator.userAgent}`,
`- **${t('bug_report_screen')}**: ${screen.width}x${screen.height} @${screen.colorDepth}bit`,
`- **${t('bug_report_gpu')}**: ${gpu}`,
`- **${t('bug_report_user_agent')}**: ${navigator.userAgent}`,
].join('\n')

const errorLines: string[] = ['## Recent Errors']
const errorLines: string[] = [`## ${t('bug_report_recent_errors')}`]
if (capturedErrors.length > 0) {
for (const err of capturedErrors) {
errorLines.push(`- \`[${err.time}]\` (${err.source}) ${err.message}`)
}
} else {
errorLines.push('- _(none captured)_')
errorLines.push(`- _(${t('bug_report_none_captured')})_`)
}

return { specs, errors: errorLines.join('\n') }
Expand All @@ -137,12 +137,13 @@ function assembleReport(
description: string,
specs: string,
errors: string,
t: (key: string) => string,
): string {
const desc = description.trim()
return [
'## Description',
`## ${t('bug_report_description')}`,
'',
desc || '_No description provided_',
desc || `_ ${t('bug_report_no_description')} _`,
'',
specs,
'',
Expand Down Expand Up @@ -175,7 +176,7 @@ export function BugReportModal({ onClose }: Props) {
useEffect(() => {
let cancelled = false
setLoading(true)
buildSystemReport().then((r) => {
buildSystemReport(t).then((r) => {
if (!cancelled) {
setSystem(r)
setLoading(false)
Expand All @@ -184,11 +185,11 @@ export function BugReportModal({ onClose }: Props) {
return () => {
cancelled = true
}
}, [])
}, [t])

const report = useMemo(
() => (system ? assembleReport(description, system.specs, system.errors) : null),
[description, system],
() => (system ? assembleReport(description, system.specs, system.errors, t) : null),
[description, system, t],
)

const handleCopy = async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/modals/CategoryManagerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ export function CategoryManagerModal({
</div>
<div className="text-center">
<p className="text-sm font-medium text-muted/70">{t('no_categories_yet')}</p>
<p className="text-xs text-muted/40 mt-1">Create one above to organize your projects</p>
<p className="text-xs text-muted/40 mt-1">{t('category_empty_hint')}</p>
</div>
</div>
) : (
Expand Down
32 changes: 6 additions & 26 deletions src/components/modals/CheckForUpdatesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,7 @@ interface Props {

const PREVIEW_VERSION = '1.0.0'

const PREVIEW_NOTES = `## What's new in v1.0.0 - The Preview Update

## 🚀 New

- Revamped the Check for Updates modal with structured release notes
- Added screen reader announcements with an Accessibility settings tab

## 🐛 Fixes

- Fixed a crash when switching workspaces with pinned projects
- Fixed update checks failing silently when GitHub rate limits are hit

## ✨ Improvements

- Faster startup times across all platforms
- Reworked workspace modals with compact style pickers

## ⚠️ Known Issues

- Linux OS: AppImage won't work on some distros, use the .rpm or .deb package instead
- Windows: the taskbar may briefly show a duplicate icon until the app restarts`

const PREVIEW_STATES = [
'checking',
Expand Down Expand Up @@ -141,7 +121,7 @@ export function CheckForUpdatesModal({
? {
type: 'available',
version: PREVIEW_VERSION,
notes: PREVIEW_NOTES,
notes: t('preview_release_notes'),
downloadAndInstall: () => Promise.resolve(),
}
: { type: 'checking' },
Expand Down Expand Up @@ -191,7 +171,7 @@ export function CheckForUpdatesModal({
setState({
type: 'available',
version: PREVIEW_VERSION,
notes: PREVIEW_NOTES,
notes: t('preview_release_notes'),
downloadAndInstall: simulateDownload,
})
return
Expand Down Expand Up @@ -255,7 +235,7 @@ export function CheckForUpdatesModal({
} catch (e) {
setState({ type: 'error', message: String(e) })
}
}, [mode, githubToken, simulateDownload])
}, [mode, githubToken, simulateDownload, t])

useEffect(() => {
getVersion().then(setCurrentVersion).catch(() => setCurrentVersion(null))
Expand All @@ -277,7 +257,7 @@ export function CheckForUpdatesModal({
setState({
type: 'available',
version: PREVIEW_VERSION,
notes: PREVIEW_NOTES,
notes: t('preview_release_notes'),
downloadAndInstall: simulateDownload,
})
break
Expand All @@ -300,14 +280,14 @@ export function CheckForUpdatesModal({
setState({
type: 'portable',
version: PREVIEW_VERSION,
notes: PREVIEW_NOTES,
notes: t('preview_release_notes'),
})
break
case 'error':
setState({
type: 'error',
message:
'Preview error: GitHub API rate limit reached (HTTP 403). Add a token in Settings to keep checking.',
t('preview_error'),
})
break
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/reusables/DragHandle.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { DraggableAttributes, DraggableSyntheticListeners } from '@dnd-kit/core'
import { useTranslation } from 'react-i18next'
import { IconGrip } from '../../lib/icons'

interface DragHandleProps {
Expand All @@ -18,14 +19,15 @@ export function DragHandle({
disabled = false,
className = '',
}: DragHandleProps) {
const { t } = useTranslation('common')
if (disabled) return null

return (
<button
ref={ref}
{...attributes}
{...listeners}
aria-label="Drag to reorder"
aria-label={t('drag_to_reorder')}
className={`focus-ring z-20 w-5 h-10 rounded-full border flex items-center justify-center cursor-grab active:cursor-grabbing touch-none transition-all duration-200 ${
isDragging
? 'bg-accent border-accent text-white scale-110 shadow-md shadow-accent/30 opacity-100'
Expand Down
4 changes: 3 additions & 1 deletion src/components/reusables/LanguageFlag.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next'
import {
US,
CN,
Expand Down Expand Up @@ -31,12 +32,13 @@ export function LanguageFlag({
className?: string
title?: string
}) {
const { t } = useTranslation('common')
if (country === 'SYSTEM') {
return (
<span
aria-hidden="true"
className={`inline-flex shrink-0 items-center justify-center ${className}`}
title={title ?? 'System language'}
title={title ?? t('system_language')}
>
🌐
</span>
Expand Down
Loading
Loading