Skip to content

Commit 8d02ab8

Browse files
polish
1 parent d247a7b commit 8d02ab8

10 files changed

Lines changed: 708 additions & 268 deletions

File tree

build/app.js

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,23 @@ document.addEventListener('DOMContentLoaded', () => {
77
const copyLatexBtn = document.getElementById('copyLatexBtn');
88
const copyCSVBtn = document.getElementById('copyCSVBtn');
99
const copyHTMLBtn = document.getElementById('copyHTMLBtn');
10+
const copyKMapCSVBtn = document.getElementById('copyKMapCSVBtn');
11+
const copyKMapHTMLBtn = document.getElementById('copyKMapHTMLBtn');
1012
const errorMessage = document.getElementById('errorMessage');
1113
const truthTableContainer = document.getElementById('truthTableContainer');
14+
const kmapContainer = document.getElementById('kmapContainer');
1215
const gatesSvg = document.getElementById('gatesSvg');
1316
const cmosSvg = document.getElementById('cmosSvg');
1417

1518
const tabBlockDiagram = document.getElementById('tabBlockDiagram');
1619
const tabCMOS = document.getElementById('tabCMOS');
1720
const tabTruthTable = document.getElementById('tabTruthTable');
21+
const tabKMap = document.getElementById('tabKMap');
1822

1923
const blockDiagramTab = document.getElementById('blockDiagramTab');
2024
const cmosTab = document.getElementById('cmosTab');
2125
const truthTableTab = document.getElementById('truthTableTab');
26+
const kmapTab = document.getElementById('kmapTab');
2227

2328
// Format help modal elements
2429
const formatHelpBtn = document.getElementById('formatHelpBtn');
@@ -70,6 +75,7 @@ document.addEventListener('DOMContentLoaded', () => {
7075
currentVisualizer.renderCMOSDiagram(cmosSvg);
7176

7277
currentVisualizer.renderTruthTable(truthTableContainer);
78+
currentVisualizer.renderKMap(kmapContainer);
7379
} catch (error) {
7480
showError(error.message);
7581
}
@@ -97,6 +103,33 @@ document.addEventListener('DOMContentLoaded', () => {
97103
document.body.removeChild(ta);
98104
}
99105

106+
// Build Google-Docs-friendly HTML from a table element.
107+
function buildCopyableHTML(tableEl) {
108+
const clone = tableEl.cloneNode(true);
109+
clone.setAttribute('border', '1');
110+
clone.style.borderCollapse = 'collapse';
111+
clone.querySelectorAll('th, td').forEach(cell => {
112+
cell.style.border = '1px solid #000';
113+
cell.style.padding = '4px 8px';
114+
cell.style.textAlign = 'center';
115+
});
116+
clone.querySelectorAll('th').forEach(th => {
117+
th.style.backgroundColor = '#f5f5f5';
118+
th.style.fontWeight = 'bold';
119+
});
120+
return '<html><body><!--StartFragment-->' + clone.outerHTML + '<!--EndFragment--></body></html>';
121+
}
122+
123+
function getPlainText(tableEl) {
124+
const rows = [];
125+
tableEl.querySelectorAll('tr').forEach(tr => {
126+
const cells = [];
127+
tr.querySelectorAll('th, td').forEach(cell => cells.push(cell.textContent));
128+
rows.push(cells.join('\t'));
129+
});
130+
return rows.join('\n');
131+
}
132+
100133
// Copy as CSV (comma-separated)
101134
copyCSVBtn.addEventListener('click', () => {
102135
if (!currentVisualizer) return;
@@ -109,18 +142,42 @@ document.addEventListener('DOMContentLoaded', () => {
109142
navigator.clipboard.writeText(csv);
110143
});
111144

112-
// Copy rendered HTML table (preserves formatting when pasting into rich-text editors)
145+
// Copy rendered HTML table (preserves formatting in Google Docs, Word, etc.)
113146
copyHTMLBtn.addEventListener('click', () => {
114147
const tableEl = truthTableContainer.querySelector('table');
115148
if (!tableEl) return;
116-
const htmlStr = tableEl.outerHTML;
117-
const plainRows = [];
149+
const htmlStr = buildCopyableHTML(tableEl);
150+
const plainText = getPlainText(tableEl);
151+
const blob = new Blob([htmlStr], { type: 'text/html' });
152+
const textBlob = new Blob([plainText], { type: 'text/plain' });
153+
navigator.clipboard.write([
154+
new ClipboardItem({
155+
'text/html': blob,
156+
'text/plain': textBlob
157+
})
158+
]);
159+
});
160+
161+
// Copy K-map as CSV
162+
copyKMapCSVBtn.addEventListener('click', () => {
163+
const tableEl = kmapContainer.querySelector('table');
164+
if (!tableEl) return;
165+
const rows = [];
118166
tableEl.querySelectorAll('tr').forEach(tr => {
119167
const cells = [];
120168
tr.querySelectorAll('th, td').forEach(cell => cells.push(cell.textContent));
121-
plainRows.push(cells.join('\t'));
169+
rows.push(cells.join(','));
122170
});
123-
const plainText = plainRows.join('\n');
171+
const csv = rows.join('\n');
172+
navigator.clipboard.writeText(csv);
173+
});
174+
175+
// Copy K-map as HTML table
176+
copyKMapHTMLBtn.addEventListener('click', () => {
177+
const tableEl = kmapContainer.querySelector('table');
178+
if (!tableEl) return;
179+
const htmlStr = buildCopyableHTML(tableEl);
180+
const plainText = getPlainText(tableEl);
124181
const blob = new Blob([htmlStr], { type: 'text/html' });
125182
const textBlob = new Blob([plainText], { type: 'text/plain' });
126183
navigator.clipboard.write([
@@ -132,8 +189,8 @@ document.addEventListener('DOMContentLoaded', () => {
132189
});
133190

134191
// Tab switching
135-
const tabs = { block: tabBlockDiagram, cmos: tabCMOS, truth: tabTruthTable };
136-
const contents = { block: blockDiagramTab, cmos: cmosTab, truth: truthTableTab };
192+
const tabs = { block: tabBlockDiagram, cmos: tabCMOS, truth: tabTruthTable, kmap: tabKMap };
193+
const contents = { block: blockDiagramTab, cmos: cmosTab, truth: truthTableTab, kmap: kmapTab };
137194

138195
Object.keys(tabs).forEach(key => {
139196
tabs[key].addEventListener('click', () => {

build/index.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ <h3>Grouping</h3>
2929
</div>
3030
<div class="format-section">
3131
<h3>NOT (Negation)</h3>
32-
<p><code>!a</code> &nbsp; <code>'a</code> &nbsp; <code>not a</code></p>
32+
<p><code>!a</code> &nbsp; <code>a'</code> &nbsp; <code>~a</code> &nbsp; <code>not a</code></p>
3333
</div>
3434
<div class="format-section">
3535
<h3>AND</h3>
36-
<p><code>a*b</code> &nbsp; <code>a and b</code> &nbsp; <code>a&amp;b</code></p>
36+
<p><code>ab</code> &nbsp; <code>a*b</code> &nbsp; <code>a and b</code> &nbsp; <code>a&amp;b</code></p>
3737
</div>
3838
<div class="format-section">
3939
<h3>OR</h3>
@@ -64,6 +64,7 @@ <h3>XNOR</h3>
6464
<button id="tabBlockDiagram" class="tab-button active">Block diagram</button>
6565
<button id="tabCMOS" class="tab-button">CMOS</button>
6666
<button id="tabTruthTable" class="tab-button">Truth Table</button>
67+
<button id="tabKMap" class="tab-button">K-Map</button>
6768
</div>
6869

6970
<div id="blockDiagramTab" class="tab-content">
@@ -82,6 +83,14 @@ <h3>XNOR</h3>
8283
<div id="truthTableContainer"></div>
8384
</div>
8485

86+
<div id="kmapTab" class="tab-content" style="display:none;">
87+
<div class="table-buttons">
88+
<button id="copyKMapHTMLBtn">Copy Table</button>
89+
<button id="copyKMapCSVBtn">Copy as CSV</button>
90+
</div>
91+
<div id="kmapContainer"></div>
92+
</div>
93+
8594
<script src="parser.js"></script>
8695
<script src="visualizer.js"></script>
8796
<script src="app.js"></script>

build/parser.js

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ class BooleanExpression {
2020
return expr
2121
// NOT variants
2222
.replace(/'/g, '!') // ' to !
23+
.replace(/\u2018/g, '!') // ' to ! (left single quote)
24+
.replace(/\u2019/g, '!') // ' to ! (right single quote/apostrophe)
25+
.replace(/`/g, '!') // ` to ! (backtick)
2326
.replace(/¯/g, '!') // Overline to !
27+
.replace(/~/g, '!') // ~ to !
2428
// NOR variants
2529
.replace(//g, ' nor ') // NOR symbol
2630
.replace(//g, ' nor ') // NOR arrow symbol
@@ -164,15 +168,29 @@ class BooleanExpression {
164168

165169
const parseAnd = () => {
166170
let left = parseNot();
167-
while (peek() && (peek().type === 'AND' || peek().type === 'NAND')) {
168-
const op = consume();
169-
const right = parseNot();
170-
left = {
171-
type: op.type,
172-
left: left,
173-
right: right,
174-
operator: op.value
175-
};
171+
while (peek()) {
172+
if (peek().type === 'AND' || peek().type === 'NAND') {
173+
// Explicit AND / NAND operator
174+
const op = consume();
175+
const right = parseNot();
176+
left = {
177+
type: op.type,
178+
left: left,
179+
right: right,
180+
operator: op.value
181+
};
182+
} else if (peek().type === 'VAR' || peek().type === 'NOT' || peek().type === 'LPAREN') {
183+
// Implicit AND: adjacent terms like XYZ, X'Y, (A+B)C
184+
const right = parseNot();
185+
left = {
186+
type: 'AND',
187+
left: left,
188+
right: right,
189+
operator: 'and'
190+
};
191+
} else {
192+
break;
193+
}
176194
}
177195
return left;
178196
};
@@ -187,7 +205,21 @@ class BooleanExpression {
187205
operator: "'"
188206
};
189207
}
190-
return parsePrimary();
208+
return parsePostfix();
209+
};
210+
211+
// Postfix NOT: X' becomes NOT(X), (A+B)' becomes NOT(A OR B)
212+
const parsePostfix = () => {
213+
let node = parsePrimary();
214+
while (peek() && peek().type === 'NOT') {
215+
consume();
216+
node = {
217+
type: 'NOT',
218+
operand: node,
219+
operator: "'"
220+
};
221+
}
222+
return node;
191223
};
192224

193225
const parsePrimary = () => {

build/styles.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,40 @@ td.low {
118118
color: #666;
119119
}
120120

121+
/* K-map table */
122+
.kmap-table {
123+
border-collapse: collapse;
124+
margin-top: 10px;
125+
}
126+
127+
.kmap-table th,
128+
.kmap-table td {
129+
border: 1px solid #ccc;
130+
padding: 8px 24px;
131+
text-align: center;
132+
min-width: 70px;
133+
}
134+
135+
.kmap-table th {
136+
background: #f5f5f5;
137+
font-weight: bold;
138+
}
139+
140+
.kmap-table th.kmap-corner {
141+
background: #f5f5f5;
142+
font-weight: bold;
143+
}
144+
145+
.kmap-table td.high {
146+
background: #e8f5e9;
147+
color: #2e7d32;
148+
}
149+
150+
.kmap-table td.low {
151+
background: #fafafa;
152+
color: #666;
153+
}
154+
121155
/* Help button */
122156
.help-btn {
123157
width: 36px;

0 commit comments

Comments
 (0)