diff options
author | 2022-06-01 23:52:21 +0200 | |
---|---|---|
committer | 2022-06-02 00:11:49 +0200 | |
commit | 11720e6cdefc1da4bdd638415813b609840f8462 (patch) | |
tree | b647d77a2fe3cec6af3b81bf02ceb17e636d80b7 | |
parent | ac89490794ee3c1c033859ffea31a962a13cc96d (diff) | |
download | it-tools-11720e6cdefc1da4bdd638415813b609840f8462.tar.gz it-tools-11720e6cdefc1da4bdd638415813b609840f8462.tar.zst it-tools-11720e6cdefc1da4bdd638415813b609840f8462.zip |
feat(tools): new badge for recently created tools
33 files changed, 320 insertions, 100 deletions
diff --git a/package-lock.json b/package-lock.json index 9f817ca..16b4904 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,10 @@ "cronstrue": "^2.2.0", "crypto-js": "^4.1.1", "date-fns": "^2.28.0", - "figue": "^1.1.0", + "figue": "^1.2.0", "highlight.js": "^11.5.1", "lodash": "^4.17.21", + "mathjs": "^10.6.0", "naive-ui": "^2.28.0", "pinia": "^2.0.11", "plausible-tracker": "^0.3.5", @@ -1643,7 +1644,6 @@ "version": "7.17.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -3558,6 +3558,18 @@ "dot-prop": "^5.1.0" } }, + "node_modules/complex.js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4164,8 +4176,7 @@ "node_modules/decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" }, "node_modules/deep-eql": { "version": "3.0.1", @@ -4956,6 +4967,11 @@ "node": ">=6" } }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -5523,9 +5539,9 @@ } }, "node_modules/figue": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/figue/-/figue-1.1.0.tgz", - "integrity": "sha512-toW/IfEPBr42giaiqRtC4TkEDZA2q3E1GdzvYG7iJzIYK/fMVvzD2aqU3PJRh+QXCGp+uVxud1Zm7rpV7Fmprg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/figue/-/figue-1.2.0.tgz", + "integrity": "sha512-CXKr12kiNWjKtUK3X+YHeXKepn80s9Rg6pgZXoLQYEybgwaGJ9uGW4DrBrVK30ZWZf1mcvTbXF56AcovG7gLVw==", "dependencies": { "lodash": "^4.17.21" }, @@ -5670,6 +5686,18 @@ "node": ">=8.0.0" } }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, "node_modules/from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -6813,6 +6841,11 @@ "node": ">=8" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "node_modules/jest-diff": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", @@ -7517,6 +7550,28 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/mathjs": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-10.6.0.tgz", + "integrity": "sha512-4oI0CSX7LtcyexTSLV8uo+llj8hB5LvVE9ApjN6rBjBplQaZ4/Gr3jh0zEla9+KaCig5wonZ9oFKD+GKXFL8hg==", + "dependencies": { + "@babel/runtime": "^7.17.9", + "complex.js": "^2.1.1", + "decimal.js": "^10.3.1", + "escape-latex": "^1.2.0", + "fraction.js": "^4.2.0", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.1.0" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", @@ -9019,8 +9074,7 @@ "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/regenerator-transform": { "version": "0.15.0", @@ -9287,6 +9341,11 @@ "node": ">=4" } }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "node_modules/seemly": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.3.tgz", @@ -9990,6 +10049,11 @@ "readable-stream": "3" } }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tinypool": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.3.tgz", @@ -10108,6 +10172,14 @@ "node": ">=4" } }, + "node_modules/typed-function": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.1.0.tgz", + "integrity": "sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==", + "engines": { + "node": ">= 10" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -12426,7 +12498,6 @@ "version": "7.17.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -13935,6 +14006,11 @@ "dot-prop": "^5.1.0" } }, + "complex.js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -14418,8 +14494,7 @@ "decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" }, "deep-eql": { "version": "3.0.1", @@ -14918,6 +14993,11 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -15334,9 +15414,9 @@ } }, "figue": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/figue/-/figue-1.1.0.tgz", - "integrity": "sha512-toW/IfEPBr42giaiqRtC4TkEDZA2q3E1GdzvYG7iJzIYK/fMVvzD2aqU3PJRh+QXCGp+uVxud1Zm7rpV7Fmprg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/figue/-/figue-1.2.0.tgz", + "integrity": "sha512-CXKr12kiNWjKtUK3X+YHeXKepn80s9Rg6pgZXoLQYEybgwaGJ9uGW4DrBrVK30ZWZf1mcvTbXF56AcovG7gLVw==", "requires": { "lodash": "^4.17.21" } @@ -15439,6 +15519,11 @@ "signal-exit": "^3.0.2" } }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==" + }, "from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -16283,6 +16368,11 @@ } } }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "jest-diff": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", @@ -16835,6 +16925,22 @@ } } }, + "mathjs": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-10.6.0.tgz", + "integrity": "sha512-4oI0CSX7LtcyexTSLV8uo+llj8hB5LvVE9ApjN6rBjBplQaZ4/Gr3jh0zEla9+KaCig5wonZ9oFKD+GKXFL8hg==", + "requires": { + "@babel/runtime": "^7.17.9", + "complex.js": "^2.1.1", + "decimal.js": "^10.3.1", + "escape-latex": "^1.2.0", + "fraction.js": "^4.2.0", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^2.1.0" + } + }, "mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", @@ -17970,8 +18076,7 @@ "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { "version": "0.15.0", @@ -18165,6 +18270,11 @@ "kind-of": "^6.0.0" } }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "seemly": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.3.tgz", @@ -18711,6 +18821,11 @@ "readable-stream": "3" } }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tinypool": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.3.tgz", @@ -18801,6 +18916,11 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "typed-function": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.1.0.tgz", + "integrity": "sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==" + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", diff --git a/package.json b/package.json index e69542f..535afa6 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,10 @@ "cronstrue": "^2.2.0", "crypto-js": "^4.1.1", "date-fns": "^2.28.0", - "figue": "^1.1.0", + "figue": "^1.2.0", "highlight.js": "^11.5.1", "lodash": "^4.17.21", + "mathjs": "^10.6.0", "naive-ui": "^2.28.0", "pinia": "^2.0.11", "plausible-tracker": "^0.3.5", diff --git a/scripts/create-tool.mjs b/scripts/create-tool.mjs index ecbd8c3..97b99b2 100644 --- a/scripts/create-tool.mjs +++ b/scripts/create-tool.mjs @@ -39,16 +39,16 @@ createToolFile( <style lang="less" scoped> </style> -` +`, ); createToolFile( `index.ts`, ` import { ArrowsShuffle } from '@vicons/tabler'; -import type { ITool } from './../Tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: '${toolNameTitleCase}', path: '/${toolName}', description: '', @@ -56,7 +56,7 @@ export const tool: ITool = { component: () => import('./${toolName}.vue'), icon: ArrowsShuffle, }; -` +`, ); createToolFile(`${toolName}.service.ts`, ``); @@ -69,7 +69,7 @@ import { expect, describe, it } from 'vitest'; // describe('${toolName}', () => { // // }) -` +`, ); const toolsIndex = join(toolsDir, 'index.ts'); diff --git a/src/components/MenuIconItem.vue b/src/components/MenuIconItem.vue new file mode 100644 index 0000000..0909e56 --- /dev/null +++ b/src/components/MenuIconItem.vue @@ -0,0 +1,36 @@ +<template> + <div class="menu-icon-item"> + <n-icon :component="tool.icon" /> + <div v-if="tool.isNew" class="badge"></div> + </div> +</template> + +<script setup lang="ts"> +import type { ITool } from '@/tools/tool'; +import { useThemeVars } from 'naive-ui'; +import { toRefs } from 'vue'; + +const props = defineProps<{ tool: ITool }>(); +const { tool } = toRefs(props); + +const theme = useThemeVars(); +</script> + +<style lang="less" scoped> +.menu-icon-item { + position: relative; + + .badge { + position: absolute; + background-color: v-bind('theme.primaryColor'); + border-radius: 10px; + line-height: 1; + top: 3px; + left: -6px; + font-size: 10px; + + height: 6px; + width: 6px; + } +} +</style> diff --git a/src/components/ToolCard.vue b/src/components/ToolCard.vue index 9c00367..14cc0f4 100644 --- a/src/components/ToolCard.vue +++ b/src/components/ToolCard.vue @@ -1,10 +1,24 @@ <template> <router-link :to="tool.path"> <n-card class="tool-card"> - <n-icon class="icon" size="40" :component="tool.icon" /> + <n-space justify="space-between" align="center"> + <n-icon class="icon" size="40" :component="tool.icon" /> + <n-tag + v-if="tool.isNew" + size="small" + class="badge-new" + round + type="success" + :bordered="false" + :color="{ color: theme.primaryColor, textColor: theme.tagColor }" + > + New + </n-tag> + </n-space> <n-h3 class="title"> <n-ellipsis>{{ tool.name }}</n-ellipsis> </n-h3> + <div class="description"> <n-ellipsis :line-clamp="2" :tooltip="false"> {{ tool.description }} @@ -15,11 +29,13 @@ </template> <script setup lang="ts"> -import type { ITool } from '@/tools/Tool'; +import type { ITool } from '@/tools/tool'; +import { useThemeVars } from 'naive-ui'; import { toRefs } from 'vue'; const props = defineProps<{ tool: ITool & { category: string } }>(); const { tool } = toRefs(props); +const theme = useThemeVars(); </script> <style lang="less" scoped> diff --git a/src/config.ts b/src/config.ts index ce8e2ef..926fa08 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,6 +47,14 @@ export const config = figue({ default: false, }, }, + tools: { + newTools: { + doc: 'Tool names for tools flagged a as new', + format: 'array', + default: [], + env: 'VITE_NEW_TOOLS', + }, + }, }) .loadEnv({ ...import.meta.env, diff --git a/src/layouts/base.layout.vue b/src/layouts/base.layout.vue index 0ad887c..2caac13 100644 --- a/src/layouts/base.layout.vue +++ b/src/layouts/base.layout.vue @@ -10,6 +10,8 @@ import HeroGradient from '../assets/hero-gradient.svg?component'; import MenuLayout from '../components/MenuLayout.vue'; import NavbarButtons from '../components/NavbarButtons.vue'; import { config } from '@/config'; +import MenuIconItem from '@/components/MenuIconItem.vue'; +import type { ITool } from '@/tools/tool'; const themeVars = useThemeVars(); const route = useRoute(); @@ -18,15 +20,15 @@ const version = config.app.version; const commitSha = config.app.lastCommitSha.slice(0, 7); const makeLabel = (text: string, to: string) => () => h(RouterLink, { to }, { default: () => text }); -const makeIcon = (icon: Component) => () => h(NIcon, null, { default: () => h(icon) }); +const makeIcon = (tool: ITool) => () => h(MenuIconItem, { tool }); const menuOptions = toolsByCategory.map((category) => ({ label: category.name, key: category.name, type: 'group', - children: category.components.map(({ name, path, icon }) => ({ - label: makeLabel(name, path), - icon: makeIcon(icon), + children: category.components.map((tool) => ({ + label: makeLabel(tool.name, tool.path), + icon: makeIcon(tool), key: name, })), })); @@ -218,6 +220,11 @@ const menuOptions = toolsByCategory.map((category) => ({ } } +// ::v-deep(.n-menu-item-content-header) { +// overflow: visible !important; +// // overflow-x: hidden !important; +// } + .navigation { display: flex; align-items: center; diff --git a/src/layouts/tool.layout.vue b/src/layouts/tool.layout.vue index 58e8fb8..aae5d07 100644 --- a/src/layouts/tool.layout.vue +++ b/src/layouts/tool.layout.vue @@ -4,8 +4,10 @@ import BaseLayout from './base.layout.vue'; import { useHead } from '@vueuse/head'; import type { HeadObject } from '@vueuse/head'; import { reactive } from 'vue'; +import { useThemeVars } from 'naive-ui'; const route = useRoute(); +const theme = useThemeVars(); const head = reactive<HeadObject>({ title: `${route.meta.name} - IT Tools`, @@ -27,7 +29,21 @@ useHead(head); <base-layout> <div class="tool-layout"> <div class="tool-header"> - <n-h1>{{ route.meta.name }}</n-h1> + <n-h1> + {{ route.meta.name }} + + <n-tag + v-if="route.meta.isNew" + round + type="success" + :bordered="false" + :color="{ color: theme.primaryColor, textColor: theme.tagColor }" + > + New tool + </n-tag> + <!-- <span class="new-tool-badge">New !</span> --> + </n-h1> + <div class="separator" /> <div class="description"> {{ route.meta.description }} diff --git a/src/pages/Home.page.vue b/src/pages/Home.page.vue index 2e1f459..632b113 100644 --- a/src/pages/Home.page.vue +++ b/src/pages/Home.page.vue @@ -9,7 +9,7 @@ useHead({ title: 'IT Tools - Handy online tools for developers' }); <template> <div class="home-page"> <n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8"> - <n-gi v-for="tool in toolsWithCategory" :key="tool.name"> + <n-gi v-for="tool in toolsWithCategory.reverse().sort(({ isNew }) => (isNew ? -1 : 1))" :key="tool.name"> <tool-card :tool="tool" /> </n-gi> </n-grid> diff --git a/src/tools/base64-converter/index.ts b/src/tools/base64-converter/index.ts index 4d9b1ec..aa8980d 100644 --- a/src/tools/base64-converter/index.ts +++ b/src/tools/base64-converter/index.ts @@ -1,7 +1,7 @@ import { FileDigit } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Base64 converter', path: '/base64-converter', description: "Convert string, files or images into a it's base64 representation.", @@ -9,4 +9,4 @@ export const tool: ITool = { component: () => import('./base64-converter.vue'), icon: FileDigit, redirectFrom: ['/file-to-base64', '/base64-string-converter'], -}; +}); diff --git a/src/tools/bcrypt/index.ts b/src/tools/bcrypt/index.ts index ad0f8d4..f70a3a6 100644 --- a/src/tools/bcrypt/index.ts +++ b/src/tools/bcrypt/index.ts @@ -1,7 +1,7 @@ import { LockSquare } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Bcrypt', path: '/bcrypt', description: @@ -9,4 +9,4 @@ export const tool: ITool = { keywords: ['bcrypt', 'hash', 'compare', 'password', 'salt', 'round', 'storage', 'crypto'], component: () => import('./bcrypt.vue'), icon: LockSquare, -}; +}); diff --git a/src/tools/bip39-generator/index.ts b/src/tools/bip39-generator/index.ts index 99bf045..f649e18 100644 --- a/src/tools/bip39-generator/index.ts +++ b/src/tools/bip39-generator/index.ts @@ -1,11 +1,11 @@ import { AlignJustified } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'BIP39 passphrase generator', path: '/bip39-generator', description: 'Generate BIP39 passphrase from existing or random mnemonic, or get the mnemonic from the passphrase.', keywords: ['BIP39', 'passphrase', 'generator', 'mnemonic', 'entropy'], component: () => import('./bip39-generator.vue'), icon: AlignJustified, -}; +}); diff --git a/src/tools/case-converter/index.ts b/src/tools/case-converter/index.ts index 810ce9a..710a03f 100644 --- a/src/tools/case-converter/index.ts +++ b/src/tools/case-converter/index.ts @@ -1,7 +1,7 @@ import { LetterCaseToggle } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Case converter', path: '/case-converter', description: 'Change the case of a string and chose between different formats', @@ -22,4 +22,4 @@ export const tool: ITool = { ], component: () => import('./case-converter.vue'), icon: LetterCaseToggle, -}; +}); diff --git a/src/tools/color-converter/index.ts b/src/tools/color-converter/index.ts index 024c95c..c82689c 100644 --- a/src/tools/color-converter/index.ts +++ b/src/tools/color-converter/index.ts @@ -1,7 +1,7 @@ import { Palette } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Color converter', path: '/color-converter', description: 'Convert color between the different formats (hex, rgb, hsl and css name)', @@ -9,4 +9,4 @@ export const tool: ITool = { component: () => import('./color-converter.vue'), icon: Palette, redirectFrom: ['/color-picker-converter'], -}; +}); diff --git a/src/tools/crontab-generator/index.ts b/src/tools/crontab-generator/index.ts index 08bba85..49b2838 100644 --- a/src/tools/crontab-generator/index.ts +++ b/src/tools/crontab-generator/index.ts @@ -1,7 +1,7 @@ import { Alarm } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Crontab generator', path: '/crontab-generator', description: 'Validate and generate crontab and get the human readable description of the cron schedule.', @@ -22,4 +22,4 @@ export const tool: ITool = { ], component: () => import('./crontab-generator.vue'), icon: Alarm, -}; +}); diff --git a/src/tools/date-time-converter/index.ts b/src/tools/date-time-converter/index.ts index 989703e..4bc66bf 100644 --- a/src/tools/date-time-converter/index.ts +++ b/src/tools/date-time-converter/index.ts @@ -1,11 +1,11 @@ import { Calendar } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Date-time converter', path: '/date-converter', description: 'Convert date and time into the various different formats', keywords: ['date', 'time', 'converter', 'iso', 'utc', 'timezone', 'year', 'month', 'day', 'minute', 'seconde'], component: () => import('./date-time-converter.vue'), icon: Calendar, -}; +}); diff --git a/src/tools/device-information/index.ts b/src/tools/device-information/index.ts index 2796195..e55ae28 100644 --- a/src/tools/device-information/index.ts +++ b/src/tools/device-information/index.ts @@ -1,7 +1,7 @@ import { DeviceDesktop } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Device information', path: '/device-information', description: 'Get information about your current device (screen size, pixel-ratio, user agent, ...)', @@ -20,4 +20,4 @@ export const tool: ITool = { ], component: () => import('./device-information.vue'), icon: DeviceDesktop, -}; +}); diff --git a/src/tools/encryption/index.ts b/src/tools/encryption/index.ts index 468a7fb..9a95f4b 100644 --- a/src/tools/encryption/index.ts +++ b/src/tools/encryption/index.ts @@ -1,7 +1,7 @@ import { Lock } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Encrypt / decrypt text', path: '/encryption', description: 'Encrypt and decrypt text clear text using crypto algorithm like AES, TripleDES, Rabbit or RC4.', @@ -9,4 +9,4 @@ export const tool: ITool = { component: () => import('./encryption.vue'), icon: Lock, redirectFrom: ['/cypher'], -}; +}); diff --git a/src/tools/git-memo/index.ts b/src/tools/git-memo/index.ts index 1b027b7..c91ee81 100644 --- a/src/tools/git-memo/index.ts +++ b/src/tools/git-memo/index.ts @@ -1,7 +1,7 @@ import { BrandGit } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Git cheatsheet', path: '/git-memo', description: @@ -9,4 +9,4 @@ export const tool: ITool = { keywords: ['git', 'push', 'force', 'pull', 'commit', 'amend', 'rebase', 'merge', 'reset', 'soft', 'hard', 'lease'], component: () => import('./git-memo.vue'), icon: BrandGit, -}; +}); diff --git a/src/tools/hash-text/index.ts b/src/tools/hash-text/index.ts index 0cbe2b8..3012747 100644 --- a/src/tools/hash-text/index.ts +++ b/src/tools/hash-text/index.ts @@ -1,7 +1,7 @@ import { EyeOff } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Hash text', path: '/hash-text', description: @@ -24,4 +24,4 @@ export const tool: ITool = { component: () => import('./hash-text.vue'), icon: EyeOff, redirectFrom: ['/hash'], -}; +}); diff --git a/src/tools/html-entities/index.ts b/src/tools/html-entities/index.ts index 5d3b850..4907dc6 100644 --- a/src/tools/html-entities/index.ts +++ b/src/tools/html-entities/index.ts @@ -1,11 +1,11 @@ import { Code } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Escape html entities', path: '/html-entities', description: 'Escape or unescape html entities (replace <,>, &, " and \' to their html version)', keywords: ['html', 'entities', 'escape', 'unescape', 'special', 'characters', 'tags'], component: () => import('./html-entities.vue'), icon: Code, -}; +}); diff --git a/src/tools/integer-base-converter/index.ts b/src/tools/integer-base-converter/index.ts index 7cdfd4d..0008568 100644 --- a/src/tools/integer-base-converter/index.ts +++ b/src/tools/integer-base-converter/index.ts @@ -1,11 +1,11 @@ import { ArrowsLeftRight } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Integer base converter', path: '/base-converter', description: 'Convert number between different bases (decimal, hexadecimal, binary, octal, base64, ...)', keywords: ['integer', 'number', 'base', 'conversion', 'decimal', 'hexadecimal', 'binary', 'octal', 'base64'], component: () => import('./integer-base-converter.vue'), icon: ArrowsLeftRight, -}; +}); diff --git a/src/tools/json-viewer/index.ts b/src/tools/json-viewer/index.ts index 67a17dc..23f1b8a 100644 --- a/src/tools/json-viewer/index.ts +++ b/src/tools/json-viewer/index.ts @@ -1,11 +1,11 @@ import { Braces } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'JSON viewer', path: '/json-viewer', description: 'Prettify JSON string to a human friendly readable format.', keywords: ['json', 'viewer', 'prettify', 'format'], component: () => import('./json-viewer.vue'), icon: Braces, -}; +}); diff --git a/src/tools/lorem-ipsum-generator/index.ts b/src/tools/lorem-ipsum-generator/index.ts index b3d5a96..1767d85 100644 --- a/src/tools/lorem-ipsum-generator/index.ts +++ b/src/tools/lorem-ipsum-generator/index.ts @@ -1,7 +1,7 @@ import { AlignJustified } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Lorem ipsum generator', path: '/lorem-ipsum-generator', description: @@ -9,4 +9,4 @@ export const tool: ITool = { keywords: ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'placeholder', 'text', 'filler', 'random', 'generator'], component: () => import('./lorem-ipsum-generator.vue'), icon: AlignJustified, -}; +}); diff --git a/src/tools/qr-code-generator/index.ts b/src/tools/qr-code-generator/index.ts index 6bd9163..4c2f86b 100644 --- a/src/tools/qr-code-generator/index.ts +++ b/src/tools/qr-code-generator/index.ts @@ -1,7 +1,7 @@ import { Qrcode } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'QR Code generator', path: '/qrcode-generator', description: @@ -9,4 +9,4 @@ export const tool: ITool = { keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent'], component: () => import('./qr-code-generator.vue'), icon: Qrcode, -}; +}); diff --git a/src/tools/random-port-generator/index.ts b/src/tools/random-port-generator/index.ts index 1373c41..febdc2a 100644 --- a/src/tools/random-port-generator/index.ts +++ b/src/tools/random-port-generator/index.ts @@ -1,11 +1,11 @@ import { Server } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Random port generator', path: '/random-port-generator', description: 'Generate random port numbers outside of the range of "known" ports (0-1023).', keywords: ['system', 'port', 'lan', 'generator', 'random', 'development', 'computer'], component: () => import('./random-port-generator.vue'), icon: Server, -}; +}); diff --git a/src/tools/roman-numeral-converter/index.ts b/src/tools/roman-numeral-converter/index.ts index bea4dec..f2dbdc0 100644 --- a/src/tools/roman-numeral-converter/index.ts +++ b/src/tools/roman-numeral-converter/index.ts @@ -1,11 +1,11 @@ import { LetterX } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Roman numeral converter', path: '/roman-numeral-converter', description: 'Convert Roman numerals to numbers and convert numbers to Roman numerals.', keywords: ['roman', 'arabic', 'converter', 'X', 'I', 'V', 'L', 'C', 'D', 'M'], component: () => import('./roman-numeral-converter.vue'), icon: LetterX, -}; +}); diff --git a/src/tools/text-statistics/index.ts b/src/tools/text-statistics/index.ts index 82d49f1..def0b6d 100644 --- a/src/tools/text-statistics/index.ts +++ b/src/tools/text-statistics/index.ts @@ -1,7 +1,7 @@ import { FileText } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Text statistics', path: '/text-statistics', description: "Get information about a text, the amount of characters, the amount of words, it's size, ...", @@ -9,4 +9,4 @@ export const tool: ITool = { component: () => import('./text-statistics.vue'), icon: FileText, redirectFrom: ['/text-stats'], -}; +}); diff --git a/src/tools/token-generator/index.ts b/src/tools/token-generator/index.ts index 1a9ab67..bed5f00 100644 --- a/src/tools/token-generator/index.ts +++ b/src/tools/token-generator/index.ts @@ -1,7 +1,7 @@ import { ArrowsShuffle } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Token generator', path: '/token-generator', description: @@ -9,4 +9,4 @@ export const tool: ITool = { keywords: ['token', 'random', 'string', 'alphanumeric', 'symbols', 'number', 'letters', 'lowercase', 'uppercase'], component: () => import('./token-generator.tool.vue'), icon: ArrowsShuffle, -}; +}); diff --git a/src/tools/tool.ts b/src/tools/tool.ts index 4cd462c..b2ebf49 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -1,3 +1,4 @@ +import { config } from '@/config'; import type { Component } from 'vue'; export interface ITool { @@ -8,6 +9,7 @@ export interface ITool { component: () => Promise<Component>; icon: Component; redirectFrom?: string[]; + isNew: boolean; } export interface ToolCategory { @@ -15,3 +17,17 @@ export interface ToolCategory { icon: Component; components: ITool[]; } + +type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; + +export function defineTool( + tool: WithOptional<ITool, 'isNew'>, + { newTools }: { newTools: string[] } = { newTools: config.tools.newTools }, +) { + const isNew = newTools.includes(tool.name); + + return { + isNew, + ...tool, + }; +} diff --git a/src/tools/url-encoder/index.ts b/src/tools/url-encoder/index.ts index ccd4219..bd19b89 100644 --- a/src/tools/url-encoder/index.ts +++ b/src/tools/url-encoder/index.ts @@ -1,11 +1,11 @@ import { Link } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Encode/decode url formatted strings', path: '/url-encoder', description: 'Encode to url-encoded format (also known as "percent-encoded") or decode from it.', keywords: ['url', 'encode', 'decode', 'percent', '%20', 'format'], component: () => import('./url-encoder.vue'), icon: Link, -}; +}); diff --git a/src/tools/url-parser/index.ts b/src/tools/url-parser/index.ts index 548be39..d1c8dfe 100644 --- a/src/tools/url-parser/index.ts +++ b/src/tools/url-parser/index.ts @@ -1,7 +1,7 @@ import { Unlink } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'Url parser', path: '/url-parser', description: @@ -9,4 +9,4 @@ export const tool: ITool = { keywords: ['url', 'parser', 'protocol', 'origin', 'params', 'port', 'username', 'password', 'href'], component: () => import('./url-parser.vue'), icon: Unlink, -}; +}); diff --git a/src/tools/uuid-generator/index.ts b/src/tools/uuid-generator/index.ts index 3bd1025..2b4b3d3 100644 --- a/src/tools/uuid-generator/index.ts +++ b/src/tools/uuid-generator/index.ts @@ -1,7 +1,7 @@ import { Fingerprint } from '@vicons/tabler'; -import type { ITool } from '../tool'; +import { defineTool } from '../tool'; -export const tool: ITool = { +export const tool = defineTool({ name: 'UUIDs v4 generator', path: '/uuid-generator', description: @@ -9,4 +9,4 @@ export const tool: ITool = { keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'], component: () => import('./uuid-generator.vue'), icon: Fingerprint, -}; +}); |