Membuat aplikasi dekstop dengan electron js
mkdir test-app
cd test-app
npm init -y
Pasang Electron:
npm install electron --save-dev
Buat file utama: Buat file bernama main.js
di root proyek kamu:
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
},
});
// Memuat file index.html
win.loadFile('index.html');
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
Buat file HTML utama: Buat file index.html
di root proyek kamu:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Markdown App</title>
</head>
<body>
<div id="app">
<textarea id="editor" placeholder="Write your notes here..." style="width: 100%; height: 90vh;"></textarea>
<div id="preview" style="border-top: 1px solid #ccc; height: 10vh; overflow-y: auto;"></div>
</div>
<script src="renderer.js"></script>
</body>
</html>
Tambahkan renderer script: Buat file renderer.js
untuk menangani logika frontend:
const textarea = document.getElementById('editor');
const preview = document.getElementById('preview');
textarea.addEventListener('input', () => {
const text = textarea.value;
preview.innerHTML = convertMarkdownToHTML(text);
});
function convertMarkdownToHTML(markdown) {
// Gunakan library Markdown seperti marked.js untuk merender
// npm install marked
const marked = require('marked');
return marked(markdown);
}
Tambahkan library markdown: Kamu bisa menggunakan library seperti marked
untuk merender Markdown ke HTML.
Instal library marked
:
npm install marked
Periksa File package.json
- astikan bahwa
package.json
memiliki entri "main" yang menunjuk ke file utama aplikasi, biasanyamain.js
atau nama lain yang sesuai. - Contoh konfigurasi
package.json
yang benar:
{
"name": "test-app",
"version": "1.0.0",
"description": "A simple Electron app",
"main": "main.js", // pastikan nama file utama benar
"scripts": {
"start": "electron ."
},
"dependencies": {
"electron": "^latest"
}
}
Jalankan
npm start
Rencananya pengen bikin app semacam obsidian, membuat mindmap berdasarkan list yang dibuat. berarti butuh library tambahan yakni d3.js, bisa simpan .png
juga untuk
Berarti pasang electron dan d3.js
npm install electron d3 --save-dev
Edit file index.html
: Buat file HTML untuk antarmuka dasar aplikasi.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mindmap Generator</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
}
#editor {
width: 90%;
height: 150px;
margin-top: 20px;
}
#mindmap {
width: 90%;
height: 600px;
margin-top: 20px;
border: 1px solid #ccc;
}
button {
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Mindmap Generator</h1>
<textarea id="editor" placeholder="- Root Node
-- Child Node 1
--- Sub-Child Node 1.1
--- Sub-Child Node 1.2
---- Sub-Sub-Child Node 1.2.1
-- Child Node 2
--- Sub-Child Node 2.1"></textarea>
<button onclick="generateMindmap()">Generate Mindmap</button>
<button onclick="saveAsImage()">Save as Image</button>
<div id="mindmap"></div>
<script src="renderer.js"></script>
<script src="https://d3js.org/d3.v6.min.js"></script>
</body>
</html>
Bikin file renderer.js
: Tambahkan logika untuk memproses input teks menjadi mindmap menggunakan D3.js.
function generateMindmap() {
// Ambil input dari textarea dan pecah menjadi array baris
const input = document.getElementById('editor').value; // ambil nilai elemen textarea dengan id 'editor'
const lines = input.split('\n').filter(line => line.trim() !== ''); // Pecah input menjadi array baris dan hilangkan baris kosong
// Struktur root node sebagai awal mindmap
const root = { name: 'Root', children: [] }; // Node utama dengan array children kosong
let stack = [{ node: root, level: -1 }]; // Stack digunakan untuk melacak node parent berdasarkan level
// Loop untuk mengubah setiap baris input menjadi node dalam tree
lines.forEach(line => {
const trimmedLine = line.trim(); // Hapus spasi di awal dan akhir
const level = (line.match(/^-+/) || [''])[0].length - 1; // Tentukan level berdasarkan jumlah tanda "-"
const node = { name: trimmedLine.replace(/^-+/, '').trim(), children: [] }; // Buat node baru
// Cari parent node yang sesuai dengan level saat ini
while (stack.length > 0 && stack[stack.length - 1].level >= level) {
stack.pop(); // Kembali ke node parent di stack yang memiliki level lebih rendah
}
// Tambahkan node ke parent saat ini yang ditemukan di stack
if (stack.length > 0) {
stack[stack.length - 1].node.children.push(node);
}
// Tambahkan node baru ke stack dengan level saat ini
stack.push({ node, level });
});
const nodes = [];
const links = [];
// Fungsi untuk melakukan traversal tree dan mengisi nodes dan links untuk D3.js
function traverse(node, parent = null) {
nodes.push(node); // Tambahkan node ke array nodes
if (parent) {
links.push({ source: parent, target: node }); // Tambahkan link antara parent dan child
}
if (node.children) {
node.children.forEach(child => traverse(child, node)); // Rekursif untuk setiap anak
}
}
traverse(root); // Mulai traversal dari root
// Render mindmap sebagai force-directed graph menggunakan D3.js
renderForceDirectedGraph(nodes, links);
}
function renderForceDirectedGraph(nodes, links) {
// Ambil ukuran elemen mindmap untuk menentukan ukuran canvas SVG
const width = document.getElementById('mindmap').clientWidth;
const height = document.getElementById('mindmap').clientHeight;
// Bersihkan konten sebelumnya di elemen mindmap
document.getElementById('mindmap').innerHTML = '';
// Buat elemen SVG untuk visualisasi graf
const svg = d3
.select('#mindmap')
.append('svg')
.attr('width', width)
.attr('height', height);
// Buat simulasi graf force-directed dengan D3.js
const simulation = d3
.forceSimulation(nodes)
.force('link', d3.forceLink(links).id(d => d.name).distance(100)) // Buat gaya link antara nodes
.force('charge', d3.forceManyBody().strength(-300)) // Atur gaya tolak-menolak antar nodes
.force('center', d3.forceCenter(width / 2, height / 2)); // Pusatkan graf
// Tambahkan elemen link (garis penghubung) ke graf
const link = svg
.append('g')
.attr('stroke', '#ccc')
.selectAll('line')
.data(links)
.enter()
.append('line');
// Tambahkan elemen node (lingkaran) ke graf
const node = svg
.append('g')
.selectAll('circle')
.data(nodes)
.enter()
.append('circle')
.attr('r', 5) // Ukuran node
.attr('fill', '#69b3a2') // Warna node
.call(
d3.drag() // Tambahkan event drag ke nodes
.on('start', dragStarted)
.on('drag', dragged)
.on('end', dragEnded)
);
// Tambahkan label teks ke node
const label = svg
.append('g')
.selectAll('text')
.data(nodes)
.enter()
.append('text')
.text(d => d.name) // Tampilkan nama node
.attr('x', 8)
.attr('y', 3)
.attr('font-size', '10px');
// Fungsi untuk memperbarui posisi elemen saat simulasi berjalan
simulation.on('tick', () => {
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
node
.attr('cx', d => d.x)
.attr('cy', d => d.y);
label
.attr('x', d => d.x + 8)
.attr('y', d => d.y + 3);
});
// Fungsi drag untuk node
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart(); // Aktifkan simulasi
d.fx = d.x; // Tetapkan posisi x tetap selama drag
d.fy = d.y; // Tetapkan posisi y tetap selama drag
}
function dragged(event, d) {
d.fx = event.x; // Perbarui posisi x selama drag
d.fy = event.y; // Perbarui posisi y selama drag
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0); // Matikan simulasi
d.fx = null; // Hapus posisi tetap setelah drag selesai
d.fy = null;
}
}
function saveAsImage() {
// Ambil elemen SVG
const svg = document.querySelector('#mindmap svg');
// Periksa apakah mindmap sudah di-generate
if (!svg) {
alert('Mindmap belum dibuat.');
return;
}
// Konversi elemen SVG ke string
const svgData = new XMLSerializer().serializeToString(svg);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set ukuran canvas sesuai ukuran SVG
const svgSize = svg.getBoundingClientRect();
canvas.width = svgSize.width;
canvas.height = svgSize.height;
// Tambahkan latar belakang putih
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Buat gambar dari data SVG
const img = new Image();
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(svgBlob);
img.onload = function () {
// Gambar SVG ke canvas
ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url);
// Buat dan klik tautan unduhan untuk menyimpan gambar
const link = document.createElement('a');
link.download = 'mindmap.png';
link.href = canvas.toDataURL('image/png');
link.click();
};
img.src = url; // Set sumber gambar dari data SVG
}
Lalu penting untuk menyesuaikan package.json
{
"name": "obsidian",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"d3": "^7.9.0",
"electron": "^33.2.0"
},
"dependencies": {
"marked": "^15.0.0"
}
}
Oke karena referensinya Obsidian saya akan bikin bagian kiri input list text bagian kanan visualisasi mindmapnya, live berubah maka ada perubahan yang perlu dilakukan di renderer.json
tambahkan Event Listener untuk merender mindmap secara langsung saat input teks diubah oleh user.
document.getElementById('editor').addEventListener('input', generateMindmap);
Lalu image nya bisa disimpan perlu Event Listener juga di bagian elemen mindmap
Tambahkan di renderer.json
// Tambahkan event listener untuk klik kanan pada elemen mindmap
document.getElementById('mindmap').addEventListener('contextmenu', function (event) {
event.preventDefault(); // Mencegah menu konteks default
saveAsImage(); // Panggil fungsi untuk menyimpan gambar
});