Skip to content
Merged
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
40 changes: 19 additions & 21 deletions app/components/device-detail/device-detail-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import {
LandPlot,
Image as ImageIcon,
} from 'lucide-react'
import { Fragment, useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { isTablet, isBrowser } from 'react-device-detect'
import Draggable, { type DraggableData } from 'react-draggable'
import { useTranslation } from 'react-i18next'
import {
useLoaderData,
useMatches,
Expand All @@ -29,6 +30,7 @@ import {
useSearchParams,
Link,
} from 'react-router'
import { MarkdownContent } from '../markdown-content'
import SensorIcon from '../sensor-icon'
import Spinner from '../spinner'
import {
Expand Down Expand Up @@ -92,6 +94,7 @@ export default function DeviceDetailBox() {
const navigate = useNavigate()
const matches = useMatches()
const { toast } = useToast()
const { t } = useTranslation("device-detail-box")

const sensorIds = new Set()

Expand Down Expand Up @@ -159,13 +162,6 @@ export default function DeviceDetailBox() {
setOffsetPositionY(data.y)
}

const addLineBreaks = (text: string) =>
text.split('\\n').map((text, index) => (
<Fragment key={`${text}-${index}`}>
{text}
<br />
</Fragment>
))

useEffect(() => {
let interval: any = null
Expand Down Expand Up @@ -230,33 +226,33 @@ export default function DeviceDetailBox() {
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Share this link</AlertDialogTitle>
<AlertDialogTitle>{t('share_link')}</AlertDialogTitle>
<ShareLink />
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Close</AlertDialogCancel>
<AlertDialogCancel>{t('close')}</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<span className="sr-only">{t('open_menu')}</span>
<EllipsisVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="dark:bg-dark-background dark:text-dark-text"
>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuLabel>{t('actions')}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer"
disabled={true}
>
<Scale className="mr-2 h-4 w-4" />
<span>Compare</span>
<span>{t('compare')}</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Archive className="mr-2 h-4 w-4" />
Expand All @@ -268,7 +264,7 @@ export default function DeviceDetailBox() {
title="Open archive"
className="w-full cursor-pointer"
>
Archive
{t('open_archive')}
</a>
</span>
</DropdownMenuItem>
Expand All @@ -282,7 +278,7 @@ export default function DeviceDetailBox() {
title="Open external link"
className="w-full cursor-pointer"
>
External Link
{t('open_external_link')}
</a>
</span>
</DropdownMenuItem>
Expand Down Expand Up @@ -431,10 +427,12 @@ export default function DeviceDetailBox() {
>
<AccordionItem value="item-1">
<AccordionTrigger className="font-bold dark:dark:text-zinc-100">
Description
{t('description')}
</AccordionTrigger>
<AccordionContent>
{addLineBreaks(data.device.description)}
<MarkdownContent>
{data?.device.description}
</MarkdownContent>
</AccordionContent>
</AccordionItem>
</Accordion>
Expand All @@ -447,7 +445,7 @@ export default function DeviceDetailBox() {
>
<AccordionItem value="item-1">
<AccordionTrigger className="font-bold dark:dark:text-zinc-100">
Sensors
{t('sensors')}
</AccordionTrigger>
<AccordionContent>
<div
Expand Down Expand Up @@ -639,9 +637,9 @@ export default function DeviceDetailBox() {
setOpen(true)
}}
/>
<AlertTitle>Compare devices</AlertTitle>
<AlertTitle>{t('compare_devices')}</AlertTitle>
<AlertDescription className="inline">
Choose a device from the map to compare with.
{t('choose_device_for_comparison')}
</AlertDescription>
</Alert>
)}
Expand All @@ -660,7 +658,7 @@ export default function DeviceDetailBox() {
</div>
</TooltipTrigger>
<TooltipContent>
<p>Open device details</p>
<p>{t('open_device_details')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
Expand Down
43 changes: 42 additions & 1 deletion app/components/device/new/general-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useFormContext, useFieldArray } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { MarkdownContent } from '~/components/markdown-content'
import { Badge } from '~/components/ui/badge'
import { Checkbox } from '~/components/ui/checkbox'
import { Label } from '~/components/ui/label'
Expand All @@ -25,6 +26,8 @@ export function GeneralInfoStep() {
name: 'tags', // Tags array
})

const description = watch('description') || ''

const currentExposure = watch('exposure') // Watch exposure value

// State for temporary expiration date
Expand Down Expand Up @@ -80,7 +83,7 @@ export function GeneralInfoStep() {
]

return (
<div className="flex h-full flex-col justify-evenly space-y-4 p-2">
<div className="flex h-full flex-col space-y-4 p-2">
<div>
<Label htmlFor="name">Name</Label>
<Input
Expand All @@ -90,6 +93,44 @@ export function GeneralInfoStep() {
className="w-full rounded-md border p-2"
/>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="description">{t('description')}</Label>
<textarea
id="description"
{...register('description')}
maxLength={5000}
placeholder={`## ${t('my_station')}

${t('installed_on_roof')}

- PM2.5
- Temperature

[${t('project_website')}](https://example.com)`}
className="min-h-[220px] w-full rounded-md border p-3 font-mono text-sm"
/>
<div className="text-sm text-muted-foreground">
{description.length} / 5000
</div>
<div className="text-sm text-muted-foreground">
{t('markdown_supported')}
</div>
</div>

<div className="space-y-2">
<Label>{t('preview')}</Label>
<div className="min-h-[220px] rounded-md border p-3">
{description.trim() ? (
<MarkdownContent>{description}</MarkdownContent>
) : (
<p className="text-sm text-muted-foreground">
{t('nothing_to_preview')}
</p>
)}
</div>
</div>
</div>
<div>
<Label htmlFor="exposure">{t('exposure')}</Label>
<div className="mt-2 flex flex-wrap gap-2">
Expand Down
5 changes: 5 additions & 0 deletions app/components/device/new/new-device-stepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const generalInfoSchema = z.object({
.string()
.min(2, 'Name must be at least 2 characters')
.min(1, 'Name is required'),
description: z
.string()
.max(5000, 'Description should not exceed 5000 characters')
.optional()
.nullable(),
exposure: z.enum(['indoor', 'outdoor', 'mobile', 'unknown'], {
errorMap: () => ({ message: 'Exposure is required' }),
}),
Expand Down
61 changes: 61 additions & 0 deletions app/components/markdown-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Markdown from 'markdown-to-jsx'

type MarkdownContentProps = {
children: string
className?: string
}

export function MarkdownContent({
children,
className = '',
}: MarkdownContentProps) {
return (
<div className={`prose max-w-none dark:prose-invert ${className}`}>
<Markdown
options={{
overrides: {
h1: {
component: ({ children }) => (
<h1 className="text-3xl font-bold mb-4">{children}</h1>
),
},
h2: {
component: ({ children }) => (
<h2 className="text-2xl font-semibold mb-3">{children}</h2>
),
},
p: {
component: ({ children }) => (
<p className="mb-3 leading-7">{children}</p>
),
},
a: {
component: ({ children, href }) => (
<a
href={href}
className="text-blue-600 underline hover:text-blue-800"
target="_blank"
rel="noreferrer"
>
{children}
</a>
),
},
ul: {
component: ({ children, className, ...props }) => (
<ul
className={`${className ?? ''} list-inside list-disc space-y-1`}
{...props}
>
{children}
</ul>
),
},
},
}}
>
{children}
</Markdown>
</div>
)
}
Loading
Loading