F: Improve OSS <-> RSForm UI
Some checks are pending
Backend CI / build (3.12) (push) Waiting to run
Frontend CI / build (22.x) (push) Waiting to run

This commit is contained in:
Ivan 2024-08-01 00:36:06 +03:00
parent 4f9f27dfd3
commit 600b0c01ef
21 changed files with 185 additions and 59 deletions

View File

@ -5,6 +5,7 @@ from .data_access import (
LibraryItemBaseSerializer,
LibraryItemCloneSerializer,
LibraryItemDetailsSerializer,
LibraryItemReferenceSerializer,
LibraryItemSerializer,
UsersListSerializer,
UserTargetSerializer,

View File

@ -17,6 +17,14 @@ class LibraryItemBaseSerializer(serializers.ModelSerializer):
read_only_fields = ('id',)
class LibraryItemReferenceSerializer(serializers.ModelSerializer):
''' Serializer: reference to LibraryItem. '''
class Meta:
''' serializer metadata. '''
model = LibraryItem
fields = 'id', 'alias'
class LibraryItemSerializer(serializers.ModelSerializer):
''' Serializer: LibraryItem entry limited access. '''
class Meta:

View File

@ -84,7 +84,7 @@ class OperationUpdateSerializer(serializers.Serializer):
oss = cast(LibraryItem, self.context['oss'])
for operation in attrs['arguments']:
if operation.oss != oss:
if operation.oss_id != oss.pk:
raise serializers.ValidationError({
'arguments': msg.operationNotInOSS(oss.title)
})
@ -110,7 +110,7 @@ class OperationUpdateSerializer(serializers.Serializer):
raise serializers.ValidationError({
f'{original_cst.pk}': msg.substituteDouble(original_cst.alias)
})
if original_cst.schema == substitution_cst.schema:
if original_cst.schema_id == substitution_cst.schema_id:
raise serializers.ValidationError({
'alias': msg.substituteTrivial(original_cst.alias)
})
@ -131,7 +131,7 @@ class OperationTargetSerializer(serializers.Serializer):
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, attrs['target'])
if oss and operation.oss != oss:
if oss and operation.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.operationNotInOSS(oss.title)
})
@ -155,7 +155,7 @@ class SetOperationInputSerializer(serializers.Serializer):
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, attrs['target'])
if oss and operation.oss != oss:
if oss and operation.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.operationNotInOSS(oss.title)
})

View File

