mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-04 09:17:48 +08:00
refactor(ui): unify top level pages structure, use standard language codes and time zones (#13573)
### What problem does this PR solve? - Unify top level pages structure - Standardize locale language codes (BCP 47) and time zones (IANA tz) > **Note:** > Newly created user info brings non-standard default values `timezone: "UTC+8\tAsia/Shanghai"` and `language: "English"`. ### Type of change - [x] Refactoring
This commit is contained in:
@ -26,8 +26,9 @@ const ApiContent = ({ id, idKey }: { id?: string; idKey: string }) => {
|
||||
const isDarkTheme = useIsDarkTheme();
|
||||
|
||||
return (
|
||||
<div className="pb-2 flex flex-col w-full">
|
||||
<BackendServiceApi show={showApiKeyModal}></BackendServiceApi>
|
||||
<div className="flex flex-col w-full">
|
||||
<BackendServiceApi show={showApiKeyModal} />
|
||||
|
||||
<div className="text-left py-4">
|
||||
<Button onClick={tocVisible ? hideToc : showToc}>
|
||||
{tocVisible ? t('hideToc') : t('showToc')}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { combineRefs } from '@/lib/utils';
|
||||
import { transformFile2Base64 } from '@/utils/file-util';
|
||||
import { LucidePencil, LucidePlus, LucideX } from 'lucide-react';
|
||||
import {
|
||||
@ -11,7 +12,6 @@ import {
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { Modal } from './ui/modal/modal';
|
||||
|
||||
type AvatarUploadProps = {
|
||||
@ -42,6 +42,7 @@ export const AvatarUpload = forwardRef<HTMLInputElement, AvatarUploadProps>(
|
||||
const [isCropModalOpen, setIsCropModalOpen] = useState(false);
|
||||
const [imageToCrop, setImageToCrop] = useState<string | null>(null);
|
||||
const [cropArea, setCropArea] = useState({ x: 0, y: 0, size: 200 });
|
||||
const innerInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const imageRef = useRef<HTMLImageElement>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@ -210,7 +211,6 @@ export const AvatarUpload = forwardRef<HTMLInputElement, AvatarUploadProps>(
|
||||
const handleWheel = useCallback((e: React.WheelEvent) => {
|
||||
if (!imageRef.current) return;
|
||||
|
||||
e.preventDefault();
|
||||
const image = imageRef.current;
|
||||
const delta = e.deltaY > 0 ? 0.9 : 1.1; // Zoom factor
|
||||
|
||||
@ -245,10 +245,10 @@ export const AvatarUpload = forwardRef<HTMLInputElement, AvatarUploadProps>(
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
/*
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
setTimeout(() => {
|
||||
console.log('container', container);
|
||||
// initCropArea();
|
||||
if (imageToCrop && container && isCropModalOpen) {
|
||||
container.addEventListener(
|
||||
@ -265,34 +265,59 @@ export const AvatarUpload = forwardRef<HTMLInputElement, AvatarUploadProps>(
|
||||
}
|
||||
}, 100);
|
||||
}, [handleWheel, imageToCrop, isCropModalOpen]);
|
||||
*/
|
||||
|
||||
return (
|
||||
<div className="flex justify-start items-end space-x-2">
|
||||
<div className="relative group">
|
||||
<input
|
||||
placeholder=""
|
||||
type="file"
|
||||
title=""
|
||||
accept="image/*"
|
||||
className="peer/input size-0 absolute top-0 left-0 opacity-0 pointer-events-none"
|
||||
onChange={handleChange}
|
||||
ref={combineRefs(ref, innerInputRef)}
|
||||
data-testid={uploadInputTestId}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
|
||||
{!avatarBase64Str ? (
|
||||
<div
|
||||
className="
|
||||
border border-dashed border-borer-button rounded-md size-16
|
||||
flex flex-col gap-1 justify-center items-center text-sm text-text-secondary transition-colors
|
||||
group-has-[input:focus-visible]:border-accent-primary group-has-[input:focus-visible]:text-text-primary"
|
||||
<Button
|
||||
variant="dashed"
|
||||
size="icon"
|
||||
className="size-16 flex flex-col items-center gap-1 !bg-transparent"
|
||||
onClick={() => {
|
||||
innerInputRef.current?.click();
|
||||
}}
|
||||
>
|
||||
<LucidePlus className="size-4" />
|
||||
<span>{t('common.upload')}</span>
|
||||
</div>
|
||||
</Button>
|
||||
) : (
|
||||
<div className="size-16 relative grid place-content-center">
|
||||
<Avatar className="size-16 rounded-md">
|
||||
<AvatarImage className="block" src={avatarBase64Str} alt="" />
|
||||
<AvatarFallback></AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div
|
||||
className="
|
||||
absolute inset-0 bg-black/50 flex items-center justify-center
|
||||
transition-opacity opacity-0 group-hover:opacity-100 group-has-[input:focus-visible]:opacity-100"
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
className="group/button size-full p-0 transition-all relative gap-0 overflow-hidden"
|
||||
onClick={() => {
|
||||
innerInputRef.current?.click();
|
||||
}}
|
||||
>
|
||||
<LucidePencil className="size-5 opacity-75" />
|
||||
</div>
|
||||
<Avatar className="size-full rounded-none">
|
||||
<AvatarImage className="block" src={avatarBase64Str} alt="" />
|
||||
<AvatarFallback />
|
||||
</Avatar>
|
||||
|
||||
<div
|
||||
className="
|
||||
absolute inset-0 flex items-center justify-center
|
||||
bg-black/40 opacity-0 transition-opacity
|
||||
group-hover/button:opacity-100 group-focus-visible/button:opacity-100"
|
||||
>
|
||||
<LucidePencil className="size-5 opacity-75" />
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleRemove}
|
||||
@ -306,18 +331,8 @@ export const AvatarUpload = forwardRef<HTMLInputElement, AvatarUploadProps>(
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Input
|
||||
placeholder=""
|
||||
type="file"
|
||||
title=""
|
||||
accept="image/*"
|
||||
className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
|
||||
onChange={handleChange}
|
||||
ref={ref}
|
||||
data-testid={uploadInputTestId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ms-1 text-xs text-text-secondary">
|
||||
{tips ?? t('knowledgeConfiguration.photoTip')}
|
||||
</div>
|
||||
@ -357,7 +372,7 @@ export const AvatarUpload = forwardRef<HTMLInputElement, AvatarUploadProps>(
|
||||
height: '300px',
|
||||
touchAction: 'none',
|
||||
}}
|
||||
// onWheel={handleWheel}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<img
|
||||
ref={imageRef}
|
||||
|
||||
@ -5,13 +5,13 @@ type CardContainerProps = { className?: string } & PropsWithChildren;
|
||||
|
||||
export function CardContainer({ children, className }: CardContainerProps) {
|
||||
return (
|
||||
<section
|
||||
<div
|
||||
className={cn(
|
||||
'grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5',
|
||||
'grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 auto-rows-auto content-start',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { isValidElement, PropsWithChildren, ReactNode } from 'react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import './index.less';
|
||||
|
||||
type CardContainerProps = { className?: string } & PropsWithChildren;
|
||||
@ -8,26 +8,6 @@ export function CardSineLineContainer({
|
||||
children,
|
||||
className,
|
||||
}: CardContainerProps) {
|
||||
const flattenChildren = (children: ReactNode): ReactNode[] => {
|
||||
const result: ReactNode[] = [];
|
||||
|
||||
const traverse = (child: ReactNode) => {
|
||||
if (Array.isArray(child)) {
|
||||
child.forEach(traverse);
|
||||
} else if (isValidElement(child) && child.props.children) {
|
||||
result.push(child);
|
||||
} else {
|
||||
result.push(child);
|
||||
}
|
||||
};
|
||||
|
||||
traverse(children);
|
||||
return result;
|
||||
};
|
||||
const childArray = flattenChildren(children);
|
||||
const childCount = childArray.length;
|
||||
console.log(childArray, childCount);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
|
||||
@ -83,8 +83,10 @@ export const EmptyAppCard = (props: {
|
||||
size?: 'small' | 'large';
|
||||
children?: React.ReactNode;
|
||||
testId?: string;
|
||||
tabIndex?: number;
|
||||
}) => {
|
||||
const { type, showIcon, className, isSearch, children, testId } = props;
|
||||
const { type, showIcon, className, isSearch, children, testId, tabIndex } =
|
||||
props;
|
||||
const { t } = useTranslation();
|
||||
let defaultClass = '';
|
||||
let style = {};
|
||||
@ -110,10 +112,10 @@ export const EmptyAppCard = (props: {
|
||||
<EmptyCard
|
||||
onClick={isSearch ? undefined : props.onClick}
|
||||
data-testid={testId}
|
||||
tabIndex={isSearch ? undefined : 0}
|
||||
tabIndex={tabIndex ?? (isSearch ? undefined : 0)}
|
||||
icon={showIcon ? cardData.icon : undefined}
|
||||
title={isSearch ? notFound : title}
|
||||
className={cn('cursor-pointer', className)}
|
||||
className={cn(!isSearch && 'cursor-pointer', className)}
|
||||
style={style}
|
||||
// description={EmptyCardData[type].description}
|
||||
>
|
||||
|
||||
@ -94,7 +94,7 @@ export default function ListFilterBar({
|
||||
|
||||
return (
|
||||
<div className={cn('flex justify-between items-center', className)}>
|
||||
<div className="text-2xl font-semibold flex items-center gap-2.5">
|
||||
<h1 className="text-2xl font-semibold flex items-center gap-2.5">
|
||||
{typeof icon === 'string' ? (
|
||||
// <IconFont name={icon} className="size-6"></IconFont>
|
||||
<HomeIcon
|
||||
@ -105,11 +105,11 @@ export default function ListFilterBar({
|
||||
icon
|
||||
)}
|
||||
{leftPanel || title}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div className="flex gap-4 items-center" role="toolbar">
|
||||
{preChildren}
|
||||
{showFilter && (
|
||||
{filters?.length && showFilter && (
|
||||
<FilterPopover
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
|
||||
@ -165,10 +165,8 @@ export const SelectWithSearch = forwardRef<
|
||||
)}
|
||||
>
|
||||
{selectLabel || value ? (
|
||||
<span className="flex min-w-0 options-center gap-2">
|
||||
<span className="leading-none truncate">
|
||||
{selectLabel || value}
|
||||
</span>
|
||||
<span className="flex min-w-0 options-center gap-2 truncate">
|
||||
{selectLabel || value}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-text-disabled">{placeholder}</span>
|
||||
@ -209,10 +207,10 @@ export const SelectWithSearch = forwardRef<
|
||||
<CommandEmpty>
|
||||
<div dangerouslySetInnerHTML={{ __html: emptyData }}></div>
|
||||
</CommandEmpty>
|
||||
{options.map((group, idx) => {
|
||||
{options.map((group) => {
|
||||
if (group.options) {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<Fragment key={group.value}>
|
||||
<CommandGroup heading={group.label} className="mb-1">
|
||||
{group.options.map((option) => (
|
||||
<CommandItem
|
||||
|
||||
@ -93,7 +93,8 @@ const buttonVariants = cva(
|
||||
|
||||
// Static
|
||||
// Button has no interaction transitions
|
||||
static: 'text-text-secondary',
|
||||
static:
|
||||
'text-text-secondary hover:text-text-primary focus-visible:text-text-primary',
|
||||
},
|
||||
size: {
|
||||
auto: '',
|
||||
|
||||
@ -9,8 +9,8 @@ import {
|
||||
} from '@/components/ui/pagination';
|
||||
import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { t } from 'i18next';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type RAGFlowPaginationType = {
|
||||
showQuickJumper?: boolean;
|
||||
@ -28,6 +28,7 @@ export function RAGFlowPagination({
|
||||
onChange,
|
||||
showSizeChanger = true,
|
||||
}: RAGFlowPaginationType) {
|
||||
const { t } = useTranslation();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [currentPageSize, setCurrentPageSize] = useState('10');
|
||||
|
||||
@ -36,7 +37,7 @@ export function RAGFlowPagination({
|
||||
label: <span>{t('pagination.page', { page: x })}</span>,
|
||||
value: x.toString(),
|
||||
}));
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
const pages = useMemo(() => {
|
||||
const num = Math.ceil(total / pageSize);
|
||||
@ -134,7 +135,7 @@ export function RAGFlowPagination({
|
||||
}, [pages, currentPage]);
|
||||
|
||||
return (
|
||||
<section className="flex items-center justify-end text-text-sub-title-invert">
|
||||
<div className="flex items-center justify-end text-text-sub-title-invert">
|
||||
<span className="mr-4 text-text-primary">
|
||||
{t('pagination.total', { total: total })}
|
||||
</span>
|
||||
@ -181,6 +182,6 @@ export function RAGFlowPagination({
|
||||
triggerClassName="bg-bg-card border-transparent"
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -105,8 +105,6 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
|
||||
const isObject = typeof option === 'object';
|
||||
const actualValue = isObject ? option.value : option;
|
||||
|
||||
console.log(actualValue);
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={actualValue}
|
||||
|
||||
Reference in New Issue
Block a user