refactor(ui): replaced some n-input with c-input-text

This commit is contained in:
Corentin Thomasset 2023-05-14 21:26:18 +02:00 committed by Corentin THOMASSET
parent aad8d84e13
commit 77f2efc0b9
39 changed files with 738 additions and 448 deletions

72
auto-imports.d.ts vendored
View file

@ -19,7 +19,9 @@ declare global {
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
const customRef: typeof import('vue')['customRef']
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
@ -39,9 +41,6 @@ declare global {
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const logicAnd: typeof import('@vueuse/core')['logicAnd']
const logicNot: typeof import('@vueuse/core')['logicNot']
const logicOr: typeof import('@vueuse/core')['logicOr']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
@ -92,8 +91,9 @@ declare global {
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRef: typeof import('@vueuse/core')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('@vueuse/core')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
@ -104,6 +104,19 @@ declare global {
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
@ -114,8 +127,8 @@ declare global {
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useCached: typeof import('@vueuse/core')['useCached']
const useClamp: typeof import('@vueuse/core')['useClamp']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
const useCounter: typeof import('@vueuse/core')['useCounter']
@ -189,12 +202,18 @@ declare global {
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const useParentElement: typeof import('@vueuse/core')['useParentElement']
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
@ -208,14 +227,17 @@ declare global {
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
const useSorted: typeof import('@vueuse/core')['useSorted']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStepper: typeof import('@vueuse/core')['useStepper']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
@ -227,6 +249,8 @@ declare global {
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToNumber: typeof import('@vueuse/core')['useToNumber']
const useToString: typeof import('@vueuse/core')['useToString']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
@ -247,8 +271,10 @@ declare global {
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchDeep: typeof import('@vueuse/core')['watchDeep']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchPostEffect: typeof import('vue')['watchPostEffect']
@ -282,7 +308,9 @@ declare module 'vue' {
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
@ -302,9 +330,6 @@ declare module 'vue' {
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly logicAnd: UnwrapRef<typeof import('@vueuse/core')['logicAnd']>
readonly logicNot: UnwrapRef<typeof import('@vueuse/core')['logicNot']>
readonly logicOr: UnwrapRef<typeof import('@vueuse/core')['logicOr']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
@ -355,8 +380,9 @@ declare module 'vue' {
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRef: UnwrapRef<typeof import('@vueuse/core')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('@vueuse/core')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
@ -367,6 +393,19 @@ declare module 'vue' {
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
@ -377,8 +416,8 @@ declare module 'vue' {
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClamp: UnwrapRef<typeof import('@vueuse/core')['useClamp']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
@ -452,12 +491,18 @@ declare module 'vue' {
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
@ -471,14 +516,17 @@ declare module 'vue' {
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
@ -490,6 +538,8 @@ declare module 'vue' {
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
@ -510,8 +560,10 @@ declare module 'vue' {
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>

5
components.d.ts vendored
View file

@ -56,7 +56,12 @@ declare module '@vue/runtime-core' {
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
HtmlWysiwygEditor: typeof import('./src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue')['default']
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
IconMdiEye: typeof import('~icons/mdi/eye')['default']
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default']
Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default']

View file

@ -1,5 +1,5 @@
<template>
<n-form-item :label="inputLabel" v-bind="validationAttrs">
<n-form-item :label="inputLabel" v-bind="validationAttrs as any">
<n-input
ref="inputElement"
v-model:value="input"
@ -10,7 +10,7 @@
autocorrect="off"
autocapitalize="off"
spellcheck="false"
:input-props="{ 'data-test-id': 'input' }"
:input-props="{ 'data-test-id': 'input' } as any"
/>
</n-form-item>
<n-form-item :label="outputLabel">

View file

@ -1,21 +1,20 @@
<template>
<n-input v-model:value="value">
<c-input-text v-model:value="value">
<template #suffix>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" @click="onCopyClicked">
<n-icon :component="ContentCopyFilled" />
<c-button circle variant="text" size="small" @click="onCopyClicked">
<icon-mdi-content-copy />
</c-button>
</template>
{{ tooltipText }}
</n-tooltip>
</template>
</n-input>
</c-input-text>
</template>
<script setup lang="ts">
import { useVModel, useClipboard } from '@vueuse/core';
import { ContentCopyFilled } from '@vicons/material';
import { ref } from 'vue';
const props = defineProps<{ value: string }>();
@ -35,9 +34,3 @@ function onCopyClicked() {
}, 2000);
}
</script>
<style scoped>
::v-deep(.n-input-wrapper) {
padding-right: 5px;
}
</style>

View file

@ -20,7 +20,7 @@
</c-card>
<c-card title="Base64 to string">
<n-form-item label="Base64 string to decode" v-bind="b64Validation.attrs">
<n-form-item label="Base64 string to decode" v-bind="b64Validation.attrs as any">
<n-input v-model:value="base64Input" type="textarea" placeholder="Your base64 string..." rows="5" />
</n-form-item>

View file

@ -1,17 +1,15 @@
<template>
<div>
<n-form-item label="Username">
<n-input v-model:value="username" placeholder="Your username..." clearable />
</n-form-item>
<n-form-item label="Password">
<n-input
v-model:value="password"
placeholder="Your password..."
type="password"
show-password-on="click"
clearable
/>
</n-form-item>
<c-input-text v-model:value="username" label="Username" placeholder="Your username..." clearable raw-text mb-5 />
<c-input-text
v-model:value="password"
label="Password"
placeholder="Your password..."
clearable
raw-text
mb-2
type="password"
/>
<c-card>
<n-statistic label="Authorization header:" class="header">

View file

@ -1,21 +1,20 @@
<template>
<c-card title="Hash">
<n-form label-width="120">
<n-form-item label="Your string: " label-placement="left">
<n-input
v-model:value="input"
placeholder="Your string to bcrypt..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<n-form-item label="Salt count: " label-placement="left">
<n-input-number v-model:value="saltCount" placeholder="Salt rounds..." :max="10" :min="0" w-full />
</n-form-item>
<n-input :value="hashed" readonly style="text-align: center" />
</n-form>
<c-input-text
v-model:value="input"
placeholder="Your string to bcrypt..."
raw-text
label="Your string: "
label-position="left"
label-width="120px"
mb-2
/>
<n-form-item label="Salt count: " label-placement="left" label-width="120">
<n-input-number v-model:value="saltCount" placeholder="Salt rounds..." :max="10" :min="0" w-full />
</n-form-item>
<c-input-text :value="hashed" readonly text-center />
<n-space justify="center" mt-5>
<c-button @click="copy"> Copy hash </c-button>
</n-space>
@ -24,24 +23,10 @@
<c-card title="Compare string with hash">
<n-form label-width="120">
<n-form-item label="Your string: " label-placement="left">
<n-input
v-model:value="compareString"
placeholder="Your string to compare..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
<c-input-text v-model:value="compareString" placeholder="Your string to compare..." raw-text />
</n-form-item>
<n-form-item label="Your hash: " label-placement="left">
<n-input
v-model:value="compareHash"
placeholder="Your hahs to compare..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
<c-input-text v-model:value="compareHash" placeholder="Your hahs to compare..." raw-text />
</n-form-item>
<n-form-item label="Do they match ? " label-placement="left" :show-feedback="false">
<div class="compare-result" :class="{ positive: compareMatch }">

View file

@ -1,11 +1,15 @@
<template>
<n-scrollbar style="flex: 1" x-scrollable>
<n-space :wrap="false" style="flex: 1" justify="center" :size="0" mb-5>
<n-space :wrap="false" style="flex: 1" justify="center" :size="12" mb-5>
<div v-for="(suite, index) of suites" :key="index">
<c-card style="width: 292px; margin: 0 8px 5px">
<n-form-item label="Suite name:" :show-feedback="false" label-placement="left">
<n-input v-model:value="suite.title" placeholder="Suite name..." />
</n-form-item>
<c-card style="width: 294px">
<c-input-text
v-model:value="suite.title"
label-position="left"
label="Suite name"
placeholder="Suite name..."
clearable
/>
<n-divider></n-divider>
<n-form-item label="Suite values" :show-feedback="false">
@ -33,9 +37,7 @@
<div style="flex: 0 0 100%">
<div style="max-width: 600px; margin: 0 auto">
<n-space justify="center">
<n-form-item label="Unit:" label-placement="left">
<n-input v-model:value="unit" placeholder="Unit (eg: ms)" />
</n-form-item>
<c-input-text v-model:value="unit" placeholder="Unit (eg: ms)" label="Unit" label-position="left" mb-4 />
<c-button
@click="

View file

@ -16,7 +16,8 @@
:validation-status="entropyValidation.status"
>
<n-input-group>
<n-input v-model:value="entropy" placeholder="Your string..." />
<c-input-text v-model:value="entropy" placeholder="Your string..." />
<c-button @click="refreshEntropy">
<n-icon size="22">
<Refresh />
@ -37,15 +38,7 @@
:validation-status="mnemonicValidation.status"
>
<n-input-group>
<n-input
v-model:value="passphrase"
style="text-align: center; flex: 1"
placeholder="Your mnemonic..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
<c-input-text v-model:value="passphrase" placeholder="Your mnemonic..." raw-text />
<c-button @click="copyPassphrase">
<n-icon size="22" :component="Copy" />

View file

@ -1,9 +1,15 @@
<template>
<c-card>
<n-form label-width="120" label-placement="left" :show-feedback="false">
<n-form-item label="Your string:">
<n-input v-model:value="input" />
</n-form-item>
<c-input-text
v-model:value="input"
label="Your string"
label-position="left"
label-width="120px"
label-align="right"
placeholder="Your string..."
raw-text
/>
<n-divider />

View file

@ -4,8 +4,8 @@
<div class="duration">{{ formatMs(counter) }}</div>
</c-card>
<n-space justify="center" mt-5>
<c-button v-if="!isRunning" secondary type="primary" @click="resume">Start</c-button>
<c-button v-else secondary type="warning" @click="pause">Stop</c-button>
<c-button v-if="!isRunning" type="primary" @click="resume">Start</c-button>
<c-button v-else type="warning" @click="pause">Stop</c-button>
<c-button @click="counter = 0">Reset</c-button>
</n-space>

View file

@ -9,25 +9,25 @@
/>
</n-form-item>
<n-form-item label="color name:">
<input-copyable v-model:value="name" :on-input="(v: string) => onInputUpdated(v, 'name')" />
<input-copyable v-model:value="name" @update:value="(v: string) => onInputUpdated(v, 'name')" />
</n-form-item>
<n-form-item label="hex:">
<input-copyable v-model:value="hex" :on-input="(v: string) => onInputUpdated(v, 'hex')" />
<input-copyable v-model:value="hex" @update:value="(v: string) => onInputUpdated(v, 'hex')" />
</n-form-item>
<n-form-item label="rgb:">
<input-copyable v-model:value="rgb" :on-input="(v: string) => onInputUpdated(v, 'rgb')" />
<input-copyable v-model:value="rgb" @update:value="(v: string) => onInputUpdated(v, 'rgb')" />
</n-form-item>
<n-form-item label="hsl:">
<input-copyable v-model:value="hsl" :on-input="(v: string) => onInputUpdated(v, 'hsl')" />
<input-copyable v-model:value="hsl" @update:value="(v: string) => onInputUpdated(v, 'hsl')" />
</n-form-item>
<n-form-item label="hwb:">
<input-copyable v-model:value="hwb" :on-input="(v: string) => onInputUpdated(v, 'hwb')" />
<input-copyable v-model:value="hwb" @update:value="(v: string) => onInputUpdated(v, 'hwb')" />
</n-form-item>
<n-form-item label="lch:">
<input-copyable v-model:value="lch" :on-input="(v: string) => onInputUpdated(v, 'lch')" />
<input-copyable v-model:value="lch" @update:value="(v: string) => onInputUpdated(v, 'lch')" />
</n-form-item>
<n-form-item label="cmyk:">
<input-copyable v-model:value="cmyk" :on-input="(v: string) => onInputUpdated(v, 'cmyk')" />
<input-copyable v-model:value="cmyk" @update:value="(v: string) => onInputUpdated(v, 'cmyk')" />
</n-form-item>
</n-form>
</c-card>
@ -54,15 +54,19 @@ const cmyk = ref('');
const lch = ref('');
function onInputUpdated(value: string, omit: string) {
const color = colord(value);
try {
const color = colord(value);
if (omit !== 'name') name.value = color.toName({ closest: true }) ?? '';
if (omit !== 'hex') hex.value = color.toHex();
if (omit !== 'rgb') rgb.value = color.toRgbString();
if (omit !== 'hsl') hsl.value = color.toHslString();
if (omit !== 'hwb') hwb.value = color.toHwbString();
if (omit !== 'cmyk') cmyk.value = color.toCmykString();
if (omit !== 'lch') lch.value = color.toLchString();
if (omit !== 'name') name.value = color.toName({ closest: true }) ?? '';
if (omit !== 'hex') hex.value = color.toHex();
if (omit !== 'rgb') rgb.value = color.toRgbString();
if (omit !== 'hsl') hsl.value = color.toHslString();
if (omit !== 'hwb') hwb.value = color.toHwbString();
if (omit !== 'cmyk') cmyk.value = color.toCmykString();
if (omit !== 'lch') lch.value = color.toLchString();
} catch {
//
}
}
onInputUpdated(hex.value, 'hex');

View file

@ -1,13 +1,15 @@
<template>
<c-card>
<n-form-item
class="cron"
:show-label="false"
:feedback="cronValidation.message"
:validation-status="cronValidation.status"
>
<n-input v-model:value="cron" size="large" placeholder="* * * * *" />
</n-form-item>
<div mx-auto max-w-sm>
<c-input-text
v-model:value="cron"
size="large"
placeholder="* * * * *"
:validation-rules="cronValidationRules"
mb-3
/>
</div>
<div class="cron-string">
{{ cronString }}
</div>
@ -86,7 +88,6 @@
import cronstrue from 'cronstrue';
import { isValidCron } from 'cron-validator';
import { computed, reactive, ref } from 'vue';
import { useValidation } from '@/composable/validation';
import { useStyleStore } from '@/stores/style.store';
function isCronValid(v: string) {
@ -185,30 +186,20 @@ const cronString = computed(() => {
return ' ';
});
const cronValidation = useValidation({
source: cron,
rules: [
{
validator: (value) => isCronValid(value),
message: 'This cron is invalid',
},
],
});
const cronValidationRules = [
{
validator: (value: string) => isCronValid(value),
message: 'This cron is invalid',
},
];
</script>
<style lang="less" scoped>
.cron {
::v-deep(input) {
font-size: 30px;
font-family: monospace;
padding: 5px;
text-align: center;
margin: auto;
max-width: 400px;
display: block;
.n-input {
font-size: 30px;
font-family: monospace;
padding: 5px;
}
}
.cron-string {

View file

@ -1,6 +1,6 @@
<template>
<div>
<n-form-item :show-label="false" v-bind="validation.attrs">
<n-form-item :show-label="false" v-bind="validation.attrs as any">
<n-input-group>
<n-input
v-model:value="inputDate"
@ -8,7 +8,7 @@
:on-input="onDateInputChanged"
placeholder="Put you date string here..."
clearable
:input-props="{ 'data-test-id': 'date-time-converter-input' }"
:input-props="{ 'data-test-id': 'date-time-converter-input' } as any"
/>
<n-select
@ -20,16 +20,19 @@
</n-input-group>
</n-form-item>
<n-divider style="margin-top: 0" />
<div v-for="{ name, fromDate } in formats" :key="name" mt-1>
<n-input-group>
<n-input-group-label style="flex: 0 0 170px"> {{ name }}: </n-input-group-label>
<input-copyable
:value="formatDateUsingFormatter(fromDate, normalizedDate)"
placeholder="Invalid date..."
:input-props="{ 'data-test-id': name }"
/>
</n-input-group>
</div>
<input-copyable
v-for="{ name, fromDate } in formats"
:key="name"
:label="name"
label-width="150px"
label-position="left"
label-align="right"
:value="formatDateUsingFormatter(fromDate, normalizedDate)"
placeholder="Invalid date..."
:test-id="name"
readonly
mt-2
/>
</div>
</template>

View file

@ -7,12 +7,15 @@
type="textarea"
placeholder="The string to cypher"
:autosize="{ minRows: 4 }"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<n-space vertical>
<n-form-item label="Your secret key:" :show-feedback="false">
<n-input v-model:value="cypherSecret" />
</n-form-item>
<c-input-text v-model:value="cypherSecret" label="Your secret key:" clearable raw-text />
<n-form-item label="Encryption algorithm:" :show-feedback="false">
<n-select
v-model:value="cypherAlgo"
@ -43,12 +46,15 @@
type="textarea"
placeholder="The string to cypher"
:autosize="{ minRows: 4 }"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<n-space vertical>
<n-form-item label="Your secret key:" :show-feedback="false">
<n-input v-model:value="decryptSecret" />
</n-form-item>
<c-input-text v-model:value="decryptSecret" label="Your secret key:" clearable raw-text />
<n-form-item label="Encryption algorithm:" :show-feedback="false">
<n-select
v-model:value="decryptAlgo"

View file

@ -22,59 +22,54 @@
<n-alert v-if="error" style="margin-top: 25px" type="error">{{ error }}</n-alert>
<n-divider />
<n-input-group>
<n-input-group-label style="flex: 0 0 170px"> Binary (2): </n-input-group-label>
<input-copyable
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 2 })"
readonly
placeholder="Binary version will be here..."
/>
</n-input-group>
<input-copyable
label="Binary (2)"
v-bind="inputProps"
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 2 })"
placeholder="Binary version will be here..."
/>
<n-input-group>
<n-input-group-label style="flex: 0 0 170px"> Octal (8): </n-input-group-label>
<input-copyable
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 8 })"
readonly
placeholder="Octal version will be here..."
/>
</n-input-group>
<input-copyable
label="Octal (8)"
v-bind="inputProps"
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 8 })"
placeholder="Octal version will be here..."
/>
<n-input-group>
<n-input-group-label style="flex: 0 0 170px"> Decimal (10): </n-input-group-label>
<input-copyable
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 10 })"
readonly
placeholder="Decimal version will be here..."
/>
</n-input-group>
<input-copyable
label="Decimal (10)"
v-bind="inputProps"
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 10 })"
placeholder="Decimal version will be here..."
/>
<n-input-group>
<n-input-group-label style="flex: 0 0 170px"> Hexadecimal (16): </n-input-group-label>
<input-copyable
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 16 })"
readonly
placeholder="Decimal version will be here..."
/>
</n-input-group>
<input-copyable
label="Hexadecimal (16)"
v-bind="inputProps"
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 16 })"
placeholder="Hexadecimal version will be here..."
/>
<input-copyable
label="Base64 (64)"
v-bind="inputProps"
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 64 })"
placeholder="Base64 version will be here..."
/>
<div flex items-baseline>
<n-input-group style="width: 160px; margin-right: 10px">
<n-input-group-label> Custom: </n-input-group-label>
<n-input-number v-model:value="outputBase" max="64" min="2" />
</n-input-group>
<n-input-group>
<n-input-group-label style="flex: 0 0 170px"> Base64 (64): </n-input-group-label>
<input-copyable
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 64 })"
readonly
placeholder="Base64 version will be here..."
/>
</n-input-group>
<n-input-group>
<n-input-group-label style="flex: 0 0 85px"> Custom: </n-input-group-label>
<n-input-number v-model:value="outputBase" style="flex: 0 0 86px" max="64" min="2" />
<input-copyable
flex-1
v-bind="inputProps"
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: outputBase })"
readonly
:placeholder="`Base ${outputBase} will be here...`"
/>
</n-input-group>
</div>
</c-card>
</div>
</template>
@ -88,6 +83,14 @@ import InputCopyable from '../../components/InputCopyable.vue';
const styleStore = useStyleStore();
const inputProps = {
labelPosition: 'left',
labelWidth: '170px',
labelAlign: 'right',
readonly: true,
'mb-2': '',
} as const;
const input = ref('42');
const inputBase = ref(10);
const outputBase = ref(42);