@ -4,11 +4,17 @@ from typing import Optional, cast
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q
from rest_framework import serializers
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
from apps.library.models import LibraryItem
from apps.library.serializers import LibraryItemBaseSerializer, LibraryItemDetailsSerializer
from apps.library.serializers import (
LibraryItemBaseSerializer,
LibraryItemDetailsSerializer,
LibraryItemReferenceSerializer
)
from apps.oss.models import Inheritance
from shared import messages as msg
from ..models import Constituenta, CstType, RSForm
@ -90,6 +96,12 @@ class RSFormSerializer(serializers.ModelSerializer):
items = serializers.ListField(
child=CstSerializer()
)
inheritance = serializers.ListField(
child=serializers.ListField(child=serializers.IntegerField())
)
oss = serializers.ListField(
child=LibraryItemReferenceSerializer()
)
class Meta:
''' serializer metadata. '''
@ -101,6 +113,15 @@ class RSFormSerializer(serializers.ModelSerializer):
result['items'] = []
for cst in RSForm(instance).constituents().order_by('order'):
result['items'].append(CstSerializer(cst).data)
result['inheritance'] = []
for link in Inheritance.objects.filter(Q(child__schema=instance) | Q(parent__schema=instance)):
result['inheritance'].append([link.child.pk, link.parent.pk])
result['oss'] = []
for oss in LibraryItem.objects.filter(items__result=instance).only('alias'):
result['oss'].append({
'id': oss.pk,
'alias': oss.alias
})
return result
def to_versioned_data(self) -> dict:
@ -109,6 +130,8 @@ class RSFormSerializer(serializers.ModelSerializer):
del result['versions']
del result['subscribers']
del result['editors']
del result['inheritance']
del result['oss']
del result['owner']
del result['visible']
@ -210,7 +233,7 @@ class CstTargetSerializer(serializers.Serializer):
def validate(self, attrs):
schema = cast(LibraryItem, self.context['schema'])
cst = cast(Constituenta, attrs['target'])
if schema and cst.schema != schema:
if schema and cst.schema_id != schema.pk:
raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
})
@ -224,7 +247,7 @@ class CstTargetSerializer(serializers.Serializer):
class CstRenameSerializer(serializers.Serializer):
''' Serializer: Constituenta renaming. '''
target = PKField(many=False, queryset=Constituenta.objects.all())
target = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
alias = serializers.CharField()
cst_type = serializers.CharField()
@ -232,7 +255,7 @@ class CstRenameSerializer(serializers.Serializer):
attrs = super().validate(attrs)
schema = cast(LibraryItem, self.context['schema'])
cst = cast(Constituenta, attrs['target'])
if cst.schema != schema:
if cst.schema_id != schema.pk:
raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
})
@ -258,7 +281,7 @@ class CstListSerializer(serializers.Serializer):
return attrs
for item in attrs['items']:
if item.schema != schema:
if item.schema_id != schema.pk:
raise serializers.ValidationError({
f'{item.pk}': msg.constituentaNotInRSform(schema.title)
})
@ -272,8 +295,8 @@ class CstMoveSerializer(CstListSerializer):
class SubstitutionSerializerBase(serializers.Serializer):
''' Serializer: Basic substitution. '''
original = PKField(many=False, queryset=Constituenta.objects.all())
substitution = PKField(many=False, queryset=Constituenta.objects.all())
original = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
substitution = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
class CstSubstituteSerializer(serializers.Serializer):
@ -297,11 +320,11 @@ class CstSubstituteSerializer(serializers.Serializer):
raise serializers.ValidationError({
'alias': msg.substituteTrivial(original_cst.alias)
})
if original_cst.schema != schema:
if original_cst.schema_id != schema.pk:
raise serializers.ValidationError({
'original': msg.constituentaNotInRSform(schema.title)
})
if substitution_cst.schema != schema:
if substitution_cst.schema_id != schema.pk:
raise serializers.ValidationError({
'substitution': msg.constituentaNotInRSform(schema.title)
})
@ -329,7 +352,7 @@ class InlineSynthesisSerializer(serializers.Serializer):
})
constituents = cast(list[Constituenta], attrs['items'])
for cst in constituents:
if cst.schema != schema_in:
if cst.schema_id != schema_in.pk:
raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNotInRSform(schema_in.title)
})
@ -337,12 +360,12 @@ class InlineSynthesisSerializer(serializers.Serializer):
for item in attrs['substitutions']:
original_cst = cast(Constituenta, item['original'])
substitution_cst = cast(Constituenta, item['substitution'])
if original_cst.schema == schema_in:
if original_cst.schema_id == schema_in.pk:
if original_cst not in constituents:
raise serializers.ValidationError({
f'{original_cst.pk}': msg.substitutionNotInList()
})
if substitution_cst.schema != schema_out:
if substitution_cst.schema_id != schema_out.pk:
raise serializers.ValidationError({
f'{substitution_cst.pk}': msg.constituentaNotInRSform(schema_out.title)
})
@ -351,7 +374,7 @@ class InlineSynthesisSerializer(serializers.Serializer):
raise serializers.ValidationError({
f'{substitution_cst.pk}': msg.substitutionNotInList()
})
if original_cst.schema != schema_out:
if original_cst.schema_id != schema_out.pk:
raise serializers.ValidationError({
f'{original_cst.pk}': msg.constituentaNotInRSform(schema_out.title)
})

