Skip to content

Commit

Permalink
Merge pull request #98 from besscroft/dev
Browse files Browse the repository at this point in the history
v0.9.4
  • Loading branch information
besscroft authored Jul 4, 2024
2 parents 0f568bd + 017f4d3 commit 47a6f0f
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 69 deletions.
12 changes: 12 additions & 0 deletions app/api/open/get-image-blob/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'server-only'
import { NextRequest } from 'next/server'

export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const imageUrl = searchParams.get('imageUrl')
// @ts-ignore
const blob = await fetch(imageUrl).then(res => res.blob())
return new Response(blob)
}

export const revalidate = 0
11 changes: 2 additions & 9 deletions app/api/v1/get-tags/route.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import 'server-only'
import { fetchTagsList } from '~/server/lib/query'
import { fetchTagsListAndNotDefault } from '~/server/lib/query'

export async function GET() {
const res = await fetchTagsList();
const data = [{
name: '首页',
tag_value: '/'
}]
if (Array.isArray(res) && res.length > 0) {
data.push(...res)
}
const data = await fetchTagsListAndNotDefault();
return Response.json(data)
}

Expand Down
13 changes: 2 additions & 11 deletions components/BlurImage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
'use client'

import React, { useState } from 'react'
import React from 'react'
import { useButtonStore } from '~/app/providers/button-store-Providers'
import { Image } from '@nextui-org/image'
import { cn } from '~/utils'

