Browse Source

feat: parameter input mask

Ahmad Kholid 2 years ago
parent
commit
1fe95e8d51

+ 0 - 1
package.json

@@ -60,7 +60,6 @@
     "jspdf": "^2.5.1",
     "lodash.clonedeep": "^4.5.0",
     "lodash.merge": "^4.6.2",
-    "maska": "^1.5.0",
     "mitt": "^3.0.0",
     "mousetrap": "^1.6.5",
     "nanoid": "^4.0.0",

+ 10 - 0
src/components/newtab/workflow/edit/EditWorkflowParameters.vue

@@ -104,6 +104,8 @@
 import { reactive, watch } from 'vue';
 import cloneDeep from 'lodash.clonedeep';
 import * as workflowParameters from '@business/parameters';
+import ParameterInputValue from './Parameter/ParameterInputValue.vue';
+import ParameterInputOptions from './Parameter/ParameterInputOptions.vue';
 
 const props = defineProps({
   data: {
@@ -117,6 +119,13 @@ const paramTypes = {
   string: {
     id: 'string',
     name: 'Input (string)',
+    options: ParameterInputOptions,
+    valueComp: ParameterInputValue,
+    data: {
+      masks: [],
+      useMask: false,
+      unmaskValue: false,
+    },
   },
   number: {
     id: 'number',
@@ -136,6 +145,7 @@ function addParameter() {
     type: 'string',
     defaultValue: '',
     placeholder: 'Text',
+    data: paramTypes.string.data,
   });
 }
 function updateParam(index, value) {

+ 44 - 23
src/components/newtab/workflow/edit/Parameter/ParameterInputOptions.vue

@@ -10,16 +10,27 @@
       class="ml-1 text-gray-600 dark:text-gray-200"
       size="20"
     />
+    <label v-if="false" class="flex items-center ml-4">
+      <ui-switch v-model="options.unmaskValue" />
+      <span class="ml-2">Return unmask value</span>
+    </label>
   </div>
   <div v-if="options.useMask" class="mt-4">
     <p>Masks</p>
-    <div class="grid grid-cols-3 gap-4">
+    <div class="space-y-2">
       <div
         v-for="(mask, index) in options.masks"
         :key="index"
         class="flex items-center"
       >
-        <ui-input v-model="options.masks[index]" placeholder="###-###-###" />
+        <ui-input
+          v-model="options.masks[index].mask"
+          placeholder="aaa-aaa-aaa"
+        />
+        <ui-checkbox v-model="mask.isRegex" class="ml-4">
+          Is RegEx
+        </ui-checkbox>
+        <div class="flex-grow" />
         <v-remixicon
           name="riDeleteBin7Line"
           class="cursor-pointer flex-shrink-0 ml-1"
@@ -27,9 +38,6 @@
         />
       </div>
     </div>
-    <ui-button class="mt-4" @click="options.masks.push('')">
-      Add mask
-    </ui-button>
     <template v-if="false">
       <p>Custom tokens</p>
       <div class="grid grid-cols-2 gap-4">
@@ -60,7 +68,7 @@
   </div>
 </template>
 <script setup>
-import { reactive, watch } from 'vue';
+import { reactive, watch, onMounted } from 'vue';
 import cloneDeep from 'lodash.clonedeep';
 
 const props = defineProps({
@@ -77,37 +85,37 @@ const emit = defineEmits(['update:modelValue']);
 
 const maskInfo = `
 Add mask to the input field
-<p class="mt-2">Supported tokens</p>
+<p class="mt-2">Supported patterns</p>
 <table class="tokens">
 	<tbody>
 		<tr>
-			<td>#</td>
-			<td>Number (0-9)</td>
-		</tr>
-		<tr>
-			<td>X</td>
-			<td>Number (0-9) or Letter (A-Z|a-z)</td>
+			<td>0</td>
+			<td>Any digit</td>
 		</tr>
 		<tr>
-			<td>S</td>
-			<td>Letter (A-Z|a-z)</td>
+			<td>a</td>
+			<td>Any letter</td>
 		</tr>
 		<tr>
-			<td>A</td>
-			<td>Letter (A-Z)</td>
+			<td>*</td>
+			<td>Any char</td>
 		</tr>
 		<tr>
-			<td>a</td>
-			<td>Letter (a-z)</td>
+			<td>[]</td>
+			<td>Make input optional</td>
 		</tr>
 		<tr>
-			<td>!</td>
-			<td>Escape character</td>
+			<td>{}</td>
+			<td>Include fixed part in unmasked value</td>
 		</tr>
 		<tr>
-			<td>*</td>
-			<td>Repeat character</td>
+			<td>\`</td>
+			<td>Prevent symbols shift back</td>
 		</tr>
+    <tr>
+      <td>!</td>
+      <td>Escape char</td>
+    </tr>
 	<tbody>
 </table>
 `;
@@ -118,6 +126,13 @@ const options = reactive({
   ...cloneData,
 });
 
+function addMask() {
+  options.masks.push({
+    isRegex: false,
+    mask: '',
+    lazy: false,
+  });
+}
 function addToken() {
   options.customTokens.push({
     symbol: '',
@@ -132,4 +147,10 @@ watch(
   },
   { deep: true }
 );
+
+onMounted(() => {
+  if (options.masks.length === 0) {
+    addMask();
+  }
+});
 </script>

+ 46 - 0
src/components/newtab/workflow/edit/Parameter/ParameterInputValue.vue

@@ -0,0 +1,46 @@
+<template>
+  <ui-input
+    :model-value="modelValue"
+    :mask="mask"
+    type="text"
+    class="w-full"
+    :placeholder="paramData.placeholder"
+    @change="$emit('update:modelValue', $event)"
+  />
+</template>
+<script setup>
+import { computed } from 'vue';
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: '',
+  },
+  paramData: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+defineEmits(['update:modelValue']);
+
+const mask = computed(() => {
+  const options = props.paramData.data;
+  if (!options || !options.useMask) return null;
+
+  const masks = options.masks.map((item) => {
+    const cloneMask = { ...item };
+    if (cloneMask.isRegex) cloneMask.mask = new RegExp(cloneMask.mask);
+    else cloneMask.mask = cloneMask.mask.replaceAll('!', '\\');
+
+    delete cloneMask.isRegex;
+
+    return cloneMask;
+  });
+
+  if (masks.length === 1) return masks[0];
+
+  return {
+    mask: masks,
+  };
+});
+</script>

+ 95 - 83
src/components/ui/UiInput.vue

@@ -29,6 +29,7 @@
         }"
         :id="componentId"
         v-autofocus="autofocus"
+        v-imask="mask"
         :class="[
           inputClass,
           {
@@ -49,94 +50,105 @@
     </div>
   </div>
 </template>
-<script>
-import { useComponentId } from '@/composable/componentId';
+<script setup>
 /* eslint-disable vue/require-prop-types */
-export default {
-  props: {
-    modelModifiers: {
-      default: () => ({}),
-    },
-    disabled: {
-      type: Boolean,
-      default: false,
-    },
-    readonly: {
-      type: Boolean,
-      default: false,
-    },
-    autofocus: {
-      type: Boolean,
-      default: false,
-    },
-    modelValue: {
-      type: [String, Number],
-      default: '',
-    },
-    inputClass: {
-      type: String,
-      default: '',
-    },
-    prependIcon: {
-      type: String,
-      default: '',
-    },
-    label: {
-      type: String,
-      default: '',
-    },
-    list: {
-      type: String,
-      default: null,
-    },
-    type: {
-      type: String,
-      default: 'text',
-    },
-    placeholder: {
-      type: String,
-      default: '',
-    },
-    max: {
-      type: [String, Number],
-      default: null,
-    },
-    min: {
-      type: [String, Number],
-      default: null,
-    },
-    autocomplete: {
-      type: String,
-      default: null,
-    },
-    step: {
-      type: String,
-      default: null,
-    },
-  },
-  emits: ['update:modelValue', 'change', 'keydown', 'blur', 'keyup', 'focus'],
-  setup(props, { emit }) {
-    const componentId = useComponentId('ui-input');
+import { IMaskDirective as vImask } from 'vue-imask';
+import { useComponentId } from '@/composable/componentId';
+
+const props = defineProps({
+  modelModifiers: {
+    default: () => ({}),
+  },
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
+  readonly: {
+    type: Boolean,
+    default: false,
+  },
+  autofocus: {
+    type: Boolean,
+    default: false,
+  },
+  modelValue: {
+    type: [String, Number, Object],
+    default: '',
+  },
+  inputClass: {
+    type: String,
+    default: '',
+  },
+  prependIcon: {
+    type: String,
+    default: '',
+  },
+  label: {
+    type: String,
+    default: '',
+  },
+  list: {
+    type: String,
+    default: null,
+  },
+  type: {
+    type: String,
+    default: 'text',
+  },
+  placeholder: {
+    type: String,
+    default: '',
+  },
+  max: {
+    type: [String, Number],
+    default: null,
+  },
+  min: {
+    type: [String, Number],
+    default: null,
+  },
+  autocomplete: {
+    type: String,
+    default: null,
+  },
+  step: {
+    type: String,
+    default: null,
+  },
+  mask: {
+    type: [Array, Object],
+    default: null,
+  },
+  unmaskValue: Boolean,
+});
+const emit = defineEmits([
+  'update:modelValue',
+  'change',
+  'keydown',
+  'blur',
+  'keyup',
+  'focus',
+]);
 
-    function emitValue(event) {
-      let { value } = event.target;
+const componentId = useComponentId('ui-input');
 
-      if (props.modelModifiers.lowercase) {
-        value = value.toLocaleLowerCase();
-      } else if (props.modelModifiers.number) {
-        value = +value;
-      }
+function emitValue(event) {
+  let { value } = event.target;
 
-      emit('update:modelValue', value);
-      emit('change', value);
-    }
+  if (props.mask && props.unmaskValue) {
+    const { maskRef } = event.target;
+    if (maskRef && maskRef.unmaskedValue) value = maskRef.unmaskedValue;
+  }
 
-    return {
-      emitValue,
-      componentId,
-    };
-  },
-};
+  if (props.modelModifiers.lowercase) {
+    value = value.toLocaleLowerCase();
+  } else if (props.modelModifiers.number) {
+    value = +value;
+  }
+
+  emit('update:modelValue', value);
+  emit('change', value);
+}
 </script>
 <style>
 .input-ui input[type='color'] {

+ 0 - 2
src/lib/compsUi.js

@@ -1,4 +1,3 @@
-import { maska } from 'maska';
 import VTooltip from '../directives/VTooltip';
 import VAutofocus from '../directives/VAutofocus';
 import VClosePopover from '../directives/VClosePopover';
@@ -20,7 +19,6 @@ function componentsExtractor(app, components) {
 }
 
 export default function (app) {
-  app.directive('maska', maska);
   app.directive('tooltip', VTooltip);
   app.directive('autofocus', VAutofocus);
   app.directive('close-popover', VClosePopover);

+ 13 - 3
src/params/App.vue

@@ -44,8 +44,8 @@
           <ul class="space-y-2">
             <li v-for="(param, paramIdx) in workflow.params" :key="paramIdx">
               <component
-                :is="workflowParameters[param.type].valueComp"
-                v-if="workflowParameters[param.type]"
+                :is="paramsList[param.type].valueComp"
+                v-if="paramsList[param.type]"
                 v-model="param.value"
                 :label="param.name"
                 :param-data="param"
@@ -82,8 +82,18 @@
 import { onMounted, ref, computed } from 'vue';
 import browser from 'webextension-polyfill';
 import * as workflowParameters from '@business/parameters';
-import dayjs from '@/lib/dayjs';
 import { useTheme } from '@/composable/theme';
+import dayjs from '@/lib/dayjs';
+import ParameterInputValue from '@/components/newtab/workflow/edit/Parameter/ParameterInputValue.vue';
+
+const paramsList = {
+  ...workflowParameters,
+  string: {
+    id: 'string',
+    name: 'Input (string)',
+    valueComp: ParameterInputValue,
+  },
+};
 
 const theme = useTheme();
 theme.init();

+ 0 - 5
yarn.lock

@@ -4727,11 +4727,6 @@ make-dir@^3.0.2, make-dir@^3.1.0:
   dependencies:
     semver "^6.0.0"
 
-maska@^1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/maska/-/maska-1.5.0.tgz#6e2f6386d290ab7dc8b44cfc04f34891e06a1e25"
-  integrity sha512-BwZXzs5gHeu6wtn3iWFqrKRtcsM3sTpkHvfAngVNVNlN7tl9ZyQUeHTz11s9Sy7Bq1MoQ+xyR/+IzghY8nR84Q==
-
 media-typer@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"