mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-11-04 14:03:13 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d09086e78 | ||
|
|
acf8bc11db | ||
|
|
71e98e93e5 | ||
|
|
1b5d4e72bd | ||
|
|
8476cf319b | ||
|
|
0ff853437b |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -2,6 +2,26 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.15.0](https://github.com/CorentinTh/it-tools/compare/v2.14.1...v2.15.0) (2022-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **search-bar:** better search back result ([71e98e9](https://github.com/CorentinTh/it-tools/commit/71e98e93e5752cba934f67d679088524c4d3d2ad))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **integer-base-converter:** handle non-decimal char and better error message ([8476cf3](https://github.com/CorentinTh/it-tools/commit/8476cf319b7ebae87c7928592604a54833ac56ef))
|
||||||
|
* **tool-card:** correct text color on light mode for card description ([acf8bc1](https://github.com/CorentinTh/it-tools/commit/acf8bc11dbab85ab361edbe400ebbe5e52a11b89))
|
||||||
|
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* **search-bar:** improved tool fuzzy search ([1b5d4e7](https://github.com/CorentinTh/it-tools/commit/1b5d4e72bdb222dd721a1e484c3e5d73bb62d2b1))
|
||||||
|
|
||||||
|
### [2.14.1](https://github.com/CorentinTh/it-tools/compare/v2.14.0...v2.14.1) (2022-11-23)
|
||||||
|
|
||||||
## [2.14.0](https://github.com/CorentinTh/it-tools/compare/v2.13.0...v2.14.0) (2022-11-23)
|
## [2.14.0](https://github.com/CorentinTh/it-tools/compare/v2.13.0...v2.14.0) (2022-11-23)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "it-tools",
|
"name": "it-tools",
|
||||||
"version": "2.14.0",
|
"version": "2.15.0",
|
||||||
"description": "Collection of handy online tools for developers, with great UX. ",
|
"description": "Collection of handy online tools for developers, with great UX. ",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"productivity",
|
"productivity",
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"figue": "^1.2.0",
|
"figue": "^1.2.0",
|
||||||
|
"fuse.js": "^6.6.2",
|
||||||
"highlight.js": "^11.6.0",
|
"highlight.js": "^11.6.0",
|
||||||
"json5": "^2.2.1",
|
"json5": "^2.2.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@@ -38,6 +38,7 @@ specifiers:
|
|||||||
eslint-plugin-import: ^2.26.0
|
eslint-plugin-import: ^2.26.0
|
||||||
eslint-plugin-vue: ^8.7.1
|
eslint-plugin-vue: ^8.7.1
|
||||||
figue: ^1.2.0
|
figue: ^1.2.0
|
||||||
|
fuse.js: ^6.6.2
|
||||||
highlight.js: ^11.6.0
|
highlight.js: ^11.6.0
|
||||||
jsdom: ^19.0.0
|
jsdom: ^19.0.0
|
||||||
json5: ^2.2.1
|
json5: ^2.2.1
|
||||||
@@ -81,6 +82,7 @@ dependencies:
|
|||||||
crypto-js: 4.1.1
|
crypto-js: 4.1.1
|
||||||
date-fns: 2.29.3
|
date-fns: 2.29.3
|
||||||
figue: 1.2.0
|
figue: 1.2.0
|
||||||
|
fuse.js: 6.6.2
|
||||||
highlight.js: 11.6.0
|
highlight.js: 11.6.0
|
||||||
json5: 2.2.1
|
json5: 2.2.1
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
@@ -4054,6 +4056,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/fuse.js/6.6.2:
|
||||||
|
resolution: {integrity: sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/gensync/1.0.0-beta.2:
|
/gensync/1.0.0-beta.2:
|
||||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useFuzzySearch } from '@/composable/fuzzySearch';
|
||||||
import { tools } from '@/tools';
|
import { tools } from '@/tools';
|
||||||
|
import type { ITool } from '@/tools/tool';
|
||||||
import { SearchRound } from '@vicons/material';
|
import { SearchRound } from '@vicons/material';
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core';
|
import { useMagicKeys, whenever } from '@vueuse/core';
|
||||||
import { deburr } from 'lodash';
|
import { computed, h, ref } from 'vue';
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import SearchBarItem from './SearchBarItem.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryString = ref('');
|
const queryString = ref('');
|
||||||
|
|
||||||
const cleanString = (s: string) => deburr(s.trim().toLowerCase());
|
const { searchResult } = useFuzzySearch({
|
||||||
|
search: queryString,
|
||||||
|
data: tools,
|
||||||
|
options: { keys: [{ name: 'name', weight: 2 }, 'description', 'keywords'] },
|
||||||
|
});
|
||||||
|
|
||||||
const searchableTools = tools.map(({ name, description, keywords, path }) => ({
|
const toolToOption = (tool: ITool) => ({ label: tool.name, value: tool.path, tool });
|
||||||
searchableText: [name, description, ...keywords].map(cleanString).join(' '),
|
|
||||||
path,
|
|
||||||
name,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
const query = cleanString(queryString.value);
|
if (queryString.value === '') {
|
||||||
|
return tools.map(toolToOption);
|
||||||
|
}
|
||||||
|
|
||||||
return searchableTools
|
return searchResult.value.map(toolToOption);
|
||||||
.filter(({ searchableText }) => searchableText.includes(query))
|
|
||||||
.map(({ name, path }) => ({ label: name, value: path }));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function onSelect(path: string) {
|
function onSelect(path: string) {
|
||||||
@@ -44,6 +46,10 @@ const keys = useMagicKeys({
|
|||||||
whenever(keys.ctrl_k, () => {
|
whenever(keys.ctrl_k, () => {
|
||||||
focusTarget.value.focus();
|
focusTarget.value.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderOption({ tool }: { tool: ITool }) {
|
||||||
|
return h(SearchBarItem, { tool });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -51,8 +57,10 @@ whenever(keys.ctrl_k, () => {
|
|||||||
<n-auto-complete
|
<n-auto-complete
|
||||||
v-model:value="queryString"
|
v-model:value="queryString"
|
||||||
:options="options"
|
:options="options"
|
||||||
:input-props="{ autocomplete: 'disabled' }"
|
:on-select="(value) => onSelect(String(value))"
|
||||||
:on-select="onSelect"
|
:render-label="renderOption"
|
||||||
|
:default-value="'aa'"
|
||||||
|
:get-show="() => true"
|
||||||
>
|
>
|
||||||
<template #default="{ handleInput, handleBlur, handleFocus, value: slotValue }">
|
<template #default="{ handleInput, handleBlur, handleFocus, value: slotValue }">
|
||||||
<n-input
|
<n-input
|
||||||
@@ -61,6 +69,7 @@ whenever(keys.ctrl_k, () => {
|
|||||||
clearable
|
clearable
|
||||||
placeholder="Search a tool... [Ctrl + K]"
|
placeholder="Search a tool... [Ctrl + K]"
|
||||||
:value="slotValue"
|
:value="slotValue"
|
||||||
|
:input-props="{ autocomplete: 'disabled' }"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
@focus="handleFocus"
|
@focus="handleFocus"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
@@ -74,8 +83,4 @@ whenever(keys.ctrl_k, () => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped></style>
|
||||||
// ::v-deep(.n-input__border) {
|
|
||||||
// border: none;
|
|
||||||
// }
|
|
||||||
</style>
|
|
||||||
|
|||||||
45
src/components/SearchBarItem.vue
Normal file
45
src/components/SearchBarItem.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ITool } from '@/tools/tool';
|
||||||
|
import { toRefs } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{ tool: ITool }>();
|
||||||
|
const { tool } = toRefs(props);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="search-bar-item">
|
||||||
|
<n-icon class="icon" :component="tool.icon" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="name">{{ tool.name }}</div>
|
||||||
|
<div class="description">{{ tool.description }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.search-bar-item {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 10px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
opacity: 0.7;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -50,7 +50,7 @@ a {
|
|||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
color: #ffffff;
|
color: v-bind('theme.textColorBase');
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@@ -59,7 +59,7 @@ a {
|
|||||||
|
|
||||||
.description {
|
.description {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
color: #ffffff;
|
color: v-bind('theme.textColorBase');
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/composable/fuzzySearch.ts
Normal file
23
src/composable/fuzzySearch.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { get, type MaybeRef } from '@vueuse/core';
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
export { useFuzzySearch };
|
||||||
|
|
||||||
|
function useFuzzySearch<Data>({
|
||||||
|
search,
|
||||||
|
data,
|
||||||
|
options = {},
|
||||||
|
}: {
|
||||||
|
search: MaybeRef<string>;
|
||||||
|
data: Data[];
|
||||||
|
options?: Fuse.IFuseOptions<Data>;
|
||||||
|
}) {
|
||||||
|
const fuse = new Fuse(data, options);
|
||||||
|
|
||||||
|
const searchResult = computed(() => {
|
||||||
|
return fuse.search(get(search)).map(({ item }) => item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { searchResult };
|
||||||
|
}
|
||||||
@@ -6,6 +6,12 @@ export const lightThemeOverrides: GlobalThemeOverrides = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Layout: { color: '#f1f5f9' },
|
Layout: { color: '#f1f5f9' },
|
||||||
|
|
||||||
|
AutoComplete: {
|
||||||
|
peers: {
|
||||||
|
InternalSelectMenu: { height: '500px' },
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const darkThemeOverrides: GlobalThemeOverrides = {
|
export const darkThemeOverrides: GlobalThemeOverrides = {
|
||||||
@@ -16,6 +22,12 @@ export const darkThemeOverrides: GlobalThemeOverrides = {
|
|||||||
primaryColorSuppl: '#36AD6AFF',
|
primaryColorSuppl: '#36AD6AFF',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
AutoComplete: {
|
||||||
|
peers: {
|
||||||
|
InternalSelectMenu: { height: '500px', color: '#1e1e1e' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
Menu: {
|
Menu: {
|
||||||
itemHeight: '32px',
|
itemHeight: '32px',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function convertBase({ value, fromBase, toBase }: { value: string; fromBa
|
|||||||
.reverse()
|
.reverse()
|
||||||
.reduce((carry: number, digit: string, index: number) => {
|
.reduce((carry: number, digit: string, index: number) => {
|
||||||
if (!fromRange.includes(digit)) {
|
if (!fromRange.includes(digit)) {
|
||||||
throw new Error('Invalid digit `' + digit + '` for base ' + fromBase + '.');
|
throw new Error('Invalid digit "' + digit + '" for base ' + fromBase + '.');
|
||||||
}
|
}
|
||||||
return (carry += fromRange.indexOf(digit) * Math.pow(fromBase, index));
|
return (carry += fromRange.indexOf(digit) * Math.pow(fromBase, index));
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div v-if="styleStore.isSmallScreen">
|
<div v-if="styleStore.isSmallScreen">
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input-group-label style="flex: 0 0 120px"> Input number: </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 120px"> Input number: </n-input-group-label>
|
||||||
<n-input-number v-model:value="inputNumber" min="0" style="width: 100%" />
|
<n-input v-model:value="input" style="width: 100%" :status="error ? 'error' : undefined" />
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input-group-label style="flex: 0 0 120px"> Input base: </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 120px"> Input base: </n-input-group-label>
|
||||||
@@ -14,51 +14,65 @@
|
|||||||
|
|
||||||
<n-input-group v-else>
|
<n-input-group v-else>
|
||||||
<n-input-group-label style="flex: 0 0 120px"> Input number: </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 120px"> Input number: </n-input-group-label>
|
||||||
<n-input-number v-model:value="inputNumber" min="0" />
|
<n-input v-model:value="input" :status="error ? 'error' : undefined" />
|
||||||
<n-input-group-label style="flex: 0 0 120px"> Input base: </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 120px"> Input base: </n-input-group-label>
|
||||||
<n-input-number v-model:value="inputBase" max="64" min="2" />
|
<n-input-number v-model:value="inputBase" max="64" min="2" />
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
|
|
||||||
|
<n-alert v-if="error" style="margin-top: 25px" type="error">{{ error }}</n-alert>
|
||||||
<n-divider />
|
<n-divider />
|
||||||
|
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input-group-label style="flex: 0 0 170px"> Binary (2): </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 170px"> Binary (2): </n-input-group-label>
|
||||||
<input-copyable :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 2 })" readonly />
|
<input-copyable
|
||||||
|
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 2 })"
|
||||||
|
readonly
|
||||||
|
placeholder="Binary version will be here..."
|
||||||
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
|
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input-group-label style="flex: 0 0 170px"> Octal (8): </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 170px"> Octal (8): </n-input-group-label>
|
||||||
<input-copyable :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 8 })" readonly />
|
<input-copyable
|
||||||
|
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 8 })"
|
||||||
|
readonly
|
||||||
|
placeholder="Octal version will be here..."
|
||||||
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
|
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input-group-label style="flex: 0 0 170px"> Decimal (10): </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 170px"> Decimal (10): </n-input-group-label>
|
||||||
<input-copyable
|
<input-copyable
|
||||||
:value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 10 })"
|
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 10 })"
|
||||||
readonly
|
readonly
|
||||||
|
placeholder="Decimal version will be here..."
|
||||||
/>
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
|
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input-group-label style="flex: 0 0 170px"> Hexadecimal (16): </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 170px"> Hexadecimal (16): </n-input-group-label>
|
||||||
<input-copyable
|
<input-copyable
|
||||||
:value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 16 })"
|
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 16 })"
|
||||||
readonly
|
readonly
|
||||||
|
placeholder="Decimal version will be here..."
|
||||||
/>
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
|
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input-group-label style="flex: 0 0 170px"> Base64 (64): </n-input-group-label>
|
<n-input-group-label style="flex: 0 0 170px"> Base64 (64): </n-input-group-label>
|
||||||
<input-copyable
|
<input-copyable
|
||||||
:value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 64 })"
|
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 64 })"
|
||||||
readonly
|
readonly
|
||||||
|
placeholder="Base64 version will be here..."
|
||||||
/>
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
<n-input-group-label style="flex: 0 0 85px"> Custom: </n-input-group-label>
|
<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" />
|
<n-input-number v-model:value="outputBase" style="flex: 0 0 86px" max="64" min="2" />
|
||||||
<input-copyable
|
<input-copyable
|
||||||
:value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: outputBase })"
|
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: outputBase })"
|
||||||
readonly
|
readonly
|
||||||
|
:placeholder="`Base ${outputBase} will be here...`"
|
||||||
/>
|
/>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
</n-card>
|
</n-card>
|
||||||
@@ -66,16 +80,31 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useStyleStore } from '@/stores/style.store';
|
import { useStyleStore } from '@/stores/style.store';
|
||||||
|
import { getErrorMessageIfThrows } from '@/utils/error';
|
||||||
import { convertBase } from './integer-base-converter.model';
|
import { convertBase } from './integer-base-converter.model';
|
||||||
import InputCopyable from '../../components/InputCopyable.vue';
|
import InputCopyable from '../../components/InputCopyable.vue';
|
||||||
|
|
||||||
const styleStore = useStyleStore();
|
const styleStore = useStyleStore();
|
||||||
|
|
||||||
const inputNumber = ref(42);
|
const input = ref('42');
|
||||||
const inputBase = ref(10);
|
const inputBase = ref(10);
|
||||||
const outputBase = ref(42);
|
const outputBase = ref(42);
|
||||||
|
|
||||||
|
function errorlessConvert(...args: Parameters<typeof convertBase>) {
|
||||||
|
try {
|
||||||
|
return convertBase(...args);
|
||||||
|
} catch (err) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = computed(() =>
|
||||||
|
getErrorMessageIfThrows(() =>
|
||||||
|
convertBase({ value: input.value, fromBase: inputBase.value, toBase: outputBase.value }),
|
||||||
|
),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
29
src/utils/error.test.ts
Normal file
29
src/utils/error.test.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { getErrorMessageIfThrows } from './error';
|
||||||
|
|
||||||
|
describe('error util', () => {
|
||||||
|
describe('getErrorMessageIfThrows', () => {
|
||||||
|
it('get an error message if the callback throws, undefined instead', () => {
|
||||||
|
expect(
|
||||||
|
getErrorMessageIfThrows(() => {
|
||||||
|
throw 'message';
|
||||||
|
}),
|
||||||
|
).to.equal('message');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getErrorMessageIfThrows(() => {
|
||||||
|
throw new Error('message');
|
||||||
|
}),
|
||||||
|
).to.equal('message');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getErrorMessageIfThrows(() => {
|
||||||
|
throw { message: 'message' };
|
||||||
|
}),
|
||||||
|
).to.equal('message');
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
expect(getErrorMessageIfThrows(() => {})).to.equal(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
24
src/utils/error.ts
Normal file
24
src/utils/error.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export { getErrorMessageIfThrows };
|
||||||
|
|
||||||
|
function getErrorMessageIfThrows(cb: () => unknown) {
|
||||||
|
try {
|
||||||
|
cb();
|
||||||
|
return undefined;
|
||||||
|
} catch (err) {
|
||||||
|
if (_.isString(err)) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isError(err)) {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isObject(err) && _.has(err, 'message')) {
|
||||||
|
return (err as { message: string }).message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'An error as occurred.';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user