View file

@ -1,23 +1,20 @@
<template>
<div>
<n-form-item label="An ipv4 address:" v-bind="validationAttrs">
<n-input v-model:value="rawIpAddress" placeholder="An ipv4 address..." />
</n-form-item>
<c-input-text v-model:value="rawIpAddress" label="The ipv4 address:" placeholder="The ipv4 address..." readonly />
<n-divider style="margin-top: 0" mt-0 />
<n-divider />
<n-form-item
<input-copyable
v-for="{ label, value } of convertedSections"
:key="label"
:label="label"
label-placement="left"
label-width="100"
>
<input-copyable
:value="validationAttrs.validationStatus === 'error' ? '' : value"
placeholder="Set a correct ipv4 address"
/>
</n-form-item>
label-position="left"
label-width="100px"
label-align="right"
mb-2
:value="validationAttrs.validationStatus === 'error' ? '' : value"
placeholder="Set a correct ipv4 address"
/>
</div>
</template>
@ -33,7 +30,7 @@ const convertedSections = computed(() => {
return [
{
label: 'Decimal : ',
label: 'Decimal: ',
value: String(ipInDecimal),
},
{

View file

@ -3,10 +3,10 @@
<n-space item-style="flex:1 1 0">
<div>
<n-space item-style="flex:1 1 0">
<n-form-item label="Start address" v-bind="startIpValidation.attrs">
<n-form-item label="Start address" v-bind="startIpValidation.attrs as any">
<n-input v-model:value="rawStartAddress" placeholder="Start IPv4 address..." />
</n-form-item>
<n-form-item label="End address" v-bind="endIpValidation.attrs">
<n-form-item label="End address" v-bind="endIpValidation.attrs as any">
<n-input v-model:value="rawEndAddress" placeholder="End IPv4 address..." />
</n-form-item>
</n-space>

View file

@ -1,8 +1,12 @@
<template>
<div>
<n-form-item label="An IPv4 address with or without mask" v-bind="validationAttrs">
<n-input v-model:value="ip" />
</n-form-item>
<c-input-text
v-model:value="ip"
label="An IPv4 address with or without mask"
placeholder="The ipv4 address..."
:validation-rules="ipValidationRules"
mb-4
/>
<div v-if="networkInfo">
<n-table>
@ -37,7 +41,6 @@
import { computed } from 'vue';
import { Netmask } from 'netmask';
import { withDefaultOnError } from '@/utils/defaults';
import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import { useStorage } from '@vueuse/core';
import { ArrowLeft, ArrowRight } from '@vicons/tabler';
@ -50,15 +53,12 @@ const getNetworkInfo = (address: string) => new Netmask(address.trim());
const networkInfo = computed(() => withDefaultOnError(() => getNetworkInfo(ip.value), undefined));
const { attrs: validationAttrs } = useValidation({
source: ip,
rules: [
{
message: 'We cannot parse this address, check the format',
validator: (value) => isNotThrowing(() => getNetworkInfo(value.trim())),
},
],
});
const ipValidationRules = [
{
message: 'We cannot parse this address, check the format',
validator: (value: string) => isNotThrowing(() => getNetworkInfo(value.trim())),
},
];
const sections: {
label: string;

View file

@ -1,30 +1,32 @@
<template>
<div>
<n-space vertical :size="50">
<n-alert title="Info" type="info">
This tool uses the first method suggested by IETF using the current timestamp plus the mac address, sha1 hashed,
and the lower 40 bits to generate your random ULA.
</n-alert>
<n-alert title="Info" type="info">
This tool uses the first method suggested by IETF using the current timestamp plus the mac address, sha1 hashed,
and the lower 40 bits to generate your random ULA.
</n-alert>
<n-form-item label="MAC address:" v-bind="validationAttrs">
<n-input
v-model:value="macAddress"
size="large"
placeholder="Type a MAC address"
clearable
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
</n-space>
<c-input-text
v-model:value="macAddress"
placeholder="Type a MAC address"
clearable
label="MAC address:"
raw-text
my-8
:validation="addressValidation"
/>
<div v-if="validationAttrs.validationStatus !== 'error'">
<n-input-group v-for="{ label, value } in calculatedSections" :key="label" style="margin: 5px 0">
<n-input-group-label style="flex: 0 0 160px"> {{ label }} </n-input-group-label>
<input-copyable :value="value" readonly />
</n-input-group>
<div v-if="addressValidation.isValid">
<input-copyable
v-for="{ label, value } in calculatedSections"
:key="label"
:value="value"
:label="label"
label-width="160px"
label-align="right"
label-position="left"
readonly
mb-2
/>
</div>
</div>
</template>
@ -59,7 +61,7 @@ const calculatedSections = computed(() => {
];
});
const { attrs: validationAttrs } = macAddressValidation(macAddress);
const addressValidation = macAddressValidation(macAddress);
</script>
<style lang="less" scoped></style>

View file

@ -1,5 +1,5 @@
<template>
<n-form-item label="Your first json" v-bind="leftJsonValidation.attrs">
<n-form-item label="Your first json" v-bind="leftJsonValidation.attrs as any">
<n-input
v-model:value="rawLeftJson"
placeholder="Paste your first json here..."
@ -9,10 +9,10 @@
autocorrect="off"
autocapitalize="off"
spellcheck="false"
:input-props="{ 'data-test-id': 'leftJson' }"
:input-props="{ 'data-test-id': 'leftJson' } as any"
/>
</n-form-item>
<n-form-item label="Your json to compare" v-bind="rightJsonValidation.attrs">
<n-form-item label="Your json to compare" v-bind="rightJsonValidation.attrs as any">
<n-input
v-model:value="rawRightJson"
placeholder="Paste your json to compare here..."
@ -22,7 +22,7 @@
autocorrect="off"
autocapitalize="off"
spellcheck="false"
:input-props="{ 'data-test-id': 'rightJson' }"
:input-props="{ 'data-test-id': 'rightJson' } as any"
/>
</n-form-item>

View file

@ -30,8 +30,8 @@ test.describe('Tool - List converter', () => {
3
5`);
await page.getByTestId('removeDuplicates').check();
await page.getByTestId('itemPrefix').locator('input').fill("'");
await page.getByTestId('itemSuffix').locator('input').fill("'");
await page.getByTestId('itemPrefix').fill("'");
await page.getByTestId('itemSuffix').fill("'");
const result = await page.getByTestId('area-content').innerText();
expect(result.trim()).toEqual("'1', '2', '4', '3', '5'");

View file

@ -36,37 +36,39 @@
/>
</n-form-item>
<n-form-item label="Separator" label-placement="left" label-width="120" :show-feedback="false" mb-2>
<n-input v-model:value="conversionConfig.separator" placeholder="," />
</n-form-item>
<c-input-text
v-model:value="conversionConfig.separator"
label="Separator"
label-position="left"
label-width="120px"
label-align="right"
mb-2
placeholder=","
/>
<n-form-item label="Wrap item" label-placement="left" label-width="120" :show-feedback="false" mb-2>
<n-input-group>
<n-input
v-model:value="conversionConfig.itemPrefix"
placeholder="Item prefix"
data-test-id="itemPrefix"
/>
<n-input
v-model:value="conversionConfig.itemSuffix"
placeholder="Item suffix"
data-test-id="itemSuffix"
/>
</n-input-group>
<c-input-text
v-model:value="conversionConfig.itemPrefix"
placeholder="Item prefix"
test-id="itemPrefix"
/>
<c-input-text
v-model:value="conversionConfig.itemSuffix"
placeholder="Item suffix"
test-id="itemSuffix"
/>
</n-form-item>
<n-form-item label="Wrap list" label-placement="left" label-width="120" :show-feedback="false" mb-2>
<n-input-group>
<n-input
v-model:value="conversionConfig.listPrefix"
placeholder="List prefix"
data-test-id="listPrefix"
/>
<n-input
v-model:value="conversionConfig.listSuffix"
placeholder="List suffix"
data-test-id="listSuffix"
/>
</n-input-group>
<c-input-text
v-model:value="conversionConfig.listPrefix"
placeholder="List prefix"
test-id="listPrefix"
/>
<c-input-text
v-model:value="conversionConfig.listSuffix"
placeholder="List suffix"
test-id="listSuffix"
/>
</n-form-item>
</div>
</div>

View file

@ -1,6 +1,6 @@
<template>
<div>
<n-form-item label="MAC address:" v-bind="validationAttrs">
<n-form-item label="MAC address:" v-bind="validationAttrs as any">
<n-input
v-model:value="macAddress"
size="large"

View file

@ -5,7 +5,7 @@
<n-input-group v-for="{ key, type, label, placeholder, ...element } of elements" :key="key">
<n-input-group-label style="flex: 0 0 110px">{{ label }}</n-input-group-label>
<n-input v-if="type === 'input'" v-model:value="metadata[key]" :placeholder="placeholder" />
<c-input-text v-if="type === 'input'" v-model:value="metadata[key]" :placeholder="placeholder" clearable />
<n-dynamic-input
v-else-if="type === 'input-multiple'"
v-model:value="metadata[key]"

View file

@ -1,19 +1,23 @@
<template>
<div style="max-width: 350px">
<n-form-item label="Secret" v-bind="secretValidationAttrs">
<n-input v-model:value="secret" placeholder="Paste your TOTP secret...">
<template #suffix>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" @click="refreshSecret">
<n-icon :component="Refresh" />
</c-button>
</template>
Generate secret token
</n-tooltip>
</template>
</n-input>
</n-form-item>
<c-input-text
v-model:value="secret"
label="Secret"
placeholder="Paste your TOTP secret..."
mb-5
:validation-rules="secretValidationRules"
>
<template #suffix>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" size="small" @click="refreshSecret">
<icon-mdi-refresh />
</c-button>
</template>
Generate secret token
</n-tooltip>
</template>
</c-input-text>
<div>
<token-display :tokens="tokens" style="margin-top: 2px" />
@ -27,49 +31,52 @@
</n-space>
</div>
<div style="max-width: 350px">
<n-form-item label="Secret in hexadecimal">
<input-copyable :value="base32toHex(secret)" readonly placeholder="Secret in hex will be displayed here" />
</n-form-item>
<input-copyable
label="Secret in hexadecimal"
:value="base32toHex(secret)"
readonly
placeholder="Secret in hex will be displayed here"
mb-5
/>
<n-form-item label="Epoch">
<input-copyable
:value="Math.floor(now / 1000).toString()"
readonly
placeholder="Epoch in sec will be displayed here"
/>
</n-form-item>
<n-form-item label="Iteration" :show-feedback="false">
<n-input-group>
<n-input-group-label style="width: 110px">Count:</n-input-group-label>
<input-copyable
:value="String(getCounterFromTime({ now, timeStep: 30 }))"
readonly
placeholder="Iteration count will be displayed here"
/>
</n-input-group>
</n-form-item>
<input-copyable
label="Epoch"
:value="Math.floor(now / 1000).toString()"
readonly
mb-5
placeholder="Epoch in sec will be displayed here"
/>
<n-form-item label="Iteration" :show-label="false" style="margin-top: 5px">
<n-input-group>
<n-input-group-label style="width: 110px">Padded hex:</n-input-group-label>
<input-copyable
:value="getCounterFromTime({ now, timeStep: 30 }).toString(16).padStart(16, '0')"
readonly
placeholder="Iteration count in hex will be displayed here"
/>
</n-input-group>
</n-form-item>
<p>Iteration</p>
<input-copyable
:value="String(getCounterFromTime({ now, timeStep: 30 }))"
readonly
label="Count:"
label-position="left"
label-width="90px"
label-align="right"
placeholder="Iteration count will be displayed here"
/>
<input-copyable
:value="getCounterFromTime({ now, timeStep: 30 }).toString(16).padStart(16, '0')"
readonly
placeholder="Iteration count in hex will be displayed here"
label-position="left"
label-width="90px"
label-align="right"
label="Padded hex:"
/>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Refresh } from '@vicons/tabler';
import { useTimestamp } from '@vueuse/core';
import { useThemeVars } from 'naive-ui';
import { useStyleStore } from '@/stores/style.store';
import InputCopyable from '@/components/InputCopyable.vue';
import { useValidation } from '@/composable/validation';
import { computedRefreshable } from '@/composable/computedRefreshable';
import { generateTOTP, buildKeyUri, generateSecret, base32toHex, getCounterFromTime } from './otp.service';
import { useQRCode } from '../qr-code-generator/useQRCode';
@ -106,19 +113,16 @@ const { qrcode } = useQRCode({
options: { width: 210 },
});
const { attrs: secretValidationAttrs } = useValidation({
source: secret,
rules: [
{
message: 'Secret should be a base32 string',
validator: (value) => value.toUpperCase().match(/^[A-Z234567]+$/),
},
{
message: 'Please set a secret',
validator: (value) => value !== '',
},
],
});
const secretValidationRules = [
{
message: 'Secret should be a base32 string',
validator: (value: string) => value.toUpperCase().match(/^[A-Z234567]+$/),
},
{
message: 'Please set a secret',
validator: (value: string) => value !== '',
},
];
</script>
<style lang="less" scoped>

View file

@ -3,9 +3,14 @@
<n-form-item label="Default country code:">
<n-select v-model:value="defaultCountryCode" :options="countriesOptions" />
</n-form-item>
<n-form-item label="Phone number:" v-bind="validation.attrs">
<n-input v-model:value="rawPhone" placeholder="Enter a phone number" />
</n-form-item>
<c-input-text
v-model:value="rawPhone"
placeholder="Enter a phone number"
label="Phone number:"
:validation="validation"
mb-5
/>
<n-table v-if="parsedDetails">
<tbody>

View file

@ -2,7 +2,7 @@
<div>
<c-card title="Arabic to roman">
<n-space align="center" justify="space-between">
<n-form-item v-bind="validationNumeral">
<n-form-item v-bind="validationNumeral as any">
<n-input-number v-model:value="inputNumeral" :min="1" style="width: 200px" :show-button="false" />
</n-form-item>
<div class="result">
@ -15,13 +15,12 @@
</c-card>
<c-card title="Roman to arabic" mt-5>
<n-space align="center" justify="space-between">
<n-form-item v-bind="validationRoman">
<n-input v-model:value="inputRoman" style="width: 200px" />
</n-form-item>
<c-input-text v-model:value="inputRoman" style="width: 200px" :validation="validationRoman" />
<div class="result">
{{ outputNumeral }}
</div>
<c-button :disabled="validationRoman.validationStatus === 'error'" @click="copyArabic"> Copy </c-button>
<c-button :disabled="!validationRoman.isValid" @click="copyArabic"> Copy </c-button>
</n-space>
</c-card>
</div>
@ -55,7 +54,7 @@ const { attrs: validationNumeral } = useValidation({
const inputRoman = ref('XLII');
const outputNumeral = computed(() => romanToArabic(inputRoman.value));
const { attrs: validationRoman } = useValidation({
const validationRoman = useValidation({
source: inputRoman,
rules: [
{

View file

@ -1,7 +1,7 @@
<template>
<div style="flex: 0 0 100%">
<n-space item-style="flex: 1 1 0" style="margin: 0 auto; max-width: 600px" justify="center">
<n-form-item label="Bits :" v-bind="bitsValidationAttrs" label-placement="left" label-width="100">
<n-form-item label="Bits :" v-bind="bitsValidationAttrs as any" label-placement="left" label-width="100">
<n-input-number v-model:value="bits" min="256" max="16384" step="8" />
</n-form-item>

View file

@ -21,9 +21,15 @@
<n-form-item label="Font size">
<n-input-number v-model:value="fontSize" placeholder="Font size..." min="1" />
</n-form-item>
<n-form-item label="Custom text">
<n-input v-model:value="customText" :placeholder="`Default is ${width}x${height}`" />
</n-form-item>
<c-input-text
v-model:value="customText"
label="Custom text"
:placeholder="`Default is ${width}x${height}`"
label-position="left"
label-width="100px"
label-align="right"
/>
</n-space>
<n-form-item label="Use exact size" label-placement="left">
<n-switch v-model:value="useExactSize" />

View file

@ -1,8 +1,12 @@
<template>
<div>
<n-form-item label="Your text to convert to NATO phonetic alphabet">
<n-input v-model:value="input" placeholder="Put your text here..." clearable />
</n-form-item>
<c-input-text
v-model:value="input"
label="Your text to convert to NATO phonetic alphabet"
placeholder="Put your text here..."
clearable
mb-5
/>
<n-space v-if="natoText" vertical>
<n-text>Your text in NATO phonetic alphabet</n-text>

View file

@ -1,51 +1,59 @@
<template>
<c-card>
<n-form-item label="Your url to parse:" :feedback="validation.message" :validation-status="validation.status">
<n-input v-model:value="urlToParse" placeholder="Your url to parse..." />
</n-form-item>
<c-input-text
v-model:value="urlToParse"
label="Your url to parse:"
placeholder="Your url to parse..."
raw-text
:validation-rules="urlValidationRules"
/>
<n-divider style="margin-top: 0" />
<n-divider />
<n-form>
<n-input-group v-for="{ title, key } in properties" :key="key">
<n-input-group-label style="flex: 0 0 120px"> {{ title }}: </n-input-group-label>
<input-copyable :value="(urlParsed?.[key] as string) ?? ''" readonly placeholder=" " />
</n-input-group>
<input-copyable
v-for="{ title, key } in properties"
:key="key"
:label="title"
:value="(urlParsed?.[key] as string) ?? ''"
readonly
label-position="left"
label-width="110px"
mb-2
placeholder=" "
/>
<n-input-group
v-for="[k, v] in Object.entries(Object.fromEntries(urlParsed?.searchParams.entries() ?? []))"
:key="k"
>
<n-input-group-label style="flex: 0 0 120px">
<n-icon :component="SubdirectoryArrowRightRound" />
</n-input-group-label>
<input-copyable :value="k" readonly />
<input-copyable :value="v" readonly />
</n-input-group>
</n-form>
<div
v-for="[k, v] in Object.entries(Object.fromEntries(urlParsed?.searchParams.entries() ?? []))"
:key="k"
mb-2
w-full
flex
>
<div style="flex: 1 0 110px">
<icon-mdi-arrow-right-bottom />
</div>
<input-copyable :value="k" readonly />
<input-copyable :value="v" readonly />
</div>
</c-card>
</template>
<script setup lang="ts">
import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import { withDefaultOnError } from '@/utils/defaults';
import { SubdirectoryArrowRightRound } from '@vicons/material';
import { computed, ref } from 'vue';
import InputCopyable from '../../components/InputCopyable.vue';
const urlToParse = ref('https://me:pwd@it-tools.tech:3000/url-parser?key1=value&key2=value2#the-hash');
const urlParsed = computed(() => withDefaultOnError(() => new URL(urlToParse.value), undefined));
const validation = useValidation({
source: urlToParse,
rules: [
{
validator: (value) => isNotThrowing(() => new URL(value)),
message: 'Invalid url',
},
],
});
const urlValidationRules = [
{
validator: (value: string) => isNotThrowing(() => new URL(value)),
message: 'Invalid url',
},
];
const properties: { title: string; key: keyof URL }[] = [
{ title: 'Protocol', key: 'protocol' },

View file

@ -25,6 +25,18 @@
>
A
</c-button>
<c-button
v-for="buttonType of buttonTypes"
:key="buttonType"
:variant="buttonVariant"
:type="buttonType"
:size="buttonSize"
circle
mx-1
>
<icon-mdi-content-copy />
</c-button>
</div>
</div>
</template>
@ -33,7 +45,7 @@
import _ from 'lodash';
const buttonVariants = ['basic', 'text'] as const;
const buttonTypes = ['default', 'primary'] as const;
const buttonTypes = ['default', 'primary', 'warning'] as const;
const buttonSizes = ['small', 'medium', 'large'] as const;
</script>

View file

@ -56,10 +56,10 @@ const createTheme = ({ style }: { style: 'light' | 'dark' }) => {
pressedBackground: darken(theme.primary.colorFaded, 30),
}),
warning: createState({
textColor: theme.text.baseColor,
backgroundColor: theme.warning.color,
hoverBackground: theme.warning.colorHover,
pressedBackground: theme.warning.colorPressed,
textColor: theme.warning.color,
backgroundColor: theme.warning.colorFaded,
hoverBackground: lighten(theme.warning.colorFaded, 30),
pressedBackground: darken(theme.warning.colorFaded, 30),
}),
},
text: {
@ -76,10 +76,10 @@ const createTheme = ({ style }: { style: 'light' | 'dark' }) => {
pressedBackground: darken(theme.primary.colorFaded, 30),
}),
warning: createState({
textColor: theme.text.baseColor,
backgroundColor: theme.warning.color,
hoverBackground: theme.warning.colorHover,
pressedBackground: theme.warning.colorPressed,
textColor: darken(theme.warning.color, 20),
backgroundColor: 'transparent',
hoverBackground: theme.warning.colorFaded,
pressedBackground: darken(theme.warning.colorFaded, 30),
}),
},
};

View file

@ -18,7 +18,7 @@ import { useAppTheme } from '../theme/themes';
const props = withDefaults(
defineProps<{
type?: 'default' | 'primary';
type?: 'default' | 'primary' | 'warning';
variant?: 'basic' | 'text';
disabled?: boolean;
round?: boolean;

View file

@ -2,6 +2,9 @@
<h2>Default</h2>
<c-input-text value="qsd" />
<c-input-text
value="Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?"
/>
<h2>With placeholder</h2>
@ -24,16 +27,50 @@
<h2>Validation</h2>
<c-input-text
v-model:value="value"
:validation-rules="[{ message: 'Length must be > 10', validator: (value) => value.length > 10 }]"
/>
<c-input-text v-model:value="value" :validation-rules="validationRules" mb-2 />
<c-input-text v-model:value="value" :validation-rules="validationRules" mb-2 label-position="left" label="Yo " />
<c-input-text v-model:value="value" :validation="validation" />
<c-input-text v-model:value="value" :validation="validation" multiline rows="3" />
<h2>Clearable</h2>
<c-input-text v-model:value="value" clearable />
<h2>Type password</h2>
<c-input-text value="value" type="password" />
<h2>Multiline</h2>
<c-input-text value="value" multiline label="Label" mb-2 rows="1" />
<c-input-text value="value" multiline label="Label" mb-2 />
<c-input-text
value="Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?"
multiline
mb-2
/>
<c-input-text v-model:value="valueLong" multiline autosize mb-2 rows="5" />
<c-input-text
value="Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?"
multiline
clearable
/>
</template>
<script lang="ts" setup>
import { useValidation } from '@/composable/validation';
const value = ref('value');
const valueLong = ref(
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?',
);
const validationRules = [{ message: 'Length must be > 10', validator: (value: string) => value.length > 10 }];
const validation = useValidation({
source: value,
rules: validationRules,
});
</script>

View file

@ -1,7 +1,8 @@
import { describe, expect, it, beforeEach } from 'vitest';
import { shallowMount } from '@vue/test-utils';
import { shallowMount, mount } from '@vue/test-utils';
import { setActivePinia, createPinia } from 'pinia';
import _ from 'lodash';
import { useValidation } from '@/composable/validation';
import CInputText from './c-input-text.vue';
describe('CInputText', () => {
@ -71,10 +72,28 @@ describe('CInputText', () => {
it('renders a feedback message for invalid rules', async () => {
const wrapper = shallowMount(CInputText, {
props: { rules: [{ validator: () => false, message: 'Message' }] },
props: { validationRules: [{ validator: () => false, message: 'Message' }] },
});
expect(wrapper.get('.feedback').text()).to.equal('Message');
const feedback = wrapper.find('.feedback');
expect(feedback.exists()).to.equal(true);
expect(feedback.text()).to.equal('Message');
});
it('if the value become valid according to rules, the feedback disappear', async () => {
const wrapper = shallowMount(CInputText, {
props: {
validationRules: [{ validator: (value: string) => value === 'Hello', message: 'Value should be Hello' }],
},
});
const feedback = wrapper.find('.feedback');
expect(feedback.exists()).to.equal(true);
expect(feedback.text()).to.equal('Value should be Hello');
await wrapper.setProps({ value: 'Hello' });
expect(wrapper.find('.feedback').exists()).to.equal(false);
});
it('feedback does not render for valid rules', async () => {
@ -84,4 +103,58 @@ describe('CInputText', () => {
expect(wrapper.find('.feedback').exists()).to.equal(false);
});
it('renders a feedback message for invalid custom validation wrapper', async () => {
const wrapper = shallowMount(CInputText, {
props: {
validation: useValidation({ source: ref(), rules: [{ validator: () => false, message: 'Message' }] }),
},
});
const feedback = wrapper.find('.feedback');
expect(feedback.exists()).to.equal(true);
expect(feedback.text()).to.equal('Message');
});
it('feedback does not render for valid custom validation wrapper', async () => {
const wrapper = shallowMount(CInputText, {
props: {
validation: useValidation({ source: ref(), rules: [{ validator: () => true, message: 'Message' }] }),
},
});
expect(wrapper.find('.feedback').exists()).to.equal(false);
});
it('if the value become valid according to the custom validation wrapper, the feedback disappear', async () => {
const source = ref('');
const wrapper = shallowMount(CInputText, {
props: {
validation: useValidation({
source,
rules: [{ validator: (value: string) => value === 'Hello', message: 'Value should be Hello' }],
}),
},
});
const feedback = wrapper.find('.feedback');
expect(feedback.exists()).to.equal(true);
expect(feedback.text()).to.equal('Value should be Hello');
source.value = 'Hello';
await wrapper.vm.$nextTick();
expect(wrapper.find('.feedback').exists()).to.equal(false);
});
it('[prop:testId] renders a test id on the input', async () => {
const wrapper = mount(CInputText, {
props: {
testId: 'TEST',
},
});
expect(wrapper.get('input').attributes('data-test-id')).to.equal('TEST');
});
});

View file

@ -1,32 +1,60 @@
<template>
<div class="c-input-text" :class="{ disabled, error: !validation.isValid, 'label-left': labelPosition === 'left' }">
<div
class="c-input-text"
:class="{ disabled, error: !validation.isValid, 'label-left': labelPosition === 'left', multiline }"
>
<label v-if="label" :for="id" class="label"> {{ label }} </label>
<div class="input-wrapper">
<slot name="prefix" />
<div class="feedback-wrapper">
<div ref="inputWrapperRef" class="input-wrapper">
<slot name="prefix" />
<input
:id="id"
v-model="value"
type="text"
class="input"
:placeholder="placeholder"
:readonly="readonly"
:disabled="disabled"
:data-test-id="testId"
:autocapitalize="autocapitalize ?? (rawText ? 'off' : undefined)"
:autocomplete="autocomplete ?? (rawText ? 'off' : undefined)"
:autocorrect="autocorrect ?? (rawText ? 'off' : undefined)"
:spellcheck="spellcheck ?? (rawText ? false : undefined)"
/>
<textarea
v-if="multiline"
:id="id"
ref="textareaRef"
v-model="value"
class="input"
:placeholder="placeholder"
:readonly="readonly"
:disabled="disabled"
:data-test-id="testId"
:autocapitalize="autocapitalize ?? (rawText ? 'off' : undefined)"
:autocomplete="autocomplete ?? (rawText ? 'off' : undefined)"
:autocorrect="autocorrect ?? (rawText ? 'off' : undefined)"
:spellcheck="spellcheck ?? (rawText ? false : undefined)"
:rows="rows"
/>
<c-button v-if="clearable && value" variant="text" circle size="small" @click="value = ''">
<icon-mdi-close />
</c-button>
<slot name="suffix" />
<input
v-else
:id="id"
v-model="value"
:type="htmlInputType"
class="input"
size="1"
:placeholder="placeholder"
:readonly="readonly"
:disabled="disabled"
:data-test-id="testId"
:autocapitalize="autocapitalize ?? (rawText ? 'off' : undefined)"
:autocomplete="autocomplete ?? (rawText ? 'off' : undefined)"
:autocorrect="autocorrect ?? (rawText ? 'off' : undefined)"
:spellcheck="spellcheck ?? (rawText ? false : undefined)"
/>
<c-button v-if="clearable && value" variant="text" circle size="small" @click="value = ''">
<icon-mdi-close />
</c-button>
<c-button v-if="type === 'password'" variant="text" circle size="small" @click="showPassword = !showPassword">
<icon-mdi-eye v-if="!showPassword" />
<icon-mdi-eye-off v-if="showPassword" />
</c-button>
<slot name="suffix" />
</div>
<span v-if="!validation.isValid" class="feedback"> {{ validation.message }} </span>
</div>
<span v-if="!validation.isValid" class="feedback"> {{ validation.message }} </span>
</div>
</template>
@ -45,6 +73,7 @@ const props = withDefaults(
readonly?: boolean;
disabled?: boolean;
validationRules?: UseValidationRule<string>[];
validation?: ReturnType<typeof useValidation>;
labelPosition?: 'top' | 'left';
labelWidth?: string;
labelAlign?: 'left' | 'right';
@ -55,6 +84,10 @@ const props = withDefaults(
autocorrect?: 'on' | 'off' | string;
spellcheck?: 'true' | 'false' | boolean;
rawText?: boolean;
type?: 'text' | 'password';
multiline?: boolean;
rows?: number | string;
autosize?: boolean;
}>(),
{
value: '',
@ -64,6 +97,7 @@ const props = withDefaults(
readonly: false,
disabled: false,
validationRules: () => [],
validation: undefined,
labelPosition: 'top',
labelWidth: 'auto',
labelAlign: 'left',
@ -74,20 +108,58 @@ const props = withDefaults(
autocorrect: undefined,
spellcheck: undefined,
rawText: false,
type: 'text',
multiline: false,
rows: 3,
autosize: false,
},
);
const emit = defineEmits(['update:value']);
const value = useVModel(props, 'value', emit);
const showPassword = ref(false);
const { id, placeholder, label, validationRules, labelPosition, labelWidth, labelAlign } = toRefs(props);
const { id, placeholder, label, validationRules, labelPosition, labelWidth, labelAlign, autosize } = toRefs(props);
const validation = useValidation({
rules: validationRules,
source: value,
});
const validation =
props.validation ??
useValidation({
rules: validationRules,
source: value,
});
const theme = useTheme();
const appTheme = useAppTheme();
const textareaRef = ref<HTMLTextAreaElement>();
const inputWrapperRef = ref<HTMLElement>();
watch(
value,
() => {
if (props.multiline && autosize.value) {
resizeTextarea();
}
},
{ immediate: true },
);
function resizeTextarea() {
if (!textareaRef.value || !inputWrapperRef.value) {
return;
}
const { scrollHeight } = textareaRef.value;
inputWrapperRef.value.style.height = `${scrollHeight + 2}px`;
}
const htmlInputType = computed(() => {
if (props.type === 'password' && !showPassword.value) {
return 'password';
}
return 'text';
});
</script>
<style lang="less" scoped>
@ -114,29 +186,55 @@ const appTheme = useAppTheme();
}
}
& > .feedback {
& .feedback {
color: v-bind('appTheme.error.color');
}
}
& > .label {
flex-shrink: 0;
margin-bottom: 5px;
flex: 0 0 v-bind('labelWidth');
text-align: v-bind('labelAlign');
padding-right: 10px;
padding-right: 12px;
}
.input-wrapper {
.feedback-wrapper {
flex: 1 1 0;
min-width: 0;
}
.input-wrapper {
display: flex;
flex-direction: row;
align-items: center;
background-color: v-bind('theme.backgroundColor');
color: transparent;
border: 1px solid v-bind('theme.borderColor');
border-radius: 4px;
padding: 0 4px 0 12px;
transition: border-color 0.2s ease-in-out;
.multiline& {
resize: vertical;
overflow: hidden;
& > textarea {
height: 100%;
resize: none;
word-break: break-word;
white-space: pre-wrap;
overflow-wrap: break-word;
border: none;
outline: none;
font-family: inherit;
font-size: inherit;
color: v-bind('appTheme.text.baseColor');
&::placeholder {
color: v-bind('appTheme.text.mutedColor');
}
}
}
& > .input {
flex: 1 1 0;
@ -144,7 +242,6 @@ const appTheme = useAppTheme();
padding: 8px 0;
outline: none;
transition: border-color 0.2s ease-in-out;
background-color: transparent;
background-image: none;
-webkit-box-shadow: none;
@ -159,12 +256,13 @@ const appTheme = useAppTheme();
}
}
&:hover,
&:focus {
&:hover {
border-color: v-bind('appTheme.primary.color');
}
&:focus {
&:focus-within {
border-color: v-bind('appTheme.primary.color');
background-color: v-bind('theme.focus.backgroundColor');
}
}
@ -173,11 +271,11 @@ const appTheme = useAppTheme();
border-color: v-bind('appTheme.error.color');
&:hover,
&:focus {
&:focus-within {
border-color: v-bind('appTheme.error.color');
}
&:focus {
&:focus-within {
background-color: v-bind('appTheme.error.color + 22');
}
}
@ -186,7 +284,7 @@ const appTheme = useAppTheme();
opacity: 0.5;
&:hover,
&:focus {
&:focus-within {
border-color: v-bind('theme.borderColor');
}

View file

@ -21,6 +21,7 @@ export const { themes: appThemes, useTheme: useAppTheme } = defineThemes({
color: '#f59e0b',
colorHover: '#f59e0b',
colorPressed: '#f59e0b',
colorFaded: '#f59e0b2f',
},
success: {
color: '#18a058',
@ -55,6 +56,7 @@ export const { themes: appThemes, useTheme: useAppTheme } = defineThemes({
color: '#f59e0b',
colorHover: '#f59e0b',
colorPressed: '#f59e0b',
colorFaded: '#f59e0b2f',
},
success: {
color: '#18a058',