[go: up one dir, main page]

Skip to content

Create a new customization component based on the dashboard layout component

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

Problem to solve

As part our modular dashboard foundations we ant to deliver a new dashboard customization component that's based on the dashboard layout component.

Proposal

  1. Publish a new customizable_dashboards.vue that uses GlDashboardLayout.
    • See implementation notes for example.

Implementation notes

Guidelines

Why a new component?

  1. Passing the editing state to Gridstack.
  2. Editing form + UI changes don't fit into the layout component.
  3. Additional UI controls for the editing mode don't have a place in the layout component.

Example code

Roughly working example that can be used to get started with.

Click to expand
<script>
import { GlButton, GlFormInput, GlFormGroup, GlIcon } from '@gitlab/ui';
import { isEqual } from 'lodash';
import { s__, __ } from '~/locale';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import DashboardLayout from './dashboard_layout.vue';

export default {
  name: 'CustomizableDashboard',
  components: {
    DashboardLayout,
    GlButton,
    GlFormInput,
    GlIcon,
    GlFormGroup,
  },
  props: {
    config: {
      type: Object,
      required: true,
    },
    isNewDashboard: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  data() {
    return {
      editing: false,
      titleValidationError: null,
      mutableConfig: JSON.parse(JSON.stringify(this.config)),
    };
  },
  computed: {
    changesMade() {
      // Compare the dashboard configs as that is what will be saved
      return !isEqual(
        this.config,
        this.mutableConfig,
      );
    },
  },
  methods: {
    async confirmDiscardChanges() {
      const confirmText = this.isNewDashboard
        ? s__('Dashboards|Are you sure you want to cancel creating this dashboard?')
        : s__('Dashboards|Are you sure you want to cancel editing this dashboard?');

      const cancelBtnText = this.isNewDashboard
        ? s__('Dashboards|Continue creating')
        : s__('Dashboards|Continue editing');

      return confirmAction(confirmText, {
        primaryBtnText: __('Discard changes'),
        cancelBtnText,
      });
    },
    onSave() {
      this.$emit('save', this.mutableConfig);
    },
    async onCancel() {
      if (this.changesMade) {
        const confirmed = await this.confirmDiscardChanges();

        if (!confirmed) return;

        this.mutableConfig = JSON.parse(JSON.stringify(this.config));
      }
      this.editing = false;
    },
    onDashboardChanged(newConfig) {
      this.mutableConfig = newConfig;
    },
  },
  inheritAttrs: false,
};
</script>

<template>
  <dashboard-layout
    :config="mutableConfig"
    :editing="editing"
    v-bind="$attrs"
    v-on="$listeners"
    @input="onDashboardChanged"
  >
    <!-- Pass named slots -->
    <template v-for="(_, name) in $slots" :slot="name">
      <slot :name="name"></slot>
    </template>

    <!-- Pass scoped slots -->
     <!-- Might have to filter out header -->
    <template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="scope">
      <slot :name="name" v-bind="scope"></slot>
    </template>

    <template #actions>
      <gl-button
        v-if="!editing"
        icon="pencil"
        class="gl-mr-2"
        data-testid="dashboard-edit-btn"
        @click="editing = true"
        >{{ s__('Dashboards|Edit') }}</gl-button
      >
      <slot name="actions"></slot>
    </template>

    <template v-if="editing" #header>
      <div class="gl-flex gl-w-full gl-flex-col">
        <h2 class="gl-mb-6 gl-mt-0">
          {{ s__('Dashboards|Edit your dashboard') }}
        </h2>
        <div class="flex-fill gl-flex gl-flex-col">
          <gl-form-group
            :label="s__('Dashboards|Dashboard title')"
            label-for="title"
            :class="$options.FORM_GROUP_CLASS"
            class="gl-mb-4"
            data-testid="dashboard-title-form-group"
            :invalid-feedback="titleValidationError"
            :state="!titleValidationError"
          >
            <gl-form-input
              id="title"
              ref="titleInput"
              v-model="mutableConfig.title"
              dir="auto"
              type="text"
              :placeholder="s__('Dashboards|Enter a dashboard title')"
              :aria-label="s__('Dashboards|Dashboard title')"
              :class="$options.FORM_INPUT_CLASS"
              data-testid="dashboard-title-input"
              :state="!titleValidationError"
              required
            />
          </gl-form-group>
          <gl-form-group
            :label="s__('Dashboards|Dashboard description (optional)')"
            label-for="description"
            :class="$options.FORM_GROUP_CLASS"
          >
            <gl-form-input
              id="description"
              v-model="mutableConfig.description"
              dir="auto"
              type="text"
              :placeholder="s__('Dashboards|Enter a dashboard description')"
              :aria-label="s__('Dashboards|Dashboard description')"
              :class="$options.FORM_INPUT_CLASS"
              data-testid="dashboard-description-input"
            />
          </gl-form-group>
        </div>
      </div>
    </template>

    <template v-if="editing" #footer>
      <gl-button
        class="gl-my-4 gl-mr-2"
        category="primary"
        variant="confirm"
        data-testid="dashboard-save-btn"
        @click="onSave"
      >
        {{ s__('Dashboards|Save your dashboard') }}
      </gl-button>
      <gl-button category="secondary" data-testid="dashboard-cancel-edit-btn" @click="onCancel">{{
        s__('Dashboards|Cancel')
      }}</gl-button>
    </template>
  </dashboard-layout>
</template>
Edited by 🤖 GitLab Bot 🤖