mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-30 11:29:12 -04:00
feat: externalized tool configuration
This commit is contained in:
parent
c3adfe30ec
commit
690bd099ef
31 changed files with 387 additions and 300 deletions
185
tools/converter/base-converter.vue
Normal file
185
tools/converter/base-converter.vue
Normal file
|
@ -0,0 +1,185 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-text-field
|
||||
ref="inputBaseRef"
|
||||
v-model.number="inputBase"
|
||||
label="Input base"
|
||||
outlined
|
||||
type="number"
|
||||
hide-details="auto"
|
||||
:rules="baseRules"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field
|
||||
ref="inputField"
|
||||
v-model="inputNumber"
|
||||
label="Input number"
|
||||
outlined
|
||||
hide-details="auto"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<br>
|
||||
<v-divider />
|
||||
<br>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-text-field
|
||||
ref="outputBaseRef"
|
||||
v-model.number="outputBase"
|
||||
label="Output base"
|
||||
outlined
|
||||
type="number"
|
||||
:rules="baseRules"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field
|
||||
v-model="outputNumber"
|
||||
label="Output number"
|
||||
outlined
|
||||
readonly
|
||||
append-icon="mdi-content-copy"
|
||||
@click:append="copy(outputNumber)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Base converter'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-swap-horizontal'
|
||||
keywords: ['base', 'converter']
|
||||
path: '/color-picker-converter'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component, Ref} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import type {VForm} from '~/types/VForm'
|
||||
|
||||
const convertBase = (value: string, fromBase: number, toBase: number) => {
|
||||
const range = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/'.split('')
|
||||
const fromRange = range.slice(0, fromBase)
|
||||
const toRange = range.slice(0, toBase)
|
||||
let decValue = value
|
||||
.split('')
|
||||
.reverse()
|
||||
.reduce((carry: number, digit: string, index: number) => {
|
||||
if (!fromRange.includes(digit)) {
|
||||
throw new Error('Invalid digit `' + digit + '` for base ' + fromBase + '.')
|
||||
}
|
||||
return (carry += fromRange.indexOf(digit) * (Math.pow(fromBase, index)))
|
||||
}, 0)
|
||||
let newValue = ''
|
||||
while (decValue > 0) {
|
||||
newValue = toRange[decValue % toBase] + newValue
|
||||
decValue = (decValue - (decValue % toBase)) / toBase
|
||||
}
|
||||
return newValue || '0'
|
||||
}
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class BaseConverter extends Tool {
|
||||
@Ref() readonly inputBaseRef!: VForm
|
||||
@Ref() readonly outputBaseRef!: VForm
|
||||
|
||||
isMounted = false
|
||||
inputError = ''
|
||||
inputNumber = '42'
|
||||
inputBase = 10
|
||||
outputBase = 16
|
||||
baseRules = [
|
||||
(v: unknown) => !!v || 'Required',
|
||||
(v: unknown) => Number.isInteger(v) || 'Base should be an integer',
|
||||
(v: number) => v > 1 || 'Base should be > 1',
|
||||
(v: number) => v <= 64 || 'Base should be <= 64'
|
||||
]
|
||||
|
||||
mounted() {
|
||||
this.isMounted = true
|
||||
}
|
||||
|
||||
get outputNumber() {
|
||||
if (this.isMounted && this.inputBaseRef.validate() && this.outputBaseRef.validate()) {
|
||||
try {
|
||||
return convertBase(this.inputNumber, this.inputBase, this.outputBase)
|
||||
} catch (e) {
|
||||
return e.message
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// import {copyToClipboard, isInt} from "../../utils/helpers";
|
||||
//
|
||||
// const convertBase = (value, fromBase, toBase) => {
|
||||
// const range = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/'.split('');
|
||||
// const fromRange = range.slice(0, fromBase);
|
||||
// const toRange = range.slice(0, toBase);
|
||||
// let decValue = value.split('').reverse().reduce((carry, digit, index) => {
|
||||
// if (fromRange.indexOf(digit) === -1) throw new Error('Invalid digit `' + digit + '` for base ' + fromBase + '.');
|
||||
// return carry += fromRange.indexOf(digit) * (Math.pow(fromBase, index));
|
||||
// }, 0);
|
||||
// let newValue = '';
|
||||
// while (decValue > 0) {
|
||||
// newValue = toRange[decValue % toBase] + newValue;
|
||||
// decValue = (decValue - (decValue % toBase)) / toBase;
|
||||
// }
|
||||
// return newValue || '0';
|
||||
// }
|
||||
// export default {
|
||||
// name: "BaseConverter",
|
||||
// data() {
|
||||
// return {
|
||||
// inputError: '',
|
||||
// inputNumber: '42',
|
||||
// inputBase: 10,
|
||||
// outputBase: 16,
|
||||
// baseRules: [
|
||||
// v => isInt(v) || 'Base should be an integer',
|
||||
// v => !!v || 'Required',
|
||||
// v => v > 1 || 'Base should be > 1',
|
||||
// v => v <= 64 || 'Base should be <= 64'
|
||||
// ],
|
||||
// isMounted: false
|
||||
// }
|
||||
// },
|
||||
// mounted() {
|
||||
// this.isMounted = true;
|
||||
// },
|
||||
// methods: {
|
||||
// copy() {
|
||||
// copyToClipboard(this.outputNumber);
|
||||
// this.$toast.success('Copied to clipboard.')
|
||||
// }
|
||||
// },
|
||||
// computed: {
|
||||
// outputNumber() {
|
||||
// if (this.isMounted && this.$refs.inputBase.validate() && this.$refs.outputBase.validate()) {
|
||||
// try {
|
||||
// return convertBase(this.inputNumber, this.inputBase, this.outputBase)
|
||||
// } catch (e) {
|
||||
// return e.message;
|
||||
// }
|
||||
// } else {
|
||||
// return ''
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
</style>
|
58
tools/converter/base64-string-converter.vue
Normal file
58
tools/converter/base64-string-converter.vue
Normal file
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-textarea
|
||||
v-model="clearText"
|
||||
outlined
|
||||
label="Clear text"
|
||||
/>
|
||||
|
||||
<v-textarea
|
||||
v-model="base64Text"
|
||||
outlined
|
||||
readonly
|
||||
label="Base64 text"
|
||||
/>
|
||||
<div class="text-center">
|
||||
<v-btn depressed @click="copy(clearText)">
|
||||
Copy clear
|
||||
</v-btn>
|
||||
<v-btn depressed @click="copy(base64Text)">
|
||||
Copy hash
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Base64 string converter'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-text-box-outline'
|
||||
keywords: ['base64', 'base', '64', 'converter']
|
||||
path: '/base64-string-converter'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import {base64ToString, stringToBase64} from '~/utils/convert'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class Base64StringConverter extends Tool {
|
||||
clearText = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.'
|
||||
|
||||
get base64Text() {
|
||||
return stringToBase64(this.clearText)
|
||||
}
|
||||
|
||||
set base64Text(value: string) {
|
||||
this.clearText = base64ToString(value)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
188
tools/converter/color-picker-converter.vue
Normal file
188
tools/converter/color-picker-converter.vue
Normal file
|
@ -0,0 +1,188 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-row no-gutters align="center" align-content="center" justify="center">
|
||||
<v-col cols="12" sm="6" align="center">
|
||||
<v-color-picker
|
||||
v-model="rgbPicker"
|
||||
flat
|
||||
canvas-height="300"
|
||||
hide-inputs
|
||||
mode="rgba"
|
||||
@input="(v) => updateColors(v, 'picker')"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" align="center">
|
||||
<v-text-field
|
||||
ref="hex"
|
||||
outlined
|
||||
label="hex"
|
||||
:value="hex"
|
||||
:rules="rules.hex"
|
||||
append-icon="mdi-content-copy"
|
||||
@input="(v) => updateColors(v, 'hex')"
|
||||
@click:append="copy(hex)"
|
||||
/>
|
||||
<v-text-field
|
||||
ref="rgb"
|
||||
outlined
|
||||
label="rgb"
|
||||
:value="rgb"
|
||||
:rules="rules.rgb"
|
||||
append-icon="mdi-content-copy"
|
||||
@input="(v) => updateColors(v, 'rgb')"
|
||||
@click:append="copy(rgb)"
|
||||
/>
|
||||
<v-text-field
|
||||
ref="hsl"
|
||||
outlined
|
||||
label="hsl"
|
||||
:value="hsl"
|
||||
:rules="rules.hsl"
|
||||
append-icon="mdi-content-copy"
|
||||
@input="(v) => updateColors(v, 'hsl')"
|
||||
@click:append="copy(hsl)"
|
||||
/>
|
||||
<v-combobox
|
||||
ref="keyword"
|
||||
:value="keyword"
|
||||
outlined
|
||||
label="css keyword"
|
||||
:items="colorsName"
|
||||
:rules="rules.keyword"
|
||||
no-data-text="This is not an authorized color name."
|
||||
append-icon="mdi-content-copy"
|
||||
@change="(v) => updateColors(v, 'keyword')"
|
||||
@click:append="copy(keyword)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Color picker/converter'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-palette'
|
||||
keywords: ['rgb', 'hsl', 'hex', 'keyword', 'css', 'picker']
|
||||
path: '/color-picker-converter'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import colors from 'color-name'
|
||||
import convert from 'color-convert'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import type {VForm} from '~/types/VForm'
|
||||
|
||||
const required = (v: unknown) => !!v || 'A value is required'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class ColorPickerConverter extends Tool {
|
||||
rgbPicker = {
|
||||
r: 76,
|
||||
g: 175,
|
||||
b: 80
|
||||
}
|
||||
|
||||
colorsName = Object.keys(colors).sort()
|
||||
valid = true
|
||||
rules = {
|
||||
hex: [
|
||||
required,
|
||||
(v: string) => /^#(?:[0-9a-fA-F]{6})$/.test(v) || 'Format should be like #112233'
|
||||
],
|
||||
rgb: [
|
||||
required,
|
||||
(v: string) => /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.test(v) || 'Format should be like rgb(255, 0, 0)'
|
||||
],
|
||||
hsl: [
|
||||
required,
|
||||
(v: string) => /^hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)$/.test(v) || 'Format should be like hsl(360, 100%, 50%)'
|
||||
],
|
||||
keywords: [
|
||||
required,
|
||||
(v: string) => this.colorsName.includes(v) || 'Value should be from the list'
|
||||
]
|
||||
}
|
||||
|
||||
hex = '#4CAF50'
|
||||
rgb = 'rgb(76, 175, 80)'
|
||||
hsl = 'hsl(122, 39%, 49%)'
|
||||
keyword = 'mediumseagreen'
|
||||
|
||||
setHSL(r: number, g: number, b: number) {
|
||||
const [h, s, l] = convert.rgb.hsl(r, g, b)
|
||||
this.hsl = `hsl(${Math.floor(h)}, ${Math.floor(s)}%, ${Math.floor(l)}%)`
|
||||
}
|
||||
|
||||
setRGB(r: number, g: number, b: number) {
|
||||
this.rgb = `rgb(${r}, ${g}, ${b})`
|
||||
}
|
||||
|
||||
setHEX(r: number, g: number, b: number) {
|
||||
const result = convert.rgb.hex(r, g, b)
|
||||
this.hex = `#${result}`
|
||||
}
|
||||
|
||||
setKeyword(r: number, g: number, b: number) {
|
||||
this.keyword = convert.rgb.keyword(r, g, b)
|
||||
}
|
||||
|
||||
updateColors(value: string, fromType: string) {
|
||||
if (fromType === 'picker' || (this.$refs[fromType] as VForm)?.validate()) {
|
||||
if (fromType === 'rgb') {
|
||||
const [r, g, b] = value.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)?.slice(1).map(v => parseInt(v)) ?? [0, 0, 0]
|
||||
this.rgbPicker = {r, g, b}
|
||||
this.setHEX(r, g, b)
|
||||
this.setHSL(r, g, b)
|
||||
this.setKeyword(r, g, b)
|
||||
} else if (fromType === 'hex') {
|
||||
const [r, g, b] = convert.hex.rgb(value.replace(/#/g, ''))
|
||||
this.rgbPicker = {r, g, b}
|
||||
this.setRGB(r, g, b)
|
||||
this.setHSL(r, g, b)
|
||||
this.setKeyword(r, g, b)
|
||||
} else if (fromType === 'hsl') {
|
||||
const [h, s, l] = value.match(/^hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)$/)?.slice(1).map(v => parseInt(v)) ?? [0, 0, 0]
|
||||
// @ts-ignore
|
||||
const [r, g, b] = convert.hsl.rgb(h, s, l)
|
||||
this.rgbPicker = {r, g, b}
|
||||
this.setRGB(r, g, b)
|
||||
this.setHEX(r, g, b)
|
||||
this.setKeyword(r, g, b)
|
||||
} else if (fromType === 'keyword') {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const [r, g, b] = convert.keyword.rgb(value)
|
||||
this.rgbPicker = {r, g, b}
|
||||
this.setRGB(r, g, b)
|
||||
this.setHEX(r, g, b)
|
||||
this.setHSL(r, g, b)
|
||||
} catch (ignored) {
|
||||
// ignored
|
||||
}
|
||||
} else if (fromType === 'picker') {
|
||||
const {r, g, b} = value as unknown as { r: number, g: number, b: number }
|
||||
this.setRGB(r, g, b)
|
||||
this.setHEX(r, g, b)
|
||||
this.setHSL(r, g, b)
|
||||
this.setKeyword(r, g, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.updateColors(this.hex, 'hex')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
::v-deep .v-input__icon {
|
||||
height: 18px !important;
|
||||
}
|
||||
</style>
|
133
tools/converter/date-converter.vue
Normal file
133
tools/converter/date-converter.vue
Normal file
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-row>
|
||||
<v-col md="3" sm="12" class="pt-0 pb-0">
|
||||
<div class="text-center">
|
||||
<v-switch v-model="useCurrentDate" label="Use current date" />
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col md="3" sm="12" class="pt-0 pb-0">
|
||||
<v-select
|
||||
v-model="inputFormatterTitle"
|
||||
:items="formats.filter(f => !f.title.toLowerCase().includes('locale')).map(v => v.title)"
|
||||
outlined
|
||||
label="Your date format"
|
||||
placeholder="Input format"
|
||||
:disabled="useCurrentDate"
|
||||
@input="userInputChanged()"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col md="6" sm="12" class="pt-0 pb-0">
|
||||
<v-text-field
|
||||
v-model="inputString"
|
||||
outlined
|
||||
label="Your date string"
|
||||
:error="invalidInput"
|
||||
:disabled="useCurrentDate"
|
||||
@input="userInputChanged()"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<br>
|
||||
<v-divider />
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<v-text-field
|
||||
v-for="format of formats"
|
||||
:key="format.title"
|
||||
dense
|
||||
readonly
|
||||
outlined
|
||||
:label="format.title"
|
||||
:value="format.getDate(displayedDate)"
|
||||
append-icon="mdi-content-copy"
|
||||
@click:append="copy(format.getDate(displayedDate))"
|
||||
/>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Date/Time converter'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-calendar-range'
|
||||
keywords: ['date', 'time', 'converter', 'iso']
|
||||
path: '/date-converter'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import Tool from '@/components/Tool.vue'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class DateConverter extends Tool {
|
||||
inputString = ''
|
||||
inputFormatterTitle: string | null = null
|
||||
useCurrentDate = true
|
||||
displayedDate = new Date()
|
||||
invalidInput = false
|
||||
formats = [
|
||||
{
|
||||
title: 'Locale datetime',
|
||||
getDate: (displayedDate: Date) => displayedDate.toLocaleString(),
|
||||
dateFromFormat: (dateString: string) => dateString
|
||||
},
|
||||
{
|
||||
title: 'ISO 8601',
|
||||
getDate: (displayedDate: Date) => displayedDate.toISOString(),
|
||||
dateFromFormat: (dateString: string) => new Date(dateString)
|
||||
},
|
||||
{
|
||||
title: 'UTC format',
|
||||
getDate: (displayedDate: Date) => displayedDate.toUTCString(),
|
||||
dateFromFormat: (dateString: string) => new Date(dateString)
|
||||
},
|
||||
{
|
||||
title: 'UNIX Timestamp (ms)',
|
||||
getDate: (displayedDate: Date) => displayedDate.getTime(),
|
||||
dateFromFormat: (dateString: string) => new Date(parseInt(dateString))
|
||||
},
|
||||
{
|
||||
title: 'Complete',
|
||||
getDate: (displayedDate: Date) => displayedDate.toString(),
|
||||
dateFromFormat: (dateString: string) => new Date(dateString)
|
||||
}
|
||||
]
|
||||
|
||||
refreshCurrentDate() {
|
||||
if (this.useCurrentDate) {
|
||||
this.displayedDate = new Date()
|
||||
}
|
||||
}
|
||||
|
||||
userInputChanged() {
|
||||
try {
|
||||
this.invalidInput = false
|
||||
const newDate = this.formats.find(f => f.title === this.inputFormatterTitle)?.dateFromFormat(this.inputString)
|
||||
if (newDate && !isNaN((newDate as Date).getTime())) {
|
||||
this.useCurrentDate = false
|
||||
this.displayedDate = newDate as Date
|
||||
} else if (this.inputString.length > 0) {
|
||||
this.invalidInput = true
|
||||
}
|
||||
} catch (ignored) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
created() {
|
||||
setInterval(this.refreshCurrentDate.bind(this), 1000)
|
||||
this.inputFormatterTitle = this.formats[1].title
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue