Browse Source

feat(newtab): add dashboard page

Ahmad Kholid 3 years ago
parent
commit
27e1bd0aba

+ 1 - 0
package.json

@@ -22,6 +22,7 @@
   "dependencies": {
     "@medv/finder": "^2.1.0",
     "@vuex-orm/core": "^0.36.4",
+    "dayjs": "^1.10.7",
     "nanoid": "^3.1.25",
     "tiny-emitter": "^2.1.0",
     "tippy.js": "^6.3.1",

+ 22 - 7
src/assets/css/tailwind.css

@@ -16,13 +16,28 @@ select:focus,
 	outline: none;
 	@apply ring-2 ring-accent dark:ring-gray-200;
 }
-
-.hoverable {
-  @apply hover:bg-gray-800 hover:bg-opacity-5 dark:hover:bg-gray-200 dark:hover:bg-opacity-5;
+.text-overflow {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
-.bg-input {
-  @apply bg-black bg-opacity-5 hover:bg-opacity-10 dark:bg-gray-200 dark:bg-opacity-5 dark:hover:bg-opacity-10;
+
+.line-clamp {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
-.bg-box-transparent {
-	@apply bg-black bg-opacity-5 dark:bg-gray-200 dark:bg-opacity-5;
+
+@layer utilities {
+  .hoverable {
+    @apply hover:bg-gray-800 hover:bg-opacity-5 dark:hover:bg-gray-200 dark:hover:bg-opacity-5;
+  }
+  .bg-input {
+    @apply bg-black bg-opacity-5 hover:bg-opacity-10 dark:bg-gray-200 dark:bg-opacity-5 dark:hover:bg-opacity-10;
+  }
+  .bg-box-transparent {
+  	@apply bg-black bg-opacity-5 dark:bg-gray-200 dark:bg-opacity-5;
+  }
 }

+ 33 - 0
src/components/shared/SharedTaskList.vue

@@ -0,0 +1,33 @@
+<template>
+  <ui-list class="space-y-1">
+    <ui-list-item
+      v-for="task in tasks"
+      :key="task.name"
+      :active="task.status === 'running'"
+      class="relative group"
+      color="bg-box-transparent"
+    >
+      <ui-spinner
+        v-if="task.status === 'running'"
+        color="text-accent dark:text-gray-200"
+        size="20"
+      ></ui-spinner>
+      <v-remixicon
+        v-else-if="task.status === 'success'"
+        name="riCheckboxCircleLine"
+        class="-ml-0.5"
+      />
+      <p class="ml-3 flex-1">{{ task.name }}</p>
+    </ui-list-item>
+  </ui-list>
+</template>
+<script>
+export default {
+  props: {
+    tasks: {
+      type: Array,
+      default: () => [],
+    },
+  },
+};
+</script>

+ 94 - 0
src/components/ui/UiSelect.vue

@@ -0,0 +1,94 @@
+<template>
+  <div :class="{ 'inline-block': !block }" class="ui-select cursor-pointer">
+    <label v-if="label" :for="selectId" class="text-gray-200 text-sm ml-2">
+      {{ label }}
+    </label>
+    <div class="ui-select__content flex items-center w-full block relative">
+      <v-remixicon
+        v-if="prependIcon"
+        size="20"
+        :name="prependIcon"
+        class="absolute text-gray-600 dark:text-gray-200 left-0 ml-2"
+      />
+      <select
+        :id="selectId"
+        :class="{ 'pl-8': prependIcon }"
+        :value="modelValue"
+        class="
+          px-4
+          pr-10
+          transition
+          rounded-lg
+          bg-input bg-transparent
+          py-2
+          z-10
+          appearance-none
+          w-full
+          h-full
+          appearance-none
+        "
+        @change="emitValue"
+      >
+        <option v-if="placeholder" value="" disabled selected>
+          {{ placeholder }}
+        </option>
+        <slot></slot>
+      </select>
+      <v-remixicon
+        size="28"
+        name="riArrowDropDownLine"
+        class="absolute text-gray-600 dark:text-gray-200 mr-2 right-0"
+      />
+    </div>
+  </div>
+</template>
+<script>
+import { useComponentId } from '@/composable/componentId';
+
+export default {
+  props: {
+    modelValue: {
+      type: [String, Number],
+      default: '',
+    },
+    label: {
+      type: String,
+      default: '',
+    },
+    prependIcon: {
+      type: String,
+      default: '',
+    },
+    placeholder: {
+      type: String,
+      default: '',
+    },
+    block: Boolean,
+    showDetail: Boolean,
+  },
+  emits: ['update:modelValue', 'change'],
+  setup(props, { emit }) {
+    const selectId = useComponentId('select');
+
+    function emitValue({ target }) {
+      emit('update:modelValue', target.value);
+      emit('change', target.value);
+    }
+
+    return {
+      selectId,
+      emitValue,
+    };
+  },
+};
+</script>
+<style>
+.ui-select__arrow {
+  top: 50%;
+  transform: translateY(-50%) rotate(90deg);
+}
+.ui-select option,
+.ui-select optgroup {
+  @apply bg-gray-100 dark:bg-gray-700;
+}
+</style>

+ 9 - 0
src/composable/componentId.js

@@ -0,0 +1,9 @@
+let id = 0;
+
+export function useComponentId(prefix) {
+  id += 1;
+
+  if (!prefix) return id;
+
+  return `${prefix}--${id}`;
+}

+ 12 - 0
src/lib/v-remixicon.js

@@ -15,6 +15,12 @@ import {
   riCheckboxCircleLine,
   riFlowChart,
   riHistoryLine,
+  riArrowDropDownLine,
+  riAddLine,
+  riSortAsc,
+  riSortDesc,
+  riGlobalLine,
+  riMore2Line,
 } from 'v-remixicon/icons';
 
 vRemixicon.add({
@@ -33,6 +39,12 @@ vRemixicon.add({
   riCheckboxCircleLine,
   riFlowChart,
   riHistoryLine,
+  riArrowDropDownLine,
+  riAddLine,
+  riSortAsc,
+  riSortDesc,
+  riGlobalLine,
+  riMore2Line,
   mdiDrag:
     'M7,19V17H9V19H7M11,19V17H13V19H11M15,19V17H17V19H15M7,15V13H9V15H7M11,15V13H13V15H11M15,15V13H17V15H15M7,11V9H9V11H7M11,11V9H13V11H11M15,11V9H17V11H15M7,7V5H9V7H7M11,7V5H13V7H11M15,7V5H17V7H15Z',
 });

+ 1 - 1
src/newtab/App.vue

@@ -1,6 +1,6 @@
 <template>
   <app-sidebar />
-  <main class="pl-20">
+  <main class="pl-16 container mx-auto pr-2 py-6">
     <router-view />
   </main>
 </template>

+ 93 - 1
src/newtab/pages/Home.vue

@@ -1,3 +1,95 @@
 <template>
-  <p>from home</p>
+  <h1 class="text-2xl font-semibold mb-8">Dashboard</h1>
+  <div class="flex items-start">
+    <div class="w-7/12 mr-8">
+      <div class="grid gap-3 mb-8 grid-cols-3">
+        <ui-card v-for="i in 3" :key="i" class="hover:ring-2 hover:ring-accent">
+          <div class="flex items-center mb-3">
+            <span class="p-2 rounded-full bg-accent text-white inline-block">
+              <v-remixicon name="riGlobalLine" />
+            </span>
+            <div class="flex-grow"></div>
+            <button title="Execute">
+              <v-remixicon name="riPlayLine" />
+            </button>
+          </div>
+          <router-link to="/workflow">
+            <p class="line-clamp leading-tight font-semibold">Workflow name</p>
+            <p
+              class="
+                text-gray-600
+                dark:text-gray-200
+                leading-tight
+                text-overflow
+              "
+            >
+              3 Days ago
+            </p>
+          </router-link>
+        </ui-card>
+      </div>
+      <div>
+        <div class="mb-4 flex items-center justify-between">
+          <p class="font-semibold inline-block text-lg">Logs</p>
+          <router-link to="/logs" class="text-gray-600 dark:text-gray-200">
+            See all
+          </router-link>
+        </div>
+        <table class="w-full table-fixed">
+          <tbody class="divide-y">
+            <tr v-for="i in 10" :key="i" class="hoverable">
+              <td class="p-2 w-6/12 text-overflow">
+                Lorem ipsum dolor sit amet
+              </td>
+              <td class="p-2 text-gray-600 dark:text-gray-200">
+                {{ i + 1 }} Days ago
+              </td>
+              <td class="p-2 text-right">
+                <span
+                  class="
+                    inline-block
+                    py-1
+                    px-2
+                    text-sm text-green-700
+                    bg-green-500/10
+                    rounded-lg
+                  "
+                >
+                  Success
+                </span>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+    <ui-card class="flex-1">
+      <!-- <p class="mb-4">Running workflow</p> -->
+      <div class="flex items-center mb-4">
+        <span class="p-2 rounded-full bg-accent text-white inline-block">
+          <v-remixicon name="riGlobalLine" />
+        </span>
+        <div class="flex-grow"></div>
+        <ui-button class="mr-3">
+          <v-remixicon class="-ml-1 mr-1" name="riPauseLine" />
+          <span>Pause</span>
+        </ui-button>
+        <ui-button variant="accent">
+          <v-remixicon class="-ml-1 mr-1" name="riStopLine" />
+          <span>Stop</span>
+        </ui-button>
+      </div>
+      <p class="mb-2 text-lg font-semibold">Workflow name</p>
+      <shared-task-list class="bg-gray-100 rounded-lg p-2" :tasks="tasks" />
+    </ui-card>
+  </div>
 </template>
+<script setup>
+import SharedTaskList from '@/components/shared/SharedTaskList.vue';
+
+const tasks = [
+  { name: 'Open website', status: 'success' },
+  { name: 'Get data', status: 'success' },
+  { name: 'Close web', status: 'running' },
+];
+</script>

+ 31 - 1
src/newtab/pages/Workflows.vue

@@ -1,3 +1,33 @@
 <template>
-  <p>lll</p>
+  <h1 class="text-2xl font-semibold mb-5">Workflows</h1>
+  <div class="flex items-center space-x-4">
+    <ui-input
+      prepend-icon="riSearch2Line"
+      placeholder="Search..."
+      class="flex-1"
+    />
+    <div class="flex items-center workflow-sort">
+      <ui-button icon class="rounded-r-none border-gray-300 border-r">
+        <v-remixicon name="riSortAsc" />
+      </ui-button>
+      <ui-select placeholder="Sort by">
+        <option v-for="sort in sorts" :key="sort.id" :value="sort.id">
+          {{ sort.name }}
+        </option>
+      </ui-select>
+    </div>
+    <ui-button variant="accent"> New Workflow </ui-button>
+  </div>
 </template>
+<script setup>
+const sorts = [
+  { name: 'Name', id: 'name' },
+  { name: 'Created date', id: 'createdAt' },
+  { name: 'Running date', id: 'lastRunAt' },
+];
+</script>
+<style>
+.workflow-sort select {
+  @apply rounded-l-none !important;
+}
+</style>

+ 3 - 32
src/popup/pages/Workflow.vue

@@ -22,41 +22,12 @@
       </ui-button>
     </ui-card>
     <p class="mb-1">Tasks</p>
-    <ui-list class="space-y-1">
-      <ui-list-item
-        v-for="task in tasks"
-        :key="task.name"
-        :active="task.status === 'running'"
-        class="relative group"
-        color="bg-box-transparent"
-      >
-        <v-remixicon
-          name="mdiDrag"
-          class="
-            absolute
-            left-0
-            -ml-3
-            group-hover:visible
-            invisible
-            cursor-move
-          "
-        />
-        <ui-spinner
-          v-if="task.status === 'running'"
-          color="text-accent"
-          size="20"
-        ></ui-spinner>
-        <v-remixicon
-          v-else-if="task.status === 'success'"
-          name="riCheckboxCircleLine"
-          class="-ml-0.5"
-        />
-        <p class="ml-3 flex-1">{{ task.name }}</p>
-      </ui-list-item>
-    </ui-list>
+    <shared-task-list :tasks="tasks" />
   </div>
 </template>
 <script setup>
+import SharedTaskList from '@/components/shared/SharedTaskList.vue';
+
 const tasks = [
   { name: 'Open website', status: 'success' },
   { name: 'Get data', status: 'success' },

+ 5 - 0
yarn.lock

@@ -2244,6 +2244,11 @@ csstype@^2.6.8:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.18.tgz#980a8b53085f34af313410af064f2bd241784218"
   integrity sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==
 
+dayjs@^1.10.7:
+  version "1.10.7"
+  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
+  integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
+
 debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"