export default function BlurImage({ photo }: { photo: any }) {
const [isLoading, setIsLoading] = useState(true)
const { setMasonryView, setMasonryViewData } = useButtonStore(
(state) => state,
)
Expand All @@ -19,21 +17,14 @@ export default function BlurImage({ photo }: { photo: any }) {
width={photo.width}
height={photo.height}
loading="lazy"
removeWrapper
disableSkeleton
shadow="sm"
radius="none"
onClick={() => {
setMasonryView(true)
setMasonryViewData(photo)
}}
className={cn(
'duration-700 ease-in-out group-hover:opacity-75 cursor-pointer transition-all will-change-transform hover:scale-[1.01]',
isLoading
? 'scale-110 blur-2xl grayscale'
: 'scale-100 blur-0 grayscale-0'
)}
onLoad={() => setIsLoading(false)}
className="duration-700 ease-in-out group-hover:opacity-75 cursor-pointer transition-all will-change-transform hover:scale-[1.01]"
/>
</div>
)
Expand Down
136 changes: 99 additions & 37 deletions components/MasonryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@ import {
} from '~/components/ui/Dialog'
import { useButtonStore } from '~/app/providers/button-store-Providers'
import { CopyrightType, DataProps, ImageType } from '~/types'
import { Image, Tabs, Tab, Card, CardHeader, CardBody, CardFooter, Button, Chip, Link, Avatar } from '@nextui-org/react'
import { Aperture, Camera, Image as ImageIcon, Languages, CalendarDays, X, SunMedium, MoonStar, Copyright, Crosshair, Timer, CircleGauge, Copy, Share2 } from 'lucide-react'
import { Image, Tabs, Tab, Card, CardHeader, CardBody, CardFooter, Button, Chip, Link, Avatar, Tooltip } from '@nextui-org/react'
import { Aperture, Camera, Image as ImageIcon, Images, Link as LinkIcon, ImageDown, Languages, CalendarDays, X, SunMedium, MoonStar, Copyright, Crosshair, Timer, CircleGauge, Share2 } from 'lucide-react'
import * as React from 'react'
import { useTheme } from 'next-themes'
import { useRouter } from 'next-nprogress-bar'
import ExifView from '~/components/ExifView'
import { toast } from 'sonner'
import { usePathname } from 'next/navigation'
import useSWR from 'swr'

export default function MasonryItem() {
const router = useRouter()
const pathname = usePathname()
const { MasonryView, MasonryViewData, setMasonryView, setMasonryViewData } = useButtonStore(
(state) => state,
)
const {data: download = false, mutate: setDownload} = useSWR(['masonry/download', MasonryViewData.url], null)
const { theme, setTheme } = useTheme()

const props: DataProps = {
Expand All @@ -44,6 +46,30 @@ export default function MasonryItem() {
}
}

async function downloadImg() {
setDownload(true)
try {
toast.warning('开始下载,原图较大,请耐心等待!', { duration: 1500 })
await fetch(`/api/open/get-image-blob?imageUrl=${MasonryViewData.url}`)
.then((response) => response.blob())
.then((blob) => {
const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement("a");
link.href = url;
const parsedUrl = new URL(MasonryViewData.url);
const filename = parsedUrl.pathname.split('/').pop();
link.download = filename || "downloaded-file";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
} catch (e) {
toast.error('下载失败!', { duration: 500 })
} finally {
setDownload(false)
}
}

return (
<Dialog
defaultOpen={false}
Expand All @@ -57,52 +83,37 @@ export default function MasonryItem() {
>
<DialogContent className="flex flex-col">
<div className="flex items-center">
<div className="flex-1">
<div className="flex-1 overflow-hidden whitespace-nowrap">
<p>{MasonryViewData.title}</p>
</div>
<div className="flex items-center space-x-4">
{
navigator.canShare && typeof navigator.canShare === 'function' &&
<Tooltip content="分享">
<Button
isIconOnly
variant="shadow"
size="sm"
aria-label="分享"
className="bg-white dark:bg-gray-800"
onClick={() => handleOnClick()}
>
<Share2 size={20}/>
</Button>
</Tooltip>
}
<Tooltip content="切换主题">
<Button
isIconOnly
variant="shadow"
size="sm"
aria-label="分享"
aria-label="切换主题"
className="bg-white dark:bg-gray-800"
onClick={() => handleOnClick()}
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
<Share2 size={20}/>
{theme === 'light' ? <SunMedium size={20} /> : <MoonStar size={20} />}
</Button>
}
<Button
isIconOnly
variant="shadow"
size="sm"
aria-label="复制直链"
className="bg-white dark:bg-gray-800"
onClick={async () => {
try {
const url = window.location.origin + (pathname === '/' ? '/preview/' : pathname + '/preview/') + MasonryViewData.id
// @ts-ignore
await navigator.clipboard.writeText(url);
toast.success('复制直链成功!', { duration: 500 })
} catch (error) {
toast.error('复制直链失败!', { duration: 500 })
}
}}
>
<Copy size={20}/>
</Button>
<Button
isIconOnly
variant="shadow"
size="sm"
aria-label="切换主题"
className="bg-white dark:bg-gray-800"
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
{theme === 'light' ? <SunMedium size={20} /> : <MoonStar size={20} />}
</Button>
</Tooltip>
<Button
isIconOnly
variant="shadow"
Expand All @@ -124,7 +135,7 @@ export default function MasonryItem() {
<Image
className="object-contain md:max-h-[90vh]"
alt={MasonryViewData.detail}
src={MasonryViewData.url}
src={MasonryViewData.preview_url || MasonryViewData.url}
radius="none"
loading="lazy"
/>
Expand All @@ -141,6 +152,57 @@ export default function MasonryItem() {
}
>
<div className="flex flex-col space-y-2">
<div className="flex space-x-2">
<Button
color="primary"
variant="bordered"
aria-label="复制图片链接"
size="sm"
startContent={<Images size={20}/>}
onClick={async () => {
try {
const url = MasonryViewData.url
// @ts-ignore
await navigator.clipboard.writeText(url);
toast.success('复制图片链接成功!', { duration: 500 })
} catch (error) {
toast.error('复制图片链接失败!', { duration: 500 })
}
}}
>
复制图片链接
</Button>
<Button
color="primary"
variant="bordered"
aria-label="复制直链"
size="sm"
startContent={<LinkIcon size={20}/>}
onClick={async () => {
try {
const url = window.location.origin + (pathname === '/' ? '/preview/' : pathname + '/preview/') + MasonryViewData.id
// @ts-ignore
await navigator.clipboard.writeText(url);
toast.success('复制直链成功!', { duration: 500 })
} catch (error) {
toast.error('复制直链失败!', { duration: 500 })
}
}}
>
复制直链
</Button>
<Button
color="primary"
variant="bordered"
aria-label="下载原图"
size="sm"
startContent={<ImageDown size={20}/>}
onClick={() => downloadImg()}
isLoading={download}
>
下载原图
</Button>
</div>
{MasonryViewData?.exif?.model && MasonryViewData?.exif?.f_number
&& MasonryViewData?.exif?.exposure_time && MasonryViewData?.exif?.focal_length
&& MasonryViewData?.exif?.iso_speed_rating &&
Expand Down
2 changes: 1 addition & 1 deletion components/admin/list/ImageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function ImageView() {
<Image
isBlurred
isZoomed
src={imageViewData.url}
src={imageViewData.preview_url || imageViewData.url}
alt={imageViewData.detail}
/>
{imageViewData?.labels &&
Expand Down
13 changes: 2 additions & 11 deletions components/admin/list/ListImage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
'use client'

import React, { useState } from 'react'
import React from 'react'
import { Image } from '@nextui-org/image'
import { cn } from '~/utils'

export default function ListImage({ image }: { image: any }) {
const [isLoading, setIsLoading] = useState(true)

return (
<Image
src={image.preview_url || image.url}
Expand All @@ -15,13 +12,7 @@ export default function ListImage({ image }: { image: any }) {
removeWrapper
disableSkeleton
radius="none"
className={cn(
'duration-700 ease-in-out group-hover:opacity-75 object-cover',
isLoading
? 'scale-110 blur-2xl grayscale'
: 'scale-100 blur-0 grayscale-0'
)}
onLoad={() => setIsLoading(false)}
className="duration-700 ease-in-out group-hover:opacity-75 object-cover"
/>
)
}
21 changes: 21 additions & 0 deletions server/lib/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,27 @@ export async function fetchTagsList() {
return findAll;
}

export async function fetchTagsListAndNotDefault() {
const findAll = await db.tags.findMany({
where: {
del: 0
},
orderBy: [
{
sort: 'desc',
},
{
create_time: 'desc',
},
{
update_time: 'desc'
}
]
})

return findAll;
}

export async function fetchServerImagesListByTag(pageNum: number, tag: string) {
if (tag === 'all') {
tag = ''
Expand Down

0 comments on commit 47a6f0f

Please sign in to comment.