Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F5983246
TempAccountsOnboardingDialog.vue
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
TempAccountsOnboardingDialog.vue
View Options
<template>
<cdx-dialog
:open="dialogOpen"
class="ext-checkuser-temp-account-onboarding-dialog"
:title="$i18n( 'checkuser-temporary-accounts-onboarding-dialog-title' ).text()"
:hide-title="true"
@update:open="onUpdateOpen"
>
<!-- Dialog Header -->
<template #header>
<div class="ext-checkuser-temp-account-onboarding-dialog__header">
<div class="ext-checkuser-temp-account-onboarding-dialog__header__top">
<h4 class="ext-checkuser-temp-account-onboarding-dialog__header__top__title">
{{ $i18n( 'checkuser-temporary-accounts-onboarding-dialog-title' ).text() }}
</h4>
<cdx-button
class="ext-checkuser-temp-account-onboarding-dialog__header__top__button"
weight="quiet"
@click="onFinish"
>
{{ $i18n(
'checkuser-temporary-accounts-onboarding-dialog-skip-all'
).text() }}
</cdx-button>
</div>
<temp-accounts-onboarding-stepper
v-model:model-value="currentStep"
class="ext-checkuser-temp-account-onboarding-dialog__header__stepper"
:total-steps="steps.length"
></temp-accounts-onboarding-stepper>
</div>
</template>
<!-- Dialog Content -->
<div>
<div
ref="stepWrapperRef"
class="ext-checkuser-temp-account-onboarding-dialog-content"
@touchstart="onTouchStart"
@touchmove="onTouchMove">
<transition :name="computedTransitionName">
<slot :name="currentSlotName"></slot>
</transition>
</div>
</div>
<!-- Dialog Footer -->
<template #footer>
<div class="ext-checkuser-temp-account-onboarding-dialog__footer">
<div class="ext-checkuser-temp-account-onboarding-dialog__footer__navigation">
<cdx-button
v-if="currentStep !== 1"
class="
ext-checkuser-temp-account-onboarding-dialog__footer__navigation--prev
"
:aria-label="$i18n(
'checkuser-temporary-accounts-onboarding-dialog-previous-label'
).text()"
@click="navigatePrev"
>
<cdx-icon
:icon="cdxIconPrevious"
:icon-label="$i18n(
'checkuser-temporary-accounts-onboarding-dialog-previous-label'
).text()"
></cdx-icon>
</cdx-button>
<cdx-button
v-if="currentStep === steps.length"
weight="primary"
action="progressive"
class="
ext-checkuser-temp-account-onboarding-dialog__footer__navigation--next
"
@click="onFinish"
>
{{ $i18n(
'checkuser-temporary-accounts-onboarding-dialog-close-label'
).text() }}
</cdx-button>
<cdx-button
v-else
weight="primary"
action="progressive"
:aria-label="$i18n(
'checkuser-temporary-accounts-onboarding-dialog-next-label'
).text()"
class="
ext-checkuser-temp-account-onboarding-dialog__footer__navigation--next
"
@click="navigateNext"
>
<cdx-icon
:icon="cdxIconNext"
:icon-label="$i18n(
'checkuser-temporary-accounts-onboarding-dialog-next-label'
).text()"
></cdx-icon>
</cdx-button>
</div>
</div>
</template>
</cdx-dialog>
</template>
<script>
const { ref, computed, watch } = require( 'vue' );
const { CdxDialog, CdxButton, CdxIcon, useComputedDirection } = require( '@wikimedia/codex' );
const { cdxIconNext, cdxIconPrevious } = require( './icons.json' );
const TempAccountsOnboardingStepper = require( './TempAccountsOnboardingStepper.vue' );
const TRANSITION_NAMES = {
LEFT: 'ext-checkuser-temp-account-onboarding-left',
RIGHT: 'ext-checkuser-temp-account-onboarding-right'
};
/**
* The Temporary Accounts onboarding dialog component. This defines the structure of the dialog and
* the user of the component will define the content.
*/
// @vue/component
module.exports = exports = {
name: 'TempAccountsOnboardingDialog',
components: {
CdxDialog,
CdxButton,
CdxIcon,
TempAccountsOnboardingStepper
},
props: {
/**
* An array of the objects that describe each step for the dialog.
* Each object should contain at least the step name. It may contain
* a Vue ref to the component that is the step.
*
* @type {Array.<{name: string, ref: ref}>}
*/
steps: {
type: Object,
required: true
}
},
setup( props ) {
const dialogOpen = ref( true );
// Work out the slot name based on the current step.
const currentStep = ref( 1 );
const currentSlotName = computed( () => props.steps[ currentStep.value - 1 ].name );
// Work out whether the page is rtl or ltr. Needed to decide which
// direction the animation for the step transition should go.
const stepWrapperRef = ref( null );
const computedDir = useComputedDirection( stepWrapperRef );
const isRtl = computed( () => computedDir.value === 'rtl' );
// Set up the variables needed to perform navigation and also
// the associated animations.
const initialX = ref( null );
const initialY = ref( null );
const currentNavigation = ref( null );
const computedTransitionSet = computed( () => isRtl.value ?
{ next: TRANSITION_NAMES.LEFT, prev: TRANSITION_NAMES.RIGHT } :
{ next: TRANSITION_NAMES.RIGHT, prev: TRANSITION_NAMES.LEFT } );
const computedTransitionName = computed(
() => computedTransitionSet.value[ currentNavigation.value ]
);
/**
* Returns whether the dialog should allow a user to navigate to another step.
* Value is controlled by the current step.
*
* @return {boolean}
*/
function canMoveToAnotherStep() {
const currentStepRef = props.steps[ currentStep.value - 1 ].ref;
return !currentStepRef ||
currentStepRef.value === null ||
!( 'canMoveToAnotherStep' in currentStepRef.value ) ||
currentStepRef.value.canMoveToAnotherStep();
}
/**
* Method used to navigate forward.
*
* Does nothing if the current step is the last defined step,
* or if the current step is preventing moving to another step.
*/
function navigateNext() {
if ( currentStep.value < props.steps.length && canMoveToAnotherStep() ) {
currentNavigation.value = 'next';
currentStep.value++;
}
}
/**
* Method used to navigate backwards.
*
* Does nothing if the current step is the first step,
* or if the current step is preventing moving to another step.
*/
function navigatePrev() {
if ( currentStep.value > 1 && canMoveToAnotherStep() ) {
currentNavigation.value = 'prev';
currentStep.value--;
}
}
/**
* Handles a user starting a touch on their screen.
* Used to allow a user to navigate using a swipe of
* their screen.
*
* @param {TouchEvent} e
*/
function onTouchStart( e ) {
const touchEvent = e.touches[ 0 ];
initialX.value = touchEvent.clientX;
initialY.value = touchEvent.clientY;
}
/**
* Handles a user swiping to the right
*/
const onSwipeToRight = () => {
if ( isRtl.value === true ) {
navigateNext();
} else {
navigatePrev();
}
};
/**
* Handles a user swiping to the left
*/
const onSwipeToLeft = () => {
if ( isRtl.value === true ) {
navigatePrev();
} else {
navigateNext();
}
};
/**
* Handles a user finishing a touch where there was
* a movement in a direction.
* Used to allow a user to navigate using a swipe of
* their screen.
*
* @param {TouchEvent} e
*/
function onTouchMove( e ) {
if ( !initialX.value || !initialY.value ) {
return;
}
const newX = e.touches[ 0 ].clientX;
const newY = e.touches[ 0 ].clientY;
// Don't consider a touch movement a swipe to the left or right if the
// users' swipe was more up/down instead of left/right.
if ( Math.abs( initialX.value - newX ) < Math.abs( initialY.value - newY ) ) {
return;
}
if ( initialX.value > newX ) {
onSwipeToLeft();
} else {
onSwipeToRight();
}
initialX.value = null;
}
/**
* Returns whether the dialog should ignore an attempt to close the dialog
* on the first attempt to do so.
*
* Used to warn a user about unsaved changes in a dialog step.
*
* @return {boolean}
*/
function shouldWarnBeforeClosingDialog() {
const currentStepRef = props.steps[ currentStep.value - 1 ].ref;
return currentStepRef &&
currentStepRef.value !== null &&
'shouldWarnBeforeClosingDialog' in currentStepRef.value &&
currentStepRef.value.shouldWarnBeforeClosingDialog();
}
// Keep a track of whether we have warned the user when they attempted
// to close the dialog. If the step is moved, then we reset this as
// each step may have a different warning.
const warnedUserBeforeClosingDialog = ref( false );
watch( currentStep, () => {
warnedUserBeforeClosingDialog.value = false;
} );
/**
* Handles a close of the dialog when the dialog should not be seen again.
* The dialog is hidden for the user in future page loads via setting a
* preference.
*
* The close is prevented if the current step indicates that a warning should
* be displayed before closing the dialog.
*/
function onFinish() {
if ( shouldWarnBeforeClosingDialog() && !warnedUserBeforeClosingDialog.value ) {
warnedUserBeforeClosingDialog.value = true;
return;
}
const api = new mw.Api();
api.saveOption( 'checkuser-temporary-accounts-onboarding-dialog-seen', 1, { global: 'create' } );
dialogOpen.value = false;
}
/**
* Handle an attempted change to the open status of the dialog
* via an "Escape" button press or clicking outside the dialog.
*
* Using this method means that the dialog will be shown to the
* user again on a new page load. This is done to avoid the user
* not seeing the dialog if they accidentally close it.
*
* @param {boolean} newVal The new open status of the dialog
*/
function onUpdateOpen( newVal ) {
if ( shouldWarnBeforeClosingDialog() && !warnedUserBeforeClosingDialog.value ) {
warnedUserBeforeClosingDialog.value = true;
return;
}
dialogOpen.value = newVal;
}
return {
cdxIconNext,
cdxIconPrevious,
currentStep,
computedTransitionName,
currentSlotName,
onTouchStart,
onTouchMove,
stepWrapperRef,
navigateNext,
navigatePrev,
onFinish,
dialogOpen,
onUpdateOpen
};
}
};
</script>
File Metadata
Details
Attached
Mime Type
text/html
Expires
Sat, Oct 3, 22:47 (1 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
8d/70/99f350b2f7de85c26cd5f09a07f6
Default Alt Text
TempAccountsOnboardingDialog.vue (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment