1
+
import * as React from 'react'
2
+
import { classes } from '../Globals'
3
+
import * as Navigator from '../Navigator'
4
+
import { TypeContext } from '../TypeContext'
5
+
import { ModifiableEntity, Lite, Entity, EntityControlMessage, getToString, isLite } from '../Signum.Entities'
6
+
import { EntityBaseController } from './EntityBase'
7
+
import { EntityListBaseController, EntityListBaseProps, DragConfig, MoveConfig } from './EntityListBase'
8
+
import { RenderEntity } from './RenderEntity'
9
+
import { newMListElement } from '../Signum.Entities';
10
+
import { tryGetTypeInfos, getTypeInfo } from '../Reflection';
11
+
import { useController } from './LineBase'
12
+
import { TypeBadge } from './AutoCompleteConfig'
13
+
import { Accordion } from 'react-bootstrap'
14
+
import { useForceUpdate } from '../Hooks'
15
+
import { AccordionEventKey } from 'react-bootstrap/esm/AccordionContext'
16
+
17
+
export interface EntityAccordionProps extends EntityListBaseProps {
18
+
createAsLink?: boolean | ((er: EntityAccordionController) => React.ReactElement<any>);
19
+
avoidFieldSet?: boolean;
20
+
createMessage?: string;
21
+
getTitle?: (ctx: TypeContext<any /*T*/>) => React.ReactChild;
22
+
itemExtraButtons?: (er: EntityListBaseController<EntityListBaseProps>, index: number) => React.ReactElement<any>;
23
+
initialSelectedIndex?: number | null;
24
+
selectedIndex?: number | null;
25
+
onSelectTab?: (newIndex: number | null) => void;
26
+
}
27
+
28
+
function isControlled(p: EntityAccordionProps) {
29
+
30
+
if ((p.selectedIndex !== undefined) != (p.onSelectTab !== undefined))
31
+
throw new Error("selectedIndex and onSelectTab should be set together");
32
+
33
+
return p.selectedIndex != null;
34
+
}
35
+
36
+
export class EntityAccordionController extends EntityListBaseController<EntityAccordionProps> {
37
+
38
+
selectedIndex!: number | null;
39
+
setSelectedIndex!: (index: number | null) => void;
40
+
initialIsControlled!: boolean;
41
+
42
+
init(p: EntityAccordionProps) {
43
+
super.init(p);
44
+
45
+
this.initialIsControlled = React.useMemo(() => isControlled(p), []);
46
+
const currentIsControlled = isControlled(p);
47
+
if (currentIsControlled != this.initialIsControlled)
48
+
throw new Error(`selectedIndex was isControlled=${this.initialIsControlled} but now is ${currentIsControlled}`);
49
+
50
+
if (!this.initialIsControlled) {
51
+
[this.selectedIndex, this.setSelectedIndex] = React.useState<number | null>(p.initialSelectedIndex ?? null);
52
+
} else {
53
+
this.selectedIndex = p.selectedIndex!;
54
+
this.setSelectedIndex = p.onSelectTab!;
55
+
}
56
+
}
57
+
58
+
getDefaultProps(p: EntityAccordionProps) {
59
+
super.getDefaultProps(p);
60
+
p.viewOnCreate = false;
61
+
p.createAsLink = true;
62
+
}
63
+
64
+
addElement(entityOrLite: Lite<Entity> | ModifiableEntity) {
65
+
66
+
if (isLite(entityOrLite) != (this.props.type!.isLite || false))
67
+
throw new Error("entityOrLite should be already converted");
68
+
69
+
const list = this.props.ctx.value!;
70
+
list.push(newMListElement(entityOrLite));
71
+
this.setSelectedIndex(list.length - 1);
72
+
this.setValue(list);
73
+
}
74
+
}
75
+
76
+
77
+
export const EntityAccordion = React.forwardRef(function EntityAccordion(props: EntityAccordionProps, ref: React.Ref<EntityAccordionController>) {
78
+
var c = useController(EntityAccordionController, props, ref);
79
+
var p = c.props;
80
+
81
+
if (c.isHidden)
82
+
return null;
83
+
84
+
let ctx = p.ctx;
85
+
86
+
if (p.avoidFieldSet == true)
87
+
return (
88
+
<div className={classes("sf-accordion-field sf-control-container", ctx.errorClassBorder)}
89
+
{...{ ...c.baseHtmlAttributes(), ...p.formGroupHtmlAttributes, ...ctx.errorAttributes() }}>
90
+
{renderButtons()}
91
+
{renderAccordion()}
92
+
</div>
93
+
);
94
+
95
+
return (
96
+
<fieldset className={classes("sf-accordion-field sf-control-container", ctx.errorClass)}
97
+
{...{ ...c.baseHtmlAttributes(), ...c.props.formGroupHtmlAttributes, ...ctx.errorAttributes() }}>
98
+
<legend>
99
+
<div>
100
+
<span>{p.labelText}</span>
101
+
{renderButtons()}
102
+
</div>
103
+
</legend>
104
+
{renderAccordion()}
105
+
</fieldset>
106
+
);
107
+
108
+
109
+
function renderButtons() {
110
+
const buttons = (
111
+
<span className="float-end">
112
+
{p.extraButtonsBefore && p.extraButtonsBefore(c)}
113
+
{p.createAsLink == false && c.renderCreateButton(false, p.createMessage)}
114
+
{c.renderFindButton(false)}
115
+
{p.extraButtonsAfter && p.extraButtonsAfter(c)}
116
+
</span>
117
+
);
118
+
119
+
return EntityBaseController.hasChildrens(buttons) ? buttons : undefined;
120
+
}
121
+
122
+
function handleSelectTab(eventKey: AccordionEventKey | null) {
123
+
var num = eventKey == null ? null: parseInt(eventKey as string);
124
+
c.setSelectedIndex(num);
125
+
}
126
+
127
+
function renderAccordion() {
128
+
const readOnly = ctx.readOnly;
129
+
const showType = tryGetTypeInfos(ctx.propertyRoute!.typeReference().name).length > 1;
130
+
return (
131
+
<Accordion className="sf-accordion-elements" activeKey={c.selectedIndex?.toString()} onSelect={handleSelectTab}>
132
+
{
133
+
c.getMListItemContext(ctx).map((mlec, i) => (
134
+
<EntityAccordionElement key={c.keyGenerator.getKey(mlec.value)}
135
+
onRemove={c.canRemove(mlec.value) && !readOnly ? e => c.handleRemoveElementClick(e, mlec.index!) : undefined}
136
+
ctx={mlec}
137
+
move={c.canMove(mlec.value) && p.moveMode == "MoveIcons" && !readOnly ? c.getMoveConfig(false, mlec.index!, "v") : undefined}
138
+
drag={c.canMove(mlec.value) && p.moveMode == "DragIcon" && !readOnly ? c.getDragConfig(mlec.index!, "v") : undefined}
139
+
itemExtraButtons={p.itemExtraButtons ? (() => p.itemExtraButtons!(c, mlec.index!)) : undefined}
140
+
getComponent={p.getComponent}
141
+
getViewPromise={p.getViewPromise}
142
+
getTitle={p.getTitle}
143
+
title={showType ? <TypeBadge entity={mlec.value} /> : undefined} />))
144
+
}
145
+
{
146
+
p.createAsLink && p.create && !readOnly &&
147
+
(typeof p.createAsLink == "function" ? p.createAsLink(c) :
148
+
<a href="#" title={ctx.titleLabels ? EntityControlMessage.Create.niceToString() : undefined}
149
+
className="sf-line-button sf-create"
150
+
onClick={c.handleCreateClick}>
151
+
{EntityBaseController.createIcon} {p.createMessage ?? EntityControlMessage.Create.niceToString()}
152
+
</a>)
153
+
}
154
+
</Accordion>
155
+
);
156
+
}
157
+
});
158
+
159
+
160
+
export interface EntityAccordionElementProps {
161
+
ctx: TypeContext<Lite<Entity> | ModifiableEntity>;
162
+
getComponent?: (ctx: TypeContext<ModifiableEntity>) => React.ReactElement<any>;
163
+
getViewPromise?: (entity: ModifiableEntity) => undefined | string | Navigator.ViewPromise<ModifiableEntity>;
164
+
getTitle?: (ctx: TypeContext<any /*T*/>) => React.ReactChild;
165
+
onRemove?: (event: React.MouseEvent<any>) => void;
166
+
move?: MoveConfig;
167
+
drag?: DragConfig;
168
+
title?: React.ReactElement<any>;
169
+
itemExtraButtons?: () => React.ReactElement<any>;
170
+
}
171
+
172
+
export function EntityAccordionElement({ ctx, getComponent, getViewPromise, onRemove, move, drag, itemExtraButtons, title, getTitle }: EntityAccordionElementProps)
173
+
{
174
+
175
+
const forceUpdate = useForceUpdate();
176
+
177
+
return (
178
+
<Accordion.Item className={classes(drag?.dropClass, "sf-accordion-element")} eventKey={ctx.index!.toString()}
179
+
onDragEnter={drag?.onDragOver}
180
+
onDragOver={drag?.onDragOver}
181
+
onDrop={drag?.onDrop}>
182
+
183
+
<Accordion.Header {...EntityListBaseController.entityHtmlAttributes(ctx.value)}>
184
+
<div className="d-flex align-items-center flex-grow-1">
185
+
{onRemove && <a href="#" className={classes("sf-line-button", "sf-remove")}
186
+
onClick={onRemove}
187
+
title={ctx.titleLabels ? EntityControlMessage.Remove.niceToString() : undefined}>
188
+
{EntityListBaseController.removeIcon}
189
+
</a>}
190
+
191
+
{move?.renderMoveUp()}
192
+
{move?.renderMoveDown()}
193
+
{drag && <a href="#" className={classes("sf-line-button", "sf-move")}
194
+
draggable={true}
195
+
onDragStart={drag.onDragStart}
196
+
onDragEnd={drag.onDragEnd}
197
+
onKeyDown={drag.onKeyDown}
198
+
title={drag.title}>
199
+
{EntityListBaseController.moveIcon}
200
+
</a>}
201
+
{itemExtraButtons && itemExtraButtons()}
202
+
{'\xa0'}
203
+
{getTitle ? getTitle(ctx) : getToString(ctx.value)}
204
+
</div>
205
+
</Accordion.Header>
206
+
<Accordion.Body>
207
+
<RenderEntity ctx={ctx} getComponent={getComponent} getViewPromise={getViewPromise} onRefresh={forceUpdate} />
208
+
</Accordion.Body>
209
+
</Accordion.Item>
210
+
);
211
+
}
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4