mirror of
https://github.com/wgh136/nysoure.git
synced 2025-12-16 15:51:14 +00:00
characters
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { CharactorParams } from "../network/models";
|
||||
import { CharacterParams } from "../network/models";
|
||||
import { network } from "../network/network";
|
||||
import showToast from "./toast";
|
||||
import { useTranslation } from "../utils/i18n";
|
||||
import Button from "./button";
|
||||
|
||||
export default function CharactorEditor({charactor, setCharactor, onDelete}: {
|
||||
charactor: CharactorParams;
|
||||
setCharactor: (charactor: CharactorParams) => void;
|
||||
charactor: CharacterParams;
|
||||
setCharactor: (charactor: CharacterParams) => void;
|
||||
onDelete: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -39,7 +40,7 @@ export default function CharactorEditor({charactor, setCharactor, onDelete}: {
|
||||
input.click();
|
||||
}
|
||||
|
||||
return <div className="h-52 shadow rounded-2xl overflow-clip flex">
|
||||
return <div className="h-52 shadow rounded-2xl overflow-clip flex bg-base-100">
|
||||
<div className="w-36 h-full cursor-pointer relative" onClick={uploadImage}>
|
||||
{
|
||||
isUploading ?
|
||||
@@ -94,4 +95,37 @@ export default function CharactorEditor({charactor, setCharactor, onDelete}: {
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export function FetchVndbCharactersButton({vnID, onFetch}: {
|
||||
vnID: string;
|
||||
onFetch: (characters: CharacterParams[]) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [isFetching, setFetching] = useState(false);
|
||||
const fetchCharacters = async () => {
|
||||
// validate vnID (v123456)
|
||||
if (!/^v\d+$/.test(vnID)) {
|
||||
showToast({
|
||||
type: "error",
|
||||
message: t("Invalid VNDB ID format"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setFetching(true);
|
||||
const res = await network.getCharactersFromVNDB(vnID);
|
||||
setFetching(false);
|
||||
if (res.success && res.data) {
|
||||
onFetch(res.data);
|
||||
} else {
|
||||
showToast({
|
||||
type: "error",
|
||||
message: t("Failed to fetch characters from VNDB"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return <Button isLoading={isFetching} onClick={fetchCharacters}>
|
||||
{t("Fetch from VNDB")}
|
||||
</Button>;
|
||||
}
|
||||
@@ -49,10 +49,10 @@ export interface CreateResourceParams {
|
||||
images: number[];
|
||||
gallery: number[];
|
||||
gallery_nsfw: number[];
|
||||
charactors: CharactorParams[];
|
||||
characters: CharacterParams[];
|
||||
}
|
||||
|
||||
export interface CharactorParams {
|
||||
export interface CharacterParams {
|
||||
name: string;
|
||||
alias: string[];
|
||||
cv: string;
|
||||
@@ -96,7 +96,7 @@ export interface ResourceDetails {
|
||||
related: Resource[];
|
||||
gallery: number[];
|
||||
galleryNsfw: number[];
|
||||
charactors: CharactorParams[];
|
||||
charactors: CharacterParams[];
|
||||
}
|
||||
|
||||
export interface Storage {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
CommentWithRef,
|
||||
Collection,
|
||||
Statistics,
|
||||
CharacterParams,
|
||||
} from "./models.ts";
|
||||
|
||||
class Network {
|
||||
@@ -802,6 +803,14 @@ class Network {
|
||||
axios.get(`${this.apiBaseUrl}/config/statistics`),
|
||||
);
|
||||
}
|
||||
|
||||
async getCharactersFromVNDB(vnID: string): Promise<Response<CharacterParams[]>> {
|
||||
return this._callApi(() =>
|
||||
axios.get(`${this.apiBaseUrl}/resource/vndb/characters`, {
|
||||
params: { vnid: vnID },
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const network = new Network();
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
MdDelete,
|
||||
MdOutlineInfo,
|
||||
} from "react-icons/md";
|
||||
import { CharactorParams, Tag } from "../network/models.ts";
|
||||
import { CharacterParams, Tag } from "../network/models.ts";
|
||||
import { network } from "../network/network.ts";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import showToast from "../components/toast.ts";
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
SelectAndUploadImageButton,
|
||||
UploadClipboardImageButton,
|
||||
} from "../components/image_selector.tsx";
|
||||
import CharactorEditor from "../components/charactor_edit.tsx";
|
||||
import CharactorEditor, { FetchVndbCharactersButton } from "../components/charactor_edit.tsx";
|
||||
|
||||
export default function EditResourcePage() {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
@@ -31,7 +31,7 @@ export default function EditResourcePage() {
|
||||
const [links, setLinks] = useState<{ label: string; url: string }[]>([]);
|
||||
const [galleryImages, setGalleryImages] = useState<number[]>([]);
|
||||
const [galleryNsfw, setGalleryNsfw] = useState<number[]>([]);
|
||||
const [charactors, setCharactors] = useState<CharactorParams[]>([]);
|
||||
const [charactors, setCharactors] = useState<CharacterParams[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitting, setSubmitting] = useState(false);
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
@@ -107,7 +107,7 @@ export default function EditResourcePage() {
|
||||
links: links,
|
||||
gallery: galleryImages,
|
||||
gallery_nsfw: galleryNsfw,
|
||||
charactors: charactors,
|
||||
characters: charactors,
|
||||
});
|
||||
if (res.success) {
|
||||
setSubmitting(false);
|
||||
@@ -444,9 +444,9 @@ export default function EditResourcePage() {
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="flex my-2">
|
||||
<button
|
||||
className={"btn my-2"}
|
||||
className={"btn h-9"}
|
||||
type={"button"}
|
||||
onClick={() => {
|
||||
setCharactors([...charactors, { name: "", alias: [], cv: "", image: 0 }]);
|
||||
@@ -455,6 +455,17 @@ export default function EditResourcePage() {
|
||||
<MdAdd />
|
||||
{t("Add Character")}
|
||||
</button>
|
||||
{
|
||||
links.find(link => link.label.toLowerCase() === "vndb") &&
|
||||
<div className="ml-4">
|
||||
<FetchVndbCharactersButton
|
||||
vnID={links.find(link => link.label.toLowerCase() === "vndb")?.url.split("/").pop() ?? ""}
|
||||
onFetch={(fetchedCharacters) => {
|
||||
setCharactors(fetchedCharacters);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{error && (
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
MdDelete,
|
||||
MdOutlineInfo,
|
||||
} from "react-icons/md";
|
||||
import { CharactorParams, Tag } from "../network/models.ts";
|
||||
import { CharacterParams, Tag } from "../network/models.ts";
|
||||
import { network } from "../network/network.ts";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useTranslation } from "../utils/i18n";
|
||||
@@ -32,7 +32,7 @@ export default function PublishPage() {
|
||||
const [galleryNsfw, setGalleryNsfw] = useState<number[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitting, setSubmitting] = useState(false);
|
||||
const [charactors, setCharactors] = useState<CharactorParams[]>([]);
|
||||
const [charactors, setCharactors] = useState<CharacterParams[]>([]);
|
||||
const isFirstLoad = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -111,7 +111,7 @@ export default function PublishPage() {
|
||||
links: links,
|
||||
gallery: galleryImages,
|
||||
gallery_nsfw: galleryNsfw,
|
||||
charactors: charactors,
|
||||
characters: charactors,
|
||||
});
|
||||
if (res.success) {
|
||||
localStorage.removeItem("publish_data");
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
Tag,
|
||||
Resource,
|
||||
Collection,
|
||||
CharactorParams,
|
||||
CharacterParams,
|
||||
} from "../network/models.ts";
|
||||
import { network } from "../network/network.ts";
|
||||
import showToast from "../components/toast.ts";
|
||||
@@ -1931,6 +1931,9 @@ function Gallery({ images, nsfw }: { images: number[], nsfw: number[] }) {
|
||||
nsfw = [];
|
||||
}
|
||||
|
||||
// 如果图片数量超过8张,显示数字而不是圆点
|
||||
const showDots = images.length <= 8;
|
||||
|
||||
return (
|
||||
<>
|
||||
<dialog
|
||||
@@ -2016,21 +2019,31 @@ function Gallery({ images, nsfw }: { images: number[], nsfw: number[] }) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 底部圆点 */}
|
||||
{/* 底部指示器 */}
|
||||
{images.length > 1 && (
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
|
||||
{images.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`w-2 h-2 rounded-full transition-all ${
|
||||
index === currentIndex
|
||||
? "bg-primary w-4"
|
||||
: "bg-base-content/30 hover:bg-base-content/50"
|
||||
}`}
|
||||
onClick={() => goToIndex(index)}
|
||||
aria-label={`Go to image ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2">
|
||||
{showDots ? (
|
||||
/* 圆点指示器 */
|
||||
<div className="flex gap-2">
|
||||
{images.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`w-2 h-2 rounded-full transition-all ${
|
||||
index === currentIndex
|
||||
? "bg-primary w-4"
|
||||
: "bg-base-content/30 hover:bg-base-content/50"
|
||||
}`}
|
||||
onClick={() => goToIndex(index)}
|
||||
aria-label={`Go to image ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
/* 数字指示器 */
|
||||
<div className="bg-base-100/20 px-2 py-1 rounded-full text-xs">
|
||||
{currentIndex + 1} / {images.length}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -2065,7 +2078,7 @@ function GalleryImage({src, nfsw}: {src: string, nfsw: boolean}) {
|
||||
);
|
||||
}
|
||||
|
||||
function Characters({ charactors }: { charactors: CharactorParams[] }) {
|
||||
function Characters({ charactors }: { charactors: CharacterParams[] }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!charactors || charactors.length === 0) {
|
||||
@@ -2084,7 +2097,7 @@ function Characters({ charactors }: { charactors: CharactorParams[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
function CharacterCard({ charactor }: { charactor: CharactorParams }) {
|
||||
function CharacterCard({ charactor }: { charactor: CharacterParams }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleCVClick = (e: React.MouseEvent) => {
|
||||
|
||||
@@ -35,7 +35,7 @@ type ResourceParams struct {
|
||||
Images []uint `json:"images"`
|
||||
Gallery []uint `json:"gallery"`
|
||||
GalleryNsfw []uint `json:"gallery_nsfw"`
|
||||
Charactors []CharactorParams `json:"charactors"`
|
||||
Charactors []CharactorParams `json:"characters"`
|
||||
}
|
||||
|
||||
type CharactorParams struct {
|
||||
|
||||
Reference in New Issue
Block a user