Skip to content

Commit

Permalink
add session overwrite interface
Browse files Browse the repository at this point in the history
  • Loading branch information
ChlodAlejandro committed Jul 12, 2022
1 parent f140e8b commit 6db4dc3
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 50 deletions.
3 changes: 3 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"deputy.session.continue.help": "Continue working on \"$1\" and pick up where you left off.",
"deputy.session.tabActive.head": "You are working on this case page from another tab.",
"deputy.session.tabActive.help": "Deputy can only run on one case page and tab at a time. Navigate to the other tab to continue working.",
"deputy.session.otherActive.head": "Deputy is currently working on a different case page.",
"deputy.session.otherActive.help": "Deputy can only run on one case page and tab at a time. You can force-stop the earlier session, but progress or data on the previously-open case may be lost.",
"deputy.session.otherActive.button": "Stop session",
"deputy.session.add": "Start working on this section",

"deputy.session.section.close": "Close section",
Expand Down
21 changes: 21 additions & 0 deletions src/DeputyCommunications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ export interface DeputySessionStartedMessage {
caseId: number;
}

/**
* Broadcast whenever a session is ready for use. This means row status requests
* other listeners have been attached and are ready for use.
*
* TODO: Currently unused. To be worked on later.
*/
export interface DeputySessionReadyMessage {
type: 'sessionReady';
caseId: number;
}

/**
* Asks the currently-active Deputy session to stop and close immediately.
*/
export interface DeputySessionStopMessage {
type: 'sessionStop';
}

/**
* Requests the status of a given page. Used prior to showing the page toolbar.
* If no response is received, the page is not part of an active CCI session and
Expand Down Expand Up @@ -159,6 +177,7 @@ export interface DeputyPageNextRevisionResponse {
const OneWayDeputyMessageMap = <const>{
sessionRequest: 'sessionResponse',
sessionResponse: 'sessionRequest',
sessionStop: 'acknowledge',
pageStatusRequest: 'pageStatusResponse',
pageStatusResponse: 'pageStatusRequest',
pageStatusUpdate: 'acknowledge',
Expand All @@ -175,6 +194,8 @@ export type DeputyMessage =
| DeputyResponseMessage
| DeputySessionClosedMessage
| DeputySessionStartedMessage
| DeputySessionReadyMessage
| DeputySessionStopMessage
| DeputyPageStatusRequestMessage
| DeputyPageStatusResponseMessage
| DeputyPageStatusUpdateMessage
Expand Down
199 changes: 151 additions & 48 deletions src/session/DeputyRootSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ import unwrapWidget from '../util/unwrapWidget';
import swapElements from '../util/swapElements';
import DeputyCCISessionTabActiveMessage from '../ui/root/DeputyCCISessionTabActiveMessage';
import sectionHeadingName from '../util/sectionHeadingName';
import { DeputyMessageEvent, DeputySessionRequestMessage } from '../DeputyCommunications';
import {
DeputyMessageEvent,
DeputySessionRequestMessage,
DeputySessionStopMessage
} from '../DeputyCommunications';
import DeputyCCISessionAddSection from '../ui/root/DeputyCCISessionAddSection';
import DeputyContributionSurveySection from '../ui/root/DeputyContributionSurveySection';
import { SessionInformation } from './DeputySession';
import DeputyCCISessionOverwriteMessage from '../ui/root/DeputyCCISessionOverwriteMessage';

/**
* The DeputyRootSession. Instantiated only when:
Expand All @@ -33,13 +38,89 @@ export default class DeputyRootSession {
static async initEntryInterface( _casePage?: DeputyCasePage ): Promise<void> {
const continuing = _casePage != null;
const casePage = continuing ? _casePage : await DeputyCasePage.build();
const startLink: HTMLElement[] = [];

casePage.findContributionSurveyHeadings()
.forEach( ( heading: ContributionSurveyHeading ) => {
heading.appendChild(
DeputyCCISessionStartLink( heading, casePage )
);
const link = DeputyCCISessionStartLink( heading, casePage );
startLink.push( link as HTMLElement );
heading.appendChild( link );
} );

window.deputy.comms.addEventListener( 'sessionStarted', () => {
// Re-build interface.
startLink.forEach( ( link: HTMLElement ) => {
removeElement( link );
} );
window.deputy.session.init();
}, { once: true } );
}

/**
* Shows the interface for continuing a previous session. This includes
* the `[continue CCI session]` notice at the top of each CCI page section heading
* and a single message box showing when the page was last worked on on top of the
* first CCI heading found.
*
* @param casePage The case page to continue with
*/
static async initOverwriteMessage( casePage: DeputyCasePage ): Promise<void> {
await mw.loader.using(
[ 'oojs-ui-core', 'oojs-ui.styles.icons-content' ],
() => {
const firstHeading = casePage.findContributionSurveyHeadings()[ 0 ];
if ( firstHeading ) {
// Insert element directly into widget (not as text, or else event
// handlers will be destroyed).
const messageBox = new OO.ui.MessageWidget( {
classes: [
'deputy', 'dp-cs-session-notice', 'dp-cs-session-otherActive'
],
type: 'notice',
icon: 'alert',
label: new OO.ui.HtmlSnippet(
DeputyCCISessionOverwriteMessage().innerHTML
)
} );

const stopButton = new OO.ui.ButtonWidget( {
classes: [ 'dp-cs-session-stop' ],
label: mw.message( 'deputy.session.otherActive.button' ).text(),
flags: [ 'primary', 'destructive' ]
} );
stopButton.on( 'click', async () => {
const session = await window.deputy.comms.sendAndWait( {
type: 'sessionStop'
} );

if ( session === null ) {
// Session did not close cleanly. Tab must be closed. Force-stop
// the session.
await window.deputy.session.clearSession();
removeElement( unwrapWidget( messageBox ) );
await window.deputy.session.init();
} else {
// Handled by session close listener.
}
} );
window.deputy.comms.addEventListener( 'sessionClosed', () => {
// Closed externally. Re-build interface.
removeElement( unwrapWidget( messageBox ) );
window.deputy.session.init();
} );
swapElements(
unwrapWidget( messageBox )
.querySelector( '.dp-cs-session-stop' ),
unwrapWidget( stopButton )
);

firstHeading.insertAdjacentElement(
'beforebegin',
unwrapWidget( messageBox )
);
}
}
);
}

/**
Expand Down Expand Up @@ -83,6 +164,7 @@ export default class DeputyRootSession {
await this.initTabActiveInterface();
};
continueButton.on( 'click', () => {
removeElement( unwrapWidget( messageBox ) );
DeputyRootSession.continueSession( casePage );
window.deputy.comms.removeEventListener(
'sessionStarted',
Expand Down Expand Up @@ -111,6 +193,48 @@ export default class DeputyRootSession {
] );
}

/**
* Shows the interface for an attempted Deputy execution on a different tab than
* expected. This prevents Deputy from running entirely to avoid loss of progress
* and desynchronization.
*
* @param _casePage The current case page (not the active one)
*/
static async initTabActiveInterface( _casePage?: DeputyCasePage ): Promise<void> {
const casePage = _casePage ?? await DeputyCasePage.build();

return mw.loader.using(
[ 'oojs-ui-core', 'oojs-ui.styles.icons-content' ],
() => {
const firstHeading = casePage.findContributionSurveyHeadings()[ 0 ];
if ( firstHeading ) {
const messageBox = new OO.ui.MessageWidget( {
classes: [
'deputy', 'dp-cs-session-notice', 'dp-cs-session-tabActive'
],
type: 'info',
label: new OO.ui.HtmlSnippet(
DeputyCCISessionTabActiveMessage().innerHTML
)
} );
firstHeading.insertAdjacentElement(
'beforebegin',
unwrapWidget( messageBox )
);

window.deputy.comms.addEventListener(
'sessionClosed',
async () => {
removeElement( unwrapWidget( messageBox ) );
await window.deputy.session.init();
},
{ once: true }
);
}
}
);
}

/**
* Starts a Deputy session.
*
Expand Down Expand Up @@ -167,48 +291,6 @@ export default class DeputyRootSession {
return ( await window.deputy.storage.setKV( 'session', session ) ) ? session : null;
}

/**
* Shows the interface for an attempted Deputy execution on a different tab than
* expected. This prevents Deputy from running entirely to avoid loss of progress
* and desynchronization.
*
* @param _casePage The current case page (not the active one)
*/
static async initTabActiveInterface( _casePage?: DeputyCasePage ): Promise<void> {
const casePage = _casePage ?? await DeputyCasePage.build();

return mw.loader.using(
[ 'oojs-ui-core', 'oojs-ui.styles.icons-content' ],
() => {
const firstHeading = casePage.findContributionSurveyHeadings()[ 0 ];
if ( firstHeading ) {
const messageBox = new OO.ui.MessageWidget( {
classes: [
'deputy', 'dp-cs-session-notice', 'dp-cs-session-tabActive'
],
type: 'info',
label: new OO.ui.HtmlSnippet(
DeputyCCISessionTabActiveMessage().innerHTML
)
} );
firstHeading.insertAdjacentElement(
'beforebegin',
unwrapWidget( messageBox )
);

window.deputy.comms.addEventListener(
'sessionClosed',
async () => {
removeElement( unwrapWidget( messageBox ) );
await window.deputy.session.init();
},
{ once: true }
);
}
}
);
}

/**
* The current active session, if one exists.
*/
Expand All @@ -231,6 +313,7 @@ export default class DeputyRootSession {
* Responder for session requests.
*/
readonly sessionRequestResponder = this.sendSessionResponse.bind( this );
readonly sessionStopResponder = this.handleStopRequest.bind( this );

/*
* =========================================================================
Expand Down Expand Up @@ -274,6 +357,7 @@ export default class DeputyRootSession {
} );

window.deputy.comms.addEventListener( 'sessionRequest', this.sessionRequestResponder );
window.deputy.comms.addEventListener( 'sessionStop', this.sessionStopResponder );
window.deputy.comms.send( { type: 'sessionStarted', caseId: this.session.casePageId } );

await new Promise<void>( ( res ) => {
Expand Down Expand Up @@ -344,6 +428,22 @@ export default class DeputyRootSession {
);
}

/**
* Handles a session stop request.
*
* @param event
*/
async handleStopRequest(
event: DeputyMessageEvent<DeputySessionStopMessage>
): Promise<void> {
await this.closeSession();
window.deputy.comms.reply(
event.data, {
type: 'acknowledge'
}
);
}

/**
* Adds the "start working on this section" or "reload page" overlay and button
* to a given section.
Expand Down Expand Up @@ -385,12 +485,15 @@ export default class DeputyRootSession {
this.closeSessionUI();

await this.casePage.saveToCache();
const oldSession = this.session;
const oldSessionId = this.session.casePageId;
window.deputy.comms.removeEventListener(
'sessionRequest', this.sessionRequestResponder
);
await window.deputy.session.clearSession();
window.deputy.comms.send( { type: 'sessionClosed', caseId: oldSession.casePageId } );
window.deputy.comms.send( { type: 'sessionClosed', caseId: oldSessionId } );

// Re-initialize session interface objects.
await window.deputy.session.init();
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/session/DeputySession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ export default class DeputySession {
await casePage.bumpActive();
}
} else if ( DeputyCasePage.isCasePage() ) {
// TODO: Show "start work" with session replacement warning
console.log( 'session replacement warning goes here' );
const casePage = await DeputyCasePage.build();

await DeputyRootSession.initOverwriteMessage( casePage );
} else {
await this.normalPageInitialization();
window.deputy.comms.addEventListener( 'sessionStarted', () => {
Expand Down
19 changes: 19 additions & 0 deletions src/ui/root/DeputyCCISessionOverwriteMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { h } from 'tsx-dom';

/**
* Displayed when a user has previously worked on a case page and is now visiting
* the page again (with no active session). Displayed inside an OOUI message box.
*
* @return HTML element
*/
export default function () {

return <span>
<b>{
mw.message( 'deputy.session.otherActive.head' ).text()
}</b><br/>{
mw.message( 'deputy.session.otherActive.help' ).text()
}<br/><span class="dp-cs-session-stop" />
{/* The above <span> will be replaced in DeputySession. */}
</span>;
}

0 comments on commit 6db4dc3

Please sign in to comment.