View File

@ -102,6 +102,8 @@ class TestRSFormViewset(EndpointTester):
self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved)
self.assertEqual(response.data['subscribers'], [self.user.pk])
self.assertEqual(response.data['editors'], [])
self.assertEqual(response.data['inheritance'], [])
self.assertEqual(response.data['oss'], [])
self.executeOK(item=self.unowned_id)
self.executeForbidden(item=self.private_id)

View File

@ -62,6 +62,7 @@ export { VscLibrary as IconLibrary } from 'react-icons/vsc';
export { IoLibrary as IconLibrary2 } from 'react-icons/io5';
export { BiDiamond as IconTemplates } from 'react-icons/bi';
export { GiHoneycomb as IconOSS } from 'react-icons/gi';
export { LuBaby as IconChild } from 'react-icons/lu';
export { RiHexagonLine as IconRSForm } from 'react-icons/ri';
export { LuArchive as IconArchive } from 'react-icons/lu';
export { LuDatabase as IconDatabase } from 'react-icons/lu';

View File

@ -20,6 +20,7 @@ function BadgeConstituenta({ value, prefixID, theme }: BadgeConstituentaProps) {
'min-w-[3.1rem] max-w-[3.1rem]', // prettier: split lines
'px-1',
'border rounded-md',
value.is_inherited && 'border-dashed',
'text-center font-medium whitespace-nowrap'
)}
style={{

View File

@ -13,7 +13,10 @@ interface InfoConstituentaProps extends CProps.Div {
function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) {
return (
<div className={clsx('dense min-w-[15rem]', className)} {...restProps}>
<h2>Конституента {data.alias}</h2>
<h2>
Конституента {data.alias}
{data.is_inherited ? ' (наследуется)' : ''}
</h2>
{data.term_resolved ? (
<p>
<b>Термин: </b>

View File

@ -102,8 +102,6 @@ function PickSubstitutions({
};
const toDelete = substitutions.map(item => item.original);
const replacements = substitutions.map(item => item.substitution);
console.log(toDelete, replacements);
console.log(newSubstitution);
if (
toDelete.includes(newSubstitution.original) ||
toDelete.includes(newSubstitution.substitution) ||

View File

@ -59,6 +59,8 @@ export class RSFormLoader {
}
private inferCstAttributes() {
const inherit_children = new Set(this.schema.inheritance.map(item => item[0]));
const inherit_parents = new Set(this.schema.inheritance.map(item => item[1]));
this.graph.topologicalOrder().forEach(cstID => {
const cst = this.cstByID.get(cstID)!;
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
@ -66,6 +68,8 @@ export class RSFormLoader {
cst.cst_class = inferClass(cst.cst_type, cst.is_template);
cst.children = [];
cst.children_alias = [];
cst.is_inherited = inherit_children.has(cst.id);
cst.is_inherited_parent = inherit_parents.has(cst.id);
cst.is_simple_expression = this.inferSimpleExpression(cst);
if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) {
return;
@ -165,6 +169,7 @@ export class RSFormLoader {
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0),
0
),
count_inherited: items.reduce((sum, cst) => sum + ((cst as IConstituenta).is_inherited ? 1 : 0), 0),
count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0),
count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0),

View File

@ -75,7 +75,7 @@ export interface ILibraryItem {
}
/**
* Represents library item constant data loaded for both OSS and RSForm.
* Represents {@link ILibraryItem} constant data loaded for both OSS and RSForm.
*/
export interface ILibraryItemData extends ILibraryItem {
subscribers: UserID[];
@ -83,7 +83,12 @@ export interface ILibraryItemData extends ILibraryItem {
}
/**
* Represents library item extended data with versions.
* Represents {@link ILibraryItem} minimal reference data.
*/
export interface ILibraryItemReference extends Pick<ILibraryItem, 'id' | 'alias'> {}
/**
* Represents {@link ILibraryItem} extended data with versions.
*/
export interface ILibraryItemVersioned extends ILibraryItemData {
version?: VersionID;
@ -91,7 +96,7 @@ export interface ILibraryItemVersioned extends ILibraryItemData {
}
/**
* Represents common library item editor controller.
* Represents common {@link ILibraryItem} editor controller.
*/
export interface ILibraryItemEditor {
schema?: ILibraryItemData;

View File

@ -4,7 +4,7 @@
import { Graph } from '@/models/Graph';
import { ILibraryItem, ILibraryItemVersioned, LibraryItemID } from './library';
import { ILibraryItem, ILibraryItemReference, ILibraryItemVersioned, LibraryItemID } from './library';
import { ICstSubstitute } from './oss';
import { IArgumentInfo, ParsingStatus, ValueClass } from './rslang';
@ -111,6 +111,8 @@ export interface IConstituenta extends IConstituentaData {
status: ExpressionStatus;
is_template: boolean;
is_simple_expression: boolean;
is_inherited: boolean;
is_inherited_parent: boolean;
parent?: ConstituentaID;
parent_alias?: string;
children: number[];
@ -183,6 +185,7 @@ export interface IRSFormStats {
count_errors: number;
count_property: number;
count_incalculable: number;
count_inherited: number;
count_text_term: number;
count_definition: number;
@ -198,10 +201,19 @@ export interface IRSFormStats {
count_theorem: number;
}
/**
* Represents data for {@link IRSForm} provided by backend.
*/
export interface IRSFormData extends ILibraryItemVersioned {
items: IConstituentaData[];
inheritance: ConstituentaID[][];
oss: ILibraryItemReference[];
}
/**
* Represents formal explication for set of concepts.
*/
export interface IRSForm extends ILibraryItemVersioned {
export interface IRSForm extends IRSFormData {
items: IConstituenta[];
stats: IRSFormStats;
graph: Graph;
@ -209,13 +221,6 @@ export interface IRSForm extends ILibraryItemVersioned {
cstByID: Map<ConstituentaID, IConstituenta>;
}
/**
* Represents data for {@link IRSForm} provided by backend.
*/
export interface IRSFormData extends ILibraryItemVersioned {
items: IConstituentaData[];
}
/**
* Represents data, used for cloning {@link IRSForm}.
*/

View File

@ -133,6 +133,8 @@ export function createMockConstituenta(id: ConstituentaID, alias: string, commen
definition_resolved: '',
status: ExpressionStatus.INCORRECT,
is_template: false,
is_inherited: false,
is_inherited_parent: false,
cst_class: CstClass.DERIVED,
parse: {
status: ParsingStatus.INCORRECT,

View File

@ -21,7 +21,7 @@ function InputNode(node: OssNodeInternal) {
<>
<Handle type='source' position={Position.Bottom} />
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'>
<Overlay position='top-[-0.2rem] right-[-0.2rem]'>
<MiniButton
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
noHover

View File

@ -22,7 +22,7 @@ function OperationNode(node: OssNodeInternal) {
<>
<Handle type='source' position={Position.Bottom} />
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'>
<Overlay position='top-[-0.2rem] right-[-0.2rem]'>
<MiniButton
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
noHover

View File

@ -5,8 +5,10 @@ import { AnimatePresence } from 'framer-motion';
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
import { IconSave } from '@/components/Icons';
import { IconChild, IconSave } from '@/components/Icons';
import RefsInput from '@/components/RefsInput';
import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay';
import SubmitButton from '@/components/ui/SubmitButton';
import TextArea from '@/components/ui/TextArea';
import AnimateFade from '@/components/wrap/AnimateFade';
@ -182,7 +184,7 @@ function FormConstituenta({
}
value={expression}
activeCst={state}
disabled={disabled}
disabled={disabled || state?.is_inherited}
toggleReset={toggleReset}
onChange={newValue => setExpression(newValue)}
setTypification={setTypification}
@ -229,15 +231,26 @@ function FormConstituenta({
Добавить комментарий
</button>
) : null}
{!disabled || processing ? (
<div className='self-center flex'>
<SubmitButton
key='cst_form_submit'
id='cst_form_submit'
text='Сохранить изменения'
className='self-center'
disabled={disabled || !isModified}
icon={<IconSave size='1.25rem' />}
/>
{state?.is_inherited ? (
<Overlay position='right-[-2rem]'>
<MiniButton
icon={<IconChild size='1.25rem' className='clr-text-red' />}
disabled
titleHtml='Внимание!</br> Конституента имеет потомков<br/> в операционной схеме синтеза'
/>
</Overlay>
) : null}
</div>
) : null}
</AnimatePresence>
</form>

View File

@ -1,19 +1,21 @@
'use client';
import clsx from 'clsx';
import { useEffect, useLayoutEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { IconSave } from '@/components/Icons';
import { IconOSS, IconSave } from '@/components/Icons';
import SelectVersion from '@/components/select/SelectVersion';
import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay';
import SubmitButton from '@/components/ui/SubmitButton';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import { useRSForm } from '@/context/RSFormContext';
import useDropdown from '@/hooks/useDropdown';
import { ILibraryUpdateData, LibraryItemType } from '@/models/library';
import { limits, patterns } from '@/utils/constants';
import { information } from '@/utils/labels';
import { limits, patterns, prefixes } from '@/utils/constants';
import { useRSEdit } from '../RSEditContext';
import ToolbarItemAccess from './ToolbarItemAccess';
@ -26,8 +28,8 @@ interface FormRSFormProps {
}
function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
const { schema, update, processing } = useRSForm();
const controller = useRSEdit();
const schema = controller.schema;
const [title, setTitle] = useState('');
const [alias, setAlias] = useState('');
@ -35,6 +37,8 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
const [visible, setVisible] = useState(false);
const [readOnly, setReadOnly] = useState(false);
const ossMenu = useDropdown();
useEffect(() => {
if (!schema) {
setIsModified(false);
@ -85,9 +89,37 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
visible: visible,
read_only: readOnly
};
update(data, () => toast.success(information.changesSaved));
controller.updateSchema(data);
};
const ossSelector = useMemo(
() =>
schema && schema?.oss.length > 0 ? (
<Overlay position='left-[12.5rem] top-[-0.4rem]'>
<div ref={ossMenu.ref}>
<MiniButton
icon={<IconOSS size='1.25rem' className='icon-primary' />}
noHover
title='Связанные операционные схемы'
hideTitle={ossMenu.isOpen}
onClick={() => ossMenu.toggle()}
/>
<Dropdown isOpen={ossMenu.isOpen} className='mt-[-0.1rem]'>
{schema.oss.map((reference, index) => (
<DropdownButton
className='min-w-[5rem]'
key={`${prefixes.oss_list}${index}`}
text={reference.alias}
onClick={event => controller.viewOSS(reference.id, event.ctrlKey || event.metaKey)}
/>
))}
</Dropdown>
</div>
</Overlay>
) : null,
[schema, ossMenu, controller]
);
return (
<form id={id} className={clsx('mt-1 min-w-[22rem] sm:w-[30rem]', 'flex flex-col pt-1')} onSubmit={handleSubmit}>
<TextInput
@ -99,6 +131,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
disabled={!controller.isContentEditable}
onChange={event => setTitle(event.target.value)}
/>
{ossSelector}
<div className='flex justify-between w-full gap-3 mb-3'>
<TextInput
id='schema_alias'
@ -143,7 +176,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
<SubmitButton
text='Сохранить изменения'
className='self-center mt-4'
loading={processing}
loading={controller.isProcessing}
disabled={!isModified}
icon={<IconSave size='1.25rem' />}
/>

View File

@ -15,12 +15,15 @@ function RSFormStats({ stats }: RSFormStatsProps) {
<Divider margins='my-2' className='sm:hidden' />
<LabeledValue id='count_all' label='Всего конституент' text={stats.count_all} />
<LabeledValue id='count_errors' label='Некорректных' text={stats.count_errors} />
{stats.count_inherited !== 0 ? (
<LabeledValue id='count_inherited' label='Наследованные' text={stats.count_inherited} />
) : null}
<LabeledValue id='count_errors' label='Некорректные' text={stats.count_errors} />
{stats.count_property !== 0 ? (
<LabeledValue id='count_property' label='Неразмерных' text={stats.count_property} />
<LabeledValue id='count_property' label='Неразмерные' text={stats.count_property} />
) : null}
{stats.count_incalculable !== 0 ? (
<LabeledValue id='count_incalculable' label='Невычислимых' text={stats.count_incalculable} />
<LabeledValue id='count_incalculable' label='Невычислимые' text={stats.count_incalculable} />
) : null}
<Divider margins='my-2' />

View File

@ -24,7 +24,14 @@ import DlgInlineSynthesis from '@/dialogs/DlgInlineSynthesis';
import DlgRenameCst from '@/dialogs/DlgRenameCst';
import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
import { AccessPolicy, IVersionData, LocationHead, VersionID } from '@/models/library';
import {
AccessPolicy,
ILibraryUpdateData,
IVersionData,
LibraryItemID,
LocationHead,
VersionID
} from '@/models/library';
import { ICstSubstituteData } from '@/models/oss';
import {
ConstituentaID,
@ -56,6 +63,8 @@ export interface IRSEditContext {
canProduceStructure: boolean;
nothingSelected: boolean;
updateSchema: (data: ILibraryUpdateData) => void;
setOwner: (newOwner: UserID) => void;
setAccessPolicy: (newPolicy: AccessPolicy) => void;
promptEditors: () => void;
@ -68,6 +77,7 @@ export interface IRSEditContext {
toggleSelect: (target: ConstituentaID) => void;
deselectAll: () => void;
viewOSS: (target: LibraryItemID, newTab?: boolean) => void;
viewVersion: (version?: VersionID, newTab?: boolean) => void;
createVersion: () => void;
restoreVersion: () => void;
@ -177,11 +187,21 @@ export const RSEditState = ({
[model.schema, setAccessLevel, model.isOwned, user, adminMode]
);
const updateSchema = useCallback(
(data: ILibraryUpdateData) => model.update(data, () => toast.success(information.changesSaved)),
[model]
);
const viewVersion = useCallback(
(version?: VersionID, newTab?: boolean) => router.push(urls.schema(model.itemID, version), newTab),
[router, model]
);
const viewOSS = useCallback(
(target: LibraryItemID, newTab?: boolean) => router.push(urls.oss(target), newTab),
[router]
);
const createVersion = useCallback(() => {
if (isModified && !promptUnsaved()) {
return;
@ -571,6 +591,7 @@ export const RSEditState = ({
<RSEditContext.Provider
value={{
schema: model.schema,
updateSchema,
selected,
isMutable,
isContentEditable,
@ -591,6 +612,7 @@ export const RSEditState = ({
setSelected(prev => (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])),
deselectAll: () => setSelected([]),
viewOSS,
viewVersion,
createVersion,
restoreVersion,

View File

@ -62,7 +62,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
);
return (
<div className='flex border-b clr-input overflow-hidden'>
<div className='flex border-b clr-input'>
<SearchBar
id='constituents_search'
noBorder

View File

@ -139,6 +139,7 @@ export const globals = {
*/
export const prefixes = {
page_size: 'page_size_',
oss_list: 'oss_list_',
cst_list: 'cst_list_',
cst_inline_synth_list: 'cst_inline_synth_list_',
cst_inline_synth_substitutes: 'cst_inline_synth_substitutes_',