mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-26 09:46:15 -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>
|
108
tools/crypto/bip39-generator.vue
Normal file
108
tools/crypto/bip39-generator.vue
Normal file
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-select
|
||||
v-model="language"
|
||||
outlined
|
||||
label="Language"
|
||||
:items="languageList"
|
||||
@change="languageChanged"
|
||||
/>
|
||||
<v-text-field
|
||||
ref="entropy"
|
||||
v-model="entropy"
|
||||
outlined
|
||||
label="Entropy"
|
||||
append-icon="mdi-content-copy"
|
||||
:rules="rules.entropy"
|
||||
@click:append="copy(entropy)"
|
||||
/>
|
||||
<v-text-field
|
||||
ref="passphrase"
|
||||
v-model="passphrase"
|
||||
outlined
|
||||
label="Passphrase"
|
||||
append-icon="mdi-content-copy"
|
||||
:rules="rules.passphrase"
|
||||
@click:append="copy(passphrase)"
|
||||
/>
|
||||
<div class="text-center">
|
||||
<v-btn @click="refresh">
|
||||
refresh
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'BIP39 passphrase generator'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-message-text-lock-outline'
|
||||
keywords: ['BIP39', 'passphrase', 'generator']
|
||||
path: '/bip39-generator'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import * as bip39 from 'bip39'
|
||||
import {shuffle} from '@/utils/string'
|
||||
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 getRandomBuffer = () => Buffer.from(shuffle('0123456789abcdef'.repeat(16)).substring(0, 32), 'hex')
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class Bip39Generator extends Tool {
|
||||
@Ref() readonly entropyRef!: VForm
|
||||
@Ref() readonly passphraseRef!: VForm
|
||||
buffer = getRandomBuffer()
|
||||
language: string = 'english'
|
||||
languageList = Object
|
||||
.keys(bip39.wordlists)
|
||||
.filter(k => !k.match(/[A-Z]{2}/))
|
||||
.map(k => ({
|
||||
text: k.split('_').map(k => k.charAt(0).toUpperCase() + k.slice(1)).join(' '),
|
||||
value: k
|
||||
}))
|
||||
|
||||
rules = {
|
||||
passphrase: [(v: string | undefined) => (!!v && bip39.validateMnemonic(v)) || 'Invalid mnemonic.'],
|
||||
entropy: [(v: string | undefined) => (!!v && !!v.match(/[0-9a-fA-F]{32}/)) || 'Invalid entropy.']
|
||||
}
|
||||
|
||||
get entropy() {
|
||||
return this.buffer.toString('hex')
|
||||
}
|
||||
|
||||
set entropy(value) {
|
||||
if (this.entropyRef.validate()) {
|
||||
this.buffer = Buffer.from(value, 'hex')
|
||||
}
|
||||
}
|
||||
|
||||
get passphrase() {
|
||||
return bip39.entropyToMnemonic(this.buffer)
|
||||
}
|
||||
|
||||
set passphrase(value) {
|
||||
if (this.passphraseRef.validate()) {
|
||||
this.buffer = Buffer.from(bip39.mnemonicToEntropy(value), 'hex')
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.buffer = getRandomBuffer()
|
||||
}
|
||||
|
||||
languageChanged() {
|
||||
bip39.setDefaultWordlist(this.language)
|
||||
this.passphrase = bip39.entropyToMnemonic(this.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
104
tools/crypto/cypher-uncyfer-text.vue
Normal file
104
tools/crypto/cypher-uncyfer-text.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-row justify="center" align="center">
|
||||
<v-col cols="12" lg="8" md="12">
|
||||
<v-textarea
|
||||
v-model="key"
|
||||
outlined
|
||||
label="Encryption key"
|
||||
rows="1"
|
||||
@input="encrypt"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="4" md="12">
|
||||
<v-select
|
||||
v-model="algorithm"
|
||||
:items="Object.keys(algorithms)"
|
||||
label="Algorithm"
|
||||
outlined
|
||||
@change="encrypt"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-textarea
|
||||
v-model="decrypted"
|
||||
outlined
|
||||
label="Clear text"
|
||||
@input="encrypt"
|
||||
/>
|
||||
|
||||
<v-textarea
|
||||
v-model="encrypted"
|
||||
outlined
|
||||
label="Cyphered text"
|
||||
@input="decrypt"
|
||||
/>
|
||||
<div class="text-center">
|
||||
<v-btn depressed @click="copy(encrypted)">
|
||||
Copy result
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Cypher / uncypher text'
|
||||
description: 'Cypher and uncyfer text.'
|
||||
icon: 'mdi-lock-open'
|
||||
keywords: ['cypher', 'uncypher', 'text', 'AES', 'TripleDES', 'Rabbit', 'RabbitLegacy', 'RC4']
|
||||
path: '/cypher-uncyfer-text'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import Tool from '@/components/Tool.vue'
|
||||
import CryptoJS from 'crypto-js'
|
||||
|
||||
const algos = {
|
||||
AES: CryptoJS.AES,
|
||||
TripleDES: CryptoJS.TripleDES,
|
||||
Rabbit: CryptoJS.Rabbit,
|
||||
RabbitLegacy: CryptoJS.RabbitLegacy,
|
||||
RC4: CryptoJS.RC4
|
||||
}
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class CypherUncyferText extends Tool {
|
||||
algorithm: keyof typeof algos = 'AES'
|
||||
algorithms: typeof algos = algos
|
||||
key = 'sup3r s3cr3t k3y'
|
||||
decrypted = 'Lorem ipsum dolor sit amet.'
|
||||
encrypted = ''
|
||||
|
||||
mounted() {
|
||||
this.encrypt()
|
||||
}
|
||||
|
||||
encrypt() {
|
||||
try {
|
||||
this.encrypted = this.algorithms[this.algorithm]
|
||||
.encrypt(this.decrypted.trim(), this.key)
|
||||
.toString()
|
||||
} catch (ignored) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
decrypt() {
|
||||
try {
|
||||
this.decrypted = this.algorithms[this.algorithm]
|
||||
.decrypt(this.encrypted.trim(), this.key)
|
||||
.toString(CryptoJS.enc.Utf8)
|
||||
} catch (ignored) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
</style>
|
76
tools/crypto/hash-text.vue
Normal file
76
tools/crypto/hash-text.vue
Normal file
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-textarea
|
||||
v-model="inputText"
|
||||
outlined
|
||||
label="Text to hash"
|
||||
/>
|
||||
|
||||
<v-select
|
||||
v-model="algorithm"
|
||||
:items="Object.keys(algorithms)"
|
||||
label="Algorithm"
|
||||
outlined
|
||||
/>
|
||||
|
||||
<v-textarea
|
||||
v-model="hashed"
|
||||
outlined
|
||||
readonly
|
||||
label="Hashed text"
|
||||
/>
|
||||
<div class="text-center">
|
||||
<v-btn depressed @click="copy(hashed)">
|
||||
Copy hash
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Hash text'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-script-text-play'
|
||||
keywords: ['hash', 'text', 'MD5', 'SHA1', 'SHA256', 'SHA224', 'SHA512', 'SHA384', 'SHA3', 'RIPEMD160']
|
||||
path: '/hash-text'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import CryptoJS from 'crypto-js'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
|
||||
const algos = {
|
||||
MD5: CryptoJS.MD5,
|
||||
SHA1: CryptoJS.SHA1,
|
||||
SHA256: CryptoJS.SHA256,
|
||||
SHA224: CryptoJS.SHA224,
|
||||
SHA512: CryptoJS.SHA512,
|
||||
SHA384: CryptoJS.SHA384,
|
||||
SHA3: CryptoJS.SHA3,
|
||||
RIPEMD160: CryptoJS.RIPEMD160
|
||||
}
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class HashText extends Tool {
|
||||
inputText = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.'
|
||||
algorithm: keyof typeof algos = 'SHA256'
|
||||
algorithms: typeof algos = algos
|
||||
|
||||
get hashed() {
|
||||
if (this.algorithms[this.algorithm]) {
|
||||
return this.algorithms[this.algorithm](this.inputText).toString()
|
||||
} else {
|
||||
this.$toast.error('Invalid algorithm.')
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
90
tools/crypto/token-generator.vue
Normal file
90
tools/crypto/token-generator.vue
Normal file
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-row no-gutters>
|
||||
<v-col lg="6" md="12">
|
||||
<v-switch v-model="withLowercase" label="Lowercase (abc...)" />
|
||||
<v-switch v-model="withUppercase" label="Uppercase (ABC...)" />
|
||||
</v-col>
|
||||
<v-col lg="6" md="12">
|
||||
<v-switch v-model="withNumbers" label="Numbers (123...)" />
|
||||
<v-switch v-model="withSpecials" label="Specials (#]-...)" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-slider v-model="length" :label="`Length (${length})`" min="1" max="512" />
|
||||
|
||||
<v-textarea v-model="token" outlined />
|
||||
|
||||
<div class="text-center">
|
||||
<v-btn depressed class="mr-4" @click="refreshToken()">
|
||||
Refresh
|
||||
</v-btn>
|
||||
<v-btn depressed @click="copy(token)">
|
||||
Copy token
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Token generator'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-key-chain-variant'
|
||||
keywords: ['token', 'random', 'string', 'alphanumeric', 'symbols']
|
||||
path: '/token-generator'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import {shuffle} from '~/utils/string'
|
||||
|
||||
const lowercase = 'abcdefghijklmopqrstuvwxyz'
|
||||
const uppercase = 'ABCDEFGHIJKLMOPQRSTUVWXYZ'
|
||||
const numbers = '0123456789'
|
||||
const specials = '.,;:!?./-"\'#{([-|\\@)]=}*+'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class TokenGenerator extends Tool {
|
||||
withNumbers = true;
|
||||
withLowercase = true;
|
||||
withUppercase = true;
|
||||
withSpecials = false;
|
||||
length = 64;
|
||||
refreshBool = true;
|
||||
|
||||
refreshToken() {
|
||||
this.refreshBool = !this.refreshBool
|
||||
}
|
||||
|
||||
get token() {
|
||||
if (this.refreshBool) {
|
||||
(() => {
|
||||
})()
|
||||
} // To force recomputation
|
||||
|
||||
let result = ''
|
||||
if (this.withLowercase) {
|
||||
result += lowercase
|
||||
}
|
||||
if (this.withUppercase) {
|
||||
result += uppercase
|
||||
}
|
||||
if (this.withNumbers) {
|
||||
result += numbers
|
||||
}
|
||||
if (this.withSpecials) {
|
||||
result += specials
|
||||
}
|
||||
|
||||
return shuffle(result.repeat(this.length)).substring(0, this.length)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
90
tools/crypto/uuid-generator.vue
Normal file
90
tools/crypto/uuid-generator.vue
Normal file
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-text-field
|
||||
v-model.number="quantity"
|
||||
outlined
|
||||
type="number"
|
||||
label="Quantity"
|
||||
dense
|
||||
class="quantity"
|
||||
:rules="rules.quantity"
|
||||
/>
|
||||
<v-textarea
|
||||
v-model="token"
|
||||
outlined
|
||||
class="centered-input"
|
||||
:rows="quantity <= 10 ? quantity : 10"
|
||||
readonly
|
||||
/>
|
||||
|
||||
<div class="text-center">
|
||||
<v-btn depressed class="mr-4" @click="computeToken">
|
||||
Refresh
|
||||
</v-btn>
|
||||
<v-btn depressed @click="copy(token, `UUID${quantity > 1 ? 's' : ''} copied !`)">
|
||||
Copy UUID{{ quantity > 1 ? 's' : '' }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'UUIDs generator'
|
||||
description: 'A universally unique identifier (UUID) is a 128-bit number used to identify information in computer systems. '
|
||||
icon: 'mdi-fingerprint'
|
||||
keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity']
|
||||
path: '/uuid-generator'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component, Ref, Watch} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import { VTextField } from 'vuetify/lib'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
|
||||
const generateUuid = () => '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => ((c as unknown as number) ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> (c as unknown as number) / 4).toString(16))
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class UuidGenerator extends Tool {
|
||||
@Ref() readonly quantityEl! : typeof VTextField
|
||||
token = ''
|
||||
quantity = 1
|
||||
rules = {
|
||||
quantity: [
|
||||
(v: any) => !!v || 'Quantity is required',
|
||||
(v: any) => (v > 0 && v <= 50) || 'Quantity should be > 0 and <= 50',
|
||||
(v: any) => Number.isInteger(v) || 'Quantity should be an integer'
|
||||
]
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.computeToken()
|
||||
}
|
||||
|
||||
@Watch('quantity')
|
||||
computeToken() {
|
||||
this.token = Array.from({length: this.quantity}, generateUuid).join('\n')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.quantity {
|
||||
width: 100px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
|
||||
::v-deep input {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .centered-input textarea {
|
||||
text-align: center;
|
||||
margin-top: 13px !important;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
</style>
|
62
tools/memos/git-memo.vue
Normal file
62
tools/memos/git-memo.vue
Normal file
|
@ -0,0 +1,62 @@
|
|||
<tool>
|
||||
title: 'Git memo'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-git'
|
||||
keywords: ['git', 'memo', 'cheat', 'sheet']
|
||||
path: '/git-memo'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import Memo from '~/components/Memo.vue'
|
||||
|
||||
@Component
|
||||
export default class GitMemo extends Memo {
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en:
|
||||
memo:
|
||||
- section: Titre de la section 1
|
||||
items:
|
||||
- text: Tip n°1
|
||||
- text: Tip n°2 (avec sous titre
|
||||
subtitle: Un super sous titre
|
||||
- text: Tip n°3 (avec du code !)
|
||||
code: rm -rf / --no-preserve-root
|
||||
- text: Tip n°4 (avec du code et un sous titre !)
|
||||
code: rm -rf / --no-preserve-root
|
||||
subtitle: Un super sous titre, yeah
|
||||
|
||||
- section: Titre de la section 2
|
||||
items:
|
||||
- text: Tip n°1
|
||||
- text: Tip n°2
|
||||
- text: Tip n°3 (avec du code !)
|
||||
code: rm -rf / --no-preserve-root
|
||||
- text: Tip n°4
|
||||
code: "multiline\ncode sample"
|
||||
|
||||
- section: Titre de la section 3
|
||||
items:
|
||||
- text: Tip n°1
|
||||
- text: Tip n°2 (avec sous titre
|
||||
subtitle: Un super sous titre
|
||||
- text: Tip n°3 (avec du code !)
|
||||
code: rm -rf / --no-preserve-root
|
||||
- text: Tip n°4 (avec du code et un sous titre !)
|
||||
code: rm -rf / --no-preserve-root
|
||||
subtitle: Un super sous titre, yeah
|
||||
|
||||
- section: Titre de la section 4
|
||||
items:
|
||||
- text: Tip n°1
|
||||
- text: Tip n°2 (avec sous titre
|
||||
subtitle: Un super sous titre
|
||||
- text: Tip n°3 (avec du code !)
|
||||
code: rm -rf / --no-preserve-root
|
||||
- text: Tip n°4 (avec du code et un sous titre !)
|
||||
code: rm -rf / --no-preserve-root
|
||||
subtitle: Un super sous titre, yeah
|
||||
</i18n>
|
232
tools/sys-admin/crontab-generator.vue
Normal file
232
tools/sys-admin/crontab-generator.vue
Normal file
|
@ -0,0 +1,232 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<div class="result">
|
||||
{{ cronString }}
|
||||
</div>
|
||||
<v-text-field
|
||||
ref="cron"
|
||||
v-model="cron"
|
||||
class="cron-wrapper"
|
||||
outlined
|
||||
label="Cron"
|
||||
append-icon="fa-copy"
|
||||
:rules="[isCronValid]"
|
||||
hide-details="auto"
|
||||
@click:append="copy(cron)"
|
||||
/>
|
||||
|
||||
<v-checkbox
|
||||
v-model="cronstrueConfig.verbose"
|
||||
hide-details
|
||||
label="Verbose"
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="cronstrueConfig.use24HourTimeFormat"
|
||||
hide-details
|
||||
label="Use 24 hour time format"
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="cronstrueConfig.dayOfWeekStartIndexZero"
|
||||
hide-details
|
||||
label="Day of the week start a index 0"
|
||||
/>
|
||||
|
||||
<div class="text-center">
|
||||
<pre>
|
||||
┌──────────── [optional] seconds (0 - 59)
|
||||
| ┌────────── minute (0 - 59)
|
||||
| | ┌──────── hour (0 - 23)
|
||||
| | | ┌────── day of month (1 - 31)
|
||||
| | | | ┌──── month (1 - 12) OR jan,feb,mar,apr ...
|
||||
| | | | | ┌── day of week (0 - 6, sunday=0) OR sun,mon ...
|
||||
| | | | | |
|
||||
* * * * * * command</pre>
|
||||
</div>
|
||||
<br>
|
||||
<v-simple-table dense>
|
||||
<template #default>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">
|
||||
Symbol
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Meaning
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Example
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Equivalent
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>*</td>
|
||||
<td>Any value</td>
|
||||
<td>
|
||||
<pre>* * * *</pre>
|
||||
</td>
|
||||
<td>Every minute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>Range of values</td>
|
||||
<td>
|
||||
<pre>1-10 * * *</pre>
|
||||
</td>
|
||||
<td>Minutes 1 through 10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>,</td>
|
||||
<td>List of values</td>
|
||||
<td>
|
||||
<pre>1,10 * * *</pre>
|
||||
</td>
|
||||
<td>At minutes 1 and 10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>/</td>
|
||||
<td>Step values</td>
|
||||
<td>
|
||||
<pre>*/10 * * *</pre>
|
||||
</td>
|
||||
<td>Every 10 minutes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@yearly</td>
|
||||
<td>Once every year at midnight of 1 January</td>
|
||||
<td>
|
||||
<pre>@yearly</pre>
|
||||
</td>
|
||||
<td>0 0 1 1 *</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@annually</td>
|
||||
<td>Same as @yearly</td>
|
||||
<td>
|
||||
<pre>@annually</pre>
|
||||
</td>
|
||||
<td>0 0 1 1 *</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@monthly</td>
|
||||
<td>Once a month at midnight on the first day</td>
|
||||
<td>
|
||||
<pre>@monthly</pre>
|
||||
</td>
|
||||
<td>0 0 1 * *</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@weekly</td>
|
||||
<td>Once a week at midnight on Sunday morning</td>
|
||||
<td>
|
||||
<pre>@weekly</pre>
|
||||
</td>
|
||||
<td>0 0 * * 0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@daily</td>
|
||||
<td>Once a day at midnight</td>
|
||||
<td>
|
||||
<pre>@daily</pre>
|
||||
</td>
|
||||
<td>0 0 * * *</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@midnight</td>
|
||||
<td>Same as @daily</td>
|
||||
<td>
|
||||
<pre>@midnight</pre>
|
||||
</td>
|
||||
<td>0 0 * * *</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@hourly</td>
|
||||
<td>Once an hour at the beginning of the hour</td>
|
||||
<td>
|
||||
<pre>@hourly</pre>
|
||||
</td>
|
||||
<td>0 * * * *</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>@reboot</td>
|
||||
<td>Run at startup</td>
|
||||
<td />
|
||||
<td />
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
</v-simple-table>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Crontab generator'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-calendar-clock'
|
||||
keywords: ['year', 'month', 'week', 'day', 'minute', 'second']
|
||||
path: '/crontab-generator'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import cronstrue from 'cronstrue'
|
||||
import {isValidCron} from 'cron-validator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class CrontabGenerator extends Tool {
|
||||
cron = '* * * * *'
|
||||
cronstrueConfig = {
|
||||
verbose: true,
|
||||
dayOfWeekStartIndexZero: true,
|
||||
use24HourTimeFormat: true,
|
||||
throwExceptionOnParseError: true
|
||||
}
|
||||
|
||||
isCronValid(v: string) {
|
||||
return isValidCron(v, {allowBlankDay: true, alias: true, seconds: true})
|
||||
}
|
||||
|
||||
get cronString() {
|
||||
if (this.isCronValid(this.cron)) {
|
||||
return cronstrue.toString(this.cron, this.cronstrueConfig)
|
||||
} else {
|
||||
return ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
::v-deep {
|
||||
.cron-wrapper input {
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
|
||||
.v-data-table {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.result {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
pre {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
59
tools/sys-admin/random-port-generator.vue
Normal file
59
tools/sys-admin/random-port-generator.vue
Normal file
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-text-field
|
||||
v-model="port"
|
||||
outlined
|
||||
class="centered-input"
|
||||
readonly
|
||||
/>
|
||||
|
||||
<div class="text-center">
|
||||
<v-btn depressed class="mr-4" @click="refreshPort">
|
||||
Refresh
|
||||
</v-btn>
|
||||
<v-btn depressed @click="copy(port.toString())">
|
||||
Copy port
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Random port generator'
|
||||
description: 'Random port generator without the range of "known" ports (0-1023).'
|
||||
icon: 'mdi-lan-pending'
|
||||
keywords: ['system', 'port', 'lan']
|
||||
path: '/random-port-generator'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import {randIntFromInterval} from '~/utils/random'
|
||||
|
||||
const generatePort = () => randIntFromInterval(1024, 65535)
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class RandomPortGenerator extends Tool {
|
||||
port!: number
|
||||
|
||||
created() {
|
||||
this.refreshPort()
|
||||
}
|
||||
|
||||
refreshPort() {
|
||||
this.port = generatePort()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
::v-deep .centered-input input {
|
||||
text-align: center;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
</style>
|
103
tools/text/lorem-ipsum-generator.vue
Normal file
103
tools/text/lorem-ipsum-generator.vue
Normal file
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-slider v-model="paragraphs" min="1" max="20" label="Paragraphs" thumb-label />
|
||||
<v-range-slider
|
||||
v-model="sentencePerParagraph"
|
||||
min="1"
|
||||
max="50"
|
||||
label="Sentences per paragraph"
|
||||
thumb-label
|
||||
/>
|
||||
<v-range-slider
|
||||
v-model="wordPerSentence"
|
||||
min="1"
|
||||
max="50"
|
||||
label="Words per sentence"
|
||||
thumb-label
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox v-model="startWithLoremIpsum" label="Start with 'Lorem ipsum ...'" hide-details />
|
||||
<v-checkbox v-model="asHTML" label="As HTML" />
|
||||
|
||||
<v-textarea
|
||||
v-model="loremIpsum"
|
||||
outlined
|
||||
readonly
|
||||
hide-details="auto"
|
||||
rows="15"
|
||||
class="text-justify"
|
||||
/>
|
||||
<div class="text-center mt-4">
|
||||
<v-btn depressed @click="copy(loremIpsum)">
|
||||
Copy
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Lorem ipsum generator'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-comment-text'
|
||||
keywords: ['Lorem', 'ipsum', 'dolor', 'sit', 'amet']
|
||||
path: '/lorem-ipsum-generator'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import {randFromArray, randIntFromInterval} from '~/utils/random'
|
||||
|
||||
const vocabulary = ['a', 'ac', 'accumsan', 'ad', 'adipiscing', 'aenean', 'aliquam', 'aliquet', 'amet', 'ante', 'aptent', 'arcu', 'at', 'auctor', 'bibendum', 'blandit', 'class', 'commodo', 'condimentum', 'congue', 'consectetur', 'consequat', 'conubia', 'convallis', 'cras', 'cubilia', 'cum', 'curabitur', 'curae', 'dapibus', 'diam', 'dictum', 'dictumst', 'dignissim', 'dolor', 'donec', 'dui', 'duis', 'egestas', 'eget', 'eleifend', 'elementum', 'elit', 'enim', 'erat', 'eros', 'est', 'et', 'etiam', 'eu', 'euismod', 'facilisi', 'faucibus', 'felis', 'fermentum', 'feugiat', 'fringilla', 'fusce', 'gravida', 'habitant', 'habitasse', 'hac', 'hendrerit', 'himenaeos', 'iaculis', 'id', 'imperdiet', 'in', 'inceptos', 'integer', 'interdum', 'ipsum', 'justo', 'lacinia', 'lacus', 'laoreet', 'lectus', 'leo', 'ligula', 'litora', 'lobortis', 'lorem', 'luctus', 'maecenas', 'magna', 'magnis', 'malesuada', 'massa', 'mattis', 'mauris', 'metus', 'mi', 'molestie', 'mollis', 'montes', 'morbi', 'mus', 'nam', 'nascetur', 'natoque', 'nec', 'neque', 'netus', 'nisi', 'nisl', 'non', 'nostra', 'nulla', 'nullam', 'nunc', 'odio', 'orci', 'ornare', 'parturient', 'pellentesque', 'penatibus', 'per', 'pharetra', 'phasellus', 'placerat', 'platea', 'porta', 'porttitor', 'posuere', 'potenti', 'praesent', 'pretium', 'primis', 'proin', 'pulvinar', 'purus', 'quam', 'quis', 'quisque', 'rhoncus', 'ridiculus', 'risus', 'rutrum', 'sagittis', 'sapien', 'scelerisque', 'sed', 'sem', 'semper', 'senectus', 'sit', 'sociis', 'sociosqu', 'sodales', 'sollicitudin', 'suscipit', 'suspendisse', 'taciti', 'tellus', 'tempor', 'tempus', 'tincidunt', 'torquent', 'tortor', 'turpis', 'ullamcorper', 'ultrices', 'ultricies', 'urna', 'varius', 'vehicula', 'vel', 'velit', 'venenatis', 'vestibulum', 'vitae', 'vivamus', 'viverra', 'volutpat', 'vulputate']
|
||||
const firstSentence = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
|
||||
const generateSentence = (length: number) => {
|
||||
let sentence = Array.from({length}).map(() => randFromArray(vocabulary)).join(' ')
|
||||
sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1) + '.'
|
||||
return sentence
|
||||
}
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class LoremIpsumGenerator extends Tool {
|
||||
paragraphs = 1
|
||||
sentencePerParagraph = [3, 8]
|
||||
wordPerSentence = [8, 15]
|
||||
startWithLoremIpsum = true
|
||||
asHTML = false
|
||||
|
||||
get loremIpsum() {
|
||||
const lorem = Array
|
||||
.from({length: this.paragraphs})
|
||||
.map(() => {
|
||||
const length = randIntFromInterval(this.sentencePerParagraph[0], this.sentencePerParagraph[1])
|
||||
return Array.from({length}).map(() => {
|
||||
const wordCount = randIntFromInterval(this.wordPerSentence[0], this.wordPerSentence[1])
|
||||
return generateSentence(wordCount)
|
||||
})
|
||||
})
|
||||
|
||||
if (this.startWithLoremIpsum) {
|
||||
lorem[0][0] = firstSentence
|
||||
}
|
||||
let result
|
||||
if (this.asHTML) {
|
||||
result = `<p>${lorem.map(s => s.join(' ')).join('</p>\n\n<p>')}</p>`
|
||||
} else {
|
||||
result = lorem.map(s => s.join(' ')).join('\n\n')
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
::v-deep {
|
||||
.v-label {
|
||||
// To align labels
|
||||
min-width: 200px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
93
tools/text/text-stats.vue
Normal file
93
tools/text/text-stats.vue
Normal file
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-textarea
|
||||
v-model="text"
|
||||
outlined
|
||||
label="Input text"
|
||||
hide-details
|
||||
auto-grow
|
||||
/>
|
||||
|
||||
<div>{{ $toolListFlat }}</div>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Character count:</strong></td>
|
||||
<td>{{ textLength }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Word count:</strong></td>
|
||||
<td>{{ textWordCount }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Line count:</strong></td>
|
||||
<td>{{ textLineCount }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Byte size:</strong></td>
|
||||
<td>{{ textSize }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'Text stats'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: mdi-text
|
||||
keywords: [ length, character, count ]
|
||||
path: '/text-stats'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import {formatBytes} from '~/utils/convert'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class TextStats extends Tool {
|
||||
text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'
|
||||
|
||||
created() {
|
||||
}
|
||||
|
||||
get textLength() {
|
||||
return this.text.length
|
||||
}
|
||||
|
||||
get textWordCount() {
|
||||
return this.text.split(/\s+/).length
|
||||
}
|
||||
|
||||
get textLineCount() {
|
||||
return this.text.split(/\r\n|\r|\n/).length
|
||||
}
|
||||
|
||||
get textSize() {
|
||||
return formatBytes(Uint8Array.from(this.text as unknown as ArrayLike<number>).buffer.byteLength, 3)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
tr {
|
||||
td {
|
||||
width: 50%;
|
||||
padding: 5px;
|
||||
|
||||
&:first-child {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
103
tools/web/file-to-base64.vue
Normal file
103
tools/web/file-to-base64.vue
Normal file
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig" no-card="true">
|
||||
<FileUploader v-model="file" />
|
||||
|
||||
<div v-if="base64 || loading" class="mt-10">
|
||||
<v-card>
|
||||
<v-card-title>Result</v-card-title>
|
||||
<v-card-text>
|
||||
<v-textarea
|
||||
v-model="base64"
|
||||
label="File in base 64"
|
||||
outlined
|
||||
readonly
|
||||
hide-details
|
||||
:loading="loading"
|
||||
/>
|
||||
<div class="text-center mt-4">
|
||||
<v-btn depressed @click="copy(base64)">
|
||||
Copy base64
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'File to base64'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-file-link-outline'
|
||||
keywords: ['file', 'base64']
|
||||
path: '/file-to-base64'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Watch} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import Tool from '@/components/Tool.vue'
|
||||
import FileUploader from '~/components/FileUploader.vue'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin],
|
||||
components: {FileUploader}
|
||||
})
|
||||
export default class FileToBase64 extends Tool {
|
||||
file: Blob | null = null
|
||||
loading = false
|
||||
base64 = ''
|
||||
|
||||
handleBase64(base64: string) {
|
||||
this.base64 = base64
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@Watch('file')
|
||||
onFile() {
|
||||
if (this.file) {
|
||||
this.loading = true
|
||||
this.base64 = ''
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => this.handleBase64(reader.result as string)
|
||||
reader.onerror = () => this.handleBase64('[An error as occurred]')
|
||||
reader.readAsDataURL(this.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// export default {
|
||||
// name: 'FileToBase64',
|
||||
// components: {FileUploader},
|
||||
// data() {
|
||||
// return {
|
||||
// file: undefined,
|
||||
// loading: false,
|
||||
// base64: '',
|
||||
// copyBase64() {
|
||||
// copyToClipboard(this.base64)
|
||||
// this.$toast.success('Copied to clipboard.')
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// watch: {
|
||||
// file() {
|
||||
// this.loading = true
|
||||
// this.base64 = ''
|
||||
// const reader = new FileReader()
|
||||
// reader.onload = () => this.handleBase64(reader.result)
|
||||
// reader.onerror = () => this.handleBase64('[An error as occurred]')
|
||||
// reader.readAsDataURL(this.file)
|
||||
// }
|
||||
// },
|
||||
// methods: {
|
||||
// handleBase64(base64) {
|
||||
// this.base64 = base64
|
||||
// this.loading = false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
136
tools/web/qrcode-generator.vue
Normal file
136
tools/web/qrcode-generator.vue
Normal file
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<ToolWrapper :config="$toolConfig">
|
||||
<v-row justify="center" align="center">
|
||||
<v-col cols="12" lg="6" sm="12">
|
||||
<v-text-field
|
||||
v-model="value"
|
||||
outlined
|
||||
label="Data"
|
||||
:rules="rules.value"
|
||||
/>
|
||||
<v-slider v-model="size" min="100" max="1920" label="Size (preview will not change): " thumb-label />
|
||||
<v-select
|
||||
v-model="level"
|
||||
outlined
|
||||
:items="levels"
|
||||
label="Error resistance"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6" sm="12">
|
||||
<ColorInput v-model="fgColor" label="Foreground color" />
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" sm="12">
|
||||
<ColorInput v-model="bgColor" label="Background color" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" lg="6" sm="12" class="text-center mt-5 ">
|
||||
<qrcode-vue
|
||||
:value="value"
|
||||
:size="size"
|
||||
:level="level"
|
||||
:background="bgColor"
|
||||
:foreground="fgColor"
|
||||
render-as="svg"
|
||||
class-name="qrcode-wrapper"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div class="text-center mb-sm-2 mt-4">
|
||||
<v-btn class="mr-1" color="primary" @click="download('png')">
|
||||
download as png
|
||||
</v-btn>
|
||||
<v-btn class="ml-1" color="primary" @click="download('svg')">
|
||||
download as svg
|
||||
</v-btn>
|
||||
</div>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<tool>
|
||||
title: 'QR-code generator'
|
||||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.'
|
||||
icon: 'mdi-qrcode'
|
||||
keywords: ['editor']
|
||||
path: '/qrcode-generator'
|
||||
</tool>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import colors from 'color-name'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import {downloadBase64File} from '~/utils/file'
|
||||
import {stringToBase64} from '~/utils/convert'
|
||||
import ColorInput from '~/components/ColorInput.vue'
|
||||
|
||||
@Component({
|
||||
components: {QrcodeVue, ColorInput},
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class QrcodeGenerator extends Tool {
|
||||
value = 'https://it-tools.tech'
|
||||
size = 300
|
||||
level = 'M'
|
||||
bgColor = 'transparent'
|
||||
fgColor = '#ffffff'
|
||||
levels = [
|
||||
{text: 'Low', value: 'L'},
|
||||
{text: 'Medium', value: 'M'},
|
||||
{text: 'Quartile', value: 'Q'},
|
||||
{text: 'High', value: 'H'}
|
||||
]
|
||||
|
||||
rules = {
|
||||
value: [
|
||||
(v: string) => v.length > 0 || 'Value is needed'
|
||||
],
|
||||
color: [
|
||||
(v: string) => {
|
||||
v = v.trim()
|
||||
const isFFFFFF = /^#[0-9a-fA-F]{6}$/.test(v)
|
||||
const isFFF = /^#[0-9a-fA-F]{3}$/.test(v)
|
||||
const isRGB = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.test(v)
|
||||
const isHSL = /^hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)$/.test(v)
|
||||
const isKeyword = v in colors
|
||||
const isTransparent = v === 'transparent'
|
||||
return isFFFFFF || isFFF || isKeyword || isTransparent || isRGB || isHSL || 'Incorrect color.'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
download(type: string) {
|
||||
const svgEl = this.$el.querySelector('.qrcode-wrapper svg')!
|
||||
const svgString = new XMLSerializer().serializeToString(svgEl)
|
||||
const svgUrl = `data:image/svg+xml;base64,${stringToBase64(svgString)}`
|
||||
if (type === 'png') {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = this.size
|
||||
canvas.height = this.size
|
||||
const ctx = canvas.getContext('2d')!
|
||||
const image = new Image()
|
||||
image.onload = function () {
|
||||
ctx.drawImage(image, 0, 0)
|
||||
const result = canvas.toDataURL()
|
||||
downloadBase64File(result, 'qr-code')
|
||||
}
|
||||
image.src = svgUrl
|
||||
} else {
|
||||
downloadBase64File(svgUrl, 'qr-code')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
::v-deep .qrcode-wrapper {
|
||||
& > * {
|
||||
width: 300px !important;
|
||||
height: 300px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue