Skip to content

Commit 3438027

Browse files
add loader
1 parent c15212e commit 3438027

5 files changed

Lines changed: 95 additions & 15 deletions

File tree

resources/js/app-layout.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ window.ProcessMaker.navbar = new Vue({
138138
sessionMessage: "",
139139
sessionTime: "",
140140
sessionWarnSeconds: "",
141+
sessionIsRenewing: false,
141142
taskTitle: "",
142143
isMobile: false,
143144
isMobileDevice: window.ProcessMaker.mobileApp,
@@ -288,6 +289,7 @@ window.ProcessMaker.sessionModal = function (title, message, time, warnSeconds)
288289

289290
window.ProcessMaker.closeSessionModal = function () {
290291
ProcessMaker.navbar.sessionShow = false;
292+
ProcessMaker.navbar.sessionIsRenewing = false;
291293
};
292294

293295
// Set out own specific confirm modal.

resources/js/bootstrap.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ if (userID) {
355355
const sessionLeaderKey = "pm:session:leader";
356356
const sessionStateKey = "pm:session:state";
357357
const sessionWarningKey = "pm:session:warning";
358+
// Track keep-alive progress across tabs.
359+
const sessionRenewingKey = "pm:session:renewing";
358360
const sessionMessageKey = "pm:session:message";
359361
const sessionTabId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
360362
const leaderHeartbeatMs = 4000;
@@ -438,6 +440,7 @@ if (userID) {
438440
};
439441

440442
let warningState = readStorageJson(sessionWarningKey);
443+
let renewingState = readStorageJson(sessionRenewingKey);
441444

442445
const refreshWarningStateFromStorage = () => {
443446
const storedWarningState = readStorageJson(sessionWarningKey);
@@ -465,6 +468,34 @@ if (userID) {
465468
sessionDebugLog("warning-state:clear");
466469
};
467470

471+
const syncRenewingUi = () => {
472+
// Only update if the navbar component exists in this layout.
473+
if (window.ProcessMaker.navbar) {
474+
window.ProcessMaker.navbar.sessionIsRenewing = !!renewingState;
475+
}
476+
};
477+
478+
const refreshRenewingStateFromStorage = () => {
479+
const storedRenewingState = readStorageJson(sessionRenewingKey);
480+
renewingState = storedRenewingState?.isRenewing ? storedRenewingState : null;
481+
syncRenewingUi();
482+
return renewingState;
483+
};
484+
485+
const setRenewingState = (isRenewing) => {
486+
if (isRenewing) {
487+
renewingState = {
488+
isRenewing: true,
489+
ts: Date.now(),
490+
};
491+
writeStorageJson(sessionRenewingKey, renewingState);
492+
} else {
493+
renewingState = null;
494+
removeStorageKey(sessionRenewingKey);
495+
}
496+
syncRenewingUi();
497+
};
498+
468499
const sessionChannel = "BroadcastChannel" in window ? new BroadcastChannel(sessionChannelName) : null;
469500

470501
const broadcastSessionEvent = (type, data = {}) => {
@@ -577,9 +608,15 @@ if (userID) {
577608
return;
578609
}
579610

611+
if (message.type === "renewing") {
612+
setRenewingState(!!message.data?.isRenewing);
613+
return;
614+
}
615+
580616
if (message.type === "renewed" || message.type === "started" || message.type === "activity") {
581617
const timeout = Number(message.data?.timeout) || window.ProcessMaker.AccountTimeoutLength;
582618
clearWarningState();
619+
setRenewingState(false);
583620
setSessionState(timeout);
584621
if (window.ProcessMaker.closeSessionModal) {
585622
window.ProcessMaker.closeSessionModal();
@@ -592,6 +629,7 @@ if (userID) {
592629

593630
if (message.type === "expired") {
594631
clearWarningState();
632+
setRenewingState(false);
595633
window.location = "/logout?timeout=true";
596634
}
597635
};
@@ -654,8 +692,10 @@ if (userID) {
654692
if (remainingTime <= 0) {
655693
sessionDebugLog("warning:skip", { remainingTime });
656694
clearWarningState();
695+
setRenewingState(false);
657696
return;
658697
}
698+
refreshRenewingStateFromStorage();
659699
sessionDebugLog("warning:show", { remainingTime });
660700
// Guard for layouts that don't include the session modal.
661701
if (typeof window.ProcessMaker.sessionModal === "function") {

resources/js/components/Session.vue

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,46 @@
1111
<template #modal-header>
1212
<h5>{{ title }}</h5>
1313
</template>
14-
<span v-html="message" />
15-
<div class="progress">
16-
<div
17-
class="progress-bar progress-bar-striped"
18-
role="progressbar"
19-
:style="{width: percentage + '%'}"
20-
>
21-
<span
22-
align="left"
23-
class="pl-2"
24-
>{{ moment().startOf('day').seconds(time).format('mm:ss') }}</span>
14+
<div v-if="!isProcessing">
15+
<span v-html="message" />
16+
<div class="progress">
17+
<div
18+
class="progress-bar progress-bar-striped"
19+
role="progressbar"
20+
:style="{width: percentage + '%'}"
21+
>
22+
<span
23+
align="left"
24+
class="pl-2"
25+
>{{ moment().startOf('day').seconds(time).format('mm:ss') }}</span>
26+
</div>
2527
</div>
2628
</div>
29+
<div
30+
v-else
31+
class="d-flex align-items-center justify-content-center py-3"
32+
>
33+
<output
34+
class="spinner-border spinner-border-sm mr-2"
35+
aria-live="polite"
36+
/>
37+
<span>{{ ("Processing...") }}</span>
38+
</div>
2739
<template #modal-footer>
2840
<button
41+
v-if="!isProcessing"
2942
type="button"
3043
class="btn btn-outline-secondary ml-2"
31-
:disabled="disabled"
44+
:disabled="isBusy"
3245
@click="logoutNow"
3346
>
3447
{{ ('LogOut') }}
3548
</button>
3649
<button
50+
v-if="!isProcessing"
3751
type="button"
3852
class="btn btn-secondary ml-2"
39-
:disabled="disabled"
53+
:disabled="isBusy"
4054
@click="keepAlive"
4155
>
4256
{{ ('Stay Connected') }}
@@ -48,14 +62,24 @@
4862
<script>
4963
5064
export default {
51-
props: ["title", "message", "time", "warnSeconds", "shown"],
65+
props: ["title", "message", "time", "warnSeconds", "shown", "isRenewing"],
5266
data() {
5367
return {
5468
errors: {},
5569
disabled: false,
70+
localRenewing: false,
5671
};
5772
},
5873
computed: {
74+
isRenewingEffective() {
75+
return this.localRenewing || this.isRenewing;
76+
},
77+
isProcessing() {
78+
return this.isRenewingEffective;
79+
},
80+
isBusy() {
81+
return this.disabled || this.isRenewingEffective;
82+
},
5983
percentage() {
6084
if (this.time === "" || this.warnSeconds === "") {
6185
return 0;
@@ -81,11 +105,13 @@ export default {
81105
},
82106
keepAlive() {
83107
this.disabled = true;
108+
this.setRenewingState(true);
84109
85110
ProcessMaker.apiClient
86111
.post("/keep-alive", {}, { baseURL: "" })
87112
.then(() => {
88113
this.disabled = false;
114+
this.setRenewingState(false);
89115
const timeout = window.ProcessMaker.AccountTimeoutLength;
90116
if (window.ProcessMaker.sessionSync?.setSessionState) {
91117
window.ProcessMaker.sessionSync.setSessionState(timeout);
@@ -113,14 +139,23 @@ export default {
113139
const status = error?.response?.status;
114140
if (status === 401 || status === 419) {
115141
// Session expired server-side; broadcast and redirect.
142+
this.setRenewingState(false);
116143
this.broadcastExpired();
117144
window.location.href = "/logout";
118145
return;
119146
}
120147
this.disabled = false;
148+
this.setRenewingState(false);
121149
this.errors = error.response.data.errors;
122150
});
123151
},
152+
setRenewingState(isRenewing) {
153+
this.localRenewing = isRenewing;
154+
// Broadcast renewal status so other tabs show the spinner.
155+
if (window.ProcessMaker.sessionSync?.broadcast) {
156+
window.ProcessMaker.sessionSync.broadcast("renewing", { isRenewing });
157+
}
158+
},
124159
broadcastExpired() {
125160
// Sync logout state across tabs.
126161
if (window.ProcessMaker.sessionSync?.clearWarningState) {
@@ -133,6 +168,7 @@ export default {
133168
logoutNow() {
134169
// Ensure other tabs close warning before redirect.
135170
this.disabled = true;
171+
this.setRenewingState(true);
136172
this.broadcastExpired();
137173
window.location.href = "/logout";
138174
},

resources/js/next/layout/navbar.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const sessionModal = function (title, message, time, warnSeconds) {
6363

6464
const closeSessionModal = function () {
6565
ProcessMaker.navbar.sessionShow = false;
66+
ProcessMaker.navbar.sessionIsRenewing = false;
6667
};
6768

6869
// Set out own specific confirm modal.
@@ -137,6 +138,7 @@ const navbar = new Vue({
137138
sessionMessage: "",
138139
sessionTime: "",
139140
sessionWarnSeconds: "",
141+
sessionIsRenewing: false,
140142
taskTitle: "",
141143
isMobile: false,
142144
isMobileDevice: mobileApp,

resources/views/layouts/navbar.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
:variant="messageVariant" :callback="messageCallback"
3131
@close="messageShow=false">
3232
</message-modal>
33-
<session-modal id="sessionModal" :shown="sessionShow" :title="sessionTitle" :message="sessionMessage" :time="sessionTime" :warn-seconds="sessionWarnSeconds"
33+
<session-modal id="sessionModal" :shown="sessionShow" :title="sessionTitle" :message="sessionMessage" :time="sessionTime" :warn-seconds="sessionWarnSeconds" :is-renewing="sessionIsRenewing"
3434
@close="sessionShow=false">
3535
</session-modal>
3636
<div v-if="alerts.length > 0" class="alert-wrapper">

0 commit comments

Comments
 (0)