mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-17 17:40:31 +08:00
use function to generate component to avoid misunderstand
This commit is contained in:
parent
6fba9b81a0
commit
61c96bc77a
@ -11,11 +11,7 @@ import { GeneralTraitFormList } from './GeneralTraitFormList';
|
||||
import { FetchTraitForm } from './FetchTraitForm';
|
||||
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
|
||||
import SchemaField from './JsonSchemaForm/SchemaField';
|
||||
import {
|
||||
ModifyComponentPropertiesLeafOperation,
|
||||
ModifyTraitPropertiesLeafOperation,
|
||||
} from '../../operations/leaf';
|
||||
import { ModifyComponentIdBranchOperation } from '../../operations/branch';
|
||||
import { genOperation } from 'src/operations';
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
@ -36,14 +32,14 @@ export const renderField = (properties: {
|
||||
const ref = React.createRef<HTMLTextAreaElement>();
|
||||
const onBlur = () => {
|
||||
const operation = type
|
||||
? new ModifyTraitPropertiesLeafOperation({
|
||||
? genOperation('modifyTraitProperty', {
|
||||
componentId: selectedId,
|
||||
traitIndex: index,
|
||||
properties: {
|
||||
[fullKey]: ref.current?.value,
|
||||
},
|
||||
})
|
||||
: new ModifyComponentPropertiesLeafOperation({
|
||||
: genOperation('modifyComponentProperty', {
|
||||
componentId: selectedId,
|
||||
properties: {
|
||||
[fullKey]: ref.current?.value,
|
||||
@ -96,7 +92,10 @@ export const ComponentForm: React.FC<Props> = props => {
|
||||
const changeComponentId = (selectedId: string, value: string) => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyComponentIdBranchOperation({ componentId: selectedId, newId: value })
|
||||
genOperation('modifyComponentId', {
|
||||
componentId: selectedId,
|
||||
newId: value,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@ -132,7 +131,7 @@ export const ComponentForm: React.FC<Props> = props => {
|
||||
onChange={newFormData => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyComponentPropertiesLeafOperation({
|
||||
genOperation('modifyComponentProperty', {
|
||||
componentId: selectedId,
|
||||
properties: newFormData,
|
||||
})
|
||||
|
@ -8,10 +8,7 @@ import { EventHandlerSchema } from '@sunmao-ui/runtime';
|
||||
import { eventBus } from '../../../eventBus';
|
||||
import { EventHandlerForm } from './EventHandlerForm';
|
||||
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
|
||||
import {
|
||||
CreateTraitLeafOperation,
|
||||
ModifyTraitPropertiesLeafOperation,
|
||||
} from '../../../operations/leaf';
|
||||
import { genOperation } from 'src/operations';
|
||||
|
||||
type EventHandler = Static<typeof EventHandlerSchema>;
|
||||
|
||||
@ -51,7 +48,7 @@ export const EventTraitForm: React.FC<Props> = props => {
|
||||
if (!handlers) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new CreateTraitLeafOperation({
|
||||
genOperation('createTrait', {
|
||||
componentId: component.id,
|
||||
traitType: 'core/v1/event',
|
||||
properties: { handlers: [newHandler] },
|
||||
@ -62,7 +59,7 @@ export const EventTraitForm: React.FC<Props> = props => {
|
||||
const index = component.traits.findIndex(t => t.type === 'core/v1/event');
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyTraitPropertiesLeafOperation({
|
||||
genOperation('modifyTraitProperty', {
|
||||
componentId: component.id,
|
||||
traitIndex: index,
|
||||
properties: [...handlers, newHandler],
|
||||
@ -80,7 +77,7 @@ export const EventTraitForm: React.FC<Props> = props => {
|
||||
});
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyTraitPropertiesLeafOperation({
|
||||
genOperation('modifyTraitProperty', {
|
||||
componentId: component.id,
|
||||
traitIndex: index,
|
||||
properties: {
|
||||
@ -97,7 +94,7 @@ export const EventTraitForm: React.FC<Props> = props => {
|
||||
});
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyTraitPropertiesLeafOperation({
|
||||
genOperation('modifyTraitProperty', {
|
||||
componentId: component.id,
|
||||
traitIndex: index,
|
||||
properties: {
|
||||
|
@ -19,10 +19,7 @@ import { KeyValueEditor } from '../../KeyValueEditor';
|
||||
import { EventHandlerForm } from '../EventTraitForm/EventHandlerForm';
|
||||
import { eventBus } from '../../../eventBus';
|
||||
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
|
||||
import {
|
||||
ModifyTraitPropertiesLeafOperation,
|
||||
RemoveTraitLeafOperation,
|
||||
} from '../../../operations/leaf';
|
||||
import { genOperation } from 'src/operations';
|
||||
|
||||
type EventHandler = Static<typeof EventHandlerSchema>;
|
||||
|
||||
@ -49,7 +46,7 @@ export const FetchTraitForm: React.FC<Props> = props => {
|
||||
const index = component.traits.findIndex(t => t.type === 'core/v1/fetch');
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyTraitPropertiesLeafOperation({
|
||||
genOperation('modifyTraitProperty', {
|
||||
componentId: component.id,
|
||||
traitIndex: index,
|
||||
properties: values,
|
||||
@ -201,7 +198,10 @@ export const FetchTraitForm: React.FC<Props> = props => {
|
||||
const index = component.traits.findIndex(t => t.type === 'core/v1/fetch');
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new RemoveTraitLeafOperation({ componentId: component.id, index })
|
||||
genOperation('removeTrait', {
|
||||
componentId: component.id,
|
||||
index,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
@ -7,10 +7,7 @@ import { GeneralTraitForm } from './GeneralTraitForm';
|
||||
import { eventBus } from '../../../eventBus';
|
||||
import { ignoreTraitsList } from '../../../constants';
|
||||
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
|
||||
import {
|
||||
CreateTraitLeafOperation,
|
||||
RemoveTraitLeafOperation,
|
||||
} from '../../../operations/leaf';
|
||||
import { genOperation } from 'src/operations';
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
@ -25,7 +22,7 @@ export const GeneralTraitFormList: React.FC<Props> = props => {
|
||||
const initProperties = parseTypeBox(traitSpec.properties as TSchema);
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new CreateTraitLeafOperation({
|
||||
genOperation('createTrait', {
|
||||
componentId: component.id,
|
||||
traitType: type,
|
||||
properties: initProperties,
|
||||
@ -34,21 +31,24 @@ export const GeneralTraitFormList: React.FC<Props> = props => {
|
||||
};
|
||||
|
||||
const traitFields = component.traits
|
||||
.filter(t => {
|
||||
return !ignoreTraitsList.includes(t.type);
|
||||
.filter(trait => {
|
||||
return !ignoreTraitsList.includes(trait.type);
|
||||
})
|
||||
.map((t, i) => {
|
||||
.map((trait, index) => {
|
||||
const onRemoveTrait = () => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new RemoveTraitLeafOperation({ componentId: component.id, index: i })
|
||||
genOperation('removeTrait', {
|
||||
componentId: component.id,
|
||||
index,
|
||||
})
|
||||
);
|
||||
};
|
||||
return (
|
||||
<GeneralTraitForm
|
||||
key={i}
|
||||
key={index}
|
||||
component={component}
|
||||
trait={t}
|
||||
trait={trait}
|
||||
onRemove={onRemoveTrait}
|
||||
registry={registry}
|
||||
/>
|
||||
|
@ -13,12 +13,8 @@ import { KeyboardEventWrapper } from './KeyboardEventWrapper';
|
||||
import { ComponentWrapper } from './ComponentWrapper';
|
||||
import { StateEditor, SchemaEditor } from './CodeEditor';
|
||||
import { Explorer } from './Explorer';
|
||||
import {
|
||||
ModifyComponentPropertiesLeafOperation,
|
||||
ReplaceAppLeafOperation,
|
||||
} from '../operations/leaf';
|
||||
import { CreateComponentBranchOperation } from '../operations/branch';
|
||||
import { editorStore } from '../EditorStore';
|
||||
import { genOperation } from 'src/operations';
|
||||
|
||||
type ReturnOfInit = ReturnType<typeof initSunmaoUI>;
|
||||
|
||||
@ -26,7 +22,6 @@ type Props = {
|
||||
App: ReturnOfInit['App'];
|
||||
registry: ReturnOfInit['registry'];
|
||||
stateStore: ReturnOfInit['stateManager']['store'];
|
||||
apiService: ReturnOfInit['apiService'];
|
||||
};
|
||||
|
||||
export const Editor: React.FC<Props> = observer(
|
||||
@ -38,46 +33,46 @@ export const Editor: React.FC<Props> = observer(
|
||||
const [codeMode, setCodeMode] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
|
||||
const gridCallbacks: GridCallbacks = useMemo(() => {
|
||||
return {
|
||||
// drag an existing component
|
||||
onDragStop(id, layout) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyComponentPropertiesLeafOperation({
|
||||
componentId: id,
|
||||
properties: { layout },
|
||||
})
|
||||
);
|
||||
},
|
||||
// drag a new component from tool box
|
||||
onDrop(id, layout, _, e) {
|
||||
const component = e.dataTransfer?.getData('component') || '';
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new CreateComponentBranchOperation({
|
||||
componentType: component,
|
||||
parentId: id,
|
||||
slot: 'content',
|
||||
layout,
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
const gridCallbacks: GridCallbacks = useMemo(() => {
|
||||
return {
|
||||
// drag an existing component
|
||||
onDragStop(id, layout) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation('modifyComponentProperty', {
|
||||
componentId: id,
|
||||
properties: { layout },
|
||||
})
|
||||
);
|
||||
},
|
||||
// drag a new component from tool box
|
||||
onDrop(id, layout, _, e) {
|
||||
const component = e.dataTransfer?.getData('component') || '';
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation('createComponent', {
|
||||
componentType: component,
|
||||
parentId: id,
|
||||
slot: 'content',
|
||||
layout,
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
const app = useMemo<Application>(() => {
|
||||
return {
|
||||
version: 'sunmao/v1',
|
||||
kind: 'Application',
|
||||
metadata: {
|
||||
name: 'some App',
|
||||
},
|
||||
spec: {
|
||||
components,
|
||||
},
|
||||
};
|
||||
}, [components]);
|
||||
const app = useMemo<Application>(() => {
|
||||
return {
|
||||
version: 'sunmao/v1',
|
||||
kind: 'Application',
|
||||
metadata: {
|
||||
name: 'some App',
|
||||
},
|
||||
spec: {
|
||||
components,
|
||||
},
|
||||
};
|
||||
}, [components]);
|
||||
|
||||
const appComponent = useMemo(() => {
|
||||
return (
|
||||
@ -189,37 +184,41 @@ export const Editor: React.FC<Props> = observer(
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardEventWrapper selectedComponentId={selectedComponentId}>
|
||||
<Box display="flex" height="100%" width="100%" flexDirection="column">
|
||||
<EditorHeader
|
||||
scale={scale}
|
||||
setScale={setScale}
|
||||
onPreview={() => setPreview(true)}
|
||||
codeMode={codeMode}
|
||||
onCodeMode={v => {
|
||||
setCodeMode(v);
|
||||
if (!v && code) {
|
||||
eventBus.send('operation', new ReplaceAppLeafOperation(JSON.parse(code)));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box display="flex" flex="1" overflow="auto">
|
||||
{renderMain()}
|
||||
</Box>
|
||||
return (
|
||||
<KeyboardEventWrapper selectedComponentId={selectedComponentId}>
|
||||
<Box display="flex" height="100%" width="100%" flexDirection="column">
|
||||
<EditorHeader
|
||||
scale={scale}
|
||||
setScale={setScale}
|
||||
onPreview={() => setPreview(true)}
|
||||
codeMode={codeMode}
|
||||
onCodeMode={v => {
|
||||
setCodeMode(v);
|
||||
if (!v && code) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation('replaceApp', {
|
||||
app: JSON.parse(code),
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box display="flex" flex="1" overflow="auto">
|
||||
{renderMain()}
|
||||
</Box>
|
||||
{preview && (
|
||||
<PreviewModal onClose={() => setPreview(false)}>
|
||||
<Box width="100%" height="100%">
|
||||
<App
|
||||
options={JSON.parse(JSON.stringify(app))}
|
||||
debugEvent={false}
|
||||
debugStore={false}
|
||||
/>
|
||||
</Box>
|
||||
</PreviewModal>
|
||||
)}
|
||||
</KeyboardEventWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
</Box>
|
||||
{preview && (
|
||||
<PreviewModal onClose={() => setPreview(false)}>
|
||||
<Box width="100%" height="100%">
|
||||
<App
|
||||
options={JSON.parse(JSON.stringify(app))}
|
||||
debugEvent={false}
|
||||
debugStore={false}
|
||||
/>
|
||||
</Box>
|
||||
</PreviewModal>
|
||||
)}
|
||||
</KeyboardEventWrapper>
|
||||
);
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { css } from '@emotion/react';
|
||||
import React from 'react';
|
||||
import { RemoveComponentBranchOperation } from '../operations/branch';
|
||||
import { eventBus } from '../eventBus';
|
||||
import { genOperation } from 'src/operations';
|
||||
|
||||
type Props = {
|
||||
selectedComponentId: string;
|
||||
@ -25,7 +25,9 @@ export const KeyboardEventWrapper: React.FC<Props> = props => {
|
||||
case 'Backspace':
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new RemoveComponentBranchOperation({ componentId: props.selectedComponentId })
|
||||
genOperation('removeComponent', {
|
||||
componentId: props.selectedComponentId,
|
||||
})
|
||||
);
|
||||
break;
|
||||
case 'z':
|
||||
|
@ -2,15 +2,11 @@ import { Box, Text, VStack } from '@chakra-ui/react';
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
RemoveComponentBranchOperation,
|
||||
CreateComponentBranchOperation,
|
||||
} from '../../operations/branch';
|
||||
import { AdjustComponentOrderLeafOperation } from '../../operations/leaf';
|
||||
import { eventBus } from '../../eventBus';
|
||||
import { ComponentItemView } from './ComponentItemView';
|
||||
import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { ChildrenMap } from './StructureTree';
|
||||
import { genOperation } from 'src/operations';
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
@ -57,7 +53,7 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
const onDrop = (creatingComponent: string) => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new CreateComponentBranchOperation({
|
||||
genOperation('createComponent', {
|
||||
componentType: creatingComponent,
|
||||
parentId: component.id,
|
||||
slot,
|
||||
@ -88,7 +84,9 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
const onClickRemove = () => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new RemoveComponentBranchOperation({ componentId: component.id })
|
||||
genOperation('removeComponent', {
|
||||
componentId: component.id,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@ -96,10 +94,10 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
if (slots.length === 0) return;
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new CreateComponentBranchOperation({
|
||||
genOperation('createComponent', {
|
||||
componentType: creatingComponent,
|
||||
parentId: component.id,
|
||||
slot: 'content',
|
||||
slot: slots[0],
|
||||
})
|
||||
);
|
||||
};
|
||||
@ -107,7 +105,7 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
const onMoveUp = () => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new AdjustComponentOrderLeafOperation({
|
||||
genOperation('adjustComponentOrder', {
|
||||
componentId: component.id,
|
||||
orientation: 'up',
|
||||
})
|
||||
@ -117,7 +115,7 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
const onMoveDown = () => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new AdjustComponentOrderLeafOperation({
|
||||
genOperation('adjustComponentOrder', {
|
||||
componentId: component.id,
|
||||
orientation: 'down',
|
||||
})
|
||||
|
@ -6,10 +6,7 @@ import { ComponentItemView } from './ComponentItemView';
|
||||
import { ComponentTree } from './ComponentTree';
|
||||
import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
|
||||
import {
|
||||
RemoveComponentBranchOperation,
|
||||
CreateComponentBranchOperation,
|
||||
} from '../../operations/branch';
|
||||
import { genOperation as genOperation } from 'src/operations';
|
||||
|
||||
export type ChildrenMap = Map<string, SlotsMap>;
|
||||
type SlotsMap = Map<string, ApplicationComponent[]>;
|
||||
@ -62,7 +59,7 @@ export const StructureTree: React.FC<Props> = props => {
|
||||
const onClickRemove = () => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new RemoveComponentBranchOperation({
|
||||
genOperation('removeComponent', {
|
||||
componentId: dummy.id,
|
||||
})
|
||||
);
|
||||
@ -101,7 +98,7 @@ function RootItem() {
|
||||
const onDrop = (creatingComponent: string) => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new CreateComponentBranchOperation({
|
||||
genOperation('createComponent', {
|
||||
componentType: creatingComponent,
|
||||
})
|
||||
);
|
||||
|
91
packages/editor/src/operations/index.ts
Normal file
91
packages/editor/src/operations/index.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import {
|
||||
CreateComponentBranchOperation,
|
||||
CreateComponentBranchOperationContext,
|
||||
ModifyComponentIdBranchOperation,
|
||||
ModifyComponentIdBranchOperationContext,
|
||||
RemoveComponentBranchOperation,
|
||||
RemoveComponentBranchOperationContext,
|
||||
} from './branch';
|
||||
import {
|
||||
AdjustComponentOrderLeafOperation,
|
||||
AdjustComponentOrderLeafOperationContext,
|
||||
CreateTraitLeafOperation,
|
||||
CreateTraitLeafOperationContext,
|
||||
ModifyComponentPropertiesLeafOperation,
|
||||
ModifyComponentPropertiesLeafOperationContext,
|
||||
ModifyTraitPropertiesLeafOperation,
|
||||
ModifyTraitPropertiesLeafOperationContext,
|
||||
RemoveTraitLeafOperation,
|
||||
RemoveTraitLeafOperationContext,
|
||||
ReplaceAppLeafOperation,
|
||||
ReplaceAppLeafOperationContext,
|
||||
} from './leaf';
|
||||
import { IOperation } from './type';
|
||||
|
||||
const OperationConstructors: Record<
|
||||
OperationTypes,
|
||||
OperationConfigMaps[OperationTypes]['constructor']
|
||||
> = {
|
||||
createComponent: CreateComponentBranchOperation,
|
||||
removeComponent: RemoveComponentBranchOperation,
|
||||
modifyComponentProperty: ModifyComponentPropertiesLeafOperation,
|
||||
modifyComponentId: ModifyComponentIdBranchOperation,
|
||||
adjustComponentOrder: AdjustComponentOrderLeafOperation,
|
||||
createTrait: CreateTraitLeafOperation,
|
||||
removeTrait: RemoveTraitLeafOperation,
|
||||
modifyTraitProperty: ModifyTraitPropertiesLeafOperation,
|
||||
replaceApp: ReplaceAppLeafOperation,
|
||||
};
|
||||
|
||||
type OperationTypes = keyof OperationConfigMaps;
|
||||
|
||||
type OperationConfigMap<TOperation, TContext> = {
|
||||
constructor: new (context: TContext) => TOperation;
|
||||
context: TContext;
|
||||
};
|
||||
|
||||
type OperationConfigMaps = {
|
||||
createComponent: OperationConfigMap<
|
||||
CreateComponentBranchOperation,
|
||||
CreateComponentBranchOperationContext
|
||||
>;
|
||||
removeComponent: OperationConfigMap<
|
||||
RemoveComponentBranchOperation,
|
||||
RemoveComponentBranchOperationContext
|
||||
>;
|
||||
modifyComponentProperty: OperationConfigMap<
|
||||
ModifyComponentPropertiesLeafOperation,
|
||||
ModifyComponentPropertiesLeafOperationContext
|
||||
>;
|
||||
modifyComponentId: OperationConfigMap<
|
||||
ModifyComponentIdBranchOperation,
|
||||
ModifyComponentIdBranchOperationContext
|
||||
>;
|
||||
adjustComponentOrder: OperationConfigMap<
|
||||
AdjustComponentOrderLeafOperation,
|
||||
AdjustComponentOrderLeafOperationContext
|
||||
>;
|
||||
createTrait: OperationConfigMap<
|
||||
CreateTraitLeafOperation,
|
||||
CreateTraitLeafOperationContext
|
||||
>;
|
||||
removeTrait: OperationConfigMap<
|
||||
RemoveTraitLeafOperation,
|
||||
RemoveTraitLeafOperationContext
|
||||
>;
|
||||
modifyTraitProperty: OperationConfigMap<
|
||||
ModifyTraitPropertiesLeafOperation,
|
||||
ModifyTraitPropertiesLeafOperationContext
|
||||
>;
|
||||
replaceApp: OperationConfigMap<ReplaceAppLeafOperation, ReplaceAppLeafOperationContext>;
|
||||
};
|
||||
|
||||
export const genOperation = <T extends OperationTypes>(
|
||||
type: T,
|
||||
context: OperationConfigMaps[T]['context']
|
||||
): IOperation => {
|
||||
const OperationConstructor = OperationConstructors[
|
||||
type
|
||||
] as OperationConfigMaps[T]['constructor'];
|
||||
return new OperationConstructor(context as any);
|
||||
};
|
Loading…
Reference in New Issue
Block a user