Skip to content
Snippets Groups Projects
App.vue 16.14 KiB
<script setup>
import DragGrid from "../../src/DragGrid.vue";
import CircularCard from "./components/CircularCard.vue";
import NumberCounter from "./components/NumberCounter.vue";
</script>

<template>
  <div id="app">
    <h1><code>DragGrid</code> Examples</h1>
    <h2>Example 1</h2>
    <p>
      Grid with two programmatically blocked cells and one programmatically
      blocked item
    </p>
    <DragGrid
      :rows="8"
      :cols="5"
      :pos-validation="blockField"
      v-model="items"
      class="bordered"
    >
      <div id="blocker">
        This field is blocked because it's filled, the next one programmatically
      </div>
      <template #item="item">
        <div class="container">{{ item }}</div>
      </template>
    </DragGrid>

    <h2>Example 2: "Tic-Tac-Toe"</h2>
    <p>
      A grid functioning as a playing field and another functioning as a
      container for playing pieces. You can drag as many pieces as you want from
      the container to the field.
    </p>
    <div class="ttt-container">
      <DragGrid
        :rows="3"
        :cols="3"
        v-model="ticTacToe1"
        class="tic-tac-toe"
        context="ticTacToe"
        :validate-element="randomKey"
      >
        <template #item="item">
          <CircularCard>
            {{ item.key.startsWith("a") ? "X" : "O" }}
          </CircularCard>
        </template>
      </DragGrid>
      <div>
        <p>
          These two are two different grids but we can drag from right to left!
        </p>
        <p>Drag items from the container on the right to play on the left.</p>
      </div>
      <DragGrid
        :rows="1"
        :cols="2"
        v-model="ticTacToe2"
        class="tic-tac-toe"
        context="ticTacToe"
        :pos-validation="blockAllMoving"
      >
        <template #item="item">
          <CircularCard>
            {{ item.data.text }}
          </CircularCard>
        </template>
      </DragGrid>
    </div>

    <h2>Example 3: Counters</h2>
    <p>
      Showcasing local and global state: The local state of the counter
      components doesn't change when moved inside one grid, but is cleared when
      moved to the other. The global state is in the <code>data</code> attribute
      of each item and gets transferred as well. Both grids have also custom
      highlights.
    </p>
    <div class="ttt-container">
      <drag-grid
        v-model="counters"
        :cols="3"
        :rows="2"
        id="c-grid"
        @input="logInput"
        context="counter"
      >
        <template #item="item">
          <number-counter v-model="item.data.num"></number-counter>
        </template>
        <template #highlight>
          <div ref="highlight" class="custom-highlight">
            Das hier ist das Highlight
          </div>
        </template>
      </drag-grid>
      <span>← Drag here please →</span>
      <drag-grid v-model="counters2" :cols="3" :rows="2" context="counter">
        <template #item="item">
          <number-counter v-model="item.data.num"></number-counter>
        </template>
        <template #highlight>
          <div ref="highlight" class="custom-highlight">
            Das hier ist das Highlight
          </div>
        </template>
      </drag-grid>
    </div>

    <h2>Example 4: Dynamic lessons</h2>
    <p>
      These lessons are loaded from <code>computed</code> to simulate a
      non-editable source like an API. they are changed using the method
      <code>handleLessonMoved</code>. Try changing the sizes of Lesson1, the
      grid and the texts of the items! The lesson container on the side doesn't
      have a highlight and doesn't keep track of the item position. Try moving
      <code>item3</code> back and forth!
    </p>
    <div class="ttt-container">
      <div>
        <label>
          Lesson 1 has length: {{ lesson1Length }}
          <input
            type="range"
            v-model="lesson1Length"
            min="1"
            max="5"
            step="1"
          />
        </label>
        <input type="text" v-model="lessonData.lesson1" />
      </div>
      <div>
        <label>
          Lesson grid has width: {{ lesson1Cols }}
          <input type="range" v-model="lesson1Cols" min="2" max="5" step="1" />
        </label>
        <input type="text" v-model="lessonData.lesson2" />
      </div>
      <div>
        <label>
          Lesson grid has height: {{ lesson1Rows }}
          <input type="range" v-model="lesson1Rows" min="6" max="10" step="1" />
          <input type="text" v-model="lessonData.lesson3.inside" />
          <input type="text" v-model="lessonData.lesson3.outside" />
        </label>
      </div>

      <drag-grid
        :value="lessons1"
        :cols="parseInt(lesson1Cols)"
        :rows="parseInt(lesson1Rows)"
        class="bordered"
        context="lesson"
        grid-id="lesson-plan"
        @itemChanged="handleLessonMoved"
      >
        <template #item="item">
          <div class="container">{{ item }}</div>
        </template>
      </drag-grid>
      <drag-grid
        :value="lessons1"
        :cols="1"
        :rows="10"
        context="lesson"
        grid-id="lesson-storage"
        @itemChanged="handleLessonDropInContainer"
        no-highlight
      >
        <template #item="item">
          <div class="container">{{ item }}</div>
        </template>
      </drag-grid>
    </div>

    <h2>Example 5: Dynamic colors</h2>
    <p>
      This example showcases the <code>rawItem</code> with the custom method
      <code>getColor</code>. Both grids on the outside call the method with
      <code>"red"</code> while the one in the middle uses <code>"green"</code>.
      We use arrays called <code>placedA</code>, <code>placedB</code> and
      <code>placedC</code> to save, which item is contained in which grid.
      Notice: the third grid doesn't save the item position, they are always
      positioned automatically.
    </p>
    <div class="ttt-container">
      <drag-grid
        :value="colorGrid"
        :cols="4"
        :rows="4"
        grid-id="gridA"
        context="colors"
        class="bordered color-grid"
        @itemChanged="handleColorItemChange"
      >
        <template #item="item">
          <div
            :style="{
              background: item.rawItem.getColor('red'),
              color: 'white',
            }"
          >
            {{ item.data.name }}
          </div>
        </template>
      </drag-grid>
      <drag-grid
        :value="colorGrid"
        :cols="4"
        :rows="4"
        grid-id="gridB"
        context="colors"
        class="bordered color-grid"
        @itemChanged="handleColorItemChange"
      >
        <template #item="item">
          <div
            :style="{
              background: item.rawItem.getColor('green'),
              color: 'black',
            }"
          >
            {{ item.data.name }}
          </div>
        </template>
      </drag-grid>
      <drag-grid
        :value="colorGrid"
        :cols="4"
        :rows="4"
        grid-id="gridC"
        context="colors"
        class="bordered color-grid"
        @itemChanged="handleColorItemChange"
      >
        <template #item="item">
          <div
            :style="{
              background: item.rawItem.getColor('red'),
              color: 'white',
            }"
          >
            {{ item.data.name }}
          </div>
        </template>
      </drag-grid>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    blockField(x, y, key) {
      // We won't move fields with ID 'obj8' and nothing into (3, 3) and (4, 3)
      if (x === 3 && y === 3) return false;
      if (x === 4 && y === 3) return false;
      return key !== "obj8";
    },
    blockAllMoving() {
      return false;
    },
    randomKey(element) {
      if (element.key.length !== 1) return;
      element.key += Math.random().toString(36).replace("0.", "");
    },
    logInput(input) {
      console.log("New movement detected:", input);
    },
    handleLessonMoved(lesson) {
      this.logInput(lesson);
      switch (lesson.key) {
        case "lesson1":
          this.lesson1X = lesson.x;
          this.lesson1Y = lesson.y;
          break;
        case "lesson2":
          this.lesson2X = lesson.x;
          this.lesson2Y = lesson.y;
          break;
        case "lesson3":
          this.lesson3X = lesson.x;
          this.lesson3Y = lesson.y;
          this.lesson3inPlan = true;
          break;
        default:
          console.error("Something is wrong here!");
      }
    },
    handleLessonDropInContainer(lesson) {
      if (lesson.key === "lesson3") this.lesson3inPlan = false;
    },
    contains(array, search) {
      return array.filter((obj) => obj === search).length > 0;
    },
    findAndRemove(array, element) {
      let index = array.findIndex((i) => {
        return i === element;
      });
      if (index >= 0) array.splice(index, 1);
    },
    handleColorItemChange(item) {
      this[item.key].x = item.x;
      this[item.key].y = item.y;

      if (item.gridId === item.originGridId) return;

      switch (item.gridId) {
        case "gridA":
          // Moved to gridA
          this.findAndRemove(
            item.originGridId === "gridB" ? this.placedB : this.placedC,
            item.key
          );
          this.placedA.push(item.key);
          break;
        case "gridB":
          // Moved to gridB
          this.findAndRemove(
            item.originGridId === "gridA" ? this.placedA : this.placedC,
            item.key
          );
          this.placedB.push(item.key);
          break;
        case "gridC":
          // Moved to gridC
          this.findAndRemove(
            item.originGridId === "gridB" ? this.placedB : this.placedA,
            item.key
          );
          this.placedC.push(item.key);
          break;
      }
    },
  },
  data() {
    return {
      items: [
        { x: 1, y: 3, w: 1, h: 1, key: "obj1", data: {} },
        { x: 2, y: 1, w: 1, h: 1, key: "obj2", data: {} },
        { x: 3, y: 1, w: 2, h: 2, key: "obj3", data: {} },
        { x: 5, y: 2, w: 1, h: 1, key: "obj4", data: {} },
        { x: 1, y: 1, w: 1, h: 2, key: "obj5", data: {} },
        { x: 5, y: 1, w: 1, h: 1, key: "obj6", data: {} },
        { x: 2, y: 2, w: 1, h: 3, key: "obj7", data: {} },
        {
          x: 1,
          y: 4,
          w: 1,
          h: 1,
          key: "obj8",
          data: { title: "I'm blocked from moving!" },
        },
        { x: 5, y: 3, w: 1, h: 1, key: "obj9", data: {} },
      ],
      ticTacToe1: [
        { x: 1, y: 1, w: 1, h: 1, key: "a1", data: { text: "X" } },
        { x: 3, y: 3, w: 1, h: 1, key: "b1", data: { text: "O" } },
      ],
      ticTacToe2: [
        { x: 1, y: 1, w: 1, h: 1, key: "a", data: { text: "X" } },
        { x: 2, y: 1, w: 1, h: 1, key: "b", data: { text: "O" } },
      ],
      counters: [
        { x: 1, y: 1, w: 1, h: 1, key: "a", data: { num: 1 } },
        { x: 2, y: 1, w: 1, h: 1, key: "b", data: { num: 4 } },
        { x: 3, y: 2, w: 1, h: 1, key: "c", data: { num: -1 } },
      ],
      counters2: [
        { x: 1, y: 2, w: 1, h: 1, key: "d", data: { num: 30 } },
        { x: 3, y: 1, w: 1, h: 1, key: "e", data: { num: 3 } },
      ],
      lesson1Length: 1,
      lesson1Cols: 4,
      lesson1Rows: 6,
      lesson1X: 1,
      lesson1Y: 1,
      lesson2X: 2,
      lesson2Y: 1,
      lesson3X: -1,
      lesson3Y: -1,
      lesson3inPlan: false,
      lessonData: {
        lesson1: "Nothing",
        lesson2: "Hallo",
        lesson3: {
          inside: "This is lesson 3 inside the plan",
          outside: "This is lesson3 outside of the plan",
        },
      },
      placedA: ["item1", "item2"],
      placedB: ["item3", "item4"],
      placedC: ["item5", "item6"],
      item1: {
        x: 1,
        y: 1,
      },
      item2: {
        x: 2,
        y: 2,
      },
      item3: {
        x: 1,
        y: 1,
      },
      item4: {
        x: 3,
        y: 3,
      },
      item5: {
        x: 2,
        y: 4,
      },
      item6: {
        x: 4,
        y: 2,
      },
    };
  },
  computed: {
    lessons1() {
      return [
        {
          x: (grid) => {
            return grid.gridId === "lesson-plan" ? this.lesson1X : 0;
          },
          y: (grid) => {
            return grid.gridId === "lesson-plan" ? this.lesson1Y : 0;
          },
          w: 1,
          h: this.lesson1Length,
          key: "lesson1",
          data: {
            text: this.lessonData.lesson1,
          },
        },
        {
          x: (grid) => {
            return grid.gridId === "lesson-plan" ? this.lesson2X : 0;
          },
          y: (grid) => {
            return grid.gridId === "lesson-plan" ? this.lesson2Y : 0;
          },
          w: 1,
          h: parseInt(this.lesson1Length) + 1,
          key: "lesson2",
          data: {
            text: this.lessonData.lesson2,
          },
        },
        {
          x: (grid) => {
            if (this.lesson3inPlan) {
              return grid.gridId === "lesson-plan" ? this.lesson3X : -1;
            }
            return grid.gridId === "lesson-plan" ? -1 : 0;
          },
          y: (grid) => {
            if (this.lesson3inPlan) {
              return grid.gridId === "lesson-plan" ? this.lesson3Y : -1;
            }
            return grid.gridId === "lesson-plan" ? -1 : 0;
          },
          w: 1,
          h: parseInt(this.lesson1Length) + 1,
          key: "lesson3",
          data: (grid) => {
            return {
              text:
                grid.gridId === "lesson-storage"
                  ? this.lessonData.lesson3.outside
                  : this.lessonData.lesson3.inside,
            };
          },
        },
      ];
    },
    colorGrid() {
      let items = ["item1", "item2", "item3", "item4", "item5", "item6"];
      let greens = [
        "green",
        "seagreen",
        "greenyellow",
        "darkolivegreen",
        "limegreen",
        "palegreen",
      ];
      let reds = [
        "darkred",
        "indianred",
        "orangered",
        "red",
        "maroon",
        "firebrick",
      ];
      return items.map((itemName) => {
        return {
          key: itemName,
          x: (grid) => {
            if (grid.gridId === "gridA") {
              return this.contains(this.placedA, itemName)
                ? this[itemName].x
                : -1;
            } else if (grid.gridId === "gridB") {
              return this.contains(this.placedB, itemName)
                ? this[itemName].x
                : -1;
            } else {
              // gridC
              return this.contains(this.placedC, itemName) ? 0 : -1;
            }
          },
          y: (grid) => {
            if (grid.gridId === "gridA") {
              return this.contains(this.placedA, itemName)
                ? this[itemName].y
                : -1;
            } else if (grid.gridId === "gridB") {
              return this.contains(this.placedB, itemName)
                ? this[itemName].y
                : -1;
            } else {
              // gridC
              return this.contains(this.placedC, itemName) ? 0 : -1;
            }
          },
          w: 1,
          h: 1,
          data: {
            name: itemName,
          },
          getColor: (a) =>
            a === "red"
              ? reds[parseInt(itemName[4]) - 1]
              : greens[parseInt(itemName[4]) - 1],
        };
      });
    },
  },
};
</script>

<style scoped>
#blocker {
  grid-row: 3 / span 1;
  grid-column: 3 / span 1;
  background-image: linear-gradient(
    45deg,
    #edd85f 25%,
    #0f2b3d 25%,
    #0f2b3d 50%,
    #edd85f 50%,
    #edd85f 75%,
    #0f2b3d 75%,
    #0f2b3d 100%
  );
  background-size: 56.57px 56.57px;
  color: white;
  font-size: large;
  font-weight: bold;
  text-shadow: 4px 4px 4px #2c3e50;
}

.container {
  background: lightcoral;
  width: 100%;
  height: 100%;
}

.tic-tac-toe {
  max-width: 400px;
}

.ttt-container {
  display: flex;
  justify-content: space-between;
}

.custom-highlight {
  display: flex;
  align-items: center;
  justify-content: center;
  background: aquamarine;
  width: 100%;
  height: 100%;
}

.bordered {
  border: 2px solid grey;
}

.color-grid {
  min-height: 11em;
  line-height: 2em;
  box-sizing: content-box;
}

h2 {
  padding-top: 1em;
}

h2 + p {
  padding-bottom: 0.5em;
}

code {
  background: lightgray;
  padding: 0.2em;
  border-radius: 3px;
}
</style>