mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-21 15:26:15 -04:00
feat(new tool): Mermaid exporter
This commit is contained in:
parent
08d977b8cd
commit
6d868f9182
9 changed files with 1094 additions and 162 deletions
11
components.d.ts
vendored
11
components.d.ts
vendored
|
@ -127,22 +127,27 @@ declare module '@vue/runtime-core' {
|
|||
MenuBarItem: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue')['default']
|
||||
MenuIconItem: typeof import('./src/components/MenuIconItem.vue')['default']
|
||||
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
|
||||
MermaidExporter: typeof import('./src/tools/mermaid-exporter/mermaid-exporter.vue')['default']
|
||||
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
|
||||
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
||||
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NCode: typeof import('naive-ui')['NCode']
|
||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NH1: typeof import('naive-ui')['NH1']
|
||||
NH3: typeof import('naive-ui')['NH3']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NTable: typeof import('naive-ui')['NTable']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSlider: typeof import('naive-ui')['NSlider']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
||||
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
||||
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
"markdown-it": "^14.0.0",
|
||||
"marked": "^10.0.0",
|
||||
"mathjs": "^11.9.1",
|
||||
"mermaid": "^11.6.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"monaco-editor": "^0.43.0",
|
||||
"naive-ui": "^2.35.0",
|
||||
|
|
1031
pnpm-lock.yaml
generated
1031
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
|||
import { tool as base64FileConverter } from './base64-file-converter';
|
||||
import { tool as base64StringConverter } from './base64-string-converter';
|
||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||
import { tool as mermaidExporter } from './mermaid-exporter';
|
||||
import { tool as emailNormalizer } from './email-normalizer';
|
||||
|
||||
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
||||
|
@ -116,6 +117,7 @@ export const toolsByCategory: ToolCategory[] = [
|
|||
xmlToJson,
|
||||
jsonToXml,
|
||||
markdownToHtml,
|
||||
mermaidExporter,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
12
src/tools/mermaid-exporter/index.ts
Normal file
12
src/tools/mermaid-exporter/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Markdown } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'Mermaid exporter',
|
||||
path: '/mermaid-exporter',
|
||||
description: 'Convert Markdown (Mermaid) to image and allow to export to PNG, JPG & SVG',
|
||||
keywords: ['mermaid', 'exporter', 'markdown', 'MD'],
|
||||
component: () => import('./mermaid-exporter.vue'),
|
||||
icon: Markdown,
|
||||
createdAt: new Date('2025-04-11'),
|
||||
});
|
15
src/tools/mermaid-exporter/mermaid-exporter.e2e.spec.ts
Normal file
15
src/tools/mermaid-exporter/mermaid-exporter.e2e.spec.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Tool - Mermaid exporter', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/mermaid-exporter');
|
||||
});
|
||||
|
||||
test('Has correct title', async ({ page }) => {
|
||||
await expect(page).toHaveTitle('Mermaid exporter - IT Tools');
|
||||
});
|
||||
|
||||
test('', async ({ page }) => {
|
||||
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
// import { } from './mermaid-exporter.service';
|
||||
//
|
||||
// describe('mermaid-exporter', () => {
|
||||
//
|
||||
// })
|
0
src/tools/mermaid-exporter/mermaid-exporter.service.ts
Normal file
0
src/tools/mermaid-exporter/mermaid-exporter.service.ts
Normal file
177
src/tools/mermaid-exporter/mermaid-exporter.vue
Normal file
177
src/tools/mermaid-exporter/mermaid-exporter.vue
Normal file
|
@ -0,0 +1,177 @@
|
|||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||
import mermaid from 'mermaid';
|
||||
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
|
||||
const mermaidCode = ref<string>(`graph TD
|
||||
A[Start] --> B{Is it working?}
|
||||
B -- Yes --> C[Great!]
|
||||
B -- No --> D[Fix it!]
|
||||
`);
|
||||
|
||||
const mermaidContainer = ref<HTMLElement | null>(null);
|
||||
|
||||
async function renderMermaid(): Promise<void> {
|
||||
if (mermaidContainer.value) {
|
||||
mermaidContainer.value.innerHTML = '';
|
||||
try {
|
||||
mermaid.parse(mermaidCode.value);
|
||||
const { svg } = await mermaid.render('graphDiv', mermaidCode.value);
|
||||
mermaidContainer.value.innerHTML = svg;
|
||||
}
|
||||
catch (error: unknown) {
|
||||
mermaidContainer.value.innerHTML = '<p class="error">Invalid Mermaid syntax</p>';
|
||||
console.error('Mermaid error:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(mermaidCode, () => {
|
||||
nextTick(() => {
|
||||
renderMermaid();
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
renderMermaid();
|
||||
});
|
||||
|
||||
function fixSvgSize(svg: string): string {
|
||||
const match = svg.match(/viewBox="([\d\s.-]+)"/);
|
||||
if (!match) {
|
||||
return svg;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
const [minX, minY, width, height] = match[1].split(/\s+/).map(Number);
|
||||
|
||||
svg = svg.replace(/width="[^"]*"/, `width="${width}"`);
|
||||
svg = svg.replace(/height="[^"]*"/, `height="${height}"`);
|
||||
|
||||
if (!/width="/.test(svg)) {
|
||||
svg = svg.replace('<svg', `<svg width="${width}"`);
|
||||
}
|
||||
if (!/height="/.test(svg)) {
|
||||
svg = svg.replace('<svg', `<svg height="${height}"`);
|
||||
}
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
function exportAs(format: 'svg' | 'png' | 'jpg'): void {
|
||||
const container = mermaidContainer.value;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const svgElement = container.querySelector('svg');
|
||||
if (!svgElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
let svgData = new XMLSerializer().serializeToString(svgElement);
|
||||
|
||||
if (!svgData.includes('xmlns=')) {
|
||||
svgData = svgData.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
|
||||
}
|
||||
|
||||
svgData = fixSvgSize(svgData);
|
||||
|
||||
if (format === 'svg') {
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'diagram.svg';
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
||||
const reader = new FileReader();
|
||||
const scaleFactor = 3;
|
||||
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result as string;
|
||||
const image = new Image();
|
||||
|
||||
image.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = image.width * scaleFactor;
|
||||
canvas.height = image.height * scaleFactor;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.setTransform(scaleFactor, 0, 0, scaleFactor, 0, 0);
|
||||
ctx.drawImage(image, 0, 0);
|
||||
|
||||
const mime = format === 'png' ? 'image/png' : 'image/jpeg';
|
||||
const link = document.createElement('a');
|
||||
link.download = `diagram.${format}`;
|
||||
link.href = canvas.toDataURL(mime);
|
||||
link.click();
|
||||
};
|
||||
|
||||
image.src = base64data;
|
||||
};
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<c-input-text
|
||||
v-model:value="mermaidCode"
|
||||
class=""
|
||||
multiline raw-text
|
||||
placeholder="Write your Mermaid code here..."
|
||||
rows="8"
|
||||
autofocus
|
||||
label="Your Mermaid to convert:"
|
||||
/>
|
||||
<n-divider />
|
||||
<div flex justify-center class="diagram-container">
|
||||
<div ref="mermaidContainer" />
|
||||
</div>
|
||||
|
||||
<div flex justify-center class="buttons">
|
||||
<n-button @click="exportAs('png')">
|
||||
Export as PNG
|
||||
</n-button>
|
||||
<n-button @click="exportAs('jpg')">
|
||||
Export as JPG
|
||||
</n-button>
|
||||
<n-button @click="exportAs('svg')">
|
||||
Export as SVG
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.diagram-container {
|
||||
border: 1px solid var(--theme-default-color);
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--theme-default-color);
|
||||
overflow-x: auto;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.error {
|
||||
color: var(--theme-error-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue