Skip to content

Commit

Permalink
raidboss: Add Option to Order Timeline Bottom-to-Top (#5869)
Browse files Browse the repository at this point in the history
This is #4811 with conflicts resolved due to refactoring and
3ae3f6c on top.
3ae3f6c moves the keep alive logic from
html_timeline_ui to timeline so that it knows about how many bars are
still on screen and also knows when a bar has been expired and it should
consider adding more to the list.

Closes #4811.

---------

Co-authored-by: Panic Stevenson <[email protected]>
  • Loading branch information
quisquous and panicstevenson authored Oct 25, 2023
1 parent b0ac36a commit 35137ba
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 26 deletions.
30 changes: 10 additions & 20 deletions ui/raidboss/html_timeline_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ const computeBackgroundFrom = (element: HTMLElement, classList: string): string
export type ActiveBar = {
bar: TimerBar;
soonTimeout?: number;
expireTimeout?: number;
};

export class HTMLTimelineUI extends TimelineUI {
Expand Down Expand Up @@ -123,6 +122,8 @@ export class HTMLTimelineUI extends TimelineUI {
if (this.timerlist) {
this.timerlist.style.gridTemplateRows =
`repeat(${this.options.MaxNumberOfTimerBars}, min-content)`;
if (this.options.ReverseTimeline)
this.timerlist.classList.add('reversed');
}

this.activeBars = {};
Expand Down Expand Up @@ -197,11 +198,6 @@ export class HTMLTimelineUI extends TimelineUI {
if (activeBar) {
const parentDiv = activeBar.bar.parentNode;
parentDiv?.parentNode?.removeChild(parentDiv);
// Expiry timeout must be cleared so that it will not remove this new bar.
if (activeBar.expireTimeout !== undefined) {
window.clearTimeout(activeBar.expireTimeout);
activeBar.expireTimeout = undefined;
}
// Soon timeout is just an optimization to remove, as it's unnecessary.
if (activeBar.soonTimeout !== undefined) {
window.clearTimeout(activeBar.soonTimeout);
Expand All @@ -220,31 +216,22 @@ export class HTMLTimelineUI extends TimelineUI {
bar.fg = this.barExpiresSoonColor;
}

if (e.sortKey)
div.style.order = e.sortKey.toString();
if (e.sortKey) {
// Invert the order if the timer bars should "grow" in the reverse direction
div.style.order = ((this.options.ReverseTimeline ? -1 : 1) * e.sortKey).toString();
}
this.timerlist?.appendChild(div);
this.activeBars[e.id] = {
bar: bar,
soonTimeout: soonTimeout,
};
}

public override OnRemoveTimer(e: Event, expired: boolean, force = false): void {
public override OnRemoveTimer(e: Event, force: boolean): void {
const activeBar = this.activeBars[e.id];
if (!activeBar)
return;

if (activeBar.expireTimeout !== undefined)
window.clearTimeout(activeBar.expireTimeout);

if (!force && expired && this.options.KeepExpiredTimerBarsForSeconds) {
activeBar.expireTimeout = window.setTimeout(
() => this.OnRemoveTimer(e, false),
this.options.KeepExpiredTimerBarsForSeconds * 1000,
);
return;
}

const div = activeBar.bar.parentNode;
if (!(div instanceof HTMLElement))
throw new UnreachableCode();
Expand Down Expand Up @@ -314,6 +301,9 @@ export class HTMLTimelineUI extends TimelineUI {
this.debugFightTimer.stylefill = 'fill';
this.debugFightTimer.bg = 'transparent';
this.debugFightTimer.fg = 'transparent';
// Align it to the 'first' item in the timeline container
if (this.options.ReverseTimeline)
this.debugElement.classList.add('reversed');
this.debugElement.appendChild(this.debugFightTimer);
}

Expand Down
6 changes: 5 additions & 1 deletion ui/raidboss/raidboss.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,14 @@

#timeline-debug {
position: absolute;
top: 0;
left: calc(100%);
}

.reversed {
position: absolute;
bottom: 0;
}

.autoplay-helper-button {
position: absolute;
top: 0;
Expand Down
8 changes: 8 additions & 0 deletions ui/raidboss/raidboss_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,14 @@ const templateOptions: OptionsTemplate = {
type: 'checkbox',
default: true,
},
{
id: 'ReverseTimeline',
name: {
en: 'Reverse timeline order (bottom-to-top)',
},
type: 'checkbox',
default: false,
},
{
id: 'ShowTimerBarsAtSeconds',
name: {
Expand Down
1 change: 1 addition & 0 deletions ui/raidboss/raidboss_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const defaultRaidbossConfigOptions = {
KeepExpiredTimerBarsForSeconds: 0.7,
BarExpiresSoonSeconds: 6,
MaxNumberOfTimerBars: 6,
ReverseTimeline: false,
DisplayAlarmTextForSeconds: 3,
DisplayAlertTextForSeconds: 3,
DisplayInfoTextForSeconds: 3,
Expand Down
50 changes: 45 additions & 5 deletions ui/raidboss/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class TimelineUI {
/* noop */
}

public OnRemoveTimer(_e: Event, _expired: boolean, _force = false): void {
public OnRemoveTimer(_e: Event, _force = false): void {
/* noop */
}

Expand Down Expand Up @@ -135,6 +135,10 @@ export class Timeline {

protected activeSyncs: Sync[];
private activeEvents: Event[];
private keepAliveEvents: {
event: Event;
timeout: number;
}[];
private activeLastForceJumpSync?: Sync;

public ignores: { [ignoreId: string]: boolean };
Expand Down Expand Up @@ -179,6 +183,8 @@ export class Timeline {
this.activeSyncs = [];
// Sorted by event occurrence time.
this.activeEvents = [];
// Events that are no longer active but we are keeping on screen briefly.
this.keepAliveEvents = [];
// A set of names which will not be notified about.
this.ignores = {};
// Sorted by event occurrence time.
Expand Down Expand Up @@ -333,6 +339,10 @@ export class Timeline {
for (const activeEvent of this.activeEvents)
this.ui?.OnRemoveTimer(activeEvent, false);
this.activeEvents = [];
for (const keepAlive of this.keepAliveEvents) {
window.clearTimeout(keepAlive.timeout);
this.ui?.OnRemoveTimer(keepAlive.event, false);
}
}

private _ClearExceptRunningDurationTimers(fightNow: number): void {
Expand All @@ -342,16 +352,46 @@ export class Timeline {
durationEvents.push(event);
continue;
}
this.ui?.OnRemoveTimer(event, false, true);
this.ui?.OnRemoveTimer(event, true);
}
// Do not clear keep alive events here, as this is part of a sync jump
// and keep alive timing is independent of timeline time.

this.activeEvents = durationEvents;
}

private _RemoveExpiredTimers(fightNow: number): void {
let activeEvent = this.activeEvents[0];
while (this.activeEvents.length && activeEvent && activeEvent.time <= fightNow) {
this.ui?.OnRemoveTimer(activeEvent, true);
const event = activeEvent;
if (this.options.KeepExpiredTimerBarsForSeconds > 0) {
this.keepAliveEvents.push({
event: event,
timeout: window.setTimeout(
() => {
// Find and remove the first keepalive event with this id.
let found = false;
this.keepAliveEvents = this.keepAliveEvents.filter((x) => {
if (found)
return true;
if (x.event.id === event.id) {
found = true;
return false;
}
return true;
});
this.ui?.OnRemoveTimer(event, false);
// Because keepalive events are in "real time" just update the timer
// whenever any has been removed in case more bars need to be added.
this._OnUpdateTimer(Date.now());
},
this.options.KeepExpiredTimerBarsForSeconds * 1000,
),
});
} else {
this.ui?.OnRemoveTimer(activeEvent, false);
}

this.activeEvents.splice(0, 1);
activeEvent = this.activeEvents[0];
}
Expand Down Expand Up @@ -383,7 +423,7 @@ export class Timeline {
};
events.push(durationEvent);
this.activeEvents.splice(i, 1);
this.ui?.OnRemoveTimer(e, false, true);
this.ui?.OnRemoveTimer(e, true);
this.ui?.OnAddTimer(fightNow, durationEvent, true);
--i;
}
Expand All @@ -398,7 +438,7 @@ export class Timeline {
private _AddUpcomingTimers(fightNow: number): void {
while (
this.nextEventState.index < this.events.length &&
this.activeEvents.length < this.options.MaxNumberOfTimerBars
this.activeEvents.length + this.keepAliveEvents.length < this.options.MaxNumberOfTimerBars
) {
const e = this.events[this.nextEventState.index];
if (e === undefined)
Expand Down

0 comments on commit 35137ba

Please sign in to comment.