@fleetbase/ember-ui 0.3.25 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/components/event-calendar.hbs +30 -0
- package/addon/components/event-calendar.js +333 -0
- package/addon/components/modal/default.hbs +81 -2
- package/addon/components/resource-context-panel.js +1 -0
- package/addon/modifiers/constrain-view-section-width.js +57 -0
- package/addon/services/resource-context-panel.js +1 -1
- package/addon/utils/is-menu-item-active.js +4 -0
- package/app/components/event-calendar.js +1 -0
- package/app/modifiers/constrain-view-section-width.js +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="fleetbase-event-calendar"
|
|
3
|
+
...attributes
|
|
4
|
+
{{did-insert this.setup}}
|
|
5
|
+
{{did-update this.update
|
|
6
|
+
@view
|
|
7
|
+
@resources
|
|
8
|
+
@events
|
|
9
|
+
@editable
|
|
10
|
+
@droppable
|
|
11
|
+
@selectable
|
|
12
|
+
@slotMinTime
|
|
13
|
+
@slotMaxTime
|
|
14
|
+
@slotDuration
|
|
15
|
+
@slotLabelInterval
|
|
16
|
+
@slotWidth
|
|
17
|
+
@height
|
|
18
|
+
@headerToolbar
|
|
19
|
+
@locale
|
|
20
|
+
@scrollTime
|
|
21
|
+
@nowIndicator
|
|
22
|
+
@date
|
|
23
|
+
@eventContent
|
|
24
|
+
@resourceLabelContent
|
|
25
|
+
@dayCellContent
|
|
26
|
+
@slotLabelContent
|
|
27
|
+
@options
|
|
28
|
+
}}
|
|
29
|
+
{{will-destroy this.teardown}}
|
|
30
|
+
></div>
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action } from '@ember/object';
|
|
4
|
+
import { scheduleOnce } from '@ember/runloop';
|
|
5
|
+
import { createCalendar, destroyCalendar, ResourceTimeline, ResourceTimeGrid, TimeGrid, DayGrid, List, Interaction } from '@event-calendar/core';
|
|
6
|
+
import '@event-calendar/core/index.css';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* EventCalendar component wrapping @event-calendar/core (MIT licensed).
|
|
10
|
+
*
|
|
11
|
+
* This is the preferred calendar component for resource-timeline views in
|
|
12
|
+
* Fleetbase. It replaces the need for FullCalendar Premium plugins
|
|
13
|
+
* (@fullcalendar/resource-timeline etc.) which carry a commercial license
|
|
14
|
+
* incompatible with Fleetbase's dual AGPL v3 / commercial licensing model.
|
|
15
|
+
*
|
|
16
|
+
* @see https://github.com/vkurko/calendar
|
|
17
|
+
*
|
|
18
|
+
* Supported views (all MIT, no license key required):
|
|
19
|
+
* - resourceTimelineDay / resourceTimelineWeek / resourceTimelineMonth
|
|
20
|
+
* - resourceTimeGridDay / resourceTimeGridWeek
|
|
21
|
+
* - timeGridDay / timeGridWeek
|
|
22
|
+
* - dayGridMonth / dayGridWeek / dayGridDay
|
|
23
|
+
* - listDay / listWeek / listMonth / listYear
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* ```hbs
|
|
27
|
+
* <EventCalendar
|
|
28
|
+
* @view="resourceTimelineDay"
|
|
29
|
+
* @resources={{this.calendarResources}}
|
|
30
|
+
* @events={{this.calendarEvents}}
|
|
31
|
+
* @editable={{true}}
|
|
32
|
+
* @droppable={{true}}
|
|
33
|
+
* @onEventDrop={{this.handleEventDrop}}
|
|
34
|
+
* @onEventReceive={{this.handleEventReceive}}
|
|
35
|
+
* @onEventClick={{this.handleEventClick}}
|
|
36
|
+
* @onDateClick={{this.handleDateClick}}
|
|
37
|
+
* @resourceLabelContent={{this.renderResourceLabel}}
|
|
38
|
+
* @eventContent={{this.renderEventContent}}
|
|
39
|
+
* @onCalendarReady={{this.onCalendarReady}}
|
|
40
|
+
* @options={{this.extraCalendarOptions}}
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* The `@options` arg is merged last, allowing full override of any option.
|
|
45
|
+
*
|
|
46
|
+
* Callback args:
|
|
47
|
+
* @onEventDrop(info) — info.event, info.oldResource, info.newResource, info.revert
|
|
48
|
+
* @onEventReceive(info) — info.event, info.revert (external drop)
|
|
49
|
+
* @onEventClick(info) — info.event, info.el, info.jsEvent
|
|
50
|
+
* @onDateClick(info) — info.date, info.resource, info.jsEvent
|
|
51
|
+
* @onEventResize(info) — info.event, info.revert
|
|
52
|
+
* @onEventMouseEnter(info) — info.event, info.el, info.jsEvent
|
|
53
|
+
* @onEventMouseLeave(info) — info.event, info.el, info.jsEvent
|
|
54
|
+
* @onDatesSet(info) — info.start, info.end, info.view
|
|
55
|
+
* @onLoading(isLoading) — boolean
|
|
56
|
+
*
|
|
57
|
+
* Render hook args (no 'on' prefix — these return content descriptors):
|
|
58
|
+
* @eventContent — function(info) returning { html } or { domNodes }
|
|
59
|
+
* @resourceLabelContent — function(info) returning { html } or { domNodes }
|
|
60
|
+
* @dayCellContent — function(info) returning { html } or { domNodes }
|
|
61
|
+
* @slotLabelContent — function(info) returning { html } or { domNodes }
|
|
62
|
+
*/
|
|
63
|
+
export default class EventCalendarComponent extends Component {
|
|
64
|
+
/**
|
|
65
|
+
* All plugins enabled by default. Consumers can override via @plugins.
|
|
66
|
+
* @type {Array}
|
|
67
|
+
*/
|
|
68
|
+
defaultPlugins = [ResourceTimeline, ResourceTimeGrid, TimeGrid, DayGrid, List, Interaction];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Reference to the DOM element the calendar is mounted on.
|
|
72
|
+
* @type {HTMLElement}
|
|
73
|
+
*/
|
|
74
|
+
@tracked calendarEl = null;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The EventCalendar instance returned by createCalendar().
|
|
78
|
+
* Exposes .setOption(name, value) and .getOption(name).
|
|
79
|
+
* @type {Object}
|
|
80
|
+
*/
|
|
81
|
+
@tracked calendar = null;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Callback arg names that map to @event-calendar/core event options.
|
|
85
|
+
* Each entry is the raw option name; the corresponding @arg is prefixed
|
|
86
|
+
* with 'on' (e.g. 'eventDrop' → @onEventDrop).
|
|
87
|
+
* @type {string[]}
|
|
88
|
+
*/
|
|
89
|
+
callbackOptions = [
|
|
90
|
+
'eventClick',
|
|
91
|
+
'eventDrop',
|
|
92
|
+
'eventResize',
|
|
93
|
+
'eventReceive',
|
|
94
|
+
'eventLeave',
|
|
95
|
+
'eventMouseEnter',
|
|
96
|
+
'eventMouseLeave',
|
|
97
|
+
'dateClick',
|
|
98
|
+
'datesSet',
|
|
99
|
+
'loading',
|
|
100
|
+
'viewDidMount',
|
|
101
|
+
'eventDidMount',
|
|
102
|
+
'eventWillUnmount',
|
|
103
|
+
'select',
|
|
104
|
+
'unselect',
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Render hook option names. These are passed directly (no 'on' prefix)
|
|
109
|
+
* because they return content descriptors, not fire-and-forget callbacks.
|
|
110
|
+
* @type {string[]}
|
|
111
|
+
*/
|
|
112
|
+
renderHooks = ['eventContent', 'resourceLabelContent', 'resourceLabelDidMount', 'dayCellContent', 'dayCellDidMount', 'slotLabelContent', 'slotLabelDidMount', 'nowIndicatorContent'];
|
|
113
|
+
|
|
114
|
+
// -------------------------------------------------------------------------
|
|
115
|
+
// Lifecycle
|
|
116
|
+
// -------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Initialises the EventCalendar instance on the container element.
|
|
120
|
+
* Called via {{did-insert this.setup}} in the template.
|
|
121
|
+
*
|
|
122
|
+
* @param {HTMLElement} el
|
|
123
|
+
*/
|
|
124
|
+
@action setup(el) {
|
|
125
|
+
this.calendarEl = el;
|
|
126
|
+
const plugins = this.args.plugins ?? this.defaultPlugins;
|
|
127
|
+
const options = this._buildOptions();
|
|
128
|
+
this.calendar = createCalendar(el, plugins, options);
|
|
129
|
+
|
|
130
|
+
if (typeof this.args.onCalendarReady === 'function') {
|
|
131
|
+
this.args.onCalendarReady(this.calendar);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Responds to tracked arg changes and updates the calendar options.
|
|
137
|
+
* Called via {{did-update this.update ...watchedArgs}} in the template.
|
|
138
|
+
*/
|
|
139
|
+
@action update() {
|
|
140
|
+
if (!this.calendar) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
scheduleOnce('afterRender', this, this._applyDynamicOptions);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Destroys the EventCalendar instance when the component is torn down.
|
|
148
|
+
* Called via {{will-destroy this.teardown}} in the template.
|
|
149
|
+
*/
|
|
150
|
+
@action teardown() {
|
|
151
|
+
if (this.calendar) {
|
|
152
|
+
destroyCalendar(this.calendar);
|
|
153
|
+
}
|
|
154
|
+
this.calendar = null;
|
|
155
|
+
this.calendarEl = null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// -------------------------------------------------------------------------
|
|
159
|
+
// Public API helpers (callable by parent via @onCalendarReady)
|
|
160
|
+
// -------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Programmatically change the view type.
|
|
164
|
+
* @param {string} viewName e.g. 'resourceTimelineWeek'
|
|
165
|
+
*/
|
|
166
|
+
@action changeView(viewName) {
|
|
167
|
+
this._setOption('view', viewName);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Navigate the calendar to today.
|
|
172
|
+
*/
|
|
173
|
+
@action today() {
|
|
174
|
+
this._setOption('date', new Date());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Refetch events from the events source.
|
|
179
|
+
*/
|
|
180
|
+
@action refetchEvents() {
|
|
181
|
+
this._setOption('events', this.args.events ?? []);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Refetch resources from the resources source.
|
|
186
|
+
*/
|
|
187
|
+
@action refetchResources() {
|
|
188
|
+
this._setOption('resources', this.args.resources ?? []);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// -------------------------------------------------------------------------
|
|
192
|
+
// Private helpers
|
|
193
|
+
// -------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Build the full options object passed to createCalendar().
|
|
197
|
+
* @returns {Object}
|
|
198
|
+
*/
|
|
199
|
+
_buildOptions() {
|
|
200
|
+
const {
|
|
201
|
+
view,
|
|
202
|
+
resources,
|
|
203
|
+
events,
|
|
204
|
+
editable,
|
|
205
|
+
droppable,
|
|
206
|
+
selectable,
|
|
207
|
+
nowIndicator,
|
|
208
|
+
slotMinTime,
|
|
209
|
+
slotMaxTime,
|
|
210
|
+
slotDuration,
|
|
211
|
+
slotLabelInterval,
|
|
212
|
+
slotWidth,
|
|
213
|
+
firstDay,
|
|
214
|
+
height,
|
|
215
|
+
headerToolbar,
|
|
216
|
+
locale,
|
|
217
|
+
scrollTime,
|
|
218
|
+
date,
|
|
219
|
+
options: extraOptions,
|
|
220
|
+
} = this.args;
|
|
221
|
+
|
|
222
|
+
const base = {
|
|
223
|
+
view: view ?? 'resourceTimelineDay',
|
|
224
|
+
resources: resources ?? [],
|
|
225
|
+
events: events ?? [],
|
|
226
|
+
editable: editable !== false,
|
|
227
|
+
droppable: droppable !== false,
|
|
228
|
+
selectable: selectable ?? false,
|
|
229
|
+
nowIndicator: nowIndicator !== false,
|
|
230
|
+
slotMinTime: slotMinTime ?? '00:00:00',
|
|
231
|
+
slotMaxTime: slotMaxTime ?? '24:00:00',
|
|
232
|
+
firstDay: firstDay ?? 0,
|
|
233
|
+
height: height ?? '100%',
|
|
234
|
+
headerToolbar: headerToolbar ?? {
|
|
235
|
+
start: 'prev,next today',
|
|
236
|
+
center: 'title',
|
|
237
|
+
end: 'resourceTimelineDay,resourceTimelineWeek',
|
|
238
|
+
},
|
|
239
|
+
locale: locale ?? 'en',
|
|
240
|
+
scrollTime: scrollTime ?? '06:00:00',
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
if (slotDuration !== undefined) base.slotDuration = slotDuration;
|
|
244
|
+
if (slotLabelInterval !== undefined) base.slotLabelInterval = slotLabelInterval;
|
|
245
|
+
if (slotWidth !== undefined) base.slotWidth = slotWidth;
|
|
246
|
+
if (date !== undefined) base.date = date;
|
|
247
|
+
|
|
248
|
+
// Wire up callback args (@onEventDrop → eventDrop option)
|
|
249
|
+
for (const name of this.callbackOptions) {
|
|
250
|
+
const argName = `on${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
251
|
+
if (typeof this.args[argName] === 'function') {
|
|
252
|
+
base[name] = this.args[argName];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Wire up render hooks (passed directly, no 'on' prefix)
|
|
257
|
+
for (const hook of this.renderHooks) {
|
|
258
|
+
if (typeof this.args[hook] === 'function') {
|
|
259
|
+
base[hook] = this.args[hook];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Merge extra options last — allows full override of any option
|
|
264
|
+
return { ...base, ...(extraOptions ?? {}) };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Re-apply all dynamic options to the live calendar instance.
|
|
269
|
+
* Batched via scheduleOnce('afterRender') to coalesce multiple arg changes.
|
|
270
|
+
*/
|
|
271
|
+
_applyDynamicOptions() {
|
|
272
|
+
if (!this.calendar) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const dynamicKeys = [
|
|
277
|
+
'view',
|
|
278
|
+
'resources',
|
|
279
|
+
'events',
|
|
280
|
+
'editable',
|
|
281
|
+
'droppable',
|
|
282
|
+
'selectable',
|
|
283
|
+
'slotMinTime',
|
|
284
|
+
'slotMaxTime',
|
|
285
|
+
'slotDuration',
|
|
286
|
+
'height',
|
|
287
|
+
'headerToolbar',
|
|
288
|
+
'locale',
|
|
289
|
+
'scrollTime',
|
|
290
|
+
'nowIndicator',
|
|
291
|
+
'date',
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
for (const key of dynamicKeys) {
|
|
295
|
+
if (this.args[key] !== undefined) {
|
|
296
|
+
this._setOption(key, this.args[key]);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Re-wire render hooks in case they changed
|
|
301
|
+
for (const hook of this.renderHooks) {
|
|
302
|
+
if (typeof this.args[hook] === 'function') {
|
|
303
|
+
this._setOption(hook, this.args[hook]);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Re-wire callback options in case they changed
|
|
308
|
+
for (const name of this.callbackOptions) {
|
|
309
|
+
const argName = `on${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
310
|
+
if (typeof this.args[argName] === 'function') {
|
|
311
|
+
this._setOption(name, this.args[argName]);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Merge any extra options override
|
|
316
|
+
if (this.args.options) {
|
|
317
|
+
for (const [key, value] of Object.entries(this.args.options)) {
|
|
318
|
+
this._setOption(key, value);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Safely call calendar.setOption().
|
|
325
|
+
* @param {string} key
|
|
326
|
+
* @param {*} value
|
|
327
|
+
*/
|
|
328
|
+
_setOption(key, value) {
|
|
329
|
+
if (this.calendar && typeof this.calendar.setOption === 'function') {
|
|
330
|
+
this.calendar.setOption(key, value);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
{{component @options.footerComponent options=@options confirm=modal.submit modal=modal}}
|
|
34
34
|
{{else}}
|
|
35
35
|
{{#unless @options.hideFooterActions}}
|
|
36
|
-
<div class="modal-footer-actions {{@options.modalFooterActionsClass}}">
|
|
36
|
+
<div class="modal-footer-actions space-x-2 {{@options.modalFooterActionsClass}}">
|
|
37
37
|
<Button
|
|
38
|
-
class="
|
|
38
|
+
class="{{if @options.hideDeclineButton 'hidden'}}"
|
|
39
39
|
@type={{or @options.declineButtonScheme @options.declineButtonType "default"}}
|
|
40
40
|
@size={{or @options.buttonSize "md"}}
|
|
41
41
|
@icon={{or @options.declineButtonIcon "times"}}
|
|
@@ -44,6 +44,85 @@
|
|
|
44
44
|
@onClick={{modal.close}}
|
|
45
45
|
@disabled={{or @options.declineButtonDisabled @options.isLoading}}
|
|
46
46
|
/>
|
|
47
|
+
{{#each @options.actionButtons as |actionButton|}}
|
|
48
|
+
{{#if actionButton.items}}
|
|
49
|
+
<DropdownButton
|
|
50
|
+
@icon={{or actionButton.icon "ellipsis"}}
|
|
51
|
+
@size={{or actionButton.size "md"}}
|
|
52
|
+
@iconPrefix={{actionButton.prefix}}
|
|
53
|
+
@triggerClass={{actionButton.triggerClass}}
|
|
54
|
+
@renderInPlace={{or actionButton.renderInPlace true}}
|
|
55
|
+
as |dd|
|
|
56
|
+
>
|
|
57
|
+
<div class="next-dd-menu mt-1 mx-0" aria-labelledby="user-menu">
|
|
58
|
+
{{#each actionButton.items as |item|}}
|
|
59
|
+
{{#if item.separator}}
|
|
60
|
+
<div class="next-dd-menu-seperator"></div>
|
|
61
|
+
{{else}}
|
|
62
|
+
<div class="px-1 {{item.wrapperClass}}">
|
|
63
|
+
<a
|
|
64
|
+
href="javascript:;"
|
|
65
|
+
class="next-dd-item {{item.class}} {{if item.disabled 'disabled'}}"
|
|
66
|
+
disabled={{item.disabled}}
|
|
67
|
+
{{on "click" (dropdown-fn dd (or item.fn item.onClick))}}
|
|
68
|
+
>
|
|
69
|
+
<div class="w-7 flex-grow-0 flex-shrink-0">
|
|
70
|
+
<FaIcon @icon={{item.icon}} />
|
|
71
|
+
</div>
|
|
72
|
+
<span>{{or item.text item.label}}</span>
|
|
73
|
+
</a>
|
|
74
|
+
</div>
|
|
75
|
+
{{/if}}
|
|
76
|
+
{{/each}}
|
|
77
|
+
</div>
|
|
78
|
+
</DropdownButton>
|
|
79
|
+
{{else if actionButton.component}}
|
|
80
|
+
{{component
|
|
81
|
+
actionButton.component
|
|
82
|
+
icon=actionButton.icon
|
|
83
|
+
iconPrefix=actionButton.iconPrefix
|
|
84
|
+
iconComponent=actionButton.iconComponent
|
|
85
|
+
size=(or actionButton.size "md")
|
|
86
|
+
text=actionButton.text
|
|
87
|
+
options=actionButton.options
|
|
88
|
+
items=actionButton.items
|
|
89
|
+
permission=actionButton.permission
|
|
90
|
+
isLoading=actionButton.isLoading
|
|
91
|
+
renderInPlace=actionButton.renderInPlace
|
|
92
|
+
triggerClass=actionButton.triggerClass
|
|
93
|
+
disabled=actionButton.disabled
|
|
94
|
+
onClick=actionButton.onClick
|
|
95
|
+
fn=actionButton.fn
|
|
96
|
+
perform=actionButton.perform
|
|
97
|
+
onSelect=actionButton.onSelect
|
|
98
|
+
onChange=actionButton.onChange
|
|
99
|
+
}}
|
|
100
|
+
{{else}}
|
|
101
|
+
<Button
|
|
102
|
+
@type={{actionButton.type}}
|
|
103
|
+
@text={{actionButton.text}}
|
|
104
|
+
@onClick={{if actionButton.perform (perform actionButton.perform) (or actionButton.onClick actionButton.fn)}}
|
|
105
|
+
@size={{or actionButton.size "md"}}
|
|
106
|
+
@buttonType={{actionButton.buttonType}}
|
|
107
|
+
@disabled={{actionButton.disabled}}
|
|
108
|
+
@exampleText={{actionButton.exampleText}}
|
|
109
|
+
@helpText={{actionButton.helpText}}
|
|
110
|
+
@icon={{actionButton.icon}}
|
|
111
|
+
@iconClass={{actionButton.iconClass}}
|
|
112
|
+
@iconFlip={{actionButton.iconFlip}}
|
|
113
|
+
@iconPrefix={{actionButton.iconPrefix}}
|
|
114
|
+
@iconRotation={{actionButton.iconRotation}}
|
|
115
|
+
@iconSize={{actionButton.iconSize}}
|
|
116
|
+
@isLoading={{actionButton.isLoading}}
|
|
117
|
+
@outline={{actionButton.outline}}
|
|
118
|
+
@permission={{actionButton.permission}}
|
|
119
|
+
@wrapperClass={{actionButton.wrapperClass}}
|
|
120
|
+
@textClass={{actionButton.textClass}}
|
|
121
|
+
@tooltipPlacement={{actionButton.tooltipPlacement}}
|
|
122
|
+
@visible={{actionButton.visible}}
|
|
123
|
+
/>
|
|
124
|
+
{{/if}}
|
|
125
|
+
{{/each}}
|
|
47
126
|
<Button
|
|
48
127
|
class="{{if @options.hideAcceptButton 'hidden'}}"
|
|
49
128
|
@type={{or @options.acceptButtonScheme @options.acceptButtonType "primary"}}
|
|
@@ -78,6 +78,7 @@ export default class ResourceContextPanelComponent extends Component {
|
|
|
78
78
|
resource,
|
|
79
79
|
component: tab.component ?? tab.render,
|
|
80
80
|
model: resource,
|
|
81
|
+
key: tab.key ?? tab.id ?? dasherize(tab.label ?? tab.title),
|
|
81
82
|
id: tab.id ?? tab.key ?? dasherize(tab.label ?? tab.title),
|
|
82
83
|
label: tab.label ?? tab.title,
|
|
83
84
|
icon: tab.icon,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { modifier } from 'ember-modifier';
|
|
2
|
+
|
|
3
|
+
const SIDEBAR_SELECTOR = 'nav.next-sidebar';
|
|
4
|
+
const SECTION_SELECTOR = 'section.next-view-section';
|
|
5
|
+
const TABLIST_SELECTOR = '[role="tablist"]';
|
|
6
|
+
|
|
7
|
+
function getSidebarWidth() {
|
|
8
|
+
return document.querySelector(SIDEBAR_SELECTOR)?.offsetWidth ?? 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function updateTablistMaxWidth(container, sidebarWidth) {
|
|
12
|
+
const tablist = container.querySelector(TABLIST_SELECTOR);
|
|
13
|
+
|
|
14
|
+
if (!tablist) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const pageWidth = document.body.offsetWidth;
|
|
19
|
+
tablist.style.maxWidth = `${Math.max(0, pageWidth - sidebarWidth)}px`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function updateSectionWidth(sidebarWidth) {
|
|
23
|
+
const section = document.querySelector(SECTION_SELECTOR);
|
|
24
|
+
|
|
25
|
+
if (!section) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
section.style.width = `calc(100vw - ${sidebarWidth}px)`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function updateLayout(container) {
|
|
33
|
+
const sidebarWidth = getSidebarWidth();
|
|
34
|
+
|
|
35
|
+
updateTablistMaxWidth(container, sidebarWidth);
|
|
36
|
+
updateSectionWidth(sidebarWidth);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default modifier(function constrainViewSectionWidth(element) {
|
|
40
|
+
const applyLayout = () => updateLayout(element);
|
|
41
|
+
|
|
42
|
+
applyLayout();
|
|
43
|
+
|
|
44
|
+
window.addEventListener('resize', applyLayout);
|
|
45
|
+
|
|
46
|
+
const sidebar = document.querySelector(SIDEBAR_SELECTOR);
|
|
47
|
+
const observer = new ResizeObserver(applyLayout);
|
|
48
|
+
|
|
49
|
+
if (sidebar) {
|
|
50
|
+
observer.observe(sidebar);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
window.removeEventListener('resize', applyLayout);
|
|
55
|
+
observer.disconnect();
|
|
56
|
+
};
|
|
57
|
+
});
|
|
@@ -199,7 +199,7 @@ export default class ResourceContextPanelService extends Service {
|
|
|
199
199
|
throw new Error(`Overlay with ID ${id} does not have tabs`);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
const tab = overlay.tabs.find((t) => t.key === tabKey);
|
|
202
|
+
const tab = overlay.tabs.find((t) => t.key === tabKey || t.id === tabKey);
|
|
203
203
|
if (!tab) {
|
|
204
204
|
throw new Error(`Tab with key ${tabKey} not found in overlay ${id}`);
|
|
205
205
|
}
|
|
@@ -4,6 +4,10 @@ import getUrlParam from './get-url-param';
|
|
|
4
4
|
export default function isMenuItemActive(section, slug, view = null) {
|
|
5
5
|
let path = window.location.pathname;
|
|
6
6
|
let segments = path.replace(/^\/|\/$/g, '').split('/');
|
|
7
|
+
// Hack for now for fleet-ops until we can refactor the menu system to be more route-aware and not rely on URL parsing
|
|
8
|
+
if (segments.length === 3 && segments[0] === 'fleet-ops') {
|
|
9
|
+
segments = segments.slice(1);
|
|
10
|
+
}
|
|
7
11
|
let sectionMatch = segments[0] === section;
|
|
8
12
|
let slugOnly = segments[0] === slug && section === slug && view === null;
|
|
9
13
|
let slugMatch = segments.includes(slug);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/ember-ui/components/event-calendar';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/ember-ui/modifiers/constrain-view-section-width';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/ember-ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
4
4
|
"description": "Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fleetbase-ui",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@ember/string": "^3.0.1",
|
|
40
40
|
"@embroider/addon": "^0.30.0",
|
|
41
41
|
"@embroider/macros": "^1.8.3",
|
|
42
|
+
"@event-calendar/core": "^5.6.0",
|
|
42
43
|
"@fleetbase/ember-accounting": "^0.0.1",
|
|
43
44
|
"@floating-ui/dom": "^1.0.1",
|
|
44
45
|
"@fortawesome/ember-fontawesome": "^2.0.0",
|
|
@@ -106,6 +107,7 @@
|
|
|
106
107
|
"ember-wormhole": "^0.6.0",
|
|
107
108
|
"gridstack": "^7.3.0",
|
|
108
109
|
"imask": "^6.4.3",
|
|
110
|
+
"interactjs": "^1.10.27",
|
|
109
111
|
"intl-tel-input": "^22.0.2",
|
|
110
112
|
"leaflet": "^1.9.4",
|
|
111
113
|
"postcss-at-rules-variables": "^0.3.0",
|
|
@@ -114,8 +116,7 @@
|
|
|
114
116
|
"postcss-import": "^15.1.0",
|
|
115
117
|
"postcss-mixins": "^9.0.4",
|
|
116
118
|
"postcss-preset-env": "^9.1.1",
|
|
117
|
-
"tailwindcss": "^3.1.8"
|
|
118
|
-
"interactjs": "^1.10.27"
|
|
119
|
+
"tailwindcss": "^3.1.8"
|
|
119
120
|
},
|
|
120
121
|
"devDependencies": {
|
|
121
122
|
"@babel/eslint-parser": "^7.22.15",
|