Compare commits

..

2 Commits

Author SHA1 Message Date
Corentin Thomasset
0fd99deca9 wip 2023-04-15 21:47:00 +02:00
Corentin Thomasset
f7a1383da5 chore(deps): updated dependencies versions 2023-04-14 23:32:09 +02:00
111 changed files with 2068 additions and 3103 deletions

View File

@@ -136,7 +136,6 @@
"useDeviceOrientation": true, "useDeviceOrientation": true,
"useDevicePixelRatio": true, "useDevicePixelRatio": true,
"useDevicesList": true, "useDevicesList": true,
"useDialog": true,
"useDisplayMedia": true, "useDisplayMedia": true,
"useDocumentVisibility": true, "useDocumentVisibility": true,
"useDraggable": true, "useDraggable": true,
@@ -169,7 +168,6 @@
"useKeyModifier": true, "useKeyModifier": true,
"useLastChanged": true, "useLastChanged": true,
"useLink": true, "useLink": true,
"useLoadingBar": true,
"useLocalStorage": true, "useLocalStorage": true,
"useMagicKeys": true, "useMagicKeys": true,
"useManualRefHistory": true, "useManualRefHistory": true,
@@ -177,7 +175,6 @@
"useMediaQuery": true, "useMediaQuery": true,
"useMemoize": true, "useMemoize": true,
"useMemory": true, "useMemory": true,
"useMessage": true,
"useMounted": true, "useMounted": true,
"useMouse": true, "useMouse": true,
"useMouseInElement": true, "useMouseInElement": true,
@@ -185,7 +182,6 @@
"useMutationObserver": true, "useMutationObserver": true,
"useNavigatorLanguage": true, "useNavigatorLanguage": true,
"useNetwork": true, "useNetwork": true,
"useNotification": true,
"useNow": true, "useNow": true,
"useObjectUrl": true, "useObjectUrl": true,
"useOffsetPagination": true, "useOffsetPagination": true,

View File

@@ -27,8 +27,5 @@ jobs:
- name: Run unit test - name: Run unit test
run: pnpm test run: pnpm test
- name: Type check
run: pnpm typecheck
- name: Build the app - name: Build the app
run: pnpm build run: pnpm build

2
.gitignore vendored
View File

@@ -31,3 +31,5 @@ coverage
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/playwright/.cache/ /playwright/.cache/
.vite-ssg-temp

View File

@@ -2,25 +2,6 @@
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.
## Version 2023.04.23-92bd835
### Features
- **ui-lib**: demo pages for c-lib components (92bd835)
- **new-tool**: diff of two json objects (362f2fa)
- **ipv4-range-expander**: expands a given IPv4 start and end address to a valid IPv4 subnet (#366) (df989e2)
- **date converter**: auto focus main input (6d22025)
### Bug fixes
- **ts**: cleaned legacy typechecking warning (e88c1d5)
- **mac-address-lookup**: added copy handler on button click (c311e38)
### Refactoring
- **ui-lib**: prevent c-button to shrink (61ece23)
- **ui**: replaced naive ui cards with custom ones (f080933)
- **clean**: removed unused lodash import (bb32513)
- **clean**: removed useless br tags (74073f5)
- **ui**: getting ride of naive ui buttons (c45bce3)
## Version 2023.04.14-dbad773 ## Version 2023.04.14-dbad773
### Features ### Features

8
auto-imports.d.ts vendored
View File

@@ -133,7 +133,6 @@ declare global {
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
const useDialog: typeof import('naive-ui')['useDialog']
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useDraggable: typeof import('@vueuse/core')['useDraggable'] const useDraggable: typeof import('@vueuse/core')['useDraggable']
@@ -166,7 +165,6 @@ declare global {
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLink: typeof import('vue-router')['useLink'] const useLink: typeof import('vue-router')['useLink']
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
@@ -174,7 +172,6 @@ declare global {
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMemoize: typeof import('@vueuse/core')['useMemoize'] const useMemoize: typeof import('@vueuse/core')['useMemoize']
const useMemory: typeof import('@vueuse/core')['useMemory'] const useMemory: typeof import('@vueuse/core')['useMemory']
const useMessage: typeof import('naive-ui')['useMessage']
const useMounted: typeof import('@vueuse/core')['useMounted'] const useMounted: typeof import('@vueuse/core')['useMounted']
const useMouse: typeof import('@vueuse/core')['useMouse'] const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
@@ -182,7 +179,6 @@ declare global {
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
const useNetwork: typeof import('@vueuse/core')['useNetwork'] const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNotification: typeof import('naive-ui')['useNotification']
const useNow: typeof import('@vueuse/core')['useNow'] const useNow: typeof import('@vueuse/core')['useNow']
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
@@ -396,7 +392,6 @@ declare module 'vue' {
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']> readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']> readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']> readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDialog: UnwrapRef<typeof import('naive-ui')['useDialog']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']> readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']> readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']> readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
@@ -429,7 +424,6 @@ declare module 'vue' {
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']> readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']> readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']> readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLoadingBar: UnwrapRef<typeof import('naive-ui')['useLoadingBar']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']> readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']> readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']> readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
@@ -437,7 +431,6 @@ declare module 'vue' {
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']> readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']> readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']> readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
readonly useMessage: UnwrapRef<typeof import('naive-ui')['useMessage']>
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']> readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']> readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']> readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
@@ -445,7 +438,6 @@ declare module 'vue' {
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']> readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']> readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']> readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
readonly useNotification: UnwrapRef<typeof import('naive-ui')['useNotification']>
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']> readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']> readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']> readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>

111
components.d.ts vendored
View File

@@ -9,142 +9,31 @@ export {}
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
'404.page': typeof import('./src/pages/404.page.vue')['default']
About: typeof import('./src/pages/About.vue')['default']
App: typeof import('./src/App.vue')['default']
'Base.layout': typeof import('./src/layouts/base.layout.vue')['default']
Base64FileConverter: typeof import('./src/tools/base64-file-converter/base64-file-converter.vue')['default']
Base64StringConverter: typeof import('./src/tools/base64-string-converter/base64-string-converter.vue')['default']
BasicAuthGenerator: typeof import('./src/tools/basic-auth-generator/basic-auth-generator.vue')['default']
Bcrypt: typeof import('./src/tools/bcrypt/bcrypt.vue')['default']
BenchmarkBuilder: typeof import('./src/tools/benchmark-builder/benchmark-builder.vue')['default']
Bip39Generator: typeof import('./src/tools/bip39-generator/bip39-generator.vue')['default']
CaseConverter: typeof import('./src/tools/case-converter/case-converter.vue')['default']
CButton: typeof import('./src/ui/c-button/c-button.vue')['default']
'CButton.demo': typeof import('./src/ui/c-button/c-button.demo.vue')['default']
CCard: typeof import('./src/ui/c-card/c-card.vue')['default']
'CCard.demo': typeof import('./src/ui/c-card/c-card.demo.vue')['default']
ChmodCalculator: typeof import('./src/tools/chmod-calculator/chmod-calculator.vue')['default']
Chronometer: typeof import('./src/tools/chronometer/chronometer.vue')['default']
CLink: typeof import('./src/ui/c-link/c-link.vue')['default']
'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default']
CollapsibleToolMenu: typeof import('./src/components/CollapsibleToolMenu.vue')['default'] CollapsibleToolMenu: typeof import('./src/components/CollapsibleToolMenu.vue')['default']
ColorConverter: typeof import('./src/tools/color-converter/color-converter.vue')['default']
ColoredCard: typeof import('./src/components/ColoredCard.vue')['default'] ColoredCard: typeof import('./src/components/ColoredCard.vue')['default']
CopyableIpLike: typeof import('./src/tools/ipv4-subnet-calculator/copyable-ip-like.vue')['default']
CrontabGenerator: typeof import('./src/tools/crontab-generator/crontab-generator.vue')['default']
DateTimeConverter: typeof import('./src/tools/date-time-converter/date-time-converter.vue')['default']
'Demo.routes': typeof import('./src/ui/demo/demo.routes.vue')['default']
DemoWrapper: typeof import('./src/ui/demo/demo-wrapper.vue')['default']
DeviceInformation: typeof import('./src/tools/device-information/device-information.vue')['default']
DiffViewer: typeof import('./src/tools/json-diff/diff-viewer/diff-viewer.vue')['default']
DockerRunToDockerComposeConverter: typeof import('./src/tools/docker-run-to-docker-compose-converter/docker-run-to-docker-compose-converter.vue')['default']
DynamicValues: typeof import('./src/tools/benchmark-builder/dynamic-values.vue')['default']
Editor: typeof import('./src/tools/html-wysiwyg-editor/editor/editor.vue')['default']
Encryption: typeof import('./src/tools/encryption/encryption.vue')['default']
EtaCalculator: typeof import('./src/tools/eta-calculator/eta-calculator.vue')['default']
FavoriteButton: typeof import('./src/components/FavoriteButton.vue')['default'] FavoriteButton: typeof import('./src/components/FavoriteButton.vue')['default']
FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default'] FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default']
GitMemo: typeof import('./src/tools/git-memo/git-memo.md')['default']
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
'Home.page': typeof import('./src/pages/Home.page.vue')['default']
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']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['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']
Ipv4RangeExpander: typeof import('./src/tools/ipv4-range-expander/ipv4-range-expander.vue')['default']
Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default']
Ipv6UlaGenerator: typeof import('./src/tools/ipv6-ula-generator/ipv6-ula-generator.vue')['default']
JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default']
JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default']
JsonToYaml: typeof import('./src/tools/json-to-yaml-converter/json-to-yaml.vue')['default']
JsonViewer: typeof import('./src/tools/json-viewer/json-viewer.vue')['default']
JwtParser: typeof import('./src/tools/jwt-parser/jwt-parser.vue')['default']
KeycodeInfo: typeof import('./src/tools/keycode-info/keycode-info.vue')['default']
LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default']
MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default']
MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default']
MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default']
MenuBarItem: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue')['default']
MenuIconItem: typeof import('./src/components/MenuIconItem.vue')['default'] MenuIconItem: typeof import('./src/components/MenuIconItem.vue')['default']
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default'] MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NAutoComplete: typeof import('naive-ui')['NAutoComplete']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NButton: typeof import('naive-ui')['NButton'] NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard'] NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCode: typeof import('naive-ui')['NCode']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDivider: typeof import('naive-ui')['NDivider']
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
NEllipsis: typeof import('naive-ui')['NEllipsis'] NEllipsis: typeof import('naive-ui')['NEllipsis']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi'] NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid'] NGrid: typeof import('naive-ui')['NGrid']
NH1: typeof import('naive-ui')['NH1'] NH1: typeof import('naive-ui')['NH1']
NH2: typeof import('naive-ui')['NH2']
NH3: typeof import('naive-ui')['NH3'] NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon'] NIcon: typeof import('naive-ui')['NIcon']
NImage: typeof import('naive-ui')['NImage']
NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
NP: typeof import('naive-ui')['NP']
NPageHeader: typeof import('naive-ui')['NPageHeader']
NProgress: typeof import('naive-ui')['NProgress']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSlider: typeof import('naive-ui')['NSlider']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch']
NTable: typeof import('naive-ui')['NTable']
NTag: typeof import('naive-ui')['NTag'] NTag: typeof import('naive-ui')['NTag']
NText: typeof import('naive-ui')['NText'] NText: typeof import('naive-ui')['NText']
NTooltip: typeof import('naive-ui')['NTooltip'] NTooltip: typeof import('naive-ui')['NTooltip']
NUpload: typeof import('naive-ui')['NUpload']
NUploadDragger: typeof import('naive-ui')['NUploadDragger']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default']
RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default']
ResultRow: typeof import('./src/tools/ipv4-range-expander/result-row.vue')['default']
RomanNumeralConverter: typeof import('./src/tools/roman-numeral-converter/roman-numeral-converter.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
SearchBar: typeof import('./src/components/SearchBar.vue')['default'] SearchBar: typeof import('./src/components/SearchBar.vue')['default']
SearchBarItem: typeof import('./src/components/SearchBarItem.vue')['default'] SearchBarItem: typeof import('./src/components/SearchBarItem.vue')['default']
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']
SvgPlaceholderGenerator: typeof import('./src/tools/svg-placeholder-generator/svg-placeholder-generator.vue')['default']
TemperatureConverter: typeof import('./src/tools/temperature-converter/temperature-converter.vue')['default']
TextareaCopyable: typeof import('./src/components/TextareaCopyable.vue')['default'] TextareaCopyable: typeof import('./src/components/TextareaCopyable.vue')['default']
TextStatistics: typeof import('./src/tools/text-statistics/text-statistics.vue')['default']
TextToNatoAlphabet: typeof import('./src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue')['default']
TokenDisplay: typeof import('./src/tools/otp-code-generator-and-validator/token-display.vue')['default']
'TokenGenerator.tool': typeof import('./src/tools/token-generator/token-generator.tool.vue')['default']
'Tool.layout': typeof import('./src/layouts/tool.layout.vue')['default']
ToolCard: typeof import('./src/components/ToolCard.vue')['default'] ToolCard: typeof import('./src/components/ToolCard.vue')['default']
UrlEncoder: typeof import('./src/tools/url-encoder/url-encoder.vue')['default']
UrlParser: typeof import('./src/tools/url-parser/url-parser.vue')['default']
UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default']
UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default']
UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default']
YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default']
} }
} }

View File

