添加 4 个小工具
This commit is contained in:
@@ -9,15 +9,17 @@ let navTools = {
|
|||||||
// 工具
|
// 工具
|
||||||
'download-time': {
|
'download-time': {
|
||||||
// 工具标题
|
// 工具标题
|
||||||
title: '计算下载用时',
|
title: '下载用时计算',
|
||||||
// 工具简介
|
// 工具简介
|
||||||
desc: '根据设定的文件大小和下载速度简单计算大约下载完成所需的时间。',
|
desc: '根据设定的文件大小和下载速度简单计算大约下载完成所需的时间。',
|
||||||
// 组件名称
|
// 组件名称
|
||||||
component: 'CalcDownloadTime',
|
component: 'CalcDownloadTime',
|
||||||
// 更新时间
|
// 更新时间
|
||||||
update: '',
|
update: '2021-12-06',
|
||||||
|
// 版本
|
||||||
|
version: '1',
|
||||||
// 启用状态
|
// 启用状态
|
||||||
enabled: false
|
enabled: true
|
||||||
},
|
},
|
||||||
'ratio': {
|
'ratio': {
|
||||||
title: '比例计算',
|
title: '比例计算',
|
||||||
@@ -68,9 +70,11 @@ let navTools = {
|
|||||||
},
|
},
|
||||||
'text-structure': {
|
'text-structure': {
|
||||||
title: '文本结构转换',
|
title: '文本结构转换',
|
||||||
desc: '横排、竖排、倒序等',
|
desc: '倒序、横竖互换等',
|
||||||
component: 'ConvertTextStructure',
|
component: 'ConvertTextStructure',
|
||||||
enabled: false
|
update: '2021-12-06',
|
||||||
|
version: '1',
|
||||||
|
enabled: true,
|
||||||
},
|
},
|
||||||
'timestamp': {
|
'timestamp': {
|
||||||
title: 'Unix 时间戳转换',
|
title: 'Unix 时间戳转换',
|
||||||
@@ -86,21 +90,25 @@ let navTools = {
|
|||||||
title: 'Minecraft',
|
title: 'Minecraft',
|
||||||
list: {
|
list: {
|
||||||
'chunk-location-calc': {
|
'chunk-location-calc': {
|
||||||
title: '区块位置计算',
|
title: 'Minecraft 区块位置计算',
|
||||||
component: 'MinecraftChunkLocationCalc',
|
component: 'MinecraftChunkLocationCalc',
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
'dynmap-renderdata-gen': {
|
'dynmap-renderdata-gen': {
|
||||||
title: 'Dynmap renderdata 生成',
|
title: 'Dynmap renderdata 生成',
|
||||||
desc: '生成用于 Minecraft Dynmap 插件 / 模组的 renderdata 数据。',
|
desc: '生成用于 Minecraft Dynmap 插件或模组的 renderdata 数据。',
|
||||||
component: 'MinecraftDynmapRenderdataGen',
|
component: 'MinecraftDynmapRenderdataGen',
|
||||||
enabled: false
|
update: '2021-12-06',
|
||||||
|
version: '1',
|
||||||
|
enabled: true,
|
||||||
},
|
},
|
||||||
'uuid-converter': {
|
'uuid-converter': {
|
||||||
title: 'UUID 转换',
|
title: 'Minecraft UUID 转换',
|
||||||
desc: 'UUID 与 UUID Least、UUID Most 相互转换。',
|
desc: '随机生成或转换 Minecraft 的 UUID。',
|
||||||
component: 'MinecraftUUIDConverter',
|
component: 'MinecraftUUIDConverter',
|
||||||
enabled: false
|
update: '2021-12-06',
|
||||||
|
version: '1',
|
||||||
|
enabled: true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
231
src/components/tools/CalcDownloadTime.vue
Normal file
231
src/components/tools/CalcDownloadTime.vue
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tool-page">
|
||||||
|
|
||||||
|
<div class="inputs">
|
||||||
|
<div class="title">参数</div>
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<div class="content-item">
|
||||||
|
<div class="item-label">文件大小:</div>
|
||||||
|
<el-input-number
|
||||||
|
v-model="inputs.sizeValue"
|
||||||
|
controls-position="right"
|
||||||
|
size="medium"
|
||||||
|
:precision="2"
|
||||||
|
:min="0"
|
||||||
|
:max="10000"
|
||||||
|
:step="1"
|
||||||
|
></el-input-number>
|
||||||
|
<el-select v-model="inputs.sizeUnit" size="medium">
|
||||||
|
<el-option
|
||||||
|
v-for="item in units"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.name"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-item">
|
||||||
|
<div class="item-label">已下载大小:</div>
|
||||||
|
<el-input-number
|
||||||
|
v-model="inputs.downloadedValue"
|
||||||
|
controls-position="right"
|
||||||
|
size="medium"
|
||||||
|
:precision="2"
|
||||||
|
:min="0"
|
||||||
|
:max="10000"
|
||||||
|
:step="1"
|
||||||
|
></el-input-number>
|
||||||
|
<el-select v-model="inputs.downloadedUnit" size="medium">
|
||||||
|
<el-option
|
||||||
|
v-for="item in units"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.name"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-item">
|
||||||
|
<div class="item-label">下载速度:</div>
|
||||||
|
<el-input-number
|
||||||
|
v-model="inputs.speedValue"
|
||||||
|
controls-position="right"
|
||||||
|
size="medium"
|
||||||
|
:precision="2"
|
||||||
|
:min="0"
|
||||||
|
:max="10000"
|
||||||
|
:step="1"
|
||||||
|
></el-input-number>
|
||||||
|
<el-select v-model="inputs.speedUnit" size="medium">
|
||||||
|
<el-option
|
||||||
|
v-for="item in units"
|
||||||
|
:key="item.name"
|
||||||
|
:label="`${item.name}/s`"
|
||||||
|
:value="item.name"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="outputs">
|
||||||
|
<div class="title">计算结果</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
<span class="content-label">大约需要时长:</span>
|
||||||
|
<span class="content-text">{{ outputs.duration || '未计算' }}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="content-label">大约结束时间:</span>
|
||||||
|
<span class="content-text">{{ outputs.time || '未计算' }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as dayjs from 'dayjs';
|
||||||
|
import duration from 'dayjs/plugin/duration';
|
||||||
|
|
||||||
|
dayjs.extend(duration);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CalcDownloadTime',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 单位和比率(基于 KB)
|
||||||
|
units: [
|
||||||
|
{ name: 'KiB', rate: 1 },
|
||||||
|
{ name: 'MiB', rate: 1024 },
|
||||||
|
{ name: 'GiB', rate: 1048576 },
|
||||||
|
],
|
||||||
|
// 参数
|
||||||
|
inputs: {
|
||||||
|
downloadedUnit: 'KiB',
|
||||||
|
downloadedValue: 0,
|
||||||
|
sizeUnit: 'KiB',
|
||||||
|
sizeValue: 0,
|
||||||
|
speedUnit: 'KiB',
|
||||||
|
speedValue: 0,
|
||||||
|
},
|
||||||
|
// 计算结果
|
||||||
|
outputs: {
|
||||||
|
duration: '',
|
||||||
|
time: '',
|
||||||
|
},
|
||||||
|
// 防卡顿
|
||||||
|
timer: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
// 自动计算
|
||||||
|
'inputs': {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.calc();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
/** 计算 */
|
||||||
|
calc() {
|
||||||
|
|
||||||
|
const { units } = this;
|
||||||
|
const {
|
||||||
|
downloadedUnit, downloadedValue,
|
||||||
|
sizeUnit, sizeValue,
|
||||||
|
speedUnit, speedValue,
|
||||||
|
} = this.inputs;
|
||||||
|
|
||||||
|
if (sizeValue === 0 || speedValue === 0) {
|
||||||
|
this.outputs.duration = '';
|
||||||
|
this.outputs.time = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取转换比例
|
||||||
|
const downloadedRate = units[units.findIndex((obj) => {
|
||||||
|
return (obj.name === downloadedUnit);
|
||||||
|
})].rate;
|
||||||
|
const sizeRate = units[units.findIndex((obj) => {
|
||||||
|
return (obj.name === sizeUnit);
|
||||||
|
})].rate;
|
||||||
|
const speedRate = units[units.findIndex((obj) => {
|
||||||
|
return (obj.name === speedUnit);
|
||||||
|
})].rate;
|
||||||
|
|
||||||
|
// 转为 KB 单位
|
||||||
|
const realDownloaded = downloadedValue * downloadedRate;
|
||||||
|
const realSize = sizeValue * sizeRate - realDownloaded;
|
||||||
|
const realSpeed = speedValue * speedRate;
|
||||||
|
|
||||||
|
if (realSize < 0) {
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '参数有误,请检查。',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时长(秒)
|
||||||
|
const dSeconds = (realSize / realSpeed).toFixed(0);
|
||||||
|
// 起始时间
|
||||||
|
const timeStart = dayjs();
|
||||||
|
// 结束时间
|
||||||
|
const timeEnd = timeStart.add(dSeconds, 'second');
|
||||||
|
// 时长(天,整数)
|
||||||
|
const dDays = timeEnd.diff(timeStart, 'day');
|
||||||
|
// 最后一天的起始时间
|
||||||
|
const timeLastDay = timeStart.add(dDays, 'day');
|
||||||
|
// 时长(格式化,最后一天剩余)
|
||||||
|
const dLastDay = dayjs.duration(timeEnd.diff(timeLastDay)).format('HH 时 mm 分 ss 秒');
|
||||||
|
|
||||||
|
this.outputs.duration = `${dDays} 天 ${dLastDay}`;
|
||||||
|
this.outputs.time = timeEnd.format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.inputs {
|
||||||
|
.content-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 6.5em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input-number {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .el-input__inner {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
196
src/components/tools/ConvertTextStructure.vue
Normal file
196
src/components/tools/ConvertTextStructure.vue
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tool-page">
|
||||||
|
|
||||||
|
<div class="inputs">
|
||||||
|
<div class="title">文本</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-input
|
||||||
|
v-model="inputs"
|
||||||
|
type="textarea"
|
||||||
|
:rows="8"
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="title">操作</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-select v-model="mode" size="medium">
|
||||||
|
<el-option
|
||||||
|
v-for="item in modes"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.name"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="medium"
|
||||||
|
plain
|
||||||
|
@click="convert()"
|
||||||
|
>转换</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="medium"
|
||||||
|
plain
|
||||||
|
@click="clear()"
|
||||||
|
>清空</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="outputs">
|
||||||
|
<div class="title">转换结果</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-input
|
||||||
|
v-model="outputs"
|
||||||
|
type="textarea"
|
||||||
|
:rows="8"
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ConvertTextStructure',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mode: 'reverse-h',
|
||||||
|
modes: [
|
||||||
|
{ name: 'reverse-h', label: '倒序(左右)' },
|
||||||
|
{ name: 'reverse-v', label: '倒序(上下)' },
|
||||||
|
{ name: 'horizon-vertical-normal', label: '横竖互换(半角,1 空格)' },
|
||||||
|
{ name: 'horizon-vertical-full', label: '横竖互换(全角,2 空格)' },
|
||||||
|
],
|
||||||
|
inputs: '',
|
||||||
|
outputs: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
/** 清除 */
|
||||||
|
clear() {
|
||||||
|
this.inputs = '';
|
||||||
|
this.outputs = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 转换 */
|
||||||
|
convert() {
|
||||||
|
|
||||||
|
switch (this.mode) {
|
||||||
|
case 'reverse-h':
|
||||||
|
this.cReverseH();
|
||||||
|
break;
|
||||||
|
case 'reverse-v':
|
||||||
|
this.cReverseV();
|
||||||
|
break;
|
||||||
|
case 'horizon-vertical-normal':
|
||||||
|
this.cHorizonVertical(false);
|
||||||
|
break;
|
||||||
|
case 'horizon-vertical-full':
|
||||||
|
this.cHorizonVertical(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 倒序(横向) */
|
||||||
|
cReverseH() {
|
||||||
|
/** @type {string[]} */
|
||||||
|
const inputs = this.inputs.split('\n');
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const outputs = [];
|
||||||
|
|
||||||
|
inputs.forEach((line) => {
|
||||||
|
outputs.push(line.split('').reverse().join(''));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.outputs = outputs.join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 倒序(竖向) */
|
||||||
|
cReverseV() {
|
||||||
|
/** @type {string[]} */
|
||||||
|
const inputs = this.inputs.split('\n');
|
||||||
|
|
||||||
|
this.outputs = inputs.reverse().join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 横竖互换
|
||||||
|
*
|
||||||
|
* @param {boolean} isFullAngle 是否为全角
|
||||||
|
*/
|
||||||
|
cHorizonVertical(isFullAngle) {
|
||||||
|
|
||||||
|
// 空格符号:2 / 1
|
||||||
|
const spaceChar = (isFullAngle ? ' ' : ' ');
|
||||||
|
// 空格 Unicode:  /
|
||||||
|
const spaceUnicode = (isFullAngle ? '\u2003' : '\u2002');
|
||||||
|
|
||||||
|
// 空格正则
|
||||||
|
const spaceRegC = new RegExp(spaceChar, 'g');
|
||||||
|
const spaceRegU = new RegExp(spaceUnicode, 'g');
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const inputs = this.inputs.split('\n');
|
||||||
|
|
||||||
|
/** @type {number[]} */
|
||||||
|
const inputLens = [];
|
||||||
|
|
||||||
|
/** @type {string[][]} */
|
||||||
|
const outputs = [];
|
||||||
|
|
||||||
|
// 转换空格 & 记录输入内容行长度
|
||||||
|
inputs.forEach((row, index) => {
|
||||||
|
row = row.replace(spaceRegC, spaceUnicode);
|
||||||
|
inputLens.push(row.length);
|
||||||
|
inputs[index] = row;
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputLenMax = Math.max(...inputLens);
|
||||||
|
|
||||||
|
// 创建空行
|
||||||
|
for (let i = 0; i < inputLenMax; i++) {
|
||||||
|
outputs.push([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 行列互换
|
||||||
|
inputs.forEach((row, rIndex) => {
|
||||||
|
for (let i = 0; i < inputLenMax; i++) {
|
||||||
|
const strChar = (row.charAt(i) || spaceUnicode);
|
||||||
|
outputs[i][rIndex] = strChar;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 合并行内容 & 转换空格
|
||||||
|
outputs.forEach((row, index) => {
|
||||||
|
outputs[index] = row.join('').replace(spaceRegU, spaceChar);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.outputs = outputs.join('\n');
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.inputs, .outputs {
|
||||||
|
/deep/ .el-textarea__inner {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
.el-select {
|
||||||
|
margin-right: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
692
src/components/tools/MinecraftDynmapRenderdataGen.vue
Normal file
692
src/components/tools/MinecraftDynmapRenderdataGen.vue
Normal file
@@ -0,0 +1,692 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tool-page">
|
||||||
|
|
||||||
|
<div class="notice">
|
||||||
|
<div class="title">说明</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>目前仅支持普通方块。</p>
|
||||||
|
<p>已测试游戏版本:Minecraft 1.12.2</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inputs">
|
||||||
|
<div class="title">参数</div>
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<span class="row-label">Mod ID</span>
|
||||||
|
<el-input
|
||||||
|
v-model="inputs.modID"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
clearable
|
||||||
|
required
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<span class="row-label">注释</span>
|
||||||
|
<el-input
|
||||||
|
v-model="inputs.comment"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
clearable
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<span class="row-label">方块 ID</span>
|
||||||
|
<el-input
|
||||||
|
v-model="inputs.blockID"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
placeholder="例:stone(不带命名空间)"
|
||||||
|
clearable
|
||||||
|
required
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<span class="row-label">子方块</span>
|
||||||
|
<el-input
|
||||||
|
v-model="inputs.blockSubset"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
placeholder="范围:0 ~ 15,默认:0,可以使用 *"
|
||||||
|
:minlength="0"
|
||||||
|
:maxlength="1"
|
||||||
|
clearable
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<span class="row-label">透明方块</span>
|
||||||
|
<el-checkbox v-model="inputs.isTransparent"></el-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<span class="row-label">纹理 ID</span>
|
||||||
|
<el-input
|
||||||
|
v-model="inputs.textureID"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
placeholder="默认:与方块 ID 相同"
|
||||||
|
clearable
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<span class="row-label">纹理文件路径</span>
|
||||||
|
<el-input
|
||||||
|
v-model="inputs.textureFilePath"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
placeholder="例:assets/minecraft/textures/blocks/stone.png"
|
||||||
|
clearable
|
||||||
|
required
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="title">操作</div>
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="addBlock()"
|
||||||
|
>添加方块</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="historyCtrl('undo')"
|
||||||
|
>撤销操作</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="historyCtrl('redo')"
|
||||||
|
>还原操作</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="clearResult()"
|
||||||
|
>清除结果</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="mergeResult()"
|
||||||
|
>合并结果</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="clearMerged()"
|
||||||
|
>清除合并</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="saveResult()"
|
||||||
|
>记录结果</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="loadSaves()"
|
||||||
|
>载入记录</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="clearSaves()"
|
||||||
|
>清除记录</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="outputs">
|
||||||
|
<div class="title">生成结果</div>
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="row-title">
|
||||||
|
<span>{{ inputs.modID || '*' }}-models.txt, </span>
|
||||||
|
<span>{{ inputs.modID || '*' }}-texture.txt</span>
|
||||||
|
<span> - modname</span>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="outputs.modID"
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
readonly
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="row-title">
|
||||||
|
<span>{{ inputs.modID || '*' }}-models.txt - patch</span>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="outputs.modelsPatch"
|
||||||
|
type="textarea"
|
||||||
|
:rows="10"
|
||||||
|
readonly
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="row-title">
|
||||||
|
<span>{{ inputs.modID || '*' }}-models.txt - patchblock</span>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="outputs.modelsPatchBlock"
|
||||||
|
class="part"
|
||||||
|
type="textarea"
|
||||||
|
:rows="10"
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="row-title">
|
||||||
|
<span>{{ inputs.modID || '*' }}-texture.txt - texture</span>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="outputs.textureTexture"
|
||||||
|
class="part"
|
||||||
|
type="textarea"
|
||||||
|
:rows="10"
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="row-title">
|
||||||
|
<span>{{ inputs.modID || '*' }}-texture.txt - block</span>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="outputs.textureBlock"
|
||||||
|
class="part"
|
||||||
|
type="textarea"
|
||||||
|
:rows="10"
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="row-title">
|
||||||
|
<span>合并:{{ inputs.modID || '*' }}-models.txt</span>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="outputs.mergedModels"
|
||||||
|
class="merged"
|
||||||
|
type="textarea"
|
||||||
|
:rows="10"
|
||||||
|
readonly=""
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="row-title">
|
||||||
|
<span>合并:{{ inputs.modID || '*' }}-texture.txt</span>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="outputs.mergedTexture"
|
||||||
|
class="merged"
|
||||||
|
type="textarea"
|
||||||
|
:rows="10"
|
||||||
|
readonly=""
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'MinecraftDynmapRenderdataGen',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 数据
|
||||||
|
datas: {
|
||||||
|
modelsPatch1: '',
|
||||||
|
modelsPatch2: '',
|
||||||
|
},
|
||||||
|
// 输入
|
||||||
|
inputs: {
|
||||||
|
blockID: '',
|
||||||
|
blockSubset: '',
|
||||||
|
comment: '',
|
||||||
|
isTransparent: false,
|
||||||
|
modID: '',
|
||||||
|
textureFilePath: '',
|
||||||
|
textureID: '',
|
||||||
|
},
|
||||||
|
// 输出
|
||||||
|
outputs: {
|
||||||
|
modID: '',
|
||||||
|
modelsPatch: '',
|
||||||
|
modelsPatchBlock: '',
|
||||||
|
textureTexture: '',
|
||||||
|
textureBlock: '',
|
||||||
|
mergedModels: '',
|
||||||
|
mergedTexture: '',
|
||||||
|
},
|
||||||
|
// 结果历史记录
|
||||||
|
historyIndex: 0,
|
||||||
|
historyMax: 20,
|
||||||
|
historyList: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initDatas();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化数据
|
||||||
|
*/
|
||||||
|
initDatas() {
|
||||||
|
const datas = this.datas;
|
||||||
|
const outputs = this.outputs;
|
||||||
|
|
||||||
|
// 正方体 6 个面的 patch
|
||||||
|
datas.modelsPatch1 = 'patch:id=patch0,Ox=0.0,Oy=0.0,Oz=0.0,Ux=1.0,Uy=0.0,Uz=0.0,Vx=0.0,Vy=0.0,Vz=1.0,Umin=0.0,Umax=1.0,Vmin=0.0,Vmax=1.0,VmaxAtUMax=1.0,VminAtUMax=0.0,visibility=top\npatch:id=patch1,Ox=0.0,Oy=1.0,Oz=1.0,Ux=1.0,Uy=1.0,Uz=1.0,Vx=0.0,Vy=1.0,Vz=0.0,Umin=0.0,Umax=1.0,Vmin=0.0,Vmax=1.0,VmaxAtUMax=1.0,VminAtUMax=0.0,visibility=top\npatch:id=patch2,Ox=1.0,Oy=0.0,Oz=0.0,Ux=0.0,Uy=0.0,Uz=0.0,Vx=1.0,Vy=1.0,Vz=0.0,Umin=0.0,Umax=1.0,Vmin=0.0,Vmax=1.0,VmaxAtUMax=1.0,VminAtUMax=0.0,visibility=top\npatch:id=patch3,Ox=0.0,Oy=0.0,Oz=1.0,Ux=1.0,Uy=0.0,Uz=1.0,Vx=0.0,Vy=1.0,Vz=1.0,Umin=0.0,Umax=1.0,Vmin=0.0,Vmax=1.0,VmaxAtUMax=1.0,VminAtUMax=0.0,visibility=top\npatch:id=patch4,Ox=0.0,Oy=0.0,Oz=0.0,Ux=0.0,Uy=0.0,Uz=1.0,Vx=0.0,Vy=1.0,Vz=0.0,Umin=0.0,Umax=1.0,Vmin=0.0,Vmax=1.0,VmaxAtUMax=1.0,VminAtUMax=0.0,visibility=top\npatch:id=patch5,Ox=1.0,Oy=0.0,Oz=1.0,Ux=1.0,Uy=0.0,Uz=0.0,Vx=1.0,Vy=1.0,Vz=1.0,Umin=0.0,Umax=1.0,Vmin=0.0,Vmax=1.0,VmaxAtUMax=1.0,VminAtUMax=0.0,visibility=top';
|
||||||
|
datas.modelsPatch2 = ',patch0=patch0,patch1=patch1,patch2=patch2,patch3=patch3,patch4=patch4,patch5=patch5';
|
||||||
|
|
||||||
|
outputs.modelsPatch = datas.modelsPatch1;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认对话框
|
||||||
|
*
|
||||||
|
* @param {string} [msg] 提示内容
|
||||||
|
* @param {Function} [fnConfirm] 确认后进行的操作1
|
||||||
|
* @param {Function} [fnCancel] 取消后进行的操作
|
||||||
|
*/
|
||||||
|
confirmDialog(msg = '', fnConfirm = null, fnCancel = null) {
|
||||||
|
msg = (msg || '确定要进行此操作?');
|
||||||
|
|
||||||
|
this.$confirm(msg, '确认', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).then(() => {
|
||||||
|
fnConfirm && fnConfirm();
|
||||||
|
}).catch(() => {
|
||||||
|
fnCancel && fnCancel();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史记录操作
|
||||||
|
*
|
||||||
|
* @param {string} type 类型(record、redo、undo)
|
||||||
|
*/
|
||||||
|
historyCtrl(type) {
|
||||||
|
|
||||||
|
const {
|
||||||
|
outputs,
|
||||||
|
historyIndex: hIndex,
|
||||||
|
historyMax: hMax,
|
||||||
|
historyList: hList,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
const sum = hList.length;
|
||||||
|
|
||||||
|
if (type === 'record') {
|
||||||
|
|
||||||
|
// 下标不在末尾,先删除后方内容
|
||||||
|
if (hIndex < sum - 1) {
|
||||||
|
hList.splice(hIndex + 1, sum - (hIndex + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数量超出限制,先删除
|
||||||
|
if (sum >= hMax) {
|
||||||
|
hList.splice(0, (sum - hMax + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加记录
|
||||||
|
hList.push(JSON.parse(JSON.stringify(outputs)));
|
||||||
|
|
||||||
|
// 更新下标
|
||||||
|
this.historyIndex = hList.length - 1;
|
||||||
|
|
||||||
|
} else if (type === 'redo') {
|
||||||
|
|
||||||
|
const hItem = hList[hIndex + 1];
|
||||||
|
|
||||||
|
if (hItem) {
|
||||||
|
this.outputs = JSON.parse(JSON.stringify(hItem));
|
||||||
|
this.historyIndex += 1;
|
||||||
|
} else {
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '没有可以还原的内容',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (type === 'undo') {
|
||||||
|
|
||||||
|
const hItem = hList[hIndex - 1];
|
||||||
|
|
||||||
|
if (hItem) {
|
||||||
|
this.outputs = JSON.parse(JSON.stringify(hItem));
|
||||||
|
this.historyIndex -= 1;
|
||||||
|
} else {
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '没有可以撤销的内容',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步 Mod ID
|
||||||
|
*/
|
||||||
|
syncModID() {
|
||||||
|
const { inputs, outputs } = this;
|
||||||
|
const modID = inputs.modID.toLowerCase();
|
||||||
|
|
||||||
|
inputs.modID = modID;
|
||||||
|
outputs.modID = (modID ? `modname:${modID}` : '');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加
|
||||||
|
*/
|
||||||
|
addBlock() {
|
||||||
|
|
||||||
|
/** @param {string} content 原内容,用于判断换行 */
|
||||||
|
const getComment = (content) => {
|
||||||
|
const { comment } = this.inputs;
|
||||||
|
|
||||||
|
// 前缀(\n)
|
||||||
|
const prefix = (content ? `\n` : '');
|
||||||
|
|
||||||
|
if (comment) {
|
||||||
|
return `${prefix}# ${comment}\n`;
|
||||||
|
} else {
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBlockSubset = () => {
|
||||||
|
const subset = this.inputs.blockSubset;
|
||||||
|
return (subset || '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTextureID = () => {
|
||||||
|
const { inputs } = this;
|
||||||
|
const id = (inputs.textureID || inputs.modID);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { datas, inputs, outputs } = this;
|
||||||
|
|
||||||
|
const requiredKeys = [ 'blockID', 'modID', 'textureFilePath'];
|
||||||
|
|
||||||
|
for (let i = 0; i < requiredKeys.length; i++) {
|
||||||
|
if (!inputs[requiredKeys[i]]) {
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '内容未填写完整',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.syncModID();
|
||||||
|
|
||||||
|
let str = null;
|
||||||
|
|
||||||
|
// 处理:models.txt - patchblock
|
||||||
|
str = outputs.modelsPatchBlock;
|
||||||
|
str += getComment(str);
|
||||||
|
str += 'patchblock:id=%';
|
||||||
|
str += inputs.blockID;
|
||||||
|
str += ',data=';
|
||||||
|
str += getBlockSubset(str);
|
||||||
|
str += datas.modelsPatch2;
|
||||||
|
outputs.modelsPatchBlock = str;
|
||||||
|
|
||||||
|
// 处理:texture.txt - texture
|
||||||
|
str = outputs.textureTexture;
|
||||||
|
str += getComment(str);
|
||||||
|
str += 'texture:id=';
|
||||||
|
str += getTextureID(str);
|
||||||
|
str += ',filename=';
|
||||||
|
str += inputs.textureFilePath;
|
||||||
|
str += ',xcount=1,ycount=1';
|
||||||
|
outputs.textureTexture = str;
|
||||||
|
|
||||||
|
// 处理:texture.txt - block
|
||||||
|
str = outputs.textureBlock;
|
||||||
|
str += getComment(str);
|
||||||
|
str += 'block:id=%';
|
||||||
|
str += inputs.blockID;
|
||||||
|
str += ',data=';
|
||||||
|
str += getBlockSubset(str);
|
||||||
|
str += ',allfaces=0:';
|
||||||
|
str += getTextureID(str);
|
||||||
|
str += ',stdrot=true';
|
||||||
|
if (inputs.isTransparent) {
|
||||||
|
str += ',transparency=TRANSPARENT';
|
||||||
|
}
|
||||||
|
outputs.textureBlock = str;
|
||||||
|
|
||||||
|
// 记录
|
||||||
|
this.historyCtrl('record');
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除结果
|
||||||
|
*/
|
||||||
|
clearResult() {
|
||||||
|
const { outputs } = this;
|
||||||
|
const msg = '是否清除生成结果?';
|
||||||
|
|
||||||
|
this.confirmDialog(msg, () => {
|
||||||
|
|
||||||
|
for (let key in outputs) {
|
||||||
|
outputs[key] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initDatas();
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '已清除',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
this.historyCtrl('record');
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并结果
|
||||||
|
*/
|
||||||
|
mergeResult() {
|
||||||
|
const { outputs } = this;
|
||||||
|
|
||||||
|
const modelsKeys = ['modID', 'modelsPatch', 'modelsPatchBlock'];
|
||||||
|
const textureKeys = ['modID', 'textureTexture', 'textureBlock'];
|
||||||
|
|
||||||
|
this.clearMerged();
|
||||||
|
this.syncModID();
|
||||||
|
|
||||||
|
if (!outputs.modID) {
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: 'Mod ID 为空',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modelsKeys.forEach((key, index) => {
|
||||||
|
const prefix = (index === 0 ? '' : '\n\n');
|
||||||
|
outputs.mergedModels += (prefix + outputs[key]);
|
||||||
|
});
|
||||||
|
textureKeys.forEach((key, index) => {
|
||||||
|
const prefix = (index === 0 ? '' : '\n\n');
|
||||||
|
outputs.mergedTexture += (prefix + outputs[key]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
clearMerged() {
|
||||||
|
const { outputs } = this;
|
||||||
|
|
||||||
|
outputs.mergedModels = '';
|
||||||
|
outputs.mergedTexture = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录结果
|
||||||
|
*/
|
||||||
|
saveResult() {
|
||||||
|
const { outputs } = this;
|
||||||
|
const msg = '是否记录生成结果到 localStorage?';
|
||||||
|
|
||||||
|
this.confirmDialog(msg, () => {
|
||||||
|
|
||||||
|
for (let key in outputs) {
|
||||||
|
localStorage.setItem(('tool_gdr_' + key), outputs[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '已记录',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载结果
|
||||||
|
*/
|
||||||
|
loadSaves() {
|
||||||
|
const { outputs } = this;
|
||||||
|
const records = {};
|
||||||
|
const msg = '是否加载记录?';
|
||||||
|
|
||||||
|
this.confirmDialog(msg, () => {
|
||||||
|
|
||||||
|
for (let key in outputs) {
|
||||||
|
const record = localStorage.getItem('tool_gdr_' + key);
|
||||||
|
record && (records[key] = record);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(records).length > 0) {
|
||||||
|
this.outputs = records;
|
||||||
|
this.historyCtrl('record');
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '已加载',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '没有记录',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除记录的结果
|
||||||
|
*/
|
||||||
|
clearSaves() {
|
||||||
|
const { outputs } = this;
|
||||||
|
const msg = '是否清除记录?';
|
||||||
|
|
||||||
|
this.confirmDialog(msg, () => {
|
||||||
|
|
||||||
|
for (let key in outputs) {
|
||||||
|
localStorage.removeItem('tool_gdr_' + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$message({
|
||||||
|
duration: 3000,
|
||||||
|
message: '已清除',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@rowPadding: 0.25rem;
|
||||||
|
|
||||||
|
/deep/ .el-input__inner {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .el-textarea__inner {
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
.content-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: @rowPadding 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-label {
|
||||||
|
display: inline-block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
width: 6.5em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
.content-row {
|
||||||
|
padding: @rowPadding 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.outputs {
|
||||||
|
/deep/ .part .el-textarea__inner {
|
||||||
|
border-color: #FF5722;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .merged .el-textarea__inner {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-row {
|
||||||
|
padding: @rowPadding 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-title {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
657
src/components/tools/MinecraftUUIDConverter.vue
Normal file
657
src/components/tools/MinecraftUUIDConverter.vue
Normal file
@@ -0,0 +1,657 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tool-page">
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="title">操作</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
@click="generateRandomUUID()"
|
||||||
|
>随机生成</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form">
|
||||||
|
<div class="title">表单</div>
|
||||||
|
<div class="content" @input="handleInputEv($event)">
|
||||||
|
|
||||||
|
<!-- 十六进制 -->
|
||||||
|
<div class="content-row content-hex">
|
||||||
|
<div class="inputs">
|
||||||
|
<div class="input-label">Hexadecimal: </div>
|
||||||
|
<div class="input-content">
|
||||||
|
<span>"</span>
|
||||||
|
<input
|
||||||
|
ref="inputHex"
|
||||||
|
v-model="form.hex"
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
data-input-type="hex"
|
||||||
|
placeholder="00000000-0000-0000-0000-000000000000"
|
||||||
|
spellcheck="false"
|
||||||
|
:pattern="pattern.hex"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span>"</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btns">
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
size="mini"
|
||||||
|
plain
|
||||||
|
@click="copyToClipboard('hex')"
|
||||||
|
>复制</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 高低位 -->
|
||||||
|
<div class="content-row content-halves">
|
||||||
|
<div class="inputs">
|
||||||
|
<div class="input-label">UUID Most: </div>
|
||||||
|
<div class="input-content">
|
||||||
|
<input
|
||||||
|
ref="inputMost"
|
||||||
|
v-model="form.most"
|
||||||
|
type="number"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
data-input-type="halves"
|
||||||
|
inputmode="numeric"
|
||||||
|
spellcheck="false"
|
||||||
|
:pattern="pattern.number"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span> L</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-label">UUID Least: </div>
|
||||||
|
<div class="input-content">
|
||||||
|
<input
|
||||||
|
ref="inputLeast"
|
||||||
|
v-model="form.least"
|
||||||
|
type="number"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
data-input-type="halves"
|
||||||
|
inputmode="numeric"
|
||||||
|
spellcheck="false"
|
||||||
|
:pattern="pattern.number"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span> L</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btns">
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
size="mini"
|
||||||
|
plain
|
||||||
|
@click="copyToClipboard('halves')"
|
||||||
|
>复制</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 整型数组 -->
|
||||||
|
<div class="content-row content-array">
|
||||||
|
<div class="inputs" @paste="handlePasteArray($event)">
|
||||||
|
<div class="input-label">UUID: </div>
|
||||||
|
<div class="input-content">
|
||||||
|
<span>[I; </span>
|
||||||
|
<input
|
||||||
|
ref="inputArray1"
|
||||||
|
v-model="form.array['1']"
|
||||||
|
type="number"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
data-input-type="array"
|
||||||
|
inputmode="numeric"
|
||||||
|
spellcheck="false"
|
||||||
|
:pattern="pattern.number"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span>, </span>
|
||||||
|
<input
|
||||||
|
ref="inputArray2"
|
||||||
|
v-model="form.array['2']"
|
||||||
|
type="number"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
data-input-type="array"
|
||||||
|
inputmode="numeric"
|
||||||
|
spellcheck="false"
|
||||||
|
:pattern="pattern.number"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span>, </span>
|
||||||
|
<input
|
||||||
|
ref="inputArray3"
|
||||||
|
v-model="form.array['3']"
|
||||||
|
type="number"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
data-input-type="array"
|
||||||
|
inputmode="numeric"
|
||||||
|
spellcheck="false"
|
||||||
|
:pattern="pattern.number"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span>, </span>
|
||||||
|
<input
|
||||||
|
ref="inputArray4"
|
||||||
|
v-model="form.array['4']"
|
||||||
|
type="number"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
data-input-type="array"
|
||||||
|
inputmode="numeric"
|
||||||
|
spellcheck="false"
|
||||||
|
:pattern="pattern.number"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span>]</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btns">
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
size="mini"
|
||||||
|
plain
|
||||||
|
@click="copyToClipboard('array')"
|
||||||
|
>复制</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<div class="title">说明</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>可以在“UUID”的输入框中直接粘贴多组数值</p>
|
||||||
|
<p>例如:<code>1629527812, 47203901, -2056695293, 349258036</code></p>
|
||||||
|
<p>
|
||||||
|
<span>资料参考:</span>
|
||||||
|
<el-link
|
||||||
|
type="primary"
|
||||||
|
href="https://minecraft.fandom.com/zh/wiki/通用唯一识别码"
|
||||||
|
target="_blank"
|
||||||
|
>通用唯一识别码</el-link>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span>算法参考:</span>
|
||||||
|
<el-link
|
||||||
|
type="primary"
|
||||||
|
href="https://github.com/AjaxGb/mc-uuid-converter"
|
||||||
|
target="_blank"
|
||||||
|
>AjaxGb / mc-uuid-converter</el-link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用于复制文本 -->
|
||||||
|
<textarea
|
||||||
|
ref="copyArea"
|
||||||
|
v-model="copyText"
|
||||||
|
class="text-copy"
|
||||||
|
tabindex="-1"
|
||||||
|
readonly
|
||||||
|
></textarea>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'UUIDConverter',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 兼容性
|
||||||
|
isSupported: false,
|
||||||
|
// 输入框内容
|
||||||
|
form: {
|
||||||
|
hex: '00000000-0000-0000-0000-000000000000',
|
||||||
|
most: 0,
|
||||||
|
least: 0,
|
||||||
|
array: { '1': 0, '2': 0, '3': 0, '4': 0 },
|
||||||
|
},
|
||||||
|
// 输入框正则
|
||||||
|
pattern: {
|
||||||
|
hex: '',
|
||||||
|
number: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 被复制的文本
|
||||||
|
copyText: '',
|
||||||
|
|
||||||
|
/** @type {Uint8Array} */
|
||||||
|
uuidBytes: null,
|
||||||
|
/** @type {DataView} */
|
||||||
|
uuid: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.detectFn();
|
||||||
|
this.initDatas();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
/** 检测功能兼容性 */
|
||||||
|
detectFn() {
|
||||||
|
|
||||||
|
const fnTypes = [
|
||||||
|
typeof window.BigInt,
|
||||||
|
typeof window.crypto,
|
||||||
|
typeof DataView,
|
||||||
|
typeof Uint8Array,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (fnTypes.indexOf('undefined') > -1) {
|
||||||
|
this.$message({
|
||||||
|
duration: 5000,
|
||||||
|
message: 'The browser does not support the required function.',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('setBigUint64' in DataView.prototype)) {
|
||||||
|
this.$message({
|
||||||
|
duration: 5000,
|
||||||
|
message: 'The browser does not support writing and reading 64-bit integers.',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSupported = true;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 初始化数据 */
|
||||||
|
initDatas() {
|
||||||
|
if (!this.isSupported) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuidBytes = new Uint8Array(16);
|
||||||
|
const uuid = new DataView(uuidBytes.buffer);
|
||||||
|
|
||||||
|
this.uuidBytes = uuidBytes;
|
||||||
|
this.uuid = uuid;
|
||||||
|
|
||||||
|
this.pattern.hex = '([0-9a-fA-F]{1,8}-[0-9a-fA-F]{1,4}-[0-9a-fA-F]{1,4}-[0-9a-fA-F]{1,4}-[0-9a-fA-F]{1,12}|[0-9a-fA-F]{1,32})';
|
||||||
|
this.pattern.number = '[-+]?\\d+';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制结果到剪贴板
|
||||||
|
*
|
||||||
|
* @param {string} type 类型
|
||||||
|
*/
|
||||||
|
copyToClipboard(type) {
|
||||||
|
/** @type {HTMLInputElement} */
|
||||||
|
const el = this.$refs['copyArea'];
|
||||||
|
const form = this.form;
|
||||||
|
|
||||||
|
if (!el) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'array':
|
||||||
|
this.copyText = `[I; ${Object.values(form.array).join(', ')}]`;
|
||||||
|
break;
|
||||||
|
case 'halves':
|
||||||
|
this.copyText = `UUID Most: ${form.most}L, UUID Least: ${form.least}L`;
|
||||||
|
break;
|
||||||
|
case 'hex':
|
||||||
|
this.copyText = form.hex;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
el.focus();
|
||||||
|
el.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 随机生成 UUID */
|
||||||
|
generateRandomUUID() {
|
||||||
|
window.crypto.getRandomValues(this.uuidBytes);
|
||||||
|
// Set version to 4 (random)
|
||||||
|
this.uuidBytes[6] = (this.uuidBytes[6] & 0x0f) | (4 << 4);
|
||||||
|
// Set variant to 1 (Leach–Salz)
|
||||||
|
this.uuidBytes[8] = (this.uuidBytes[8] & 0x3f) | 0x80;
|
||||||
|
// 更新
|
||||||
|
this.updateFormDatas();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理输入事件
|
||||||
|
*
|
||||||
|
* @param {InputEvent} ev 输入事件对象
|
||||||
|
*/
|
||||||
|
handleInputEv(ev) {
|
||||||
|
/** @type {HTMLInputElement} */
|
||||||
|
const el = ev.target;
|
||||||
|
const type = el.dataset.inputType;
|
||||||
|
|
||||||
|
type && this.updateFormDatas(type);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理粘贴数组事件
|
||||||
|
*
|
||||||
|
* @param {ClipboardEvent} ev 剪贴板事件对象
|
||||||
|
*/
|
||||||
|
handlePasteArray(ev) {
|
||||||
|
|
||||||
|
/** @type {HTMLInputElement} */
|
||||||
|
const el = ev.target;
|
||||||
|
const type = el.dataset.inputType;
|
||||||
|
|
||||||
|
if (type !== 'array') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = ev.clipboardData;
|
||||||
|
const values = this.form.array;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of data.items) {
|
||||||
|
// 纯文本
|
||||||
|
if (item.kind === 'string' && item.type.startsWith('text/plain')) {
|
||||||
|
// 读取
|
||||||
|
item.getAsString((text) => {
|
||||||
|
const length = 4;
|
||||||
|
const numbers = text.match(/[+-]?\d+/g);
|
||||||
|
|
||||||
|
if (numbers && numbers.length === length) {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
values[String(i + 1)] = parseInt(numbers[i]);
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateFormDatas(type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理整型数组 UUID
|
||||||
|
*
|
||||||
|
* @param {string} mode 模式(parse、unparse)
|
||||||
|
*/
|
||||||
|
handleArray(mode) {
|
||||||
|
|
||||||
|
/** @type {HTMLInputElement[]} */
|
||||||
|
const inputs = [];
|
||||||
|
|
||||||
|
// 获取元素
|
||||||
|
for (let i = 1; i <= 4; i++) {
|
||||||
|
const el = this.$refs[`inputArray${i}`];
|
||||||
|
inputs.push(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 元素不存在
|
||||||
|
if (inputs.indexOf(undefined) > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { form, uuid } = this;
|
||||||
|
|
||||||
|
if (mode === 'parse') {
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
const num = Number(form.array[String(i + 1)]);
|
||||||
|
const offset = i * 4;
|
||||||
|
|
||||||
|
if (inputs[i].validity.valid) {
|
||||||
|
uuid.setInt32(offset, num, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (mode === 'unparse') {
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
const prop = String(i + 1);
|
||||||
|
const result = uuid.getInt32(i * 4, false);
|
||||||
|
|
||||||
|
form.array[prop] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理高低位 UUID
|
||||||
|
*
|
||||||
|
* @param {string} mode 模式(parse、unparse)
|
||||||
|
*/
|
||||||
|
handleHalves(mode) {
|
||||||
|
|
||||||
|
/** @type {HTMLInputElement} */
|
||||||
|
const elMost = this.$refs['inputMost'];
|
||||||
|
|
||||||
|
/** @type {HTMLInputElement} */
|
||||||
|
const elLeast = this.$refs['inputLeast'];
|
||||||
|
|
||||||
|
// 元素不存在
|
||||||
|
if (!(elMost && elLeast)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { form, uuid } = this;
|
||||||
|
|
||||||
|
if (mode === 'parse') {
|
||||||
|
if (elMost.validity.valid) {
|
||||||
|
uuid.setBigInt64(0, window.BigInt(form.most), false);
|
||||||
|
}
|
||||||
|
if (elLeast.validity.valid) {
|
||||||
|
uuid.setBigInt64(8, window.BigInt(form.least), false);
|
||||||
|
}
|
||||||
|
} else if (mode === 'unparse') {
|
||||||
|
form.most = uuid.getBigInt64(0, false);
|
||||||
|
form.least = uuid.getBigInt64(8, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理十六进制 UUID
|
||||||
|
*
|
||||||
|
* @param {string} mode 模式(parse、unparse)
|
||||||
|
*/
|
||||||
|
handleHex(mode) {
|
||||||
|
|
||||||
|
/** @type {HTMLInputElement} */
|
||||||
|
const el = this.$refs['inputHex'];
|
||||||
|
|
||||||
|
// 元素不存在
|
||||||
|
if (!el) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { form, uuid } = this;
|
||||||
|
const groupSizes = [8, 4, 4, 4, 12];
|
||||||
|
|
||||||
|
if (mode === 'parse') {
|
||||||
|
if (!el.validity.valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hex = form.hex;
|
||||||
|
const hexText = (
|
||||||
|
hex.includes('-') ?
|
||||||
|
hex.trim()
|
||||||
|
.split('-')
|
||||||
|
.map((g, i) => g.padStart(groupSizes[i], '0'))
|
||||||
|
.join('') :
|
||||||
|
hex.trim().padStart(32, '0')
|
||||||
|
);
|
||||||
|
|
||||||
|
uuid.setBigUint64(0, window.BigInt('0x' + hexText.substring(0, 16)), false);
|
||||||
|
uuid.setBigUint64(8, window.BigInt('0x' + hexText.substring(16)), false);
|
||||||
|
} else if (mode === 'unparse') {
|
||||||
|
const hexText = (
|
||||||
|
uuid.getBigUint64(0, false).toString(16).padStart(16, '0') +
|
||||||
|
uuid.getBigUint64(8, false).toString(16).padStart(16, '0')
|
||||||
|
);
|
||||||
|
const groups = [];
|
||||||
|
|
||||||
|
let groupStart = 0;
|
||||||
|
|
||||||
|
for (const groupSize of groupSizes) {
|
||||||
|
groups.push(hexText.substring(groupStart, groupStart + groupSize));
|
||||||
|
groupStart += groupSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.hex = groups.join('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新表单数据
|
||||||
|
*
|
||||||
|
* @param {string} type 类型
|
||||||
|
*/
|
||||||
|
updateFormDatas(type) {
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
array: this.handleArray,
|
||||||
|
halves: this.handleHalves,
|
||||||
|
hex: this.handleHex,
|
||||||
|
};
|
||||||
|
const handler = handlers[type];
|
||||||
|
|
||||||
|
// 调用对应的处理函数
|
||||||
|
handler && handler('parse');
|
||||||
|
|
||||||
|
for (let key in handlers) {
|
||||||
|
if (key !== type) {
|
||||||
|
handlers[key]('unparse');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
input[type="number"],
|
||||||
|
input[type="text"] {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border: 0.0625rem solid #CCC;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
outline: none;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.5em;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
&:invalid {
|
||||||
|
background-color: #9E9E9E;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
&::-webkit-inner-spin-button,
|
||||||
|
&::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
font-family: monospace;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-row {
|
||||||
|
min-width: 37.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 0.0625rem solid #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
width: 7em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-content {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btns {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-hex {
|
||||||
|
input[type="text"] {
|
||||||
|
width: 32.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-halves {
|
||||||
|
input[type="number"] {
|
||||||
|
width: 12.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-array {
|
||||||
|
input[type="number"] {
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-copy {
|
||||||
|
width: 2rem;
|
||||||
|
height: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: default;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
</style>
|
Reference in New Issue
Block a user