mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
R: Remove unnecessary rerenders from useEffect
This commit is contained in:
parent
ca203a1bfb
commit
6fa25b51fe
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { ChevronDownIcon } from 'lucide-react';
|
||||
|
||||
import { IconRemove } from '../icons';
|
||||
|
@ -49,11 +49,12 @@ export function ComboBox<Option>({
|
|||
const [popoverWidth, setPopoverWidth] = useState<number | undefined>(undefined);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerRef.current) {
|
||||
function handleOpenChange(isOpen: boolean) {
|
||||
setOpen(isOpen);
|
||||
if (isOpen && triggerRef.current) {
|
||||
setPopoverWidth(triggerRef.current.offsetWidth);
|
||||
}
|
||||
}, [open]);
|
||||
}
|
||||
|
||||
function handleChangeValue(newValue: Option | null) {
|
||||
onChange(newValue);
|
||||
|
@ -66,7 +67,7 @@ export function ComboBox<Option>({
|
|||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
id={id}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { ChevronDownIcon } from 'lucide-react';
|
||||
|
||||
import { IconRemove } from '../icons';
|
||||
|
@ -43,11 +43,12 @@ export function ComboMulti<Option>({
|
|||
const [popoverWidth, setPopoverWidth] = useState<number | undefined>(undefined);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerRef.current) {
|
||||
function handleOpenChange(isOpen: boolean) {
|
||||
setOpen(isOpen);
|
||||
if (isOpen && triggerRef.current) {
|
||||
setPopoverWidth(triggerRef.current.offsetWidth);
|
||||
}
|
||||
}, [open]);
|
||||
}
|
||||
|
||||
function handleAddValue(newValue: Option) {
|
||||
if (value.includes(newValue)) {
|
||||
|
@ -70,7 +71,7 @@ export function ComboMulti<Option>({
|
|||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
id={id}
|
||||
|
|
|
@ -44,11 +44,12 @@ export function SelectTree<ItemType>({
|
|||
...restProps
|
||||
}: SelectTreeProps<ItemType>) {
|
||||
const foldable = new Set(items.filter(item => getParent(item) !== item).map(item => getParent(item)));
|
||||
const [folded, setFolded] = useState<ItemType[]>(items);
|
||||
const defaultFolded = items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item);
|
||||
const [folded, setFolded] = useState<ItemType[]>(defaultFolded);
|
||||
|
||||
useEffect(() => {
|
||||
setFolded(items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item));
|
||||
}, [value, getParent, items]);
|
||||
setFolded(defaultFolded);
|
||||
}, [defaultFolded]);
|
||||
|
||||
function onFoldItem(target: ItemType) {
|
||||
setFolded(prev =>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
|
||||
|
@ -13,13 +13,26 @@ import { useQueryStrings } from '@/hooks/use-query-strings';
|
|||
|
||||
import { useResetPassword } from '../backend/use-reset-password';
|
||||
|
||||
function useTokenValidation(token: string, isPending: boolean) {
|
||||
const { validateToken } = useResetPassword();
|
||||
const [isTokenValidating, setIsTokenValidating] = useState(false);
|
||||
|
||||
const validate = async () => {
|
||||
if (!isTokenValidating && !isPending) {
|
||||
await validateToken({ token });
|
||||
setIsTokenValidating(true);
|
||||
}
|
||||
};
|
||||
return { isTokenValidating, validate };
|
||||
}
|
||||
|
||||
export function Component() {
|
||||
const router = useConceptNavigation();
|
||||
const token = useQueryStrings().get('token') ?? '';
|
||||
|
||||
const { validateToken, resetPassword, isPending, error: serverError } = useResetPassword();
|
||||
const { resetPassword, isPending, error: serverError } = useResetPassword();
|
||||
const { isTokenValidating, validate } = useTokenValidation(token, isPending);
|
||||
|
||||
const [isTokenValidating, setIsTokenValidating] = useState(false);
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [newPasswordRepeat, setNewPasswordRepeat] = useState('');
|
||||
|
||||
|
@ -38,12 +51,9 @@ export function Component() {
|
|||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTokenValidating && !isPending) {
|
||||
void validateToken({ token: token });
|
||||
setIsTokenValidating(true);
|
||||
}
|
||||
}, [token, validateToken, isTokenValidating, isPending]);
|
||||
if (!isTokenValidating && !isPending) {
|
||||
void validate();
|
||||
}
|
||||
|
||||
if (isPending) {
|
||||
return <Loader />;
|
||||
|
|
|
@ -54,7 +54,7 @@ export function LibraryPage() {
|
|||
return (
|
||||
<>
|
||||
<ToolbarSearch className='top-0 h-9' total={libraryItems.length} filtered={filtered.length} />
|
||||
<div className='relative cc-fade-in flex'>
|
||||
<div className='relative flex'>
|
||||
<MiniButton
|
||||
title='Выгрузить в формате CSV'
|
||||
className='absolute z-tooltip -top-8 right-6 hidden sm:block'
|
||||
|
|
|
@ -43,11 +43,7 @@ export function EditorOssCard() {
|
|||
/>
|
||||
<div
|
||||
onKeyDown={handleInput}
|
||||
className={clsx(
|
||||
'cc-fade-in',
|
||||
'md:max-w-fit max-w-128 min-w-fit',
|
||||
'flex flex-row flex-wrap pt-8 px-6 justify-center'
|
||||
)}
|
||||
className={clsx('md:max-w-fit max-w-128 min-w-fit', 'flex flex-row flex-wrap pt-8 px-6 justify-center')}
|
||||
>
|
||||
<div className='cc-column px-3'>
|
||||
<FormOSS />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
|
@ -30,7 +30,7 @@ export function FormOSS() {
|
|||
control,
|
||||
setValue,
|
||||
reset,
|
||||
formState: { isDirty, errors }
|
||||
formState: { errors, isDirty }
|
||||
} = useForm<IUpdateLibraryItemDTO>({
|
||||
resolver: zodResolver(schemaUpdateLibraryItem),
|
||||
defaultValues: {
|
||||
|
@ -46,9 +46,11 @@ export function FormOSS() {
|
|||
const visible = useWatch({ control, name: 'visible' });
|
||||
const readOnly = useWatch({ control, name: 'read_only' });
|
||||
|
||||
useEffect(() => {
|
||||
const prevDirty = useRef(isDirty);
|
||||
if (prevDirty.current !== isDirty) {
|
||||
prevDirty.current = isDirty;
|
||||
setIsModified(isDirty);
|
||||
}, [isDirty, setIsModified]);
|
||||
}
|
||||
|
||||
function onSubmit(data: IUpdateLibraryItemDTO) {
|
||||
return updateOss(data).then(() => reset({ ...data }));
|
||||
|
|
|
@ -411,7 +411,7 @@ export function OssFlow() {
|
|||
<ContextMenu isOpen={isContextMenuOpen} onHide={() => setIsContextMenuOpen(false)} {...menuProps} />
|
||||
|
||||
<div
|
||||
className={clsx('cc-fade-in relative w-[100vw] cc-mask-sides', !containMovement && 'cursor-relocate')}
|
||||
className={clsx('relative w-[100vw] cc-mask-sides', !containMovement && 'cursor-relocate')}
|
||||
style={{ height: mainHeight, fontFamily: 'Rubik' }}
|
||||
>
|
||||
<ReactFlow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
@ -8,6 +8,7 @@ import { useLibrarySearchStore } from '@/features/library';
|
|||
import { useDeleteItem } from '@/features/library/backend/use-delete-item';
|
||||
import { RSTabID } from '@/features/rsform/pages/rsform-page/rsedit-context';
|
||||
import { useRoleStore, UserRole } from '@/features/users';
|
||||
import { useAdjustRole } from '@/features/users/stores/use-adjust-role';
|
||||
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { promptText } from '@/utils/labels';
|
||||
|
@ -27,7 +28,6 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
|||
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||
|
||||
const role = useRoleStore(state => state.role);
|
||||
const adjustRole = useRoleStore(state => state.adjustRole);
|
||||
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||
|
||||
|
@ -36,21 +36,18 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
|||
|
||||
const isOwned = !!user.id && user.id === schema.owner;
|
||||
const isMutable = role > UserRole.READER && !schema.read_only;
|
||||
const isEditor = !!user.id && schema.editors.includes(user.id);
|
||||
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
|
||||
const { deleteItem } = useDeleteItem();
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
adjustRole({
|
||||
isOwner: isOwned,
|
||||
isEditor: !!user.id && schema.editors.includes(user.id),
|
||||
isStaff: user.is_staff,
|
||||
adminMode: adminMode
|
||||
}),
|
||||
[schema, adjustRole, isOwned, user, adminMode]
|
||||
);
|
||||
useAdjustRole({
|
||||
isOwner: isOwned,
|
||||
isEditor: isEditor,
|
||||
isStaff: user.is_staff,
|
||||
adminMode: adminMode
|
||||
});
|
||||
|
||||
function navigateTab(tab: OssTabID) {
|
||||
const url = urls.oss_props({
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useParams } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
@ -12,6 +11,7 @@ import { isAxiosError } from '@/backend/api-transport';
|
|||
import { TextURL } from '@/components/control';
|
||||
import { type ErrorData } from '@/components/info-error';
|
||||
import { useQueryStrings } from '@/hooks/use-query-strings';
|
||||
import { useResetModification } from '@/hooks/use-reset-modification';
|
||||
import { useModificationStore } from '@/stores/modification';
|
||||
|
||||
import { OperationTooltip } from '../../components/tooltip-oss-item';
|
||||
|
@ -26,6 +26,8 @@ const paramsSchema = z.strictObject({
|
|||
});
|
||||
|
||||
export function OssPage() {
|
||||
useResetModification();
|
||||
|
||||
const router = useConceptNavigation();
|
||||
const params = useParams();
|
||||
const query = useQueryStrings();
|
||||
|
@ -35,11 +37,9 @@ export function OssPage() {
|
|||
tab: query.get('tab')
|
||||
});
|
||||
|
||||
const { isModified, setIsModified } = useModificationStore();
|
||||
const { isModified } = useModificationStore();
|
||||
useBlockNavigation(isModified);
|
||||
|
||||
useEffect(() => setIsModified(false), [setIsModified]);
|
||||
|
||||
if (!urlData.id) {
|
||||
router.replace({ path: urls.page404, force: true });
|
||||
return null;
|
||||
|
|
|
@ -75,7 +75,6 @@ export function EditorConstituenta() {
|
|||
tabIndex={-1}
|
||||
className={clsx(
|
||||
'relative ',
|
||||
'cc-fade-in',
|
||||
'min-h-80 max-w-[calc(min(100vw,80rem))] mx-auto',
|
||||
'flex pt-8',
|
||||
'overflow-y-auto overflow-x-clip',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
|
||||
'use client';
|
||||
|
||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-toastify';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
@ -44,19 +44,30 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
const { isModified, setIsModified } = useModificationStore();
|
||||
const isProcessing = useMutatingRSForm();
|
||||
|
||||
const { updateConstituenta: cstUpdate } = useUpdateConstituenta();
|
||||
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
|
||||
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
|
||||
const showRenameCst = useDialogsStore(state => state.showRenameCst);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
formState: { isDirty }
|
||||
} = useForm<IUpdateConstituentaDTO>({ resolver: zodResolver(schemaUpdateConstituenta) });
|
||||
|
||||
const { updateConstituenta: cstUpdate } = useUpdateConstituenta();
|
||||
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
|
||||
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
|
||||
const showRenameCst = useDialogsStore(state => state.showRenameCst);
|
||||
|
||||
} = useForm<IUpdateConstituentaDTO>({
|
||||
resolver: zodResolver(schemaUpdateConstituenta),
|
||||
defaultValues: {
|
||||
target: activeCst.id,
|
||||
item_data: {
|
||||
convention: activeCst.convention,
|
||||
term_raw: activeCst.term_raw,
|
||||
definition_raw: activeCst.definition_raw,
|
||||
definition_formal: activeCst.definition_formal
|
||||
}
|
||||
}
|
||||
});
|
||||
const [forceComment, setForceComment] = useState(false);
|
||||
const [localParse, setLocalParse] = useState<IExpressionParseDTO | null>(null);
|
||||
|
||||
const typification = useMemo(
|
||||
|
@ -80,12 +91,21 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
[activeCst, localParse]
|
||||
);
|
||||
|
||||
const [forceComment, setForceComment] = useState(false);
|
||||
const isBasic = isBasicConcept(activeCst.cst_type);
|
||||
const isElementary = isBaseSet(activeCst.cst_type);
|
||||
const showConvention = !!activeCst.convention || forceComment || isBasic;
|
||||
|
||||
useEffect(() => {
|
||||
const prevActiveCstID = useRef(activeCst.id);
|
||||
const prevToggleReset = useRef(toggleReset);
|
||||
const prevSchema = useRef(schema);
|
||||
if (
|
||||
prevActiveCstID.current !== activeCst.id ||
|
||||
prevToggleReset.current !== toggleReset ||
|
||||
prevSchema.current !== schema
|
||||
) {
|
||||
prevActiveCstID.current = activeCst.id;
|
||||
prevToggleReset.current = toggleReset;
|
||||
prevSchema.current = schema;
|
||||
reset({
|
||||
target: activeCst.id,
|
||||
item_data: {
|
||||
|
@ -97,15 +117,19 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
});
|
||||
setForceComment(false);
|
||||
setLocalParse(null);
|
||||
}, [activeCst, schema, toggleReset, reset]);
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const prevDirty = useRef(isDirty);
|
||||
if (prevDirty.current !== isDirty) {
|
||||
prevDirty.current = isDirty;
|
||||
setIsModified(isDirty);
|
||||
return () => setIsModified(false);
|
||||
}, [isDirty, activeCst, setIsModified]);
|
||||
}
|
||||
|
||||
function onSubmit(data: IUpdateConstituentaDTO) {
|
||||
return cstUpdate({ itemID: schema.id, data }).then(() => reset({ ...data }));
|
||||
return cstUpdate({ itemID: schema.id, data }).then(() => {
|
||||
setIsModified(false);
|
||||
reset({ ...data });
|
||||
});
|
||||
}
|
||||
|
||||
function handleTypeGraph(event: React.MouseEvent<Element>) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { type ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||
|
||||
import { useResetOnChange } from '@/hooks/use-reset-on-change';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
@ -69,6 +70,11 @@ export function EditorRSExpression({
|
|||
|
||||
const { checkConstituenta: checkInternal, isPending } = useCheckConstituenta();
|
||||
|
||||
useResetOnChange([activeCst, toggleReset], () => {
|
||||
setIsModified(false);
|
||||
setParseData(null);
|
||||
});
|
||||
|
||||
function checkConstituenta(
|
||||
expression: string,
|
||||
activeCst: IConstituenta,
|
||||
|
@ -85,11 +91,6 @@ export function EditorRSExpression({
|
|||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsModified(false);
|
||||
setParseData(null);
|
||||
}, [activeCst, toggleReset]);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
onChange(newValue);
|
||||
setIsModified(newValue !== activeCst.definition_formal);
|
||||
|
|
|
@ -31,10 +31,7 @@ export function EditorRSFormCard() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onKeyDown={handleInput}
|
||||
className='relative cc-fade-in md:w-fit md:max-w-fit max-w-128 flex flex-row flex-wrap px-6 pt-8'
|
||||
>
|
||||
<div onKeyDown={handleInput} className='relative md:w-fit md:max-w-fit max-w-128 flex flex-row flex-wrap px-6 pt-8'>
|
||||
<ToolbarItemCard
|
||||
className='cc-tab-tools'
|
||||
onSubmit={initiateSubmit}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use no memo'; // TODO: remove when react hook forms are compliant with react compiler
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
|
@ -41,12 +41,23 @@ export function FormRSForm() {
|
|||
reset,
|
||||
formState: { isDirty, errors }
|
||||
} = useForm<IUpdateLibraryItemDTO>({
|
||||
resolver: zodResolver(schemaUpdateLibraryItem)
|
||||
resolver: zodResolver(schemaUpdateLibraryItem),
|
||||
defaultValues: {
|
||||
id: schema.id,
|
||||
item_type: LibraryItemType.RSFORM,
|
||||
title: schema.title,
|
||||
alias: schema.alias,
|
||||
description: schema.description,
|
||||
visible: schema.visible,
|
||||
read_only: schema.read_only
|
||||
}
|
||||
});
|
||||
const visible = useWatch({ control, name: 'visible' });
|
||||
const readOnly = useWatch({ control, name: 'read_only' });
|
||||
|
||||
useEffect(() => {
|
||||
const prevSchema = useRef(schema);
|
||||
if (prevSchema.current !== schema) {
|
||||
prevSchema.current = schema;
|
||||
reset({
|
||||
id: schema.id,
|
||||
item_type: LibraryItemType.RSFORM,
|
||||
|
@ -56,11 +67,13 @@ export function FormRSForm() {
|
|||
visible: schema.visible,
|
||||
read_only: schema.read_only
|
||||
});
|
||||
}, [schema, reset]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const prevDirty = useRef(isDirty);
|
||||
if (prevDirty.current !== isDirty) {
|
||||
prevDirty.current = isDirty;
|
||||
setIsModified(isDirty);
|
||||
}, [isDirty, setIsModified]);
|
||||
}
|
||||
|
||||
function handleSelectVersion(version: CurrentVersion) {
|
||||
router.push({ path: urls.schema(schema.id, version === 'latest' ? undefined : version) });
|
||||
|
|
|
@ -126,7 +126,7 @@ export function EditorRSList() {
|
|||
const tableHeight = useFitHeight('4rem + 5px');
|
||||
|
||||
return (
|
||||
<div tabIndex={-1} onKeyDown={handleKeyDown} className='relative cc-fade-in pt-8'>
|
||||
<div tabIndex={-1} onKeyDown={handleKeyDown} className='relative pt-8'>
|
||||
{isContentEditable ? (
|
||||
<ToolbarRSList className='cc-tab-tools right-4 md:right-1/2 -translate-x-1/2 md:translate-x-0 cc-animate-position' />
|
||||
) : null}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import {
|
||||
type Edge,
|
||||
MarkerType,
|
||||
|
@ -121,17 +121,19 @@ export function TGFlow() {
|
|||
}, PARAMETER.minimalTimeout);
|
||||
}, [schema, filteredGraph, setNodes, setEdges, filter.noText, fitView, viewportInitialized, focusCst]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!viewportInitialized) {
|
||||
return;
|
||||
}
|
||||
const prevSelected = useRef<number[]>([]);
|
||||
if (
|
||||
viewportInitialized &&
|
||||
(prevSelected.current.length !== selected.length || prevSelected.current.some((id, i) => id !== selected[i]))
|
||||
) {
|
||||
prevSelected.current = selected;
|
||||
setNodes(prev =>
|
||||
prev.map(node => ({
|
||||
...node,
|
||||
selected: selected.includes(Number(node.id))
|
||||
}))
|
||||
);
|
||||
}, [selected, setNodes, viewportInitialized]);
|
||||
}
|
||||
|
||||
function handleSetSelected(newSelection: number[]) {
|
||||
setSelected(newSelection);
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
import { useLibrarySearchStore } from '@/features/library';
|
||||
import { useDeleteItem } from '@/features/library/backend/use-delete-item';
|
||||
import { useRoleStore, UserRole } from '@/features/users';
|
||||
import { useAdjustRole } from '@/features/users/stores/use-adjust-role';
|
||||
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { useModificationStore } from '@/stores/modification';
|
||||
|
@ -39,7 +40,6 @@ export const RSEditState = ({
|
|||
const router = useConceptNavigation();
|
||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||
const role = useRoleStore(state => state.role);
|
||||
const adjustRole = useRoleStore(state => state.adjustRole);
|
||||
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||
|
||||
|
@ -52,6 +52,7 @@ export const RSEditState = ({
|
|||
const isMutable = role > UserRole.READER && !schema.read_only;
|
||||
const isContentEditable = isMutable && !isArchive;
|
||||
const isAttachedToOSS = schema.oss.length > 0;
|
||||
const isEditor = !!user.id && schema.editors.includes(user.id);
|
||||
|
||||
const [selected, setSelected] = useState<number[]>([]);
|
||||
const canDeleteSelected = selected.length > 0 && selected.every(id => !schema.cstByID.get(id)?.is_inherited);
|
||||
|
@ -67,16 +68,12 @@ export const RSEditState = ({
|
|||
const showDeleteCst = useDialogsStore(state => state.showDeleteCst);
|
||||
const showCstTemplate = useDialogsStore(state => state.showCstTemplate);
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
adjustRole({
|
||||
isOwner: isOwned,
|
||||
isEditor: !!user.id && schema.editors.includes(user.id),
|
||||
isStaff: user.is_staff,
|
||||
adminMode: adminMode
|
||||
}),
|
||||
[schema, adjustRole, isOwned, user, adminMode]
|
||||
);
|
||||
useAdjustRole({
|
||||
isOwner: isOwned,
|
||||
isEditor: isEditor,
|
||||
isStaff: user.is_staff,
|
||||
adminMode: adminMode
|
||||
});
|
||||
|
||||
function handleSetFocus(newValue: IConstituenta | null) {
|
||||
setFocusCst(newValue);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useParams } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
@ -12,6 +11,7 @@ import { Divider } from '@/components/container';
|
|||
import { TextURL } from '@/components/control';
|
||||
import { type ErrorData } from '@/components/info-error';
|
||||
import { useQueryStrings } from '@/hooks/use-query-strings';
|
||||
import { useResetModification } from '@/hooks/use-reset-modification';
|
||||
import { useModificationStore } from '@/stores/modification';
|
||||
|
||||
import { ConstituentaTooltip } from '../../components/constituenta-tooltip';
|
||||
|
@ -34,6 +34,8 @@ const paramsSchema = z.strictObject({
|
|||
});
|
||||
|
||||
export function RSFormPage() {
|
||||
useResetModification();
|
||||
|
||||
const router = useConceptNavigation();
|
||||
const params = useParams();
|
||||
const query = useQueryStrings();
|
||||
|
@ -45,11 +47,9 @@ export function RSFormPage() {
|
|||
activeID: query.get('active')
|
||||
});
|
||||
|
||||
const { isModified, setIsModified } = useModificationStore();
|
||||
const { isModified } = useModificationStore();
|
||||
useBlockNavigation(isModified);
|
||||
|
||||
useEffect(() => setIsModified(false), [setIsModified]);
|
||||
|
||||
if (!urlData.id) {
|
||||
router.replace({ path: urls.page404, force: true });
|
||||
return null;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { createColumnHelper, DataTable, type IConditionalStyle } from '@/components/data-table';
|
||||
import { NoData, TextContent } from '@/components/view';
|
||||
|
@ -26,11 +26,10 @@ export function TableSideConstituents({ autoScroll = true, maxHeight }: TableSid
|
|||
const { activeCst, navigateCst } = useRSEdit();
|
||||
const items = useFilteredItems();
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeCst) {
|
||||
return;
|
||||
}
|
||||
if (autoScroll) {
|
||||
const prevActiveCstID = useRef<number | null>(null);
|
||||
if (autoScroll && prevActiveCstID.current !== activeCst?.id) {
|
||||
prevActiveCstID.current = activeCst?.id ?? null;
|
||||
if (!!activeCst) {
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(`${prefixes.cst_side_table}${activeCst.id}`);
|
||||
if (element) {
|
||||
|
@ -42,7 +41,7 @@ export function TableSideConstituents({ autoScroll = true, maxHeight }: TableSid
|
|||
}
|
||||
}, PARAMETER.refreshTimeout);
|
||||
}
|
||||
}, [activeCst, autoScroll]);
|
||||
}
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor('alias', {
|
||||
|
|
|
@ -55,7 +55,7 @@ export function FormSignup() {
|
|||
|
||||
return (
|
||||
<form
|
||||
className='cc-column cc-fade-in mx-auto w-144 px-6 py-3'
|
||||
className='cc-column mx-auto w-144 px-6 py-3'
|
||||
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||
onChange={resetErrors}
|
||||
>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { EditorProfile } from './editor-profile';
|
|||
export function UserProfilePage() {
|
||||
return (
|
||||
<RequireAuth>
|
||||
<div className='cc-fade-in flex flex-col py-2 mx-auto w-fit'>
|
||||
<div className='flex flex-col py-2 mx-auto w-fit'>
|
||||
<h1 className='mb-2 select-none'>Учетные данные пользователя</h1>
|
||||
<div className='flex py-2'>
|
||||
<EditorProfile />
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useRef } from 'react';
|
||||
|
||||
import { useRoleStore } from './role';
|
||||
|
||||
interface AdjustRoleProps {
|
||||
isOwner: boolean;
|
||||
isEditor: boolean;
|
||||
isStaff: boolean;
|
||||
adminMode: boolean;
|
||||
}
|
||||
|
||||
export function useAdjustRole(input: AdjustRoleProps) {
|
||||
const adjustRole = useRoleStore(state => state.adjustRole);
|
||||
const lastInput = useRef<string | null>(null);
|
||||
|
||||
const serializedInput = JSON.stringify(input);
|
||||
if (lastInput.current !== serializedInput) {
|
||||
lastInput.current = serializedInput;
|
||||
adjustRole(input);
|
||||
}
|
||||
}
|
13
rsconcept/frontend/src/hooks/use-reset-modification.ts
Normal file
13
rsconcept/frontend/src/hooks/use-reset-modification.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { useRef } from 'react';
|
||||
|
||||
import { useModificationStore } from '@/stores/modification';
|
||||
|
||||
export function useResetModification() {
|
||||
const { setIsModified } = useModificationStore();
|
||||
const initialized = useRef(false);
|
||||
|
||||
if (!initialized.current) {
|
||||
initialized.current = true;
|
||||
setIsModified(false);
|
||||
}
|
||||
}
|
10
rsconcept/frontend/src/hooks/use-reset-on-change.ts
Normal file
10
rsconcept/frontend/src/hooks/use-reset-on-change.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { useRef } from 'react';
|
||||
|
||||
export function useResetOnChange<T>(deps: T[], resetFn: () => void) {
|
||||
const lastDeps = useRef<string | null>(null);
|
||||
const currentDeps = JSON.stringify(deps);
|
||||
if (lastDeps.current !== currentDeps) {
|
||||
lastDeps.current = currentDeps;
|
||||
resetFn();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user