@@ -1,7 +1,8 @@
{ {
"name": "it-tools", "name": "it-tools",
"version": "2023.4.23-92bd835", "version": "2023.4.14-dbad773",
"description": "Collection of handy online tools for developers, with great UX. ", "description": "Collection of handy online tools for developers, with great UX. ",
"type": "module",
"keywords": [ "keywords": [
"productivity", "productivity",
"converter", "converter",
@@ -21,7 +22,7 @@
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite-ssg build",
"preview": "vite preview --port 5050", "preview": "vite preview --port 5050",
"test": "npm run test:unit", "test": "npm run test:unit",
"test:unit": "vitest --environment jsdom", "test:unit": "vitest --environment jsdom",
@@ -42,7 +43,6 @@
"@vicons/material": "^0.12.0", "@vicons/material": "^0.12.0",
"@vicons/tabler": "^0.12.0", "@vicons/tabler": "^0.12.0",
"@vueuse/core": "^8.9.4", "@vueuse/core": "^8.9.4",
"@vueuse/head": "^0.7.13",
"@vueuse/router": "^9.13.0", "@vueuse/router": "^9.13.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"change-case": "^4.1.2", "change-case": "^4.1.2",
@@ -73,7 +73,6 @@
"ua-parser-js": "^1.0.35", "ua-parser-js": "^1.0.35",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-router": "^4.1.6",
"yaml": "^2.2.1" "yaml": "^2.2.1"
}, },
"devDependencies": { "devDependencies": {
@@ -82,7 +81,7 @@
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/jsdom": "^16.2.15", "@types/jsdom": "^16.2.15",
"@types/lodash": "^4.14.192", "@types/lodash": "^4.14.194",
"@types/mime-types": "^2.1.1", "@types/mime-types": "^2.1.1",
"@types/netmask": "^2.0.0", "@types/netmask": "^2.0.0",
"@types/node": "^16.18.23", "@types/node": "^16.18.23",
@@ -94,12 +93,13 @@
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@typescript-eslint/parser": "^5.58.0", "@typescript-eslint/parser": "^5.58.0",
"@unocss/eslint-config": "^0.50.8", "@unocss/eslint-config": "^0.50.8",
"@vitejs/plugin-vue": "^2.3.4", "@vitejs/plugin-vue": "^4.1.0",
"@vitejs/plugin-vue-jsx": "^1.3.10", "@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/eslint-config-prettier": "^7.1.0", "@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^10.0.0", "@vue/eslint-config-typescript": "^10.0.0",
"@vue/test-utils": "^2.3.2", "@vue/test-utils": "^2.3.2",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"@vueuse/head": "^1.1.23",
"c8": "^7.13.0", "c8": "^7.13.0",
"consola": "^3.0.2", "consola": "^3.0.2",
"eslint": "^8.38.0", "eslint": "^8.38.0",
@@ -111,16 +111,18 @@
"less": "^4.1.3", "less": "^4.1.3",
"prettier": "^2.8.7", "prettier": "^2.8.7",
"start-server-and-test": "^1.15.4", "start-server-and-test": "^1.15.4",
"typescript": "~4.5.5", "typescript": "~4.9.5",
"unocss": "^0.50.8", "unocss": "^0.50.8",
"unplugin-auto-import": "^0.15.2", "unplugin-auto-import": "^0.15.3",
"unplugin-vue-components": "^0.24.1", "unplugin-vue-components": "^0.24.1",
"vite": "^2.9.15", "vite": "^4.2.1",
"vite-plugin-md": "^0.12.4", "vite-plugin-md": "^0.21.5",
"vite-plugin-pwa": "^0.11.13", "vite-plugin-pwa": "^0.14.7",
"vite-ssg": "^0.22.2",
"vite-svg-loader": "^3.6.0", "vite-svg-loader": "^3.6.0",
"vitest": "^0.13.1", "vitest": "^0.30.1",
"vue-tsc": "^0.31.4", "vue-router": "^4.1.6",
"vue-tsc": "^1.2.0",
"workbox-window": "^6.5.4", "workbox-window": "^6.5.4",
"zx": "^7.2.1" "zx": "^7.2.1"
} }

2258
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -29,9 +29,9 @@ createToolFile(
`${toolName}.vue`, `${toolName}.vue`,
` `
<template> <template>
<div> <n-card>
Lorem ipsum Lorem ipsum
</div> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,30 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; // import { computed } from 'vue';
import { useRoute, RouterView } from 'vue-router'; // import { darkTheme, NGlobalStyle, NMessageProvider, NNotificationProvider } from 'naive-ui';
import { darkTheme, NGlobalStyle, NMessageProvider, NNotificationProvider } from 'naive-ui'; // import { useRoute, RouterView } from 'vue-router';
import { darkThemeOverrides, lightThemeOverrides } from './themes'; // import { darkThemeOverrides, lightThemeOverrides } from './themes';
import { layouts } from './layouts'; // import { layouts } from './layouts';
import { useStyleStore } from './stores/style.store'; // import { useStyleStore } from './stores/style.store';
const route = useRoute(); // const route = useRoute();
const layout = computed(() => route?.meta?.layout ?? layouts.base); // const layout = computed(() => route?.meta?.layout ?? layouts.base);
const styleStore = useStyleStore(); // const styleStore = useStyleStore();
const theme = computed(() => (styleStore.isDarkTheme ? darkTheme : null)); // const theme = computed(() => (styleStore.isDarkTheme ? darkTheme : null));
const themeOverrides = computed(() => (styleStore.isDarkTheme ? darkThemeOverrides : lightThemeOverrides)); // const themeOverrides = computed(() => (styleStore.isDarkTheme ? darkThemeOverrides : lightThemeOverrides));
</script> </script>
<template> <template>
<n-config-provider :theme="theme" :theme-overrides="themeOverrides"> <!-- <n-config-provider :theme="theme" :theme-overrides="themeOverrides">
<n-global-style /> <n-global-style />
<n-message-provider placement="bottom"> <n-message-provider placement="bottom">
<n-notification-provider placement="bottom-right"> <n-notification-provider placement="bottom-right">
<component :is="layout"> <component :is="layout"> -->
<router-view /> <router-view />
</component> <!-- </component>
</n-notification-provider> </n-notification-provider>
</n-message-provider> </n-message-provider>
</n-config-provider> </n-config-provider> -->
</template> </template>
<style> <style>

View File

@@ -14,7 +14,6 @@
<n-menu <n-menu
class="menu" class="menu"
:value="(route.name as string)"
:collapsed-width="64" :collapsed-width="64"
:collapsed-icon-size="22" :collapsed-icon-size="22"
:options="tools" :options="tools"
@@ -32,7 +31,7 @@ import { ChevronRight } from '@vicons/tabler';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { useThemeVars } from 'naive-ui'; import { useThemeVars } from 'naive-ui';
import { toRefs, computed, h } from 'vue'; import { toRefs, computed, h } from 'vue';
import { RouterLink, useRoute } from 'vue-router'; import { RouterLink } from 'vue-router';
import MenuIconItem from './MenuIconItem.vue'; import MenuIconItem from './MenuIconItem.vue';
const props = withDefaults(defineProps<{ toolsByCategory?: ToolCategory[] }>(), { toolsByCategory: () => [] }); const props = withDefaults(defineProps<{ toolsByCategory?: ToolCategory[] }>(), { toolsByCategory: () => [] });

View File

@@ -1,18 +1,18 @@
<template> <template>
<c-card class="colored-card"> <n-card class="colored-card">
<n-space justify="space-between" align="center"> <n-space justify="space-between" align="center">
<n-icon class="icon" size="40" :component="icon" /> <n-icon class="icon" size="40" :component="icon" />
</n-space> </n-space>
<n-h3 class="title"> <n-h3 class="title">
<n-ellipsis>{{ title }}</n-ellipsis> <n-ellipsis>{{ title }}</n-ellipsis>
</n-h3> </n-h3>
<!--
<div class="description"> <div class="description">
<n-ellipsis :line-clamp="2" :tooltip="false"> <n-ellipsis :line-clamp="2" :tooltip="false">
<slot /> <slot />
</n-ellipsis> </n-ellipsis>
</div> </div> -->
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,15 +1,11 @@
<template> <template>
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<c-button <n-button circle quaternary :type="buttonType" :style="{ opacity: isFavorite ? 1 : 0.2 }" @click="toggleFavorite">
variant="text" <template #icon>
circle <n-icon :component="FavoriteFilled" />
:type="buttonType" </template>
:style="{ opacity: isFavorite ? 1 : 0.2 }" </n-button>
@click="toggleFavorite"
>
<n-icon :component="FavoriteFilled" />
</c-button>
</template> </template>
{{ isFavorite ? 'Remove from favorites' : 'Add to favorites' }} {{ isFavorite ? 'Remove from favorites' : 'Add to favorites' }}
</n-tooltip> </n-tooltip>

View File

@@ -3,9 +3,9 @@
<template #suffix> <template #suffix>
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<c-button circle variant="text" @click="onCopyClicked"> <n-button quaternary circle @click="onCopyClicked">
<n-icon :component="ContentCopyFilled" /> <n-icon :component="ContentCopyFilled" />
</c-button> </n-button>
</template> </template>
{{ tooltipText }} {{ tooltipText }}
</n-tooltip> </n-tooltip>

View File

@@ -1,50 +1,56 @@
<template> <template>
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<c-button <n-button
size="large"
circle circle
variant="text" quaternary
tag="a"
href="https://github.com/CorentinTh/it-tools" href="https://github.com/CorentinTh/it-tools"
rel="noopener"
target="_blank" target="_blank"
rel="noopener noreferrer"
aria-label="IT-Tools' GitHub repository" aria-label="IT-Tools' GitHub repository"
> >
<n-icon size="25" :component="BrandGithub" /> <n-icon size="25" :component="BrandGithub" />
</c-button> </n-button>
</template> </template>
Github repository Github repository
</n-tooltip> </n-tooltip>
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<c-button <n-button
size="large"
circle circle
variant="text" quaternary
tag="a"
href="https://twitter.com/ittoolsdottech" href="https://twitter.com/ittoolsdottech"
rel="noopener" rel="noopener"
target="_blank" target="_blank"
aria-label="IT Tools' Twitter account" aria-label="IT Tools' Twitter account"
> >
<n-icon size="25" :component="BrandTwitter" /> <n-icon size="25" :component="BrandTwitter" />
</c-button> </n-button>
</template> </template>
IT Tools' Twitter account IT Tools' Twitter account
</n-tooltip> </n-tooltip>
<router-link to="/about" #="{ navigate, href }" custom>
<n-tooltip trigger="hover">
<template #trigger>
<n-button tag="a" :href="href" circle quaternary size="large" aria-label="About" @click="navigate">
<n-icon size="25" :component="InfoCircle" />
</n-button>
</template>
About
</n-tooltip>
</router-link>
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<c-button circle variant="text" to="/about" aria-label="About"> <n-button size="large" circle quaternary aria-label="Toggle dark/light mode" @click="isDarkTheme = !isDarkTheme">
<n-icon size="25" :component="InfoCircle" />
</c-button>
</template>
About
</n-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" aria-label="Toggle dark/light mode" @click="toggleDarkTheme">
<n-icon v-if="isDarkTheme" size="25" :component="Sun" /> <n-icon v-if="isDarkTheme" size="25" :component="Sun" />
<n-icon v-else size="25" :component="Moon" /> <n-icon v-else size="25" :component="Moon" />
</c-button> </n-button>
</template> </template>
<span v-if="isDarkTheme">Light mode</span> <span v-if="isDarkTheme">Light mode</span>
<span v-else>Dark mode</span> <span v-else>Dark mode</span>
@@ -53,20 +59,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useStyleStore } from '@/stores/style.store'; import { useStyleStore } from '@/stores/style.store';
import { useThemeStore } from '@/ui/theme/theme.store';
import { BrandGithub, BrandTwitter, InfoCircle, Moon, Sun } from '@vicons/tabler'; import { BrandGithub, BrandTwitter, InfoCircle, Moon, Sun } from '@vicons/tabler';
import { toRefs } from 'vue'; import { toRefs } from 'vue';
const styleStore = useStyleStore(); const styleStore = useStyleStore();
const { isDarkTheme } = toRefs(styleStore); const { isDarkTheme } = toRefs(styleStore);
const themeStore = useThemeStore();
function toggleDarkTheme() {
isDarkTheme.value = !isDarkTheme.value;
themeStore.toggleTheme();
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -7,7 +7,6 @@ import { SearchRound } from '@vicons/material';
import { useMagicKeys, whenever } from '@vueuse/core'; import { useMagicKeys, whenever } from '@vueuse/core';
import type { NInput } from 'naive-ui'; import type { NInput } from 'naive-ui';
import { computed, h, ref } from 'vue'; import { computed, h, ref } from 'vue';
import { useRouter } from 'vue-router';
import SearchBarItem from './SearchBarItem.vue'; import SearchBarItem from './SearchBarItem.vue';
const toolToOption = (tool: Tool) => ({ label: tool.name, value: tool.path, tool }); const toolToOption = (tool: Tool) => ({ label: tool.name, value: tool.path, tool });
@@ -81,7 +80,7 @@ function onFocus() {
<n-auto-complete <n-auto-complete
v-model:value="queryString" v-model:value="queryString"
:options="options" :options="options"
:on-select="(value: string | number) => onSelect(String(value))" :on-select="(value) => onSelect(String(value))"
:render-label="renderOption" :render-label="renderOption"
:default-value="'aa'" :default-value="'aa'"
:get-show="() => displayDropDown" :get-show="() => displayDropDown"

View File

@@ -1,6 +1,6 @@
<template> <template>
<div style="overflow-x: hidden; width: 100%"> <div style="overflow-x: hidden; width: 100%">
<c-card class="result-card"> <n-card class="result-card">
<n-scrollbar <n-scrollbar
x-scrollable x-scrollable
trigger="none" trigger="none"
@@ -13,16 +13,16 @@
<n-tooltip v-if="value" trigger="hover"> <n-tooltip v-if="value" trigger="hover">
<template #trigger> <template #trigger>
<div class="copy-button" :class="[copyPlacement]"> <div class="copy-button" :class="[copyPlacement]">
<c-button circle important:h-10 important:w-10 @click="onCopyClicked"> <n-button circle secondary size="large" @click="onCopyClicked">
<n-icon size="22" :component="Copy" /> <n-icon size="22" :component="Copy" />
</c-button> </n-button>
</div> </div>
</template> </template>
<span>{{ tooltipText }}</span> <span>{{ tooltipText }}</span>
</n-tooltip> </n-tooltip>
</c-card> </n-card>
<n-space v-if="copyPlacement === 'outside'" justify="center" mt-4> <n-space v-if="copyPlacement === 'outside'" justify="center" mt-4>
<c-button @click="onCopyClicked"> {{ tooltipText }} </c-button> <n-button secondary @click="onCopyClicked"> {{ tooltipText }} </n-button>
</n-space> </n-space>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<template> <template>
<router-link :to="tool.path"> <router-link :to="tool.path">
<c-card class="tool-card"> <n-card class="tool-card">
<n-space justify="space-between" align="center"> <n-space justify="space-between" align="center">
<n-icon class="icon" size="40" :component="tool.icon" /> <n-icon class="icon" size="40" :component="tool.icon" />
<n-space align="center"> <n-space align="center">
@@ -29,7 +29,7 @@
<br />&nbsp; <br />&nbsp;
</n-ellipsis> </n-ellipsis>
</div> </div>
</c-card> </n-card>
</router-link> </router-link>
</template> </template>
@@ -37,14 +37,11 @@
import type { Tool } from '@/tools/tools.types'; import type { Tool } from '@/tools/tools.types';
import { useThemeVars } from 'naive-ui'; import { useThemeVars } from 'naive-ui';
import { toRefs } from 'vue'; import { toRefs } from 'vue';
import { useAppTheme } from '@/ui/theme/themes';
import FavoriteButton from './FavoriteButton.vue'; import FavoriteButton from './FavoriteButton.vue';
const props = defineProps<{ tool: Tool & { category: string } }>(); const props = defineProps<{ tool: Tool & { category: string } }>();
const { tool } = toRefs(props); const { tool } = toRefs(props);
const theme = useThemeVars(); const theme = useThemeVars();
const appTheme = useAppTheme();
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@@ -53,11 +50,8 @@ a {
} }
.tool-card { .tool-card {
transition: border-color ease 0.5s;
border-width: 2px !important;
&:hover { &:hover {
border-color: v-bind('appTheme.primary.colorHover'); border-color: var(--n-color-target);
} }
.icon { .icon {

View File

@@ -1,8 +1,9 @@
import { useClipboard, type MaybeRef, get } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
import { useMessage } from 'naive-ui'; import { useMessage } from 'naive-ui';
import type { Ref } from 'vue';
export function useCopy({ source, text = 'Copied to the clipboard' }: { source: MaybeRef<unknown>; text?: string }) { export function useCopy({ source, text = 'Copied to the clipboard' }: { source: Ref; text?: string }) {
const { copy } = useClipboard({ source: computed(() => String(get(source))) }); const { copy } = useClipboard({ source });
const message = useMessage(); const message = useMessage();
return { return {

View File

@@ -26,7 +26,7 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue:
return computed<T>({ return computed<T>({
get() { get() {
return transformer.fromQuery(proxy.value) as unknown as T; return transformer.fromQuery(proxy.value) as T;
}, },
set(value) { set(value) {
proxy.value = transformer.toQuery(value as never); proxy.value = transformer.toQuery(value as never);

View File

@@ -23,9 +23,9 @@ export const config = figue({
env: { env: {
doc: 'Application current env', doc: 'Application current env',
format: 'enum', format: 'enum',
values: ['production', 'development', 'preview', 'test'], values: ['production', 'development', 'test'],
default: 'development', default: 'development',
env: 'VITE_VERCEL_ENV', env: 'MODE',
}, },
}, },
plausible: { plausible: {

View File

@@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { NIcon, useThemeVars } from 'naive-ui'; import { NIcon, useThemeVars } from 'naive-ui';
import { computed } from 'vue'; import { computed } from 'vue';
import { RouterLink } from 'vue-router';
import { Heart, Menu2, Home2 } from '@vicons/tabler'; import { Heart, Menu2, Home2 } from '@vicons/tabler';
import { toolsByCategory } from '@/tools'; import { toolsByCategory } from '@/tools';
import { useStyleStore } from '@/stores/style.store'; import { useStyleStore } from '@/stores/style.store';
@@ -53,25 +52,38 @@ const tools = computed<ToolCategory[]>(() => [
<div> <div>
IT-Tools IT-Tools
<c-link target="_blank" rel="noopener" :href="`https://github.com/CorentinTh/it-tools/tree/v${version}`"> <n-button
text
tag="a"
target="_blank"
rel="noopener"
type="primary"
depth="3"
:href="`https://github.com/CorentinTh/it-tools/tree/v${version}`"
>
v{{ version }} v{{ version }}
</c-link> </n-button>
<template v-if="commitSha && commitSha.length > 0"> <template v-if="commitSha && commitSha.length > 0">
- -
<c-link <n-button
text
tag="a"
target="_blank" target="_blank"
rel="noopener" rel="noopener"
type="primary" type="primary"
depth="3"
:href="`https://github.com/CorentinTh/it-tools/tree/${commitSha}`" :href="`https://github.com/CorentinTh/it-tools/tree/${commitSha}`"
> >
{{ commitSha }} {{ commitSha }}
</c-link> </n-button>
</template> </template>
</div> </div>
<div> <div>
© {{ new Date().getFullYear() }} © {{ new Date().getFullYear() }}
<c-link target="_blank" rel="noopener" href="https://github.com/CorentinTh"> Corentin Thomasset </c-link> <n-button text tag="a" target="_blank" rel="noopener" type="primary" href="https://github.com/CorentinTh">
Corentin Thomasset
</n-button>
</div> </div>
</div> </div>
</div> </div>
@@ -79,24 +91,34 @@ const tools = computed<ToolCategory[]>(() => [
<template #content> <template #content>
<div class="navigation"> <div class="navigation">
<c-button <n-button
:size="styleStore.isSmallScreen ? 'medium' : 'large'" :size="styleStore.isSmallScreen ? 'medium' : 'large'"
circle circle
variant="text" quaternary
aria-label="Toggle menu" aria-label="Toggle menu"
@click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed" @click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
> >
<n-icon size="25" :component="Menu2" /> <n-icon size="25" :component="Menu2" />
</c-button> </n-button>
<n-tooltip trigger="hover"> <router-link to="/" #="{ navigate, href }" custom>
<template #trigger> <n-tooltip trigger="hover">
<c-button to="/" circle variant="text" aria-label="Home"> <template #trigger>
<n-icon size="25" :component="Home2" /> <n-button
</c-button> tag="a"
</template> :href="href"
Home :size="styleStore.isSmallScreen ? 'medium' : 'large'"
</n-tooltip> circle
quaternary
aria-label="Home"
@click="navigate"
>
<n-icon size="25" :component="Home2" />
</n-button>
</template>
Home
</n-tooltip>
</router-link>
<search-bar /> <search-bar />
@@ -104,8 +126,10 @@ const tools = computed<ToolCategory[]>(() => [
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<c-button <n-button
round round
type="primary"
tag="a"
href="https://www.buymeacoffee.com/cthmsst" href="https://www.buymeacoffee.com/cthmsst"
rel="noopener" rel="noopener"
target="_blank" target="_blank"
@@ -115,7 +139,7 @@ const tools = computed<ToolCategory[]>(() => [
> >
Buy me a coffee Buy me a coffee
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" ml-2 /> <n-icon v-if="!styleStore.isSmallScreen" :component="Heart" ml-2 />
</c-button> </n-button>
</template> </template>
Support IT Tools development ! Support IT Tools development !
</n-tooltip> </n-tooltip>
@@ -140,8 +164,8 @@ const tools = computed<ToolCategory[]>(() => [
.support-button { .support-button {
background: rgb(37, 99, 108); background: rgb(37, 99, 108);
background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%); background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
color: #fff !important; color: #fff;
transition: padding ease 0.2s !important; transition: all ease 0.2s;
&:hover { &:hover {
color: #fff; color: #fff;

View File

@@ -1,5 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRoute } from 'vue-router';
import { useHead } from '@vueuse/head'; import { useHead } from '@vueuse/head';
import type { HeadObject } from '@vueuse/head'; import type { HeadObject } from '@vueuse/head';
import { computed } from 'vue'; import { computed } from 'vue';

View File

@@ -1,25 +1,30 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
import { createHead } from '@vueuse/head';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
import { registerSW } from 'virtual:pwa-register'; import { ViteSSG } from 'vite-ssg';
import { plausible } from './plugins/plausible.plugin'; import { plausible } from './plugins/plausible.plugin';
import 'virtual:uno.css'; import 'virtual:uno.css';
registerSW(); // import { naive } from './plugins/naive.plugin';
import { naive } from './plugins/naive.plugin';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import { routes } from './router';
import { config } from './config';
// import { useToolStore } from './tools/tools.store';
const app = createApp(App); export const createApp = ViteSSG(
// the root component
App,
// vue-router options
{ routes, base: config.app.baseUrl },
// function to have custom setups
async ({ app, router, routes, isClient, initialState }) => {
// install plugins etc.
const pinia = createPinia();
app.use(pinia);
app.use(createPinia()); app.use(plausible);
app.use(createHead()); // import { registerSW } from 'virtual:pwa-register';
app.use(router); // registerSW();
app.use(naive); },
app.use(plausible); );
app.mount('#app');

View File

@@ -13,6 +13,8 @@ useHead({ title: 'Page not found - IT Tools' });
<n-text mt-4 block depth="3">Sorry, this page does not seem to exist</n-text> <n-text mt-4 block depth="3">Sorry, this page does not seem to exist</n-text>
<n-text mb-8 block depth="3">Maybe the cache is doing tricky things, try force-refreshing?</n-text> <n-text mb-8 block depth="3">Maybe the cache is doing tricky things, try force-refreshing?</n-text>
<c-button to="/"> Back home </c-button> <router-link to="/" #="{ navigate, href }" custom>
<n-button tag="a" :href="href" secondary @click="navigate"> Back home </n-button>
</router-link>
</div> </div>
</template> </template>

View File

@@ -11,21 +11,25 @@ const { tracker } = useTracker();
<n-h1>About</n-h1> <n-h1>About</n-h1>
<n-p> <n-p>
This wonderful website, made with by This wonderful website, made with by
<c-link href="https://github.com/CorentinTh" target="_blank" rel="noopener"> Corentin Thomasset </c-link>, <n-button text tag="a" href="https://github.com/CorentinTh" target="_blank" rel="noopener" type="primary">
aggregates useful tools for developer and people working in IT. If you find it useful, please fell free to share Corentin Thomasset </n-button
it to people you think may find it useful too and don't forget to pin it in your shortcut bar ! >, aggregates useful tools for developer and people working in IT. If you find it useful, please fell free to
share it to people you think may find it useful too and don't forget to pin it in your shortcut bar !
</n-p> </n-p>
<n-p> <n-p>
IT Tools is open-source (under the MIT license) and free, and will always be, but it cost me money to host and IT Tools is open-source (under the MIT license) and free, and will always be, but it cost me money to host and
renew the domain name, if you want to support my work, and encourage me to add more tools, please consider renew the domain name, if you want to support my work, and encourage me to add more tools, please consider
supporting by supporting by
<c-link <n-button
type="primary"
tag="a"
text
href="https://www.buymeacoffee.com/cthmsst" href="https://www.buymeacoffee.com/cthmsst"
rel="noopener" rel="noopener"
target="_blank" target="_blank"
@click="() => tracker.trackEvent({ eventName: 'Support button clicked' })" @click="() => tracker.trackEvent({ eventName: 'Support button clicked' })"
> >
sponsoring me </c-link sponsoring me </n-button
>. >.
</n-p> </n-p>
@@ -33,9 +37,16 @@ const { tracker } = useTracker();
<n-p> <n-p>
IT Tools is made in Vue JS (vue 3) with the the naive-ui component library and is hosted and continuously deployed IT Tools is made in Vue JS (vue 3) with the the naive-ui component library and is hosted and continuously deployed
by Vercel. Third party open-source libraries are used in some tools, you may find the complete list in the by Vercel. Third party open-source libraries are used in some tools, you may find the complete list in the
<c-link href="https://github.com/CorentinTh/it-tools/blob/main/package.json" rel="noopener" target="_blank"> <n-button
type="primary"
tag="a"
text
href="https://github.com/CorentinTh/it-tools/blob/main/package.json"
rel="noopener"
target="_blank"
>
package.json package.json
</c-link> </n-button>
file of the repository. file of the repository.
</n-p> </n-p>
@@ -43,24 +54,30 @@ const { tracker } = useTracker();
<n-p> <n-p>
If you need a tool that is currently not present here, and you think can be relevant, you are welcome to submit a If you need a tool that is currently not present here, and you think can be relevant, you are welcome to submit a
feature request in the feature request in the
<c-link <n-button
type="primary"
tag="a"
text
href="https://github.com/CorentinTh/it-tools/issues/new?assignees=CorentinTh&labels=enhancement&template=feature_request.md&title=%5BFEAT%5D%20My%20feature" href="https://github.com/CorentinTh/it-tools/issues/new?assignees=CorentinTh&labels=enhancement&template=feature_request.md&title=%5BFEAT%5D%20My%20feature"
rel="noopener" rel="noopener"
target="_blank" target="_blank"
> >
issues section issues section
</c-link> </n-button>
in the GitHub repository. in the GitHub repository.
</n-p> </n-p>
<n-p> <n-p>
And if you found a bug, or something broken that doesn't work as expected, please fill a bug report in the And if you found a bug, or something broken that doesn't work as expected, please fill a bug report in the
<c-link <n-button
type="primary"
tag="a"
text
href="https://github.com/CorentinTh/it-tools/issues/new?assignees=CorentinTh&labels=bug&template=bug_report.md&title=%5BBUG%5D%20My%20bug" href="https://github.com/CorentinTh/it-tools/issues/new?assignees=CorentinTh&labels=bug&template=bug_report.md&title=%5BBUG%5D%20My%20bug"
rel="noopener" rel="noopener"
target="_blank" target="_blank"
> >
issues section issues section
</c-link> </n-button>
in the GitHub repository. in the GitHub repository.
</n-p> </n-p>
</div> </div>

View File

@@ -12,6 +12,38 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
</script> </script>
<template> <template>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Earum voluptatibus, voluptatum excepturi quidem ipsam
dignissimos sit eius illo corrupti, provident aliquam quaerat nostrum obcaecati velit ratione neque possimus beatae
quod?
<n-grid v-if="config.showBanner" x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi>
aa
<colored-card title="You like it-tools?" :icon="Heart">
Give us a star on
<!-- <a
href="https://github.com/CorentinTh/it-tools"
rel="noopener"
target="_blank"
aria-label="IT-Tools' GitHub repository"
>GitHub</a
>
or follow us on
<a
href="https://twitter.com/ittoolsdottech"
rel="noopener"
target="_blank"
aria-label="IT-Tools' Twitter account"
>Twitter</a
>! Thank you
<n-icon :component="Heart" /> -->
</colored-card>
</n-gi>
</n-grid>
<n-button>a</n-button>
<!--
<div class="home-page"> <div class="home-page">
<div class="grid-wrapper"> <div class="grid-wrapper">
<n-grid v-if="config.showBanner" x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8"> <n-grid v-if="config.showBanner" x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
@@ -67,7 +99,7 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
</n-gi> </n-gi>
</n-grid> </n-grid>
</div> </div>
</div> </div> -->
</template> </template>
<style scoped lang="less"> <style scoped lang="less">

View File

@@ -21,7 +21,7 @@ function createPlausibleInstance({
trackLocalhost: boolean; trackLocalhost: boolean;
}; };
}) { }) {
if (config.isTrackerEnabled) { if (config.isTrackerEnabled && !import.meta.env.SSR) {
return Plausible(config); return Plausible(config);
} }

View File

@@ -1,41 +1,38 @@
import { createRouter, createWebHistory } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { layouts } from './layouts/index';
import HomePage from './pages/Home.page.vue'; import HomePage from './pages/Home.page.vue';
// import { layouts } from './layouts/index';
import NotFound from './pages/404.page.vue'; import NotFound from './pages/404.page.vue';
import { tools } from './tools'; // import { tools } from './tools';
import { config } from './config';
import { routes as demoRoutes } from './ui/demo/demo.routes';
const toolsRoutes = tools.map(({ path, name, component, ...config }) => ({ // const toolsRoutes = tools.map(({ path, name, component, ...config }) => ({
path, // path,
name, // name,
component, // component,
meta: { isTool: true, layout: layouts.toolLayout, name, ...config }, // meta: { isTool: true, layout: layouts.toolLayout, name, ...config },
})); // }));
const toolsRedirectRoutes = tools // const toolsRedirectRoutes = tools
.filter(({ redirectFrom }) => redirectFrom && redirectFrom.length > 0) // .filter(({ redirectFrom }) => redirectFrom && redirectFrom.length > 0)
.flatMap( // .flatMap(
({ path, redirectFrom }) => redirectFrom?.map((redirectSource) => ({ path: redirectSource, redirect: path })) ?? [], // ({ path, redirectFrom }) => redirectFrom?.map((redirectSource) => ({ path: redirectSource, redirect: path })) ?? [],
); // );
//
// console.log({ toolsRoutes, toolsRedirectRoutes });
const router = createRouter({ const routes: RouteRecordRaw[] = [
history: createWebHistory(config.app.baseUrl), // ...toolsRoutes,
routes: [ // ...toolsRedirectRoutes,
{ {
path: '/', path: '/',
name: 'home', name: 'home',
component: HomePage, component: HomePage,
}, },
{ // {
path: '/about', // path: '/about',
name: 'about', // name: 'about',
component: () => import('./pages/About.vue'), // component: () => import('./pages/About.vue'),
}, // },
...toolsRoutes,
...toolsRedirectRoutes,
...(config.app.env === 'development' ? demoRoutes : []),
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
],
});
export default router; // { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
];
export { routes };

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card title="Base64 to file"> <n-card title="Base64 to file">
<n-form-item <n-form-item
:feedback="base64InputValidation.message" :feedback="base64InputValidation.message"
:validation-status="base64InputValidation.status" :validation-status="base64InputValidation.status"
@@ -8,13 +8,13 @@
<n-input v-model:value="base64Input" type="textarea" placeholder="Put your base64 file string here..." rows="5" /> <n-input v-model:value="base64Input" type="textarea" placeholder="Put your base64 file string here..." rows="5" />
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button :disabled="base64Input === '' || !base64InputValidation.isValid" @click="downloadFile()"> <n-button :disabled="base64Input === '' || !base64InputValidation.isValid" secondary @click="downloadFile()">
Download file Download file
</c-button> </n-button>
</n-space> </n-space>
</c-card> </n-card>
<c-card title="File to base64"> <n-card title="File to base64">
<n-upload v-model:file-list="fileList" :show-file-list="true" :on-before-upload="onUpload" list-type="image"> <n-upload v-model:file-list="fileList" :show-file-list="true" :on-before-upload="onUpload" list-type="image">
<n-upload-dragger> <n-upload-dragger>
<div mb-2> <div mb-2>
@@ -26,9 +26,9 @@
<n-input :value="fileBase64" type="textarea" readonly placeholder="File in base64 will be here" /> <n-input :value="fileBase64" type="textarea" readonly placeholder="File in base64 will be here" />
<n-space justify="center"> <n-space justify="center">
<c-button @click="copyFileBase64()"> Copy </c-button> <n-button secondary @click="copyFileBase64()"> Copy </n-button>
</n-space> </n-space>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card title="String to base64"> <n-card title="String to base64">
<n-form-item label="String to encode"> <n-form-item label="String to encode">
<n-input v-model:value="textInput" type="textarea" placeholder="Put your string here..." rows="5" /> <n-input v-model:value="textInput" type="textarea" placeholder="Put your string here..." rows="5" />
</n-form-item> </n-form-item>
@@ -15,11 +15,11 @@
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copyTextBase64()"> Copy base64 </c-button> <n-button secondary @click="copyTextBase64()"> Copy base64 </n-button>
</n-space> </n-space>
</c-card> </n-card>
<c-card title="Base64 to string"> <n-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">
<n-input v-model:value="base64Input" type="textarea" placeholder="Your base64 string..." rows="5" /> <n-input v-model:value="base64Input" type="textarea" placeholder="Your base64 string..." rows="5" />
</n-form-item> </n-form-item>
@@ -29,9 +29,9 @@
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copyText()"> Copy decoded string </c-button> <n-button secondary @click="copyText()"> Copy decoded string </n-button>
</n-space> </n-space>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -13,15 +13,17 @@
/> />
</n-form-item> </n-form-item>
<c-card> <br />
<n-card>
<n-statistic label="Authorization header:" class="header"> <n-statistic label="Authorization header:" class="header">
<n-scrollbar x-scrollable style="max-width: 550px; margin-bottom: -10px; padding-bottom: 10px" trigger="none"> <n-scrollbar x-scrollable style="max-width: 550px; margin-bottom: -10px; padding-bottom: 10px" trigger="none">
{{ header }} {{ header }}
</n-scrollbar> </n-scrollbar>
</n-statistic> </n-statistic>
</c-card> </n-card>
<n-space justify="center" mt-5> <br />
<c-button @click="copy">Copy header</c-button> <n-space justify="center">
<n-button secondary @click="copy">Copy header</n-button>
</n-space> </n-space>
</div> </div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card title="Hash"> <n-card title="Hash">
<n-form label-width="120"> <n-form label-width="120">
<n-form-item label="Your string: " label-placement="left"> <n-form-item label="Your string: " label-placement="left">
<n-input <n-input
@@ -16,12 +16,13 @@
</n-form-item> </n-form-item>
<n-input :value="hashed" readonly style="text-align: center" /> <n-input :value="hashed" readonly style="text-align: center" />
</n-form> </n-form>
<n-space justify="center" mt-5> <br />
<c-button @click="copy"> Copy hash </c-button> <n-space justify="center">
<n-button secondary @click="copy"> Copy hash </n-button>
</n-space> </n-space>
</c-card> </n-card>
<c-card title="Compare string with hash"> <n-card title="Compare string with hash">
<n-form label-width="120"> <n-form label-width="120">
<n-form-item label="Your string: " label-placement="left"> <n-form-item label="Your string: " label-placement="left">
<n-input <n-input
@@ -49,7 +50,7 @@
</div> </div>
</n-form-item> </n-form-item>
</n-form> </n-form>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,8 +1,8 @@
<template> <template>
<n-scrollbar style="flex: 1" x-scrollable> <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="0">
<div v-for="(suite, index) of suites" :key="index"> <div v-for="(suite, index) of suites" :key="index">
<c-card style="width: 292px; margin: 0 8px 5px"> <n-card style="width: 292px; margin: 0 8px 5px">
<n-form-item label="Suite name:" :show-feedback="false" label-placement="left"> <n-form-item label="Suite name:" :show-feedback="false" label-placement="left">
<n-input v-model:value="suite.title" placeholder="Suite name..." /> <n-input v-model:value="suite.title" placeholder="Suite name..." />
</n-form-item> </n-form-item>
@@ -11,23 +11,25 @@
<n-form-item label="Suite values" :show-feedback="false"> <n-form-item label="Suite values" :show-feedback="false">
<dynamic-values v-model:values="suite.data" /> <dynamic-values v-model:values="suite.data" />
</n-form-item> </n-form-item>
</c-card> </n-card>
<n-space justify="center"> <n-space justify="center">
<c-button v-if="suites.length > 1" variant="text" @click="suites.splice(index, 1)"> <n-button v-if="suites.length > 1" quaternary @click="suites.splice(index, 1)">
<n-icon :component="Trash" depth="3" mr-2 size="18" /> <template #icon>
<n-icon :component="Trash" depth="3" />
</template>
Delete suite Delete suite
</c-button> </n-button>
<c-button <n-button quaternary @click="suites.splice(index + 1, 0, { data: [0], title: `Suite ${suites.length + 1}` })">
variant="text" <template #icon>
@click="suites.splice(index + 1, 0, { data: [0], title: `Suite ${suites.length + 1}` })" <n-icon :component="Plus" depth="3" />
> </template>
<n-icon :component="Plus" depth="3" mr-2 size="18" />
Add suite Add suite
</c-button> </n-button>
</n-space> </n-space>
</div> </div>
</n-space> </n-space>
<br />
</n-scrollbar> </n-scrollbar>
<div style="flex: 0 0 100%"> <div style="flex: 0 0 100%">
@@ -37,14 +39,15 @@
<n-input v-model:value="unit" placeholder="Unit (eg: ms)" /> <n-input v-model:value="unit" placeholder="Unit (eg: ms)" />
</n-form-item> </n-form-item>
<c-button <n-button
tertiary
@click=" @click="
suites = [ suites = [
{ title: 'Suite 1', data: [] }, { title: 'Suite 1', data: [] },
{ title: 'Suite 2', data: [] }, { title: 'Suite 2', data: [] },
] ]
" "
>Reset suites</c-button >Reset suites</n-button
> >
</n-space> </n-space>
@@ -68,9 +71,10 @@
</tr> </tr>
</tbody> </tbody>
</n-table> </n-table>
<n-space justify="center" mt-5> <br />
<c-button @click="copyAsMarkdown">Copy as markdown table</c-button> <n-space justify="center">
<c-button @click="copyAsBulletList">Copy as bullet list</c-button> <n-button tertiary @click="copyAsMarkdown">Copy as markdown table</n-button>
<n-button tertiary @click="copyAsBulletList">Copy as bullet list</n-button>
</n-space> </n-space>
</div> </div>
</div> </div>

View File

@@ -11,18 +11,22 @@
/> />
<n-tooltip> <n-tooltip>
<template #trigger> <template #trigger>
<c-button circle variant="text" @click="values.splice(index, 1)"> <n-button circle quaternary @click="values.splice(index, 1)">
<n-icon :component="Trash" depth="3" size="18" /> <template #icon>
</c-button> <n-icon :component="Trash" depth="3" />
</template>
</n-button>
</template> </template>
Delete value Delete value
</n-tooltip> </n-tooltip>
</n-space> </n-space>
<c-button @click="addValue"> <n-button tertiary @click="addValue">
<n-icon :component="Plus" depth="3" mr-2 size="18" /> <template #icon>
<n-icon :component="Plus" />
</template>
Add a measure Add a measure
</c-button> </n-button>
</div> </div>
</template> </template>

View File

@@ -1,57 +1,59 @@
<template> <template>
<div> <div>
<n-grid cols="3" x-gap="12"> <n-card>
<n-gi span="1"> <n-grid cols="3" x-gap="12">
<n-form-item label="Language:"> <n-gi span="1">
<n-select <n-form-item label="Language:">
v-model:value="language" <n-select
:options="Object.keys(languages).map((label) => ({ label, value: label }))" v-model:value="language"
:options="Object.keys(languages).map((label) => ({ label, value: label }))"
/>
</n-form-item>
</n-gi>
<n-gi span="2">
<n-form-item
label="Entropy (seed):"
:feedback="entropyValidation.message"
:validation-status="entropyValidation.status"
>
<n-input-group>
<n-input v-model:value="entropy" placeholder="Your string..." />
<n-button @click="refreshEntropy">
<n-icon size="22">
<Refresh />
</n-icon>
</n-button>
<n-button @click="copyEntropy">
<n-icon size="22">
<Copy />
</n-icon>
</n-button>
</n-input-group>
</n-form-item>
</n-gi>
</n-grid>
<n-form-item
label="Passphrase (mnemonic):"
:feedback="mnemonicValidation.message"
: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"
/> />
</n-form-item>
</n-gi>
<n-gi span="2">
<n-form-item
label="Entropy (seed):"
:feedback="entropyValidation.message"
:validation-status="entropyValidation.status"
>
<n-input-group>
<n-input v-model:value="entropy" placeholder="Your string..." />
<c-button @click="refreshEntropy">
<n-icon size="22">
<Refresh />
</n-icon>
</c-button>
<c-button @click="copyEntropy">
<n-icon size="22">
<Copy />
</n-icon>
</c-button>
</n-input-group>
</n-form-item>
</n-gi>
</n-grid>
<n-form-item
label="Passphrase (mnemonic):"
:feedback="mnemonicValidation.message"
: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-button @click="copyPassphrase"> <n-button @click="copyPassphrase">
<n-icon size="22" :component="Copy" /> <n-icon size="22" :component="Copy" />
</c-button> </n-button>
</n-input-group> </n-input-group>
</n-form-item> </n-form-item>
</n-card>
</div> </div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card> <n-card>
<n-form label-width="120" label-placement="left" :show-feedback="false"> <n-form label-width="120" label-placement="left" :show-feedback="false">
<n-form-item label="Your string:"> <n-form-item label="Your string:">
<n-input v-model:value="input" /> <n-input v-model:value="input" />
@@ -41,7 +41,7 @@
<input-copyable :value="snakeCase(input, baseConfig)" /> <input-copyable :value="snakeCase(input, baseConfig)" />
</n-form-item> </n-form-item>
</n-form> </n-form>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

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

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card> <n-card>
<n-form label-width="100" label-placement="left"> <n-form label-width="100" label-placement="left">
<n-form-item label="color picker:"> <n-form-item label="color picker:">
<n-color-picker <n-color-picker
@@ -30,7 +30,7 @@
<input-copyable v-model:value="cmyk" :on-input="(v: string) => onInputUpdated(v, 'cmyk')" /> <input-copyable v-model:value="cmyk" :on-input="(v: string) => onInputUpdated(v, 'cmyk')" />
</n-form-item> </n-form-item>
</n-form> </n-form>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card> <n-card>
<n-form-item <n-form-item
class="cron" class="cron"
:show-label="false" :show-label="false"
@@ -27,8 +27,8 @@
</n-form-item> </n-form-item>
</n-form> </n-form>
</n-space> </n-space>
</c-card> </n-card>
<c-card> <n-card>
<pre> <pre>
[optional] seconds (0 - 59) [optional] seconds (0 - 59)
| minute (0 - 59) | minute (0 - 59)
@@ -41,7 +41,7 @@
> >
<n-space v-if="styleStore.isSmallScreen" vertical> <n-space v-if="styleStore.isSmallScreen" vertical>
<c-card v-for="{ symbol, meaning, example, equivalent } in helpers" :key="symbol" important:border-none> <n-card v-for="{ symbol, meaning, example, equivalent } in helpers" :key="symbol" embedded :bordered="false">
<div> <div>
Symbol: <strong>{{ symbol }}</strong> Symbol: <strong>{{ symbol }}</strong>
</div> </div>
@@ -57,7 +57,7 @@
<div> <div>
Equivalent: <strong>{{ equivalent }}</strong> Equivalent: <strong>{{ equivalent }}</strong>
</div> </div>
</c-card> </n-card>
</n-space> </n-space>
<n-table v-else size="small"> <n-table v-else size="small">
<thead> <thead>
@@ -79,7 +79,7 @@
</tr> </tr>
</tbody> </tbody>
</n-table> </n-table>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -4,7 +4,6 @@
<n-input-group> <n-input-group>
<n-input <n-input
v-model:value="inputDate" v-model:value="inputDate"
autofocus
:on-input="onDateInputChanged" :on-input="onDateInputChanged"
placeholder="Put you date string here..." placeholder="Put you date string here..."
clearable clearable

View File

@@ -1,20 +1,22 @@
<template> <template>
<c-card v-for="{ name, information } in sections" :key="name" :title="name"> <n-card v-for="{ name, information } in sections" :key="name" :title="name">
<n-grid cols="1 400:2" x-gap="12" y-gap="12"> <n-grid cols="1 400:2" x-gap="12" y-gap="12">
<n-gi v-for="{ label, value: { value } } in information" :key="label" class="information"> <n-gi v-for="{ label, value: { value } } in information" :key="label" class="information">
<div class="label"> <n-card :bordered="false" embedded>
{{ label }} <div class="label">
</div> {{ label }}
</div>
<div class="value"> <div class="value">
<n-ellipsis v-if="value"> <n-ellipsis v-if="value">
{{ value }} {{ value }}
</n-ellipsis> </n-ellipsis>
<div v-else class="undefined-value">unknown</div> <div v-else class="undefined-value">unknown</div>
</div> </div>
</n-card>
</n-gi> </n-gi>
</n-grid> </n-grid>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -79,10 +81,6 @@ const sections = [
<style lang="less" scoped> <style lang="less" scoped>
.information { .information {
padding: 14px 16px;
border-radius: 4px;
background-color: #aaaaaa11;
.label { .label {
font-size: 14px; font-size: 14px;
opacity: 0.8; opacity: 0.8;

View File

@@ -13,13 +13,15 @@
<n-divider /> <n-divider />
<textarea-copyable :value="dockerCompose" language="yaml" /> <textarea-copyable :value="dockerCompose" language="yaml" />
<br />
<n-space justify="center" mt-5> <br />
<c-button :disabled="dockerCompose === ''" secondary @click="download"> Download docker-compose.yml </c-button> <n-space justify="center">
<n-button :disabled="dockerCompose === ''" secondary @click="download"> Download docker-compose.yml </n-button>
</n-space> </n-space>
<div v-if="notComposable.length > 0"> <div v-if="notComposable.length > 0">
<n-alert title="This options are not translatable to docker-compose" type="info" mt-5> <br />
<n-alert title="This options are not translatable to docker-compose" type="info">
<ul> <ul>
<li v-for="(message, index) of notComposable" :key="index">{{ message }}</li> <li v-for="(message, index) of notComposable" :key="index">{{ message }}</li>
</ul> </ul>
@@ -27,10 +29,10 @@
</div> </div>
<div v-if="notImplemented.length > 0"> <div v-if="notImplemented.length > 0">
<br />
<n-alert <n-alert
title="This options are not yet implemented and therefore haven't been translated to docker-compose" title="This options are not yet implemented and therefore haven't been translated to docker-compose"
type="warning" type="warning"
mt-5
> >
<ul> <ul>
<li v-for="(message, index) of notImplemented" :key="index">{{ message }}</li> <li v-for="(message, index) of notImplemented" :key="index">{{ message }}</li>
@@ -39,7 +41,8 @@
</div> </div>
<div v-if="errors.length > 0"> <div v-if="errors.length > 0">
<n-alert title="The following errors occured" type="error" mt-5> <br />
<n-alert title="The following errors occured" type="error">
<ul> <ul>
<li v-for="(message, index) of errors" :key="index">{{ message }}</li> <li v-for="(message, index) of errors" :key="index">{{ message }}</li>
</ul> </ul>

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card title="Encrypt"> <n-card title="Encrypt">
<n-space item-style="flex: 1 1 0"> <n-space item-style="flex: 1 1 0">
<n-form-item label="Your text:" :show-feedback="false"> <n-form-item label="Your text:" :show-feedback="false">
<n-input <n-input
@@ -21,7 +21,8 @@
</n-form-item> </n-form-item>
</n-space> </n-space>
</n-space> </n-space>
<n-form-item label="Your text encrypted:" :show-feedback="false" mt-5> <br />
<n-form-item label="Your text encrypted:" :show-feedback="false">
<n-input <n-input
:value="cypherOutput" :value="cypherOutput"
type="textarea" type="textarea"
@@ -34,8 +35,8 @@
spellcheck="false" spellcheck="false"
/> />
</n-form-item> </n-form-item>
</c-card> </n-card>
<c-card title="Decrypt"> <n-card title="Decrypt">
<n-space item-style="flex: 1 1 0"> <n-space item-style="flex: 1 1 0">
<n-form-item label="Your encrypted text:" :show-feedback="false"> <n-form-item label="Your encrypted text:" :show-feedback="false">
<n-input <n-input
@@ -57,7 +58,8 @@
</n-form-item> </n-form-item>
</n-space> </n-space>
</n-space> </n-space>
<n-form-item label="Your decrypted text:" :show-feedback="false" mt-5> <br />
<n-form-item label="Your decrypted text:" :show-feedback="false">
<n-input <n-input
:value="decryptOutput" :value="decryptOutput"
type="textarea" type="textarea"
@@ -70,7 +72,7 @@
spellcheck="false" spellcheck="false"
/> />
</n-form-item> </n-form-item>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -5,6 +5,7 @@
hours and 10 minutes to wash them all, and if you start now, you'll end hours and 10 minutes to wash them all, and if you start now, you'll end
{{ endAt }}. {{ endAt }}.
</n-text> </n-text>
<br />
<n-divider /> <n-divider />
<n-space item-style="flex:1 1 0"> <n-space item-style="flex:1 1 0">
<div> <div>
@@ -37,12 +38,12 @@
<n-divider /> <n-divider />
<n-space vertical> <n-space vertical>
<c-card> <n-card>
<n-statistic label="Total duration">{{ formatMsDuration(durationMs) }}</n-statistic> <n-statistic label="Total duration">{{ formatMsDuration(durationMs) }}</n-statistic>
</c-card> </n-card>
<c-card> <n-card>
<n-statistic label="It will end ">{{ endAt }}</n-statistic> <n-statistic label="It will end ">{{ endAt }}</n-statistic>
</c-card> </n-card>
</n-space> </n-space>
</div> </div>
</n-space> </n-space>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<c-card> <n-card>
<n-input v-model:value="clearText" type="textarea" placeholder="Your string to hash..." rows="3" /> <n-input v-model:value="clearText" type="textarea" placeholder="Your string to hash..." rows="3" />
<n-divider /> <n-divider />
@@ -35,7 +35,7 @@
<input-copyable :value="hashText(algo, clearText)" readonly /> <input-copyable :value="hashText(algo, clearText)" readonly />
</n-input-group> </n-input-group>
</div> </div>
</c-card> </n-card>
</div> </div>
</template> </template>

View File

@@ -43,7 +43,7 @@
<n-input readonly :value="hmac" type="textarea" placeholder="The result of the HMAC..." /> <n-input readonly :value="hmac" type="textarea" placeholder="The result of the HMAC..." />
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copy()">Copy HMAC</c-button> <n-button secondary @click="copy()">Copy HMAC</n-button>
</n-space> </n-space>
</div> </div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card title="Escape html entities"> <n-card title="Escape html entities">
<n-form-item label="Your string :"> <n-form-item label="Your string :">
<n-input <n-input
v-model:value="escapeInput" v-model:value="escapeInput"
@@ -20,10 +20,10 @@
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copyEscaped"> Copy </c-button> <n-button secondary @click="copyEscaped"> Copy </n-button>
</n-space> </n-space>
</c-card> </n-card>
<c-card title="Unescape html entities"> <n-card title="Unescape html entities">
<n-form-item label="Your escaped string :"> <n-form-item label="Your escaped string :">
<n-input <n-input
v-model:value="unescapeInput" v-model:value="unescapeInput"
@@ -44,9 +44,9 @@
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copyUnescaped"> Copy </c-button> <n-button secondary @click="copyUnescaped"> Copy </n-button>
</n-space> </n-space>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,12 +1,12 @@
<template> <template>
<c-card v-if="editor" important:p0> <n-card v-if="editor" class="editor">
<menu-bar class="editor-header" :editor="editor" /> <template #header>
<n-divider style="margin-top: 0" /> <menu-bar class="editor-header" :editor="editor" />
<n-divider style="margin-top: 0" />
</template>
<div px8 pb6> <editor-content class="editor-content" :editor="editor" />
<editor-content class="editor-content" :editor="editor" /> </n-card>
</div>
</c-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -34,6 +34,10 @@ tryOnBeforeUnmount(() => {
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
::v-deep(.n-card-header) {
padding: 0;
}
::v-deep(.ProseMirror-focused) { ::v-deep(.ProseMirror-focused) {
outline: none; outline: none;
} }

View File

@@ -1,9 +1,11 @@
<template> <template>
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<c-button circle variant="text" :type="isActive?.() ? 'primary' : 'default'" @click="action"> <n-button circle quaternary :type="isActive?.() ? 'primary' : 'default'" @click="action">
<n-icon :component="icon" /> <template #icon>
</c-button> <n-icon :component="icon" />
</template>
</n-button>
</template> </template>
{{ title }} {{ title }}

View File

@@ -20,18 +20,21 @@
<div v-for="{ codes, category } of codesByCategoryFiltered" :key="category" mb-8> <div v-for="{ codes, category } of codesByCategoryFiltered" :key="category" mb-8>
<n-h2> {{ category }} </n-h2> <n-h2> {{ category }} </n-h2>
<c-card v-for="{ code, description, name, type } of codes" :key="code" mb-2> <n-space vertical :size="20">
<n-space align="center"> <n-card v-for="{ code, description, name, type } of codes" :key="code">
<n-text strong text-lg> {{ code }} {{ name }} </n-text> <n-space align="center">
</n-space> <n-text strong text-lg> {{ code }} {{ name }} </n-text>
<n-text depth="3">{{ description }} {{ type !== 'HTTP' ? `For ${type}.` : '' }}</n-text> </n-space>
</c-card> <n-text depth="3">{{ description }} {{ type !== 'HTTP' ? `For ${type}.` : '' }}</n-text>
</n-card>
</n-space>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useFuzzySearch } from '@/composable/fuzzySearch'; import { useFuzzySearch } from '@/composable/fuzzySearch';
import _ from 'lodash';
import { SearchRound } from '@vicons/material'; import { SearchRound } from '@vicons/material';
import { codesByCategories } from './http-status-codes.constants'; import { codesByCategories } from './http-status-codes.constants';

View File

@@ -1,133 +1,132 @@
import { tool as base64FileConverter } from './base64-file-converter'; // import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter'; // import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator'; // import { tool as basicAuthGenerator } from './basic-auth-generator';
import { tool as jsonDiff } from './json-diff'; // import { tool as httpStatusCodes } from './http-status-codes';
import { tool as ipv4RangeExpander } from './ipv4-range-expander'; // import { tool as yamlToJson } from './yaml-to-json-converter';
import { tool as httpStatusCodes } from './http-status-codes'; // import { tool as jsonToYaml } from './json-to-yaml-converter';
import { tool as yamlToJson } from './yaml-to-json-converter'; // import { tool as ipv6UlaGenerator } from './ipv6-ula-generator';
import { tool as jsonToYaml } from './json-to-yaml-converter'; // import { tool as ipv4AddressConverter } from './ipv4-address-converter';
import { tool as ipv6UlaGenerator } from './ipv6-ula-generator'; // import { tool as benchmarkBuilder } from './benchmark-builder';
import { tool as ipv4AddressConverter } from './ipv4-address-converter'; // import { tool as userAgentParser } from './user-agent-parser';
import { tool as benchmarkBuilder } from './benchmark-builder'; // import { tool as ipv4SubnetCalculator } from './ipv4-subnet-calculator';
import { tool as userAgentParser } from './user-agent-parser'; // import { tool as dockerRunToDockerComposeConverter } from './docker-run-to-docker-compose-converter';
import { tool as ipv4SubnetCalculator } from './ipv4-subnet-calculator'; // import { tool as htmlWysiwygEditor } from './html-wysiwyg-editor';
import { tool as dockerRunToDockerComposeConverter } from './docker-run-to-docker-compose-converter'; // import { tool as rsaKeyPairGenerator } from './rsa-key-pair-generator';
import { tool as htmlWysiwygEditor } from './html-wysiwyg-editor'; // import { tool as textToNatoAlphabet } from './text-to-nato-alphabet';
import { tool as rsaKeyPairGenerator } from './rsa-key-pair-generator'; // import { tool as slugifyString } from './slugify-string';
import { tool as textToNatoAlphabet } from './text-to-nato-alphabet'; // import { tool as keycodeInfo } from './keycode-info';
import { tool as slugifyString } from './slugify-string'; // import { tool as jsonMinify } from './json-minify';
import { tool as keycodeInfo } from './keycode-info'; // import { tool as bcrypt } from './bcrypt';
import { tool as jsonMinify } from './json-minify'; // import { tool as bip39 } from './bip39-generator';
import { tool as bcrypt } from './bcrypt'; // import { tool as caseConverter } from './case-converter';
import { tool as bip39 } from './bip39-generator'; // import { tool as chmodCalculator } from './chmod-calculator';
import { tool as caseConverter } from './case-converter'; // import { tool as chronometer } from './chronometer';
import { tool as chmodCalculator } from './chmod-calculator'; // import { tool as colorConverter } from './color-converter';
import { tool as chronometer } from './chronometer'; // import { tool as crontabGenerator } from './crontab-generator';
import { tool as colorConverter } from './color-converter'; // import { tool as dateTimeConverter } from './date-time-converter';
import { tool as crontabGenerator } from './crontab-generator'; // import { tool as deviceInformation } from './device-information';
import { tool as dateTimeConverter } from './date-time-converter'; // import { tool as cypher } from './encryption';
import { tool as deviceInformation } from './device-information'; // import { tool as etaCalculator } from './eta-calculator';
import { tool as cypher } from './encryption'; // import { tool as gitMemo } from './git-memo';
import { tool as etaCalculator } from './eta-calculator'; // import { tool as hashText } from './hash-text';
import { tool as gitMemo } from './git-memo'; // import { tool as hmacGenerator } from './hmac-generator';
import { tool as hashText } from './hash-text'; // import { tool as htmlEntities } from './html-entities';
import { tool as hmacGenerator } from './hmac-generator'; // import { tool as baseConverter } from './integer-base-converter';
import { tool as htmlEntities } from './html-entities'; // import { tool as jsonViewer } from './json-viewer';
import { tool as baseConverter } from './integer-base-converter'; // import { tool as jwtParser } from './jwt-parser';
import { tool as jsonViewer } from './json-viewer'; // import { tool as loremIpsumGenerator } from './lorem-ipsum-generator';
import { tool as jwtParser } from './jwt-parser'; // import { tool as mathEvaluator } from './math-evaluator';
import { tool as loremIpsumGenerator } from './lorem-ipsum-generator'; // import { tool as metaTagGenerator } from './meta-tag-generator';
import { tool as mathEvaluator } from './math-evaluator'; // import { tool as mimeTypes } from './mime-types';
import { tool as metaTagGenerator } from './meta-tag-generator'; // import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator';
import { tool as mimeTypes } from './mime-types'; // import { tool as qrCodeGenerator } from './qr-code-generator';
import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator'; // import { tool as randomPortGenerator } from './random-port-generator';
import { tool as qrCodeGenerator } from './qr-code-generator'; // import { tool as romanNumeralConverter } from './roman-numeral-converter';
import { tool as randomPortGenerator } from './random-port-generator'; // import { tool as sqlPrettify } from './sql-prettify';
import { tool as romanNumeralConverter } from './roman-numeral-converter'; // import { tool as svgPlaceholderGenerator } from './svg-placeholder-generator';
import { tool as sqlPrettify } from './sql-prettify'; // import { tool as temperatureConverter } from './temperature-converter';
import { tool as svgPlaceholderGenerator } from './svg-placeholder-generator'; // import { tool as textStatistics } from './text-statistics';
import { tool as temperatureConverter } from './temperature-converter'; // import { tool as tokenGenerator } from './token-generator';
import { tool as textStatistics } from './text-statistics';
import { tool as tokenGenerator } from './token-generator';
import type { ToolCategory } from './tools.types'; import type { ToolCategory } from './tools.types';
import { tool as urlEncoder } from './url-encoder'; // import { tool as urlEncoder } from './url-encoder';
import { tool as urlParser } from './url-parser'; // import { tool as urlParser } from './url-parser';
import { tool as uuidGenerator } from './uuid-generator'; // import { tool as uuidGenerator } from './uuid-generator';
import { tool as macAddressLookup } from './mac-address-lookup'; // import { tool as macAddressLookup } from './mac-address-lookup';
export const toolsByCategory: ToolCategory[] = [ export const toolsByCategory: ToolCategory[] = [
{ {
name: 'Crypto', name: 'Crypto',
components: [tokenGenerator, hashText, bcrypt, uuidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator],
},
{
name: 'Converter',
components: [ components: [
dateTimeConverter, /* tokenGenerator, hashText, bcrypt, uuidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator*/
baseConverter,
romanNumeralConverter,
base64StringConverter,
base64FileConverter,
colorConverter,
caseConverter,
textToNatoAlphabet,
yamlToJson,
jsonToYaml,
], ],
}, },
{ // {
name: 'Web', // name: 'Converter',
components: [ // components: [
urlEncoder, // dateTimeConverter,
htmlEntities, // baseConverter,
urlParser, // romanNumeralConverter,
deviceInformation, // base64StringConverter,
basicAuthGenerator, // base64FileConverter,
metaTagGenerator, // colorConverter,
otpCodeGeneratorAndValidator, // caseConverter,
mimeTypes, // textToNatoAlphabet,
jwtParser, // yamlToJson,
keycodeInfo, // jsonToYaml,
slugifyString, // ],
htmlWysiwygEditor, // },
userAgentParser, // {
httpStatusCodes, // name: 'Web',
jsonDiff, // components: [
], // urlEncoder,
}, // htmlEntities,
{ // urlParser,
name: 'Images', // deviceInformation,
components: [qrCodeGenerator, svgPlaceholderGenerator], // basicAuthGenerator,
}, // metaTagGenerator,
{ // otpCodeGeneratorAndValidator,
name: 'Development', // mimeTypes,
components: [ // jwtParser,
gitMemo, // keycodeInfo,
randomPortGenerator, // slugifyString,
crontabGenerator, // htmlWysiwygEditor,
jsonViewer, // userAgentParser,
jsonMinify, // httpStatusCodes,
sqlPrettify, // ],
chmodCalculator, // },
dockerRunToDockerComposeConverter, // {
], // name: 'Images',
}, // components: [qrCodeGenerator, svgPlaceholderGenerator],
{ // },
name: 'Network', // {
components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, ipv6UlaGenerator], // name: 'Development',
}, // components: [
{ // gitMemo,
name: 'Math', // randomPortGenerator,
components: [mathEvaluator, etaCalculator], // crontabGenerator,
}, // jsonViewer,
{ // jsonMinify,
name: 'Measurement', // sqlPrettify,
components: [chronometer, temperatureConverter, benchmarkBuilder], // chmodCalculator,
}, // dockerRunToDockerComposeConverter,
{ // ],
name: 'Text', // },
components: [loremIpsumGenerator, textStatistics], // {
}, // name: 'Network',
// components: [ipv4SubnetCalculator, ipv4AddressConverter, macAddressLookup, ipv6UlaGenerator],
// },
// {
// name: 'Math',
// components: [mathEvaluator, etaCalculator],
// },
// {
// name: 'Measurement',
// components: [chronometer, temperatureConverter, benchmarkBuilder],
// },
// {
// name: 'Text',
// components: [loremIpsumGenerator, textStatistics],
// },
]; ];
export const tools = toolsByCategory.flatMap(({ components }) => components); export const tools = toolsByCategory.flatMap(({ components }) => components);

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<c-card> <n-card>
<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>
@@ -75,7 +75,7 @@
:placeholder="`Base ${outputBase} will be here...`" :placeholder="`Base ${outputBase} will be here...`"
/> />
</n-input-group> </n-input-group>
</c-card> </n-card>
</div> </div>
</template> </template>

View File

@@ -1,13 +0,0 @@
import { UnfoldMoreOutlined } from '@vicons/material';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'IPv4 range expander',
path: '/ipv4-range-expander',
description:
'Given a start and an end IPv4 address this tool calculates a valid IPv4 network with its CIDR notation.',
keywords: ['ipv4', 'range', 'expander', 'subnet', 'creator', 'cidr'],
component: () => import('./ipv4-range-expander.vue'),
icon: UnfoldMoreOutlined,
createdAt: new Date('2023-04-19'),
});

View File

@@ -1,32 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Tool - IPv4 range expander', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/ipv4-range-expander');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('IPv4 range expander - IT Tools');
});
test('Calculates correct for valid input', async ({ page }) => {
await page.getByPlaceholder('Start IPv4 address...').fill('192.168.1.1');
await page.getByPlaceholder('End IPv4 address...').fill('192.168.7.255');
expect(await page.getByTestId('start-address.old').textContent()).toEqual('192.168.1.1');
expect(await page.getByTestId('start-address.new').textContent()).toEqual('192.168.0.0');
expect(await page.getByTestId('end-address.old').textContent()).toEqual('192.168.7.255');
expect(await page.getByTestId('end-address.new').textContent()).toEqual('192.168.7.255');
expect(await page.getByTestId('addresses-in-range.old').textContent()).toEqual('1,791');
expect(await page.getByTestId('addresses-in-range.new').textContent()).toEqual('2,048');
expect(await page.getByTestId('cidr.old').textContent()).toEqual('');
expect(await page.getByTestId('cidr.new').textContent()).toEqual('192.168.0.0/21');
});
test('Hides result for invalid input', async ({ page }) => {
await page.getByPlaceholder('Start IPv4 address...').fill('192.168.1.1');
await page.getByPlaceholder('End IPv4 address...').fill('192.168.0.255');
await expect(page.getByTestId('result')).not.toBeVisible();
});
});

View File

@@ -1,21 +0,0 @@
import { expect, describe, it } from 'vitest';
import { calculateCidr } from './ipv4-range-expander.service';
describe('ipv4RangeExpander', () => {
describe('when there are two valid ipv4 addresses given', () => {
it('should calculate valid cidr for given addresses', () => {
const result = calculateCidr({ startIp: '192.168.1.1', endIp: '192.168.7.255' });
expect(result).toBeDefined();
expect(result?.oldSize).toEqual(1791);
expect(result?.newSize).toEqual(2048);
expect(result?.newStart).toEqual('192.168.0.0');
expect(result?.newEnd).toEqual('192.168.7.255');
expect(result?.newCidr).toEqual('192.168.0.0/21');
});
it('should return empty result for invalid input', () => {
expect(calculateCidr({ startIp: '192.168.7.1', endIp: '192.168.6.255' })).not.toBeDefined();
});
});
});

View File

@@ -1,63 +0,0 @@
import type { Ipv4RangeExpanderResult } from './ipv4-range-expander.types';
import { convertBase } from '../integer-base-converter/integer-base-converter.model';
import { ipv4ToInt } from '../ipv4-address-converter/ipv4-address-converter.service';
export { calculateCidr };
function bits2ip(ipInt: number) {
return (ipInt >>> 24) + '.' + ((ipInt >> 16) & 255) + '.' + ((ipInt >> 8) & 255) + '.' + (ipInt & 255);
}
function getRangesize(start: string, end: string) {
if (start == null || end == null) return -1;
return 1 + parseInt(end, 2) - parseInt(start, 2);
}
function getCidr(start: string, end: string) {
if (start == null || end == null) return null;
const range = getRangesize(start, end);
if (range < 1) {
return null;
}
let mask = 32;
for (let i = 0; i < 32; i++) {
if (start[i] !== end[i]) {
mask = i;
break;
}
}
const newStart = start.substring(0, mask) + '0'.repeat(32 - mask);
const newEnd = end.substring(0, mask) + '1'.repeat(32 - mask);
return { start: newStart, end: newEnd, mask: mask };
}
function calculateCidr({ startIp, endIp }: { startIp: string; endIp: string }) {
const start = convertBase({
value: ipv4ToInt({ ip: startIp }).toString(),
fromBase: 10,
toBase: 2,
});
const end = convertBase({
value: ipv4ToInt({ ip: endIp }).toString(),
fromBase: 10,
toBase: 2,
});
const cidr = getCidr(start, end);
if (cidr != null) {
const result: Ipv4RangeExpanderResult = {};
result.newEnd = bits2ip(parseInt(cidr.end, 2));
result.newStart = bits2ip(parseInt(cidr.start, 2));
result.newCidr = result.newStart + '/' + cidr.mask;
result.newSize = getRangesize(cidr.start, cidr.end);
result.oldSize = getRangesize(start, end);
return result;
}
return undefined;
}

View File

@@ -1,7 +0,0 @@
export type Ipv4RangeExpanderResult = {
oldSize?: number;
newStart?: string;
newEnd?: string;
newCidr?: string;
newSize?: number;
};

View File

@@ -1,110 +0,0 @@
<template>
<div>
<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-input v-model:value="rawStartAddress" placeholder="Start IPv4 address..." />
</n-form-item>
<n-form-item label="End address" v-bind="endIpValidation.attrs">
<n-input v-model:value="rawEndAddress" placeholder="End IPv4 address..." />
</n-form-item>
</n-space>
<n-table v-if="showResult" data-test-id="result">
<thead>
<tr>
<th scope="col">&nbsp;</th>
<th scope="col">old value</th>
<th scope="col">new value</th>
</tr>
</thead>
<tbody>
<result-row
v-for="{ label, getOldValue, getNewValue } in calculatedValues"
:key="label"
:label="label"
:old-value="getOldValue(result)"
:new-value="getNewValue(result)"
/>
</tbody>
</n-table>
<n-alert
v-else-if="startIpValidation.isValid && endIpValidation.isValid"
title="Invalid combination of start and end IPv4 address"
type="error"
>
<n-space vertical>
<n-text depth="3">
The end IPv4 address is lower than the start IPv4 address. This is not valid and no result could be
calculated. In the most cases the solution to solve this problem is to change start and end address.
</n-text>
<c-button @click="onSwitchStartEndClicked">
<n-icon mr-2 :component="Exchange" depth="3" size="22" />
Switch start and end IPv4 address
</c-button>
</n-space>
</n-alert>
</div>
</n-space>
</div>
</template>
<script setup lang="ts">
import { useValidation } from '@/composable/validation';
import { Exchange } from '@vicons/tabler';
import { isValidIpv4 } from '../ipv4-address-converter/ipv4-address-converter.service';
import type { Ipv4RangeExpanderResult } from './ipv4-range-expander.types';
import { calculateCidr } from './ipv4-range-expander.service';
import ResultRow from './result-row.vue';
const rawStartAddress = useStorage('ipv4-range-expander:startAddress', '192.168.1.1');
const rawEndAddress = useStorage('ipv4-range-expander:endAddress', '192.168.6.255');
const result = computed(() => calculateCidr({ startIp: rawStartAddress.value, endIp: rawEndAddress.value }));
const calculatedValues: {
label: string;
getOldValue: (result: Ipv4RangeExpanderResult | undefined) => string | undefined;
getNewValue: (result: Ipv4RangeExpanderResult | undefined) => string | undefined;
}[] = [
{
label: 'Start address',
getOldValue: () => rawStartAddress.value,
getNewValue: (result) => result?.newStart,
},
{
label: 'End address',
getOldValue: () => rawEndAddress.value,
getNewValue: (result) => result?.newEnd,
},
{
label: 'Addresses in range',
getOldValue: (result) => result?.oldSize?.toLocaleString(),
getNewValue: (result) => result?.newSize?.toLocaleString(),
},
{
label: 'CIDR',
getOldValue: () => '',
getNewValue: (result) => result?.newCidr,
},
];
const showResult = computed(() => endIpValidation.isValid && startIpValidation.isValid && result.value !== undefined);
const startIpValidation = useValidation({
source: rawStartAddress,
rules: [{ message: 'Invalid ipv4 address', validator: (ip) => isValidIpv4({ ip }) }],
});
const endIpValidation = useValidation({
source: rawEndAddress,
rules: [{ message: 'Invalid ipv4 address', validator: (ip) => isValidIpv4({ ip }) }],
});
function onSwitchStartEndClicked() {
const tmpStart = rawStartAddress.value;
rawStartAddress.value = rawEndAddress.value;
rawEndAddress.value = tmpStart;
}
</script>
<style lang="less" scoped></style>

View File

@@ -1,27 +0,0 @@
<template>
<tr>
<td>
<n-text strong>{{ label }}</n-text>
</td>
<td :data-test-id="testId + '.old'"><span-copyable :value="oldValue" class="monospace" /></td>
<td :data-test-id="testId + '.new'">
<span-copyable :value="newValue"></span-copyable>
</td>
</tr>
</template>
<script setup lang="ts">
import SpanCopyable from '@/components/SpanCopyable.vue';
import _ from 'lodash';
const props = withDefaults(defineProps<{ label: string; oldValue?: string; newValue?: string }>(), {
label: '',
oldValue: '',
newValue: '',
});
const { label, oldValue, newValue } = toRefs(props);
const testId = computed(() => _.kebabCase(label.value));
</script>
<style scoped lang="less"></style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<span class="value" @click="handleClick">{{ value }}</span> <span class="ip" @click="handleClick">{{ ip }}</span>
</template> </template>
{{ tooltipText }} {{ tooltipText }}
</n-tooltip> </n-tooltip>
@@ -11,13 +11,13 @@
import { useClipboard } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
import { ref, toRefs } from 'vue'; import { ref, toRefs } from 'vue';
const props = withDefaults(defineProps<{ value?: string }>(), { value: '' }); const props = withDefaults(defineProps<{ ip?: string }>(), { ip: '' });
const { value } = toRefs(props); const { ip } = toRefs(props);
const initialText = 'Copy to clipboard'; const initialText = 'Copy to clipboard';
const tooltipText = ref(initialText); const tooltipText = ref(initialText);
const { copy } = useClipboard({ source: value }); const { copy } = useClipboard({ source: ip });
function handleClick() { function handleClick() {
copy(); copy();
@@ -28,8 +28,8 @@ function handleClick() {
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.value { .ip {
cursor: pointer;
font-family: monospace; font-family: monospace;
cursor: pointer;
} }
</style> </style>

View File

@@ -12,7 +12,7 @@
<n-text strong>{{ label }}</n-text> <n-text strong>{{ label }}</n-text>
</td> </td>
<td> <td>
<span-copyable v-if="getValue(networkInfo)" :value="getValue(networkInfo)"></span-copyable> <copyable-ip-like v-if="getValue(networkInfo)" :ip="getValue(networkInfo)"></copyable-ip-like>
<n-text v-else depth="3">{{ undefinedFallback }}</n-text> <n-text v-else depth="3">{{ undefinedFallback }}</n-text>
</td> </td>
</tr> </tr>
@@ -20,14 +20,14 @@
</n-table> </n-table>
<n-space style="margin-top: 14px" justify="space-between"> <n-space style="margin-top: 14px" justify="space-between">
<c-button @click="switchToBlock({ count: -1 })"> <n-button tertiary @click="switchToBlock({ count: -1 })">
<n-icon :component="ArrowLeft" /> <n-icon :component="ArrowLeft" />
Previous block Previous block
</c-button> </n-button>
<c-button @click="switchToBlock({ count: 1 })"> <n-button tertiary @click="switchToBlock({ count: 1 })">
Next block Next block
<n-icon :component="ArrowRight" /> <n-icon :component="ArrowRight" />
</c-button> </n-button>
</n-space> </n-space>
</div> </div>
</div> </div>
@@ -41,8 +41,8 @@ import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean'; import { isNotThrowing } from '@/utils/boolean';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { ArrowLeft, ArrowRight } from '@vicons/tabler'; import { ArrowLeft, ArrowRight } from '@vicons/tabler';
import SpanCopyable from '@/components/SpanCopyable.vue';
import { getIPClass } from './ipv4-subnet-calculator.models'; import { getIPClass } from './ipv4-subnet-calculator.models';
import CopyableIpLike from './copyable-ip-like.vue';
const ip = useStorage('ipv4-subnet-calculator:ip', '192.168.0.1/24'); const ip = useStorage('ipv4-subnet-calculator:ip', '192.168.0.1/24');

View File

@@ -1,119 +0,0 @@
import _ from 'lodash';
import { useCopy } from '@/composable/copy';
import type { Difference, ArrayDifference, ObjectDifference } from '../json-diff.types';
export const DiffRootViewer = ({ diff }: { diff: Difference }) => {
return (
<div class={'diffs-viewer'}>
<ul>{DiffViewer({ diff, showKeys: false })}</ul>
</div>
);
};
const DiffViewer = ({ diff, showKeys = true }: { diff: Difference; showKeys?: boolean }) => {
const { type, status } = diff;
if (status === 'updated') {
return ComparisonViewer({ diff, showKeys });
}
if (type === 'array') {
return ChildrenViewer({ diff, showKeys, showChildrenKeys: false, openTag: '[', closeTag: ']' });
}
if (type === 'object') {
return ChildrenViewer({ diff, showKeys, openTag: '{', closeTag: '}' });
}
return LineDiffViewer({ diff, showKeys });
};
const LineDiffViewer = ({ diff, showKeys }: { diff: Difference; showKeys?: boolean }) => {
const { value, key, status, oldValue } = diff;
const valueToDisplay = status === 'removed' ? oldValue : value;
return (
<li>
<span class={[status, 'result']}>
{showKeys && (
<>
<span class={'key'}>{key}</span>
{': '}
</>
)}
{Value({ value: valueToDisplay, status })}
</span>
,
</li>
);
};
const ComparisonViewer = ({ diff, showKeys }: { diff: Difference; showKeys?: boolean }) => {
const { value, key, oldValue } = diff;
return (
<li class={'updated-line'}>
{showKeys && (
<>
<span class={'key'}>{key}</span>
{': '}
</>
)}
{Value({ value: oldValue, status: 'removed' })}
{Value({ value, status: 'added' })},
</li>
);
};
const ChildrenViewer = ({
diff,
openTag,
closeTag,
showKeys,
showChildrenKeys = true,
}: {
diff: ArrayDifference | ObjectDifference;
showKeys: boolean;
showChildrenKeys?: boolean;
openTag: string;
closeTag: string;
}) => {
const { children, key, status, type } = diff;
return (
<li>
<div class={[type, status]} style={{ display: 'inline-block' }}>
{showKeys && (
<>
<span class={'key'}>{key}</span>
{': '}
</>
)}
{openTag}
{children.length > 0 && <ul>{children.map((diff) => DiffViewer({ diff, showKeys: showChildrenKeys }))}</ul>}
{closeTag + ','}
</div>
</li>
);
};
function formatValue(value: unknown) {
if (_.isNull(value)) {
return 'null';
}
return JSON.stringify(value);
}
const Value = ({ value, status }: { value: unknown; status: string }) => {
const formatedValue = formatValue(value);
const { copy } = useCopy({ source: formatedValue });
return (
<span class={['value', status]} onClick={copy}>
{formatedValue}
</span>
);
};

View File

@@ -1,110 +0,0 @@
<template>
<div v-if="showResults">
<n-space justify="center">
<n-form-item label="Only show differences" label-placement="left">
<n-switch v-model:value="onlyShowDifferences" />
</n-form-item>
</n-space>
<c-card data-test-id="diff-result">
<n-text v-if="jsonAreTheSame" depth="3" block text-center italic> The provided JSONs are the same </n-text>
<diff-root-viewer v-else :diff="result" />
</c-card>
</div>
</template>
<script lang="ts" setup>
import { useAppTheme } from '@/ui/theme/themes';
import _ from 'lodash';
import { DiffRootViewer } from './diff-viewer.models';
import { diff } from '../json-diff.models';
const onlyShowDifferences = ref(false);
const props = defineProps<{ leftJson: unknown; rightJson: unknown }>();
const { leftJson, rightJson } = toRefs(props);
const appTheme = useAppTheme();
const result = computed(() =>
diff(leftJson.value, rightJson.value, { onlyShowDifferences: onlyShowDifferences.value }),
);
const jsonAreTheSame = computed(() => _.isEqual(leftJson.value, rightJson.value));
const showResults = computed(() => !_.isUndefined(leftJson.value) && !_.isUndefined(rightJson.value));
</script>
<style lang="less" scoped>
::v-deep(.diffs-viewer) {
color: v-bind('appTheme.text.mutedColor');
& > ul {
padding-left: 0 !important;
}
ul {
list-style: none;
padding-left: 20px;
margin: 0;
li {
.updated-line {
padding: 3px 0;
}
.result,
.array,
.object,
.value {
&:not(:last-child) {
margin-right: 4px;
}
&.added {
padding: 3px 5px;
border-radius: 4px;
background-color: v-bind('appTheme.success.colorFaded');
color: v-bind('appTheme.success.color');
.key {
color: inherit;
}
}
&.removed {
padding: 3px 5px;
border-radius: 4px;
background-color: v-bind('appTheme.error.colorFaded');
color: v-bind('appTheme.error.color');
.key {
color: inherit;
}
}
}
.value {
cursor: pointer;
border: 1px solid transparent;
transition: border-color 0.2s ease-in-out;
&.added:hover {
border-color: v-bind('appTheme.success.color');
}
&.removed:hover {
border-color: v-bind('appTheme.error.color');
}
}
.added .added,
.removed .removed {
background-color: transparent;
color: inherit;
}
.key {
font-weight: 500;
color: v-bind('appTheme.text.baseColor');
}
}
}
}
</style>

View File

@@ -1,12 +0,0 @@
import { CompareArrowsRound } from '@vicons/material';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'JSON diff',
path: '/json-diff',
description: 'Compare two JSON objects and get the differences between them.',
keywords: ['json', 'diff', 'compare', 'difference', 'object', 'data'],
component: () => import('./json-diff.vue'),
icon: CompareArrowsRound,
createdAt: new Date('2023-04-20'),
});

View File

@@ -1,39 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Tool - JSON diff', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/json-diff');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('JSON diff - IT Tools');
});
test('Identical JSONs have a custom result message', async ({ page }) => {
await page.getByTestId('leftJson').fill('{"foo":"bar"}');
await page.getByTestId('rightJson').fill('{ "foo": "bar" } ');
const result = await page.getByTestId('diff-result').innerText();
expect(result).toContain('The provided JSONs are the same');
});
test('Different JSONs have differences listed', async ({ page }) => {
await page.getByTestId('leftJson').fill('{"foo":"bar"}');
await page.getByTestId('rightJson').fill('{"foo":"buz","baz":"qux"}');
const result = await page.getByTestId('diff-result').innerText();
expect(result).toContain(`{\nfoo: "bar""buz",\nbaz: "qux",\n},`);
});
test('Different JSONs have only differences listed when "Only show differences" is checked', async ({ page }) => {
await page.getByTestId('leftJson').fill('{"foo":"bar"}');
await page.getByTestId('rightJson').fill('{"foo":"bar","baz":"qux"}');
await page.getByRole('switch').click();
const result = await page.getByTestId('diff-result').innerText();
expect(result).toContain(`{\nbaz: "qux",\n},`);
});
});

View File

@@ -1,80 +0,0 @@
import { expect, describe, it } from 'vitest';
import { diff } from './json-diff.models';
describe('json-diff models', () => {
describe('diff', () => {
it('list object differences', () => {
const obj = { a: 1, b: 2 };
const newObj = { a: 1, b: 2, c: 3 };
const result = diff(obj, newObj);
expect(result).toEqual({
key: '',
type: 'object',
children: [
{
key: 'a',
type: 'value',
value: 1,
oldValue: 1,
status: 'unchanged',
},
{
key: 'b',
type: 'value',
value: 2,
oldValue: 2,
status: 'unchanged',
},
{
key: 'c',
type: 'value',
value: 3,
oldValue: undefined,
status: 'added',
},
],
oldValue: { a: 1, b: 2 },
value: { a: 1, b: 2, c: 3 },
status: 'children-updated',
});
});
it('list array differences', () => {
const obj = [1, 2];
const newObj = [1, 2, 3];
const result = diff(obj, newObj);
expect(result).toEqual({
key: '',
type: 'array',
children: [
{
key: 0,
type: 'value',
value: 1,
oldValue: 1,
status: 'unchanged',
},
{
key: 1,
type: 'value',
value: 2,
oldValue: 2,
status: 'unchanged',
},
{
key: 2,
type: 'value',
value: 3,
oldValue: undefined,
status: 'added',
},
],
oldValue: [1, 2],
value: [1, 2, 3],
status: 'children-updated',
});
});
});
});

View File

@@ -1,140 +0,0 @@
import _ from 'lodash';
import type { Difference, DifferenceStatus } from './json-diff.types';
export { diff };
function diff(
obj: unknown,
newObj: unknown,
{ onlyShowDifferences = false }: { onlyShowDifferences?: boolean } = {},
): Difference {
if (_.isArray(obj) && _.isArray(newObj)) {
return {
key: '',
type: 'array',
children: diffArrays(obj, newObj, { onlyShowDifferences }),
oldValue: obj,
value: newObj,
status: getStatus(obj, newObj),
};
}
if (_.isObject(obj) && _.isObject(newObj)) {
return {
key: '',
type: 'object',
children: diffObjects(obj as Record<string, unknown>, newObj as Record<string, unknown>, { onlyShowDifferences }),
oldValue: obj,
value: newObj,
status: getStatus(obj, newObj),
};
}
return {
key: '',
type: 'value',
oldValue: obj,
value: newObj,
status: getStatus(obj, newObj),
};
}
function diffObjects(
obj: Record<string, unknown>,
newObj: Record<string, unknown>,
{ onlyShowDifferences = false }: { onlyShowDifferences?: boolean } = {},
): Difference[] {
const keys = Object.keys({ ...obj, ...newObj });
return keys
.map((key) => createDifference(obj?.[key], newObj?.[key], key, { onlyShowDifferences }))
.filter((diff) => !onlyShowDifferences || diff.status !== 'unchanged');
}
function createDifference(
value: unknown,
newValue: unknown,
key: string | number,
{ onlyShowDifferences = false }: { onlyShowDifferences?: boolean } = {},
): Difference {
const type = getType(value);
if (type === 'object') {
return {
key,
type,
children: diffObjects(value as Record<string, unknown>, newValue as Record<string, unknown>, {
onlyShowDifferences,
}),
oldValue: value,
value: newValue,
status: getStatus(value, newValue),
};
}
if (type === 'array') {
return {
key,
type,
children: diffArrays(value as unknown[], newValue as unknown[], { onlyShowDifferences }),
value: newValue,
oldValue: value,
status: getStatus(value, newValue),
};
}
return {
key,
type,
value: newValue,
oldValue: value,
status: getStatus(value, newValue),
};
}
function diffArrays(
arr: unknown[],
newArr: unknown[],
{ onlyShowDifferences = false }: { onlyShowDifferences?: boolean } = {},
): Difference[] {
const maxLength = Math.max(0, arr?.length, newArr?.length);
return Array.from({ length: maxLength }, (_, i) =>
createDifference(arr?.[i], newArr?.[i], i, { onlyShowDifferences }),
).filter((diff) => !onlyShowDifferences || diff.status !== 'unchanged');
}
function getType(value: unknown): 'object' | 'array' | 'value' {
if (value === null) {
return 'value';
}
if (Array.isArray(value)) {
return 'array';
}
if (typeof value === 'object') {
return 'object';
}
return 'value';
}
function getStatus(value: unknown, newValue: unknown): DifferenceStatus {
if (value === undefined) {
return 'added';
}
if (newValue === undefined) {
return 'removed';
}
const bothAreObjects = getType(value) === 'object' && getType(newValue) === 'object';
const bothAreArrays = getType(value) === 'array' && getType(newValue) === 'array';
const bothAreDeepEqual = _.isEqual(value, newValue);
if (bothAreDeepEqual) {
return 'unchanged';
}
if (bothAreObjects || bothAreArrays) {
return 'children-updated';
}
return 'updated';
}

View File

@@ -1,29 +0,0 @@
export type DifferenceStatus = 'added' | 'removed' | 'updated' | 'unchanged' | 'children-updated';
export type ObjectDifference = {
key: string | number;
type: 'object';
children: Difference[];
status: DifferenceStatus;
oldValue: unknown;
value: unknown;
};
export type ValueDifference = {
key: string | number;
type: 'value';
value: unknown;
oldValue: unknown;
status: DifferenceStatus;
};
export type ArrayDifference = {
key: number | string;
type: 'array';
children: Difference[];
status: DifferenceStatus;
oldValue: unknown;
value: unknown;
};
export type Difference = ObjectDifference | ValueDifference | ArrayDifference;

View File

@@ -1,59 +0,0 @@
<template>
<n-form-item label="Your first json" v-bind="leftJsonValidation.attrs">
<n-input
v-model:value="rawLeftJson"
placeholder="Paste your first json here..."
type="textarea"
rows="20"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
:input-props="{ 'data-test-id': 'leftJson' }"
/>
</n-form-item>
<n-form-item label="Your json to compare" v-bind="rightJsonValidation.attrs">
<n-input
v-model:value="rawRightJson"
placeholder="Paste your json to compare here..."
type="textarea"
rows="20"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
:input-props="{ 'data-test-id': 'rightJson' }"
/>
</n-form-item>
<DiffsViewer :left-json="leftJson" :right-json="rightJson" />
</template>
<script setup lang="ts">
import JSON5 from 'json5';
import { withDefaultOnError } from '@/utils/defaults';
import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import DiffsViewer from './diff-viewer/diff-viewer.vue';
const rawLeftJson = ref('');
const rawRightJson = ref('');
const leftJson = computed(() => withDefaultOnError(() => JSON5.parse(rawLeftJson.value), undefined));
const rightJson = computed(() => withDefaultOnError(() => JSON5.parse(rawRightJson.value), undefined));
const createJsonValidation = (json: Ref) =>
useValidation({
source: json,
rules: [
{
validator: (value) => value === '' || isNotThrowing(() => JSON5.parse(value)),
message: 'Invalid JSON',
},
],
});
const leftJsonValidation = createJsonValidation(rawLeftJson);
const rightJsonValidation = createJsonValidation(rawRightJson);
</script>

View File

@@ -9,7 +9,7 @@ function sortObjectKeys<T>(obj: T): T {
} }
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
return obj.map(sortObjectKeys) as unknown as T; return obj.map(sortObjectKeys) as T;
} }
return Object.keys(obj) return Object.keys(obj)

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card> <n-card>
<n-form-item label="JWT to decode" :feedback="validation.message" :validation-status="validation.status"> <n-form-item label="JWT to decode" :feedback="validation.message" :validation-status="validation.status">
<n-input v-model:value="rawJwt" type="textarea" placeholder="Put your token here..." rows="5" /> <n-input v-model:value="rawJwt" type="textarea" placeholder="Put your token here..." rows="5" />
</n-form-item> </n-form-item>
@@ -29,7 +29,7 @@
</template> </template>
</tbody> </tbody>
</n-table> </n-table>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,9 +1,9 @@
<template> <template>
<div> <div>
<c-card style="text-align: center; padding: 40px 0; margin-bottom: 26px"> <n-card style="text-align: center; padding: 40px 0; margin-bottom: 26px">
<n-h2 v-if="event">{{ event.key }}</n-h2> <n-h2 v-if="event">{{ event.key }}</n-h2>
<n-text strong depth="3">Press the key on your keyboard you want to get info about this key</n-text> <n-text strong depth="3">Press the key on your keyboard you want to get info about this key</n-text>
</c-card> </n-card>
<n-input-group v-for="({ label, value, placeholder }, i) of fields" :key="i" style="margin-bottom: 5px"> <n-input-group v-for="({ label, value, placeholder }, i) of fields" :key="i" style="margin-bottom: 5px">
<n-input-group-label style="flex: 0 0 150px"> {{ label }} </n-input-group-label> <n-input-group-label style="flex: 0 0 150px"> {{ label }} </n-input-group-label>

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card> <n-card>
<n-form-item label="Paragraphs" :show-feedback="false" label-width="200" label-placement="left"> <n-form-item label="Paragraphs" :show-feedback="false" label-width="200" label-placement="left">
<n-slider v-model:value="paragraphs" :step="1" :min="1" :max="20" /> <n-slider v-model:value="paragraphs" :step="1" :min="1" :max="20" />
</n-form-item> </n-form-item>
@@ -16,12 +16,15 @@
<n-switch v-model:value="asHTML" /> <n-switch v-model:value="asHTML" />
</n-form-item> </n-form-item>
<n-input :value="loremIpsumText" type="textarea" placeholder="Your lorem ipsum..." readonly autosize mt-5 /> <br />
<n-space justify="center" mt-5> <n-input :value="loremIpsumText" type="textarea" placeholder="Your lorem ipsum..." readonly autosize />
<c-button autofocus @click="copy"> Copy </c-button> <br />
<br />
<n-space justify="center">
<n-button secondary autofocus @click="copy"> Copy </n-button>
</n-space> </n-space>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -14,17 +14,17 @@
</n-form-item> </n-form-item>
<n-form-item label="Vendor info:"> <n-form-item label="Vendor info:">
<c-card> <n-card>
<n-text v-if="details"> <n-text v-if="details">
<div v-for="(detail, index) of details.split('\n')" :key="index">{{ detail }}</div> <div v-for="(detail, index) of details.split('\n')" :key="index">{{ detail }}</div>
</n-text> </n-text>
<n-text v-else depth="3" italic>Unknown vendor for this address</n-text> <n-text v-else depth="3" italic>Unknown vendor for this address</n-text>
</c-card> </n-card>
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button :disabled="!details" @click="copy"> Copy vendor info </c-button> <n-button :disabled="!details" tertiary> Copy vendor info </n-button>
</n-space> </n-space>
</div> </div>
</template> </template>
@@ -32,7 +32,6 @@
<script setup lang="ts"> <script setup lang="ts">
import db from 'oui/oui.json'; import db from 'oui/oui.json';
import { macAddressValidation } from '@/utils/macAddress'; import { macAddressValidation } from '@/utils/macAddress';
import { useCopy } from '@/composable/copy';
const getVendorValue = (address: string) => address.trim().replace(/[.:-]/g, '').toUpperCase().substring(0, 6); const getVendorValue = (address: string) => address.trim().replace(/[.:-]/g, '').toUpperCase().substring(0, 6);
@@ -40,8 +39,6 @@ const macAddress = ref('20:37:06:12:34:56');
const details = computed<string | undefined>(() => db[getVendorValue(macAddress.value)]); const details = computed<string | undefined>(() => db[getVendorValue(macAddress.value)]);
const { attrs: validationAttrs } = macAddressValidation(macAddress); const { attrs: validationAttrs } = macAddressValidation(macAddress);
const { copy } = useCopy({ source: details, text: 'Vendor info copied to the clipboard' });
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@@ -11,10 +11,12 @@
autocapitalize="off" autocapitalize="off"
spellcheck="false" spellcheck="false"
/> />
<br />
<br />
<c-card v-if="result !== ''" title="Result " mt-5> <n-card v-if="result !== ''" title="Result ">
{{ result }} {{ result }}
</c-card> </n-card>
</div> </div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card> <n-card>
<n-h2 style="margin-bottom: 0">Mime type to extension</n-h2> <n-h2 style="margin-bottom: 0">Mime type to extension</n-h2>
<div style="opacity: 0.8">Now witch file extensions are associated to a mime-type</div> <div style="opacity: 0.8">Now witch file extensions are associated to a mime-type</div>
<n-form-item> <n-form-item>
@@ -27,9 +27,9 @@
</n-tag> </n-tag>
</div> </div>
</div> </div>
</c-card> </n-card>
<c-card> <n-card>
<n-h2 style="margin-bottom: 0">File extension to mime type</n-h2> <n-h2 style="margin-bottom: 0">File extension to mime type</n-h2>
<div style="opacity: 0.8">Now witch mime type is associated to a file extension</div> <div style="opacity: 0.8">Now witch mime type is associated to a file extension</div>
<n-form-item> <n-form-item>
@@ -51,7 +51,7 @@
</n-tag> </n-tag>
</div> </div>
</div> </div>
</c-card> </n-card>
<div> <div>
<n-table> <n-table>

View File

@@ -5,9 +5,9 @@
<template #suffix> <template #suffix>
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<c-button circle variant="text" @click="refreshSecret"> <n-button quaternary circle @click="refreshSecret">
<n-icon :component="Refresh" /> <n-icon :component="Refresh" />
</c-button> </n-button>
</template> </template>
Generate secret token Generate secret token
</n-tooltip> </n-tooltip>
@@ -23,7 +23,7 @@
</div> </div>
<n-space justify="center" vertical align="center" style="margin-top: 10px"> <n-space justify="center" vertical align="center" style="margin-top: 10px">
<n-image :src="qrcode"></n-image> <n-image :src="qrcode"></n-image>
<c-button :href="keyUri" target="_blank">Open Key URI in new tab</c-button> <n-button secondary tag="a" :href="keyUri" target="_blank">Open Key URI in new tab</n-button>
</n-space> </n-space>
</div> </div>
<div style="max-width: 350px"> <div style="max-width: 350px">

View File

@@ -8,30 +8,31 @@
<n-input-group> <n-input-group>
<n-tooltip trigger="hover" placement="bottom"> <n-tooltip trigger="hover" placement="bottom">
<template #trigger> <template #trigger>
<c-button important:h-12 data-test-id="previous-otp" @click.prevent="copyPrevious(tokens.previous)"> <n-button data-test-id="previous-otp" secondary @click.prevent="copyPrevious(tokens.previous)">{{
{{ tokens.previous }} tokens.previous
</c-button> }}</n-button>
</template> </template>
<div>{{ previousCopied ? 'Copied !' : 'Copy previous OTP' }}</div> <div>{{ previousCopied ? 'Copied !' : 'Copy previous OTP' }}</div>
</n-tooltip> </n-tooltip>
<n-tooltip trigger="hover" placement="bottom"> <n-tooltip trigger="hover" placement="bottom">
<template #trigger> <template #trigger>
<c-button <n-button
tertiary
type="primary"
data-test-id="current-otp" data-test-id="current-otp"
class="current-otp" class="current-otp"
important:h-12
@click.prevent="copyCurrent(tokens.current)" @click.prevent="copyCurrent(tokens.current)"
> >
{{ tokens.current }} {{ tokens.current }}
</c-button> </n-button>
</template> </template>
<div>{{ currentCopied ? 'Copied !' : 'Copy current OTP' }}</div> <div>{{ currentCopied ? 'Copied !' : 'Copy current OTP' }}</div>
</n-tooltip> </n-tooltip>
<n-tooltip trigger="hover" placement="bottom"> <n-tooltip trigger="hover" placement="bottom">
<template #trigger> <template #trigger>
<c-button important:h-12 data-test-id="next-otp" @click.prevent="copyNext(tokens.next)">{{ <n-button secondary data-test-id="next-otp" @click.prevent="copyNext(tokens.next)">{{
tokens.next tokens.next
}}</c-button> }}</n-button>
</template> </template>
<div>{{ nextCopied ? 'Copied !' : 'Copy next OTP' }}</div> <div>{{ nextCopied ? 'Copied !' : 'Copy next OTP' }}</div>
</n-tooltip> </n-tooltip>

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card> <n-card>
<n-grid x-gap="12" y-gap="12" cols="1 600:3"> <n-grid x-gap="12" y-gap="12" cols="1 600:3">
<n-gi span="2"> <n-gi span="2">
<n-form label-width="130" label-placement="left"> <n-form label-width="130" label-placement="left">
@@ -28,11 +28,11 @@
<n-gi> <n-gi>
<n-space justify="center" align="center" vertical> <n-space justify="center" align="center" vertical>
<n-image :src="qrcode" width="200" /> <n-image :src="qrcode" width="200" />
<c-button @click="download"> Download qr-code </c-button> <n-button secondary @click="download"> Download qr-code </n-button>
</n-space> </n-space>
</n-gi> </n-gi>
</n-grid> </n-grid>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,13 +1,13 @@
<template> <template>
<c-card> <n-card>
<div class="port"> <div class="port">
{{ port }} {{ port }}
</div> </div>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copy"> Copy </c-button> <n-button secondary @click="copy"> Copy </n-button>
<c-button @click="refreshPort"> Refresh </c-button> <n-button secondary @click="refreshPort"> Refresh </n-button>
</n-space> </n-space>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<c-card title="Arabic to roman"> <n-card title="Arabic to roman">
<n-space align="center" justify="space-between"> <n-space align="center" justify="space-between">
<n-form-item v-bind="validationNumeral"> <n-form-item v-bind="validationNumeral">
<n-input-number v-model:value="inputNumeral" :min="1" style="width: 200px" :show-button="false" /> <n-input-number v-model:value="inputNumeral" :min="1" style="width: 200px" :show-button="false" />
@@ -8,12 +8,13 @@
<div class="result"> <div class="result">
{{ outputRoman }} {{ outputRoman }}
</div> </div>
<c-button autofocus :disabled="validationNumeral.validationStatus === 'error'" @click="copyRoman"> <n-button secondary autofocus :disabled="validationNumeral.validationStatus === 'error'" @click="copyRoman">
Copy Copy
</c-button> </n-button>
</n-space> </n-space>
</c-card> </n-card>
<c-card title="Roman to arabic" mt-5> <br />
<n-card title="Roman to arabic">
<n-space align="center" justify="space-between"> <n-space align="center" justify="space-between">
<n-form-item v-bind="validationRoman"> <n-form-item v-bind="validationRoman">
<n-input v-model:value="inputRoman" style="width: 200px" /> <n-input v-model:value="inputRoman" style="width: 200px" />
@@ -21,9 +22,11 @@
<div class="result"> <div class="result">
{{ outputNumeral }} {{ outputNumeral }}
</div> </div>
<c-button :disabled="validationRoman.validationStatus === 'error'" @click="copyArabic"> Copy </c-button> <n-button secondary autofocus :disabled="validationRoman.validationStatus === 'error'" @click="copyArabic">
Copy
</n-button>
</n-space> </n-space>
</c-card> </n-card>
</div> </div>
</template> </template>

View File

@@ -5,7 +5,7 @@
<n-input-number v-model:value="bits" min="256" max="16384" step="8" /> <n-input-number v-model:value="bits" min="256" max="16384" step="8" />
</n-form-item> </n-form-item>
<c-button @click="refreshCerts">Refresh key-pair</c-button> <n-button tertiary @click="refreshCerts">Refresh key-pair</n-button>
</n-space> </n-space>
</div> </div>

View File

@@ -14,7 +14,7 @@
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button :disabled="slug.length === 0" @click="copy">Copy slug</c-button> <n-button secondary :disabled="slug.length === 0" @click="copy">Copy slug</n-button>
</n-space> </n-space>
</div> </div>
</template> </template>

View File

@@ -38,9 +38,9 @@
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copySVG()">Copy svg</c-button> <n-button secondary @click="copySVG()">Copy svg</n-button>
<c-button @click="copyBase64()">Copy base64</c-button> <n-button secondary @click="copyBase64()">Copy base64</n-button>
<c-button @click="download()">Download svg</c-button> <n-button secondary @click="download()">Download svg</n-button>
</n-space> </n-space>
</div> </div>

View File

@@ -1,14 +1,15 @@
<template> <template>
<c-card> <n-card>
<n-input v-model:value="text" type="textarea" placeholder="Your text..." rows="5" /> <n-input v-model:value="text" type="textarea" placeholder="Your text..." rows="5" />
<br />
<n-space justify="space-around" mt-5> <br />
<n-space justify="space-around">
<n-statistic label="Character count" :value="text.length" /> <n-statistic label="Character count" :value="text.length" />
<n-statistic label="Word count" :value="text === '' ? 0 : text.split(/\s+/).length" /> <n-statistic label="Word count" :value="text === '' ? 0 : text.split(/\s+/).length" />
<n-statistic label="Line count" :value="text === '' ? 0 : text.split(/\r\n|\r|\n/).length" /> <n-statistic label="Line count" :value="text === '' ? 0 : text.split(/\r\n|\r|\n/).length" />
<n-statistic label="Byte size" :value="formatBytes(getStringSizeInBytes(text))" /> <n-statistic label="Byte size" :value="formatBytes(getStringSizeInBytes(text))" />
</n-space> </n-space>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -6,12 +6,12 @@
<n-space v-if="natoText" vertical> <n-space v-if="natoText" vertical>
<n-text>Your text in NATO phonetic alphabet</n-text> <n-text>Your text in NATO phonetic alphabet</n-text>
<c-card> <n-card>
{{ natoText }} {{ natoText }}
</c-card> </n-card>
<n-space justify="center"> <n-space justify="center">
<c-button autofocus @click="copy"> Copy NATO string </c-button> <n-button secondary autofocus @click="copy"> Copy NATO string </n-button>
</n-space> </n-space>
</n-space> </n-space>
</div> </div>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<c-card> <n-card>
<n-form label-placement="left" label-width="140"> <n-form label-placement="left" label-width="140">
<n-space justify="center" item-style="padding: 0" :size="0"> <n-space justify="center" item-style="padding: 0" :size="0">
<div> <div>
@@ -41,12 +41,13 @@
autocapitalize="off" autocapitalize="off"
spellcheck="false" spellcheck="false"
/> />
<br />
<n-space justify="center" mt-5> <br />
<c-button @click="copy"> Copy </c-button> <n-space justify="center">
<c-button @click="refreshToken"> Refresh </c-button> <n-button secondary autofocus @click="copy"> Copy </n-button>
<n-button secondary @click="refreshToken"> Refresh </n-button>
</n-space> </n-space>
</c-card> </n-card>
</div> </div>
</template> </template>

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card title="Encode"> <n-card title="Encode">
<n-form-item <n-form-item
label="Your string :" label="Your string :"
:feedback="encodedValidation.message" :feedback="encodedValidation.message"
@@ -24,10 +24,10 @@
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copyEncoded"> Copy </c-button> <n-button secondary @click="copyEncoded"> Copy </n-button>
</n-space> </n-space>
</c-card> </n-card>
<c-card title="Decode"> <n-card title="Decode">
<n-form-item <n-form-item
label="Your encoded string :" label="Your encoded string :"
:feedback="decodeValidation.message" :feedback="decodeValidation.message"
@@ -52,9 +52,9 @@
</n-form-item> </n-form-item>
<n-space justify="center"> <n-space justify="center">
<c-button @click="copyDecoded"> Copy </c-button> <n-button secondary @click="copyDecoded"> Copy </n-button>
</n-space> </n-space>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,5 +1,5 @@
<template> <template>
<c-card> <n-card>
<n-form-item label="Your url to parse:" :feedback="validation.message" :validation-status="validation.status"> <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-input v-model:value="urlToParse" placeholder="Your url to parse..." />
</n-form-item> </n-form-item>
@@ -23,7 +23,7 @@
<input-copyable :value="v" readonly /> <input-copyable :value="v" readonly />
</n-input-group> </n-input-group>
</n-form> </n-form>
</c-card> </n-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -6,7 +6,7 @@ export type UserAgentResultSection = {
icon?: Component; icon?: Component;
content: { content: {
label: string; label: string;
getValue: (blocks?: UAParser.IResult) => string | undefined; getValue: (blocks: UAParser.IResult) => string | undefined;
undefinedFallback?: string; undefinedFallback?: string;
}[]; }[];
}; };

View File

@@ -40,12 +40,12 @@ const sections: UserAgentResultSection[] = [
content: [ content: [
{ {
label: 'Name', label: 'Name',
getValue: (block) => block?.browser.name, getValue: (block) => block.browser.name,
undefinedFallback: 'No browser name available', undefinedFallback: 'No browser name available',
}, },
{ {
label: 'Version', label: 'Version',
getValue: (block) => block?.browser.version, getValue: (block) => block.browser.version,
undefinedFallback: 'No browser version available', undefinedFallback: 'No browser version available',
}, },
], ],
@@ -56,12 +56,12 @@ const sections: UserAgentResultSection[] = [
content: [ content: [
{ {
label: 'Name', label: 'Name',
getValue: (block) => block?.engine.name, getValue: (block) => block.engine.name,
undefinedFallback: 'No engine name available', undefinedFallback: 'No engine name available',
}, },
{ {
label: 'Version', label: 'Version',
getValue: (block) => block?.engine.version, getValue: (block) => block.engine.version,
undefinedFallback: 'No engine version available', undefinedFallback: 'No engine version available',
}, },
], ],
@@ -72,12 +72,12 @@ const sections: UserAgentResultSection[] = [
content: [ content: [
{ {
label: 'Name', label: 'Name',
getValue: (block) => block?.os.name, getValue: (block) => block.os.name,
undefinedFallback: 'No OS name available', undefinedFallback: 'No OS name available',
}, },
{ {
label: 'Version', label: 'Version',
getValue: (block) => block?.os.version, getValue: (block) => block.os.version,
undefinedFallback: 'No OS version available', undefinedFallback: 'No OS version available',
}, },
], ],
@@ -88,17 +88,17 @@ const sections: UserAgentResultSection[] = [
content: [ content: [
{ {
label: 'Model', label: 'Model',
getValue: (block) => block?.device.model, getValue: (block) => block.device.model,
undefinedFallback: 'No device model available', undefinedFallback: 'No device model available',
}, },
{ {
label: 'Type', label: 'Type',
getValue: (block) => block?.device.type, getValue: (block) => block.device.type,
undefinedFallback: 'No device type available', undefinedFallback: 'No device type available',
}, },
{ {
label: 'Vendor', label: 'Vendor',
getValue: (block) => block?.device.vendor, getValue: (block) => block.device.vendor,
undefinedFallback: 'No device vendor available', undefinedFallback: 'No device vendor available',
}, },
], ],
@@ -109,7 +109,7 @@ const sections: UserAgentResultSection[] = [
content: [ content: [
{ {
label: 'Architecture', label: 'Architecture',
getValue: (block) => block?.cpu.architecture, getValue: (block) => block.cpu.architecture,
undefinedFallback: 'No CPU architecture available', undefinedFallback: 'No CPU architecture available',
}, },
], ],

View File

@@ -2,7 +2,7 @@
<div> <div>
<n-grid :x-gap="12" :y-gap="8" cols="1 s:2" responsive="screen"> <n-grid :x-gap="12" :y-gap="8" cols="1 s:2" responsive="screen">
<n-gi v-for="{ heading, icon, content } in sections" :key="heading"> <n-gi v-for="{ heading, icon, content } in sections" :key="heading">
<c-card h-full> <n-card style="height: 100%">
<n-page-header> <n-page-header>
<template #title> <template #title>
{{ heading }} {{ heading }}
@@ -12,7 +12,9 @@
</template> </template>
</n-page-header> </n-page-header>
<n-space mt-5> <br />
<n-space>
<span v-for="{ label, getValue } in content" :key="label"> <span v-for="{ label, getValue } in content" :key="label">
<n-tooltip v-if="getValue(userAgentInfo)" trigger="hover"> <n-tooltip v-if="getValue(userAgentInfo)" trigger="hover">
<template #trigger> <template #trigger>
@@ -29,7 +31,7 @@
<n-text v-if="getValue(userAgentInfo) === undefined" depth="3">{{ undefinedFallback }}</n-text> <n-text v-if="getValue(userAgentInfo) === undefined" depth="3">{{ undefinedFallback }}</n-text>
</span> </span>
</n-space> </n-space>
</c-card> </n-card>
</n-gi> </n-gi>
</n-grid> </n-grid>
</div> </div>

View File

@@ -19,8 +19,8 @@
/> />
<n-space justify="center"> <n-space justify="center">
<c-button autofocus @click="copy"> Copy </c-button> <n-button secondary autofocus @click="copy"> Copy </n-button>
<c-button @click="refreshUUIDs"> Refresh </c-button> <n-button secondary @click="refreshUUIDs"> Refresh </n-button>
</n-space> </n-space>
</n-space> </n-space>
</template> </template>

View File

@@ -1,29 +0,0 @@
<template>
<div v-for="buttonVariant of buttonVariants" :key="buttonVariant">
<h2>{{ _.capitalize(buttonVariant) }}</h2>
<c-button v-for="buttonType of buttonTypes" :key="buttonType" :variant="buttonVariant" :type="buttonType" mx-1>
Button
</c-button>
<c-button
v-for="buttonType of buttonTypes"
:key="buttonType"
:variant="buttonVariant"
:type="buttonType"
circle
mx-1
>
A
</c-button>
</div>
</template>
<script lang="ts" setup>
import _ from 'lodash';
const buttonVariants = ['basic', 'text'];
const buttonTypes = ['default', 'primary'];
</script>
<style lang="less" scoped></style>

View File

@@ -1,76 +0,0 @@
import { darken, lighten } from '../color/color.models';
import { defineThemes } from '../theme/theme.models';
import { appThemes } from '../theme/themes';
const createState = ({
textColor,
backgroundColor,
hoverBackground,
hoveredTextColor = textColor,
pressedBackground,
pressedTextColor = textColor,
}: {
textColor: string;
backgroundColor: string;
hoverBackground: string;
hoveredTextColor?: string;
pressedBackground: string;
pressedTextColor?: string;
}) => ({
textColor,
backgroundColor,
hover: { textColor: hoveredTextColor, backgroundColor: hoverBackground },
pressed: { textColor: pressedTextColor, backgroundColor: pressedBackground },
});
const createTheme = ({ style }: { style: 'light' | 'dark' }) => {
const theme = appThemes[style];
return {
basic: {
default: createState({
textColor: theme.text.baseColor,
backgroundColor: theme.default.color,
hoverBackground: theme.default.colorHover,
pressedBackground: theme.default.colorPressed,
}),
primary: createState({
textColor: theme.primary.color,
backgroundColor: theme.primary.colorFaded,
hoverBackground: lighten(theme.primary.colorFaded, 30),
pressedBackground: darken(theme.primary.colorFaded, 30),
}),
warning: createState({
textColor: theme.text.baseColor,
backgroundColor: theme.warning.color,
hoverBackground: theme.warning.colorHover,
pressedBackground: theme.warning.colorPressed,
}),
},
text: {
default: createState({
textColor: theme.text.baseColor,
backgroundColor: 'transparent',
hoverBackground: theme.default.colorHover,
pressedBackground: theme.default.colorPressed,
}),
primary: createState({
textColor: theme.primary.color,
backgroundColor: 'transparent',
hoverBackground: theme.primary.colorFaded,
pressedBackground: darken(theme.primary.colorFaded, 30),
}),
warning: createState({
textColor: theme.text.baseColor,
backgroundColor: theme.warning.color,
hoverBackground: theme.warning.colorHover,
pressedBackground: theme.warning.colorPressed,
}),
},
};
};
export const { useTheme } = defineThemes({
dark: createTheme({ style: 'dark' }),
light: createTheme({ style: 'light' }),
});

View File

@@ -1,115 +0,0 @@
<template>
<component
:is="tag"
:href="href ?? to"
class="c-button"
:class="{ disabled, round, circle }"
:to="to"
@click="handleClick"
>
<slot />
</component>
</template>
<script lang="ts" setup>
import type { RouteLocationRaw } from 'vue-router';
import { useTheme } from './c-button.theme';
import { useAppTheme } from '../theme/themes';
const props = withDefaults(
defineProps<{
type?: 'default' | 'primary';
variant?: 'basic' | 'text';
disabled?: boolean;
round?: boolean;
circle?: boolean;
href?: string;
to?: RouteLocationRaw;
}>(),
{
type: 'default',
variant: 'basic',
disabled: false,
round: false,
circle: false,
href: undefined,
to: undefined,
},
);
const { variant, disabled, round, circle, href, type, to } = toRefs(props);
const emits = defineEmits(['click']);
function handleClick(event: MouseEvent) {
if (!disabled.value) {
emits('click', event);
}
}
const theme = useTheme();
const variantTheme = computed(() => theme.value[variant.value][type.value]);
const tag = computed(() => {
if (href.value) {
return 'a';
}
if (to.value) {
return 'router-link';
}
return 'button';
});
const appTheme = useAppTheme();
</script>
<style lang="less" scoped>
.c-button {
line-height: 1;
font-family: inherit;
font-size: inherit;
border: none;
text-align: center;
cursor: pointer;
text-decoration: none;
height: 34px;
font-weight: 400;
color: v-bind('variantTheme.textColor');
padding: 0 14px;
border-radius: 4px;
transition: background-color cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
background-color: v-bind('variantTheme.backgroundColor');
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-shrink: 0;
// outline-offset: 1px;
&.round {
border-radius: 100px;
}
&.circle {
border-radius: 40px;
width: 34px;
}
&:not(.disabled) {
&:hover {
background-color: v-bind('variantTheme.hover.backgroundColor');
}
&:active {
background-color: v-bind('variantTheme.pressed.backgroundColor');
}
}
&:focus {
outline: 1px solid v-bind('appTheme.primary.color');
}
&.disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
</style>

View File

@@ -1,13 +0,0 @@
<template>
<div>
<h2>Default</h2>
<c-card title="Title">
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Repudiandae ipsa reiciendis facilis officia nulla.
Laboriosam cumque molestias excepturi doloribus nulla nemo quod ratione rerum possimus. Excepturi nihil possimus
error itaque.
</c-card>
</div>
</template>
<script lang="ts" setup></script>

View File

@@ -1,12 +0,0 @@
import { defineThemes } from '../theme/theme.models';
export const { useTheme } = defineThemes({
dark: {
backgroundColor: '#232323',
borderColor: '#282828',
},
light: {
backgroundColor: '#ffffff',
borderColor: '#efeff5',
},
});

View File

@@ -1,35 +0,0 @@
<template>
<div class="c-card">
<div v-if="title" class="c-card-title">
{{ title }}
</div>
<slot />
</div>
</template>
<script lang="ts" setup>
import { useTheme } from './c-card.theme';
const props = defineProps<{
title?: string;
}>();
const { title } = toRefs(props);
const theme = useTheme();
</script>
<style lang="less" scoped>
.c-card {
background-color: v-bind('theme.backgroundColor');
border: 1px solid v-bind('theme.borderColor');
border-radius: 4px;
padding: 20px 24px;
&-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 20px;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More