Page MenuHomeWickedGov Phorge

TempAccountsOnboardingDialog.vue
No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None

TempAccountsOnboardingDialog.vue

<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

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)

Event Timeline