Commit 5c09b29f authored by Thomas Randolph's avatar Thomas Randolph

Show a link in the UI to load the content of a `renamed` file

And: reduce all the possible state combinations to a FSM

Per design review, this component needed a bunch more
interactivity. Most critically, around displaying and responding
to a load error.

Rather than set a bunch of flags and toggle various elements
based on a collection of booleans, I think it's much easier to
reason about this component if it has a Finite State Machine
representing its possible states and transitions between them.

The two notable logic additions here are a rudimentary FSM
(`transition`) and a state checker (`is`).

`transition` defines all the ways the component can go from
one state to another. If a transition is attempted that doesn't
match one of these declared transitions, it does nothing.
Every transition always results in a declared state.
these two things combined make it much easier to reason
about what the component should look like at any given
moment.

Other than the state machine (which should be replaced with
something like @xstate/fsm), the rest of these changes are
text changes from UX/technical writing review.
parent c7f3bc74
...@@ -61,3 +61,22 @@ export const DIFFS_PER_PAGE = 20; ...@@ -61,3 +61,22 @@ export const DIFFS_PER_PAGE = 20;
export const DIFF_COMPARE_BASE_VERSION_INDEX = -1; export const DIFF_COMPARE_BASE_VERSION_INDEX = -1;
export const DIFF_COMPARE_HEAD_VERSION_INDEX = -2; export const DIFF_COMPARE_HEAD_VERSION_INDEX = -2;
// State machine states
export const STATE_IDLING = 'idle';
export const STATE_LOADING = 'loading';
export const STATE_ERRORED = 'errored';
// State machine transitions
export const TRANSITION_LOAD_START = 'LOAD_START';
export const TRANSITION_LOAD_ERROR = 'LOAD_ERROR';
export const TRANSITION_LOAD_SUCCEED = 'LOAD_SUCCEED';
export const TRANSITION_ACKNOWLEDGE_ERROR = 'ACKNOWLEDGE_ERROR';
export const RENAMED_DIFF_TRANSITIONS = {
[`${STATE_IDLING}:${TRANSITION_LOAD_START}`]: STATE_LOADING,
[`${STATE_LOADING}:${TRANSITION_LOAD_ERROR}`]: STATE_ERRORED,
[`${STATE_LOADING}:${TRANSITION_LOAD_SUCCEED}`]: STATE_IDLING,
[`${STATE_ERRORED}:${TRANSITION_LOAD_START}`]: STATE_LOADING,
[`${STATE_ERRORED}:${TRANSITION_ACKNOWLEDGE_ERROR}`]: STATE_IDLING,
};
<script>
import { mapActions } from 'vuex';
import { GlAlert, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import {
TRANSITION_LOAD_START,
TRANSITION_LOAD_ERROR,
TRANSITION_LOAD_SUCCEED,
TRANSITION_ACKNOWLEDGE_ERROR,
STATE_IDLING,
STATE_LOADING,
STATE_ERRORED,
RENAMED_DIFF_TRANSITIONS,
} from '~/diffs/constants';
import { truncateSha } from '~/lib/utils/text_utility';
export default {
STATE_LOADING,
STATE_ERRORED,
TRANSITIONS: RENAMED_DIFF_TRANSITIONS,
uiText: {
showLink: __('Show file contents'),
commitLink: __('View file @ %{commitSha}'),
description: __('File renamed with no changes.'),
loadError: __('Unable to load file contents. Try again later.'),
},
components: {
GlAlert,
GlLink,
GlLoadingIcon,
GlSprintf,
},
props: {
diffFile: {
type: Object,
required: true,
},
},
data: () => ({
state: STATE_IDLING,
}),
computed: {
shortSha() {
return truncateSha(this.diffFile.content_sha);
},
canLoadFullDiff() {
return this.diffFile.alternate_viewer.name === 'text';
},
},
methods: {
...mapActions('diffs', ['switchToFullDiffFromRenamedFile']),
transition(transitionEvent) {
const key = `${this.state}:${transitionEvent}`;
if (this.$options.TRANSITIONS[key]) {
this.state = this.$options.TRANSITIONS[key];
}
},
is(state) {
return this.state === state;
},
switchToFull() {
this.transition(TRANSITION_LOAD_START);
this.switchToFullDiffFromRenamedFile({ diffFile: this.diffFile })
.then(() => {
this.transition(TRANSITION_LOAD_SUCCEED);
})
.catch(() => {
this.transition(TRANSITION_LOAD_ERROR);
});
},
clickLink(event) {
if (this.canLoadFullDiff) {
event.preventDefault();
this.switchToFull();
}
},
dismissError() {
this.transition(TRANSITION_ACKNOWLEDGE_ERROR);
},
},
};
</script>
<template> <template>
<div class="nothing-here-block">{{ __('File moved') }}</div> <div class="nothing-here-block">
<gl-loading-icon v-if="is($options.STATE_LOADING)" />
<template v-else>
<gl-alert
v-show="is($options.STATE_ERRORED)"
class="gl-mb-5 gl-text-left"
variant="danger"
@dismiss="dismissError"
>{{ $options.uiText.loadError }}</gl-alert
>
<span test-id="plaintext">{{ $options.uiText.description }}</span>
<gl-link :href="diffFile.view_path" @click="clickLink">
<span v-if="canLoadFullDiff">{{ $options.uiText.showLink }}</span>
<gl-sprintf v-else :message="$options.uiText.commitLink">
<template #commitSha>{{ shortSha }}</template>
</gl-sprintf>
</gl-link>
</template>
</div>
</template> </template>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment