Skip to content

Commit 9c8334c

Browse files
cci(page): add toolbar hide/collapse
1 parent 9aedaec commit 9c8334c

File tree

7 files changed

+195
-13
lines changed

7 files changed

+195
-13
lines changed

i18n/core/en.json

+5
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@
9393
"deputy.session.revision.new.tooltip": "This edit created a new page.",
9494
"deputy.session.revision.missing": "The revision [[Special:Diff/$1|$1]] could not be found. It may have been deleted or suppressed.",
9595

96+
"deputy.session.page.close": "Minimize the toolbar to the sidebar",
97+
"deputy.session.page.collapse": "Collapse the toolbar",
98+
"deputy.session.page.expand": "Expand the toolbar",
99+
"deputy.session.page.open": "Open Deputy toolbar",
100+
"deputy.session.page.open.tooltip": "Open the Deputy page toolbar",
96101
"deputy.session.page.diff.previous": "Navigate to the previous unassessed revision",
97102
"deputy.session.page.diff.next": "Navigate to the next unassessed revision",
98103
"deputy.session.page.diff.loadFail": "Failed to load diff. Please check your internet connection and try again.",

src/config/UserConfiguration.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import Setting from './Setting';
22
import MwApi from '../MwApi';
33
import { CopyrightProblemsResponseSet } from '../modules/ia/models/CopyrightProblemsResponse';
4-
import { generateEnumConfigurationProperties, PortletNameView } from './types';
4+
import {
5+
generateEnumConfigurationProperties,
6+
generateEnumSerializers,
7+
PortletNameView
8+
} from './types';
59
import { CompletionAction, TripleCompletionAction } from '../modules/shared/CompletionAction';
610
import { EnumValue } from '../types';
711
import DeputyVersion from '../DeputyVersion';
812
import ConfigurationBase from './ConfigurationBase';
913
import {
1014
ContributionSurveyRowSigningBehavior
1115
} from '../models/ContributionSurveyRowSigningBehavior';
16+
import { DeputyPageToolbarState } from '../ui/page/DeputyPageToolbarState';
1217
import error from '../util/error';
1318

1419
/**
@@ -159,6 +164,14 @@ export default class UserConfiguration extends ConfigurationBase {
159164
displayOptions: {
160165
type: 'checkbox'
161166
}
167+
} ),
168+
toolbarInitialState: new Setting<
169+
EnumValue<typeof DeputyPageToolbarState>,
170+
DeputyPageToolbarState
171+
>( {
172+
...generateEnumSerializers( DeputyPageToolbarState, DeputyPageToolbarState.Open ),
173+
defaultValue: DeputyPageToolbarState.Open,
174+
displayOptions: { hidden: true }
162175
} )
163176
};
164177
public readonly ante = <const>{

src/config/types.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
import fromObjectEntries from '../util/fromObjectEntries';
22
import { DisplayOptions } from './Setting';
3+
/**
4+
* Generates serializer and deserializer for serialized <b>string</b> enums.
5+
*
6+
* Trying to use anything that isn't a string enum here (union enum, numeral enum)
7+
* will likely cause serialization/deserialization failures.
8+
*
9+
* @param _enum
10+
* @param defaultValue
11+
* @return An object containing a `serializer` and `deserializer`.
12+
*/
13+
export function generateEnumSerializers<T>( _enum: T, defaultValue?: T[keyof T] ) {
14+
return {
15+
serialize: ( value: typeof _enum[keyof typeof _enum] ) =>
16+
value === defaultValue ? undefined : value,
17+
deserialize: ( value: any ) =>
18+
value as typeof _enum[keyof typeof _enum]
19+
};
20+
}
321

