mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-27 01:56: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
|
@ -5,7 +5,7 @@
|
|||
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="(items, section) in toolRoutesSections"
|
||||
v-for="(items, section) in $toolList"
|
||||
:key="section"
|
||||
cols="12"
|
||||
sm="12"
|
||||
|
@ -24,10 +24,10 @@
|
|||
exact
|
||||
>
|
||||
<v-list-item-action>
|
||||
<v-icon>{{ item.config.icon }}</v-icon>
|
||||
<v-icon>{{ item.icon }}</v-icon>
|
||||
</v-list-item-action>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.config.title" />
|
||||
<v-list-item-title v-text="item.title" />
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
@ -40,11 +40,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {Component, mixins} from 'nuxt-property-decorator'
|
||||
import {ToolRoutesMixin} from '@/mixins/tool-routes.mixin'
|
||||
import {Component, Vue} from 'nuxt-property-decorator'
|
||||
|
||||
@Component
|
||||
export default class Index extends mixins(ToolRoutesMixin) {
|
||||
export default class Index extends Vue {
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,187 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component, Ref} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import {ToolConfig} from '~/types/ToolConfig'
|
||||
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 {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
@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>
|
|
@ -1,60 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import type {ToolConfig} from '~/types/ToolConfig'
|
||||
import {base64ToString, stringToBase64} from '~/utils/convert'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class Base64StringConverter extends Tool {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,190 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<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 {ToolConfig} from '~/types/ToolConfig'
|
||||
import type {VForm} from '~/types/VForm'
|
||||
|
||||
const required = (v: unknown) => !!v || 'A value is required'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class ColorPickerConverter extends Tool {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,135 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import Tool from '@/components/Tool.vue'
|
||||
import {ToolConfig} from '@/types/ToolConfig'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class DateConverter extends Tool {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,110 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<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 {ToolConfig} from '@/types/ToolConfig'
|
||||
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 {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
@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>
|
|
@ -1,106 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import Tool from '@/components/Tool.vue'
|
||||
import type {ToolConfig} from '@/types/ToolConfig'
|
||||
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 = ''
|
||||
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
title: 'Cypher / uncypher text',
|
||||
description: 'Cypher and uncyfer text.',
|
||||
icon: 'mdi-lock-open',
|
||||
keywords: ['cypher', 'uncypher', 'text', ...Object.keys(algos).map(s => s.toLowerCase())]
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,77 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<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'
|
||||
import type {ToolConfig} from '~/types/ToolConfig'
|
||||
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 {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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', ...Object.keys(algos).map(s => s.toLowerCase())]
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,92 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import type {ToolConfig} from '~/types/ToolConfig'
|
||||
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 {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,92 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component, Ref, Watch} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import type {ToolConfig} from '@/types/ToolConfig'
|
||||
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 {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
@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>
|
|
@ -1,63 +0,0 @@
|
|||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import type {ToolConfig} from '@/types/ToolConfig'
|
||||
import Memo from '~/components/Memo.vue'
|
||||
|
||||
@Component
|
||||
export default class GitMemo extends Memo {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
|
@ -1,234 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<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'
|
||||
import type {ToolConfig} from '~/types/ToolConfig'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class CrontabGenerator extends Tool {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,61 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import type {ToolConfig} from '@/types/ToolConfig'
|
||||
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 {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
title: 'Random port generator',
|
||||
description: 'Random port generator without the range of "known" ports (0-1023).',
|
||||
icon: 'mdi-lan-pending',
|
||||
keywords: ['system', 'port', 'lan']
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,105 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import type {ToolConfig} from '~/types/ToolConfig'
|
||||
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 {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,88 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<v-textarea
|
||||
v-model="text"
|
||||
outlined
|
||||
label="Input text"
|
||||
hide-details
|
||||
auto-grow
|
||||
/>
|
||||
|
||||
<table>
|
||||
<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>
|
||||
</table>
|
||||
</ToolWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '~/mixins/copyable.mixin'
|
||||
import Tool from '~/components/Tool.vue'
|
||||
import type {ToolConfig} from '~/types/ToolConfig'
|
||||
import {formatBytes} from '~/utils/convert'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin]
|
||||
})
|
||||
export default class TextStats extends Tool {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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.'
|
||||
|
||||
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>
|
|
@ -1,105 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()" 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>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Watch} from 'nuxt-property-decorator'
|
||||
import {CopyableMixin} from '@/mixins/copyable.mixin'
|
||||
import Tool from '@/components/Tool.vue'
|
||||
import type {ToolConfig} from '@/types/ToolConfig'
|
||||
import FileUploader from '~/components/FileUploader.vue'
|
||||
|
||||
@Component({
|
||||
mixins: [CopyableMixin],
|
||||
components: {FileUploader}
|
||||
})
|
||||
export default class FileToBase64 extends Tool {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,138 +0,0 @@
|
|||
<template>
|
||||
<ToolWrapper :config="config()">
|
||||
<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>
|
||||
|
||||
<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 type {ToolConfig} from '~/types/ToolConfig'
|
||||
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 {
|
||||
config(): ToolConfig {
|
||||
return {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
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