422
/**
523
* Generates configuration properties for serialized <b>string</b> enums.
@@ -16,10 +34,7 @@ export function generateEnumConfigurationProperties<T>(
1634
defaultValue?: T[keyof T],
1735
) {
1836
return {
19-
serialize: ( value: typeof _enum[keyof typeof _enum] ) =>
20-
value === defaultValue ? undefined : value,
21-
deserialize: ( value: any ) =>
22-
value as typeof _enum[keyof typeof _enum],
37+
...generateEnumSerializers( _enum, defaultValue ),
2338
displayOptions: <DisplayOptions>{
2439
type: 'radio'
2540
},

src/css/deputy.css

+64-1
Original file line numberDiff line numberDiff line change
@@ -400,15 +400,78 @@ body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child) {
400400
left: 8px;
401401
z-index: 100;
402402

403-
padding: 8px;
404403
background-color: #fff;
405404
border: 1px solid gray;
406405
font-size: 0.9rem;
407406

408407
display: flex;
408+
}
409+
410+
.dp-pageToolbar .dp-pageToolbar-main {
411+
padding: 8px;
412+
display: flex;
409413
align-items: center;
410414
}
411415

416+
.dp-pageToolbar-actions {
417+
width: 12px;
418+
display: flex;
419+
flex-direction: column;
420+
font-size: 12px;
421+
line-height: 1em;
422+
}
423+
424+
.dp-pageToolbar-close {
425+
cursor: pointer;
426+
height: 12px;
427+
text-align: center;
428+
background-color: rgba(0, 0, 0, 0.25);
429+
}
430+
431+
.dp-pageToolbar-close:hover {
432+
transition: background-color 0.1s ease-in-out;
433+
background-color: rgba(0, 0, 0, 0.4);
434+
}
435+
436+
.dp-pageToolbar-close::before {
437+
content: '×';
438+
vertical-align: middle;
439+
position: relative;
440+
right: 1px;
441+
}
442+
443+
.dp-pageToolbar-collapse {
444+
cursor: pointer;
445+
flex: 1;
446+
background-color: rgba(0, 0, 0, 0.125);
447+
text-align: center;
448+
writing-mode: vertical-rl;
449+
position: relative;
450+
}
451+
452+
.dp-pageToolbar-collapse:hover {
453+
transition: background-color 0.1s ease-in-out;
454+
background-color: rgba(0, 0, 0, 0.25);
455+
}
456+
457+
.dp-pageToolbar-collapse::before {
458+
content: '»';
459+
position: absolute;
460+
vertical-align: middle;
461+
width: 12px;
462+
left: 0;
463+
bottom: 2px;
464+
}
465+
466+
.dp-pageToolbar-collapsed {
467+
cursor: pointer;
468+
width: 32px;
469+
height: 32px;
470+
/* logo-white.svg */
471+
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDgwIDEwODAiIHdpZHRoPSIxMDgwIiBoZWlnaHQ9IjEwODAiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojMDI5N2IxO308L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik04NzcuNTQsNDM4LjY5YTQzNy41NCw0MzcuNTQsMCwwLDEtODQuMzgsMjU4Ljc4bDI2Ny4wNiwyNjcuMjJhNjcuNTYsNjcuNTYsMCwxLDEtOTUuNTYsOTUuNTRMNjk3LjYsNzkzYTQzNi4yNyw0MzYuMjcsMCwwLDEtMjU4LjgzLDg0LjM2QzE5Ni4zOSw4NzcuMzcsMCw2ODEsMCw0MzguNjlTMTk2LjM5LDAsNDM4Ljc3LDAsODc3LjU0LDE5Ni4zNSw4NzcuNTQsNDM4LjY5Wk00MzguNzcsNzQyLjM5YzE2Ny43LDAsMzAzLjc3LTEzNiwzMDMuNzctMzAzLjdTNjA2LjQ3LDEzNSw0MzguNzcsMTM1LDEzNSwyNzEsMTM1LDQzOC42OSwyNzEuMDcsNzQyLjM5LDQzOC43Nyw3NDIuMzlaIi8+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNTI0LjgzLDUxNS4yOGExMDQuODUsMTA0Ljg1LDAsMCwxLTE0OS40MSwwYy00MS41LTQxLjg3LTQxLjUtMTEwLDAtMTUxLjk1YTEwNC45MiwxMDQuOTIsMCwwLDEsMTQ5LjUxLjA4QTUwLjc2LDUwLjc2LDAsMCwwLDU5NywyOTEuOWEyMDYuNDQsMjA2LjQ0LDAsMCwwLTI5My41NSwwYy04MC41Nyw4MS4yNy04MC41NywyMTMuNTMsMCwyOTQuODNBMjA1LjI1LDIwNS4yNSwwLDAsMCw0NTAuMTgsNjQ4aDBBMjA1LjMsMjA1LjMsMCwwLDAsNTk3LDU4Ni43MWE1MC43Nyw1MC43NywwLDAsMC03Mi4xNi03MS40M1oiLz48L3N2Zz4=') no-repeat center;
472+
background-size: 24px;
473+
}
474+
412475
@media only screen and (max-width: 768px) {
413476
.dp-pageToolbar {
414477
flex-wrap: wrap;

src/session/DeputyPageSession.ts

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export default class DeputyPageSession {
4040
readonly sessionCloseHandler = this.onSessionClosed.bind( this );
4141

4242
/**
43-
*
4443
* @param data
4544
*/
4645
init( data: DeputyPageStatusResponseMessage ) {

src/ui/page/DeputyPageToolbar.tsx

+88-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import DeputyPageMenu, { DeputyPageMenuOption } from './DeputyPageMenu';
1818
import deputyPageAnalysisOptions from './DeputyPageAnalysisOptions';
1919
import deputyPageTools from './DeputyPageTools';
2020
import error from '../../util/error';
21+
import { DeputyPageToolbarState } from './DeputyPageToolbarState';
22+
import log from '../../util/log';
2123

2224
export interface DeputyPageToolbarOptions extends Omit<DeputyPageStatusResponseMessage, 'type'> {
2325
/**
@@ -59,6 +61,8 @@ export default class DeputyPageToolbar implements DeputyUIElement {
5961
*/
6062
revision?: number;
6163

64+
state = DeputyPageToolbarState.Open;
65+
6266
readonly instanceId = generateId();
6367
readonly revisionStatusUpdateListener = this.onRevisionStatusUpdate.bind( this );
6468

@@ -73,6 +77,7 @@ export default class DeputyPageToolbar implements DeputyUIElement {
7377
this.revision = options.revision ?? mw.config.get( 'wgRevisionId' );
7478
}
7579

80+
this.state = window.deputy.config.cci.toolbarInitialState.get();
7681
this.runAsyncJobs();
7782
}
7883

@@ -389,17 +394,80 @@ export default class DeputyPageToolbar implements DeputyUIElement {
389394
</div>;
390395
}
391396

397+
/**
398+
* Rends the page toolbar actions and main section, if the dropdown is open.
399+
*/
400+
renderOpen(): JSX.Element[] {
401+
return [
402+
<div class="dp-pageToolbar-actions">
403+
<div
404+
class="dp-pageToolbar-close"
405+
role="button"
406+
title={ mw.msg( 'deputy.session.page.close' ) }
407+
onClick={ () => this.setState( DeputyPageToolbarState.Hidden ) }
408+
/>
409+
<div
410+
class="dp-pageToolbar-collapse"
411+
role="button"
412+
title={ mw.msg( 'deputy.session.page.collapse' ) }
413+
onClick={ () => this.setState( DeputyPageToolbarState.Collapsed ) }
414+
/>
415+
</div>,
416+
<div class="dp-pageToolbar-main">
417+
{ this.renderStatusDropdown() }
418+
{ this.renderCaseInfo() }
419+
{ this.renderRevisionInfo() }
420+
{ this.revisionNavigationSection =
421+
this.renderRevisionNavigationButtons() as HTMLElement }
422+
{ this.renderMenus() }
423+
</div>
424+
];
425+
}
426+
427+
/**
428+
* Renders the collapsed toolbar button.
429+
*
430+
* @return The render button, to be included in the main toolbar.
431+
*/
432+
renderCollapsed(): JSX.Element {
433+
return <div
434+
class="dp-pageToolbar-collapsed"
435+
role="button"
436+
title={ mw.msg( 'deputy.session.page.expand' ) }
437+
onClick={ () => this.setState( DeputyPageToolbarState.Open ) }
438+
/>;
439+
}
440+
392441
/**
393442
* @inheritDoc
394443
*/
395444
render(): HTMLElement {
445+
console.log( this.state );
446+
if ( this.state === DeputyPageToolbarState.Hidden ) {
447+
const portletLink = mw.util.addPortletLink(
448+
'p-tb',
449+
'#',
450+
mw.msg( 'deputy.session.page.open' ),
451+
'pt-dp-pt',
452+
mw.msg( 'deputy.session.page.open.tooltip' )
453+
);
454+
portletLink.querySelector( 'a' ).addEventListener( 'click', ( event ) => {
455+
event.preventDefault();
456+
this.setState( DeputyPageToolbarState.Open );
457+
return false;
458+
} );
459+
// Placeholder element
460+
return this.element = <div class="deputy" /> as HTMLElement;
461+
} else {
462+
const toolbar = document.getElementById( 'pt-dp-pt' );
463+
if ( toolbar ) {
464+
removeElement( toolbar );
465+
}
466+
}
467+
396468
return this.element = <div class="deputy dp-pageToolbar">
397-
{ this.renderStatusDropdown() }
398-
{ this.renderCaseInfo() }
399-
{ this.renderRevisionInfo() }
400-
{ this.revisionNavigationSection =
401-
this.renderRevisionNavigationButtons() as HTMLElement }
402-
{ this.renderMenus() }
469+
{ this.state === DeputyPageToolbarState.Open && this.renderOpen() }
470+
{ this.state === DeputyPageToolbarState.Collapsed && this.renderCollapsed() }
403471
</div> as HTMLElement;
404472
}
405473

@@ -415,6 +483,20 @@ export default class DeputyPageToolbar implements DeputyUIElement {
415483
this.nextRevisionButton?.setDisabled( disabled );
416484
}
417485

486+
/**
487+
* Sets the display state of the toolbar. This will also set the
488+
* initial state configuration option for the user.
489+
*
490+
* @param state
491+
*/
492+
setState( state: DeputyPageToolbarState ) {
493+
this.state = state;
494+
window.deputy.config.cci.toolbarInitialState.set( state );
495+
window.deputy.config.save();
496+
497+
swapElements( this.element, this.render() );
498+
}
499+
418500
/**
419501
* Performs cleanup and removes the element from the DOM.
420502
*/

src/ui/page/DeputyPageToolbarState.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export enum DeputyPageToolbarState {
2+
Open,
3+
Collapsed,
4+
Hidden
5+
}

0 commit comments

Comments
 (0)