UNPKG

@webwriter/logic-circuit

Version:

Create and simulate circuits with logic gates (AND, OR, XOR, etc.).

500 lines (452 loc) 20.1 kB
import { html, css } from 'lit'; import { LitElementWw } from '@webwriter/lit'; import { customElement, query } from 'lit/decorators.js'; import { property, state } from 'lit/decorators.js'; import ConnectorElement from '../connector'; import LukaswwLogicgates from '../../webwriter-logic-circuit'; import { getMouseCoordinates, updateLineColor } from '../helper/line-helper'; import { transferOutputToNextGate } from '../helper/gate-helper'; import SlTooltip from '@shoelace-style/shoelace/dist/components/tooltip/tooltip.component.js'; import SlButton from '@shoelace-style/shoelace/dist/components/button/button.component.js'; import SlPopup from '@shoelace-style/shoelace/dist/components/popup/popup.component.js'; import SlIcon from '@shoelace-style/shoelace/dist/components/icon/icon.component.js'; import SlMenu from '@shoelace-style/shoelace/dist/components/menu/menu.component.js'; import SlMenuItem from '@shoelace-style/shoelace/dist/components/menu-item/menu-item.component.js'; import '@shoelace-style/shoelace/dist/themes/light.css'; import { Styles } from '../styles'; import LogicCircuit from '../../webwriter-logic-circuit'; export default class Gate extends LitElementWw { static movedGate; static x; static y; widget: LogicCircuit public static get scopedElements() { return { 'sl-tooltip': SlTooltip, 'sl-button': SlButton, 'sl-popup': SlPopup, 'sl-icon': SlIcon, 'sl-menu': SlMenu, 'sl-menu-item': SlMenuItem, }; } static styles = Styles; @property({ type: Boolean }) accessor input1: boolean = null; @property({ type: Boolean }) accessor input2: boolean = null; @property({ type: Boolean }) accessor output: boolean = null; @property({ type: Boolean }) accessor output2: boolean = null; @property({ type: String }) accessor gatetype: string = null; @property({ type: Boolean }) accessor movable: boolean = false; @property({ type: String }) accessor id: string = null; @property({ type: Object }) accessor conIn1: ConnectorElement = null; @property({ type: Object }) accessor conIn2: ConnectorElement = null; @property({ type: Object }) accessor conOut: Object = null; @property({ type: Object }) accessor conOut2: Object = null; @property({ type: Boolean }) accessor showTruthTable: boolean = false; @query('#contextMenu') accessor contextMenu: SlMenu; constructor() { super(); this.addEventListener('dragstart', this.handleDragStart); this.addEventListener('dragend', this.handleDragEnd); this.addEventListener('contextmenu', this.handleContextMenu); this.input1 = null; this.input2 = null; this.output = false; this.output2 = false; this.movable = false; this.showTruthTable = false; } firstUpdated(changedProperties) { super.firstUpdated(changedProperties); let slot; if (this.shadowRoot) { let slot; switch (this.gatetype) { case 'INPUT': slot = this.shadowRoot.querySelector('slot[name="con3"]'); this.createConnectors(slot, 3); break; case 'OUTPUT': slot = this.shadowRoot.querySelector('slot[name="con1"]'); this.createConnectors(slot, 0); break; case 'NOT': slot = this.shadowRoot.querySelector('slot[name="con1"]'); this.createConnectors(slot, 0); slot = this.shadowRoot.querySelector('slot[name="con3"]'); this.createConnectors(slot, 3); break; case 'SPLITTER': slot = this.shadowRoot.querySelector('slot[name="con1"]'); this.createConnectors(slot, 0); slot = this.shadowRoot.querySelector('slot[name="con3"]'); this.createConnectors(slot, 4); slot = this.shadowRoot.querySelector('slot[name="con4"]'); this.createConnectors(slot, 5); break; default: for (let i = 1; i <= 3; i++) { slot = this.shadowRoot.querySelector('slot[name="con' + i + '"]'); this.createConnectors(slot, i); } } } } updated(changedProperties) { super.updated(changedProperties); const widget = this.widget if (this.movable === true) { if (this.gatetype === 'INPUT' && changedProperties.has('input1')) { this.calculateOutput(); this.updateConnectorColor(); transferOutputToNextGate(this.widget, this); } if (widget.simulate === true) { if (this.gatetype === 'OUTPUT' && changedProperties.has('input1')) { this.calculateOutput(); this.updateConnectorColor(); } else if (changedProperties.has('input1') || changedProperties.has('input2')) { this.calculateOutput(); transferOutputToNextGate(this.widget, this); this.updateConnectorColor(); } } if (changedProperties.has('showTruthTable')) { if (this.showTruthTable) { this.renderTruthTable(); } } updateLineColor(widget); } } handleDragStart(event) { Gate.movedGate = event.target; Gate.x = event.clientX; Gate.y = event.clientY; const tooltip = this.shadowRoot.querySelector('.tooltip') as SlTooltip; if (tooltip) { if (tooltip.open) { event.preventDefault(); tooltip.hide(); } } event.dataTransfer.setData('type', this.gatetype); const offsetX = event.clientX - this.getBoundingClientRect().left; const offsetY = event.clientY - this.getBoundingClientRect().top; event.dataTransfer.setData('offsetX', offsetX); event.dataTransfer.setData('offsetY', offsetY); event.dataTransfer.setData('movable', this.movable); event.dataTransfer.setData('id', this.id); this.hideContextMenu(); if (this.movable) { this.style.opacity = '0.001'; } } handleDragEnd(event) { event.preventDefault(); if (this.movable) { this.style.opacity = ''; } } handleContextMenu(event) { event.preventDefault(); if (this.movable) { if (this.contextMenu.style.display === 'none') { this.showContextMenu(event); } else { this.hideContextMenu(); } } } calculateOutput() {} changeInput1() { this.input1 = !this.input1; } changeInput2() { this.input2 = !this.input2; } showContextMenu(event) { const gateElements = this.widget.getGateElements(); gateElements.forEach((gate) => { gate.hideContextMenu(); }); if (this.contextMenu) { if (this.movable === true) { const zoom = this.widget.zoom; const gateX = this.getBoundingClientRect().x; const gateY = this.getBoundingClientRect().y; const svgCanvas = this.widget.wsDrag; const offsetX = getMouseCoordinates(svgCanvas, event.clientX, event.clientY, zoom).x; const offsetY = getMouseCoordinates(svgCanvas, event.clientX, event.clientY, zoom).y; this.style.zIndex = '30'; this.contextMenu.style.display = 'block'; this.contextMenu.style.left = `${offsetX}px`; this.contextMenu.style.top = `${offsetY}px`; this.contextMenu.style.transform = `scale(${1 / zoom})`; } } } hideContextMenu() { if (this.contextMenu) { this.contextMenu.style.display = 'none'; this.style.zIndex = '10'; } } deleteGate() { const workspace = document.querySelector('webwriter-logic-circuit') as LukaswwLogicgates; const gateElements = workspace.getGateElements(); const lineElements = workspace.getLineElements(); if (workspace.isDrawingLine) { workspace.isDrawingLine = false; workspace.svgPathToMouse.setAttribute('d', ''); workspace.startConnector = null; } gateElements.forEach((gate, index) => { if (this === gate) { this.remove(); gateElements.splice(index, 1); } }); const pathsToDelete = lineElements?.filter( (line) => line.start === this.conIn1 || line.start === this.conIn2 || line.start === this.conOut || line.start === this.conOut2 || line.end === this.conIn1 || line.end === this.conIn2 || line.end === this.conOut || line.end === this.conOut2 ); let lineElementsUpdated = []; workspace.lineElements.forEach((line) => { if (pathsToDelete.includes(line)) { line.lineSVG.remove(); let consArr: string[] = this.widget.reflectCons.split(",") let index: number = -1 for(let i = 0; i<consArr.length; i++){ if(consArr[i].includes(line.start.id) && consArr[i].includes(line.end.id)){ index = i } }; consArr.splice(index,1) this.widget.reflectCons = consArr.toString() } else { lineElementsUpdated.push(line); } }); workspace.lineElements = lineElementsUpdated; let consArr: string[] = this.widget.reflectGates.split(",") let index: number = -1 for(let i = 0; i<consArr.length; i++){ if(Number.parseInt(consArr[i][0]) === Number.parseInt(this.id.match(/^([a-zA-Z]+)(\d+)$/)[2])){ index = i } }; consArr.splice(index,1) this.widget.reflectGates = consArr.toString() this.hideContextMenu(); } createConnectors(slot, n) { if (this.movable) { const connector = this.widget.shadowRoot.createElement('connector-element') as ConnectorElement; connector.style.position = 'absolute'; connector.widget = this.widget switch (n) { case 0: connector.id = this.id + 'In1'; connector.type = 'input'; connector.style.left = '-7%'; connector.style.top = '44%'; this.conIn1 = connector; break; case 1: connector.id = this.id + 'In1'; connector.type = 'input'; connector.style.left = '-7%'; connector.style.top = '25%'; this.conIn1 = connector; break; case 2: connector.id = this.id + 'In2'; connector.type = 'input'; connector.style.left = '-7%'; connector.style.top = '63%'; this.conIn2 = connector; break; case 3: connector.id = this.id + 'Out'; connector.type = 'output'; connector.style.left = '94%'; connector.style.top = '44%'; this.conOut = connector; break; case 4: connector.id = this.id + 'Out2'; connector.type = 'output'; connector.style.left = '94%'; connector.style.top = '23%'; this.conOut = connector; break; case 5: connector.id = this.id + 'Out3'; connector.type = 'output'; connector.style.left = '94%'; connector.style.top = '64%'; this.conOut2 = connector; } slot.appendChild(connector); } } updateConnectorColor() { let connectorDot; let con; const slotIn1 = this.shadowRoot.querySelector('.gate').querySelector("slot[name='con1']"); const slotIn2 = this.shadowRoot.querySelector('.gate').querySelector("slot[name='con2']"); const slotOut = this.shadowRoot.querySelector('.gate').querySelector("slot[name='con3']"); const slotOut2 = this.shadowRoot.querySelector('.gate').querySelector("slot[name='con4']"); if (this.gatetype === 'INPUT') { if (this.output === true) { this.shadowRoot.querySelector('.gate').classList.add('gateTrue'); } else { this.shadowRoot.querySelector('.gate').classList.remove('gateTrue'); } } if (this.widget.simulate === true) { if (this.gatetype == 'OUTPUT') { if (this.input1 === true) { this.shadowRoot.querySelector('.gate').classList.add('gateTrue'); this.requestUpdate(); } else { this.shadowRoot.querySelector('.gate').classList.remove('gateTrue'); } } if (slotOut) { connectorDot = slotOut.querySelector('connector-element').shadowRoot.querySelector('.connector').children[0]; con = slotOut.querySelector('connector-element'); if (connectorDot && this.output === true) { connectorDot.classList.add('dotTrue'); con.value = true; } else if (connectorDot) { connectorDot.classList.remove('dotTrue'); con.value = false; } } if (slotOut2) { connectorDot = slotOut2.querySelector('connector-element').shadowRoot.querySelector('.connector').children[0]; con = slotOut2.querySelector('connector-element'); if (connectorDot && this.output === true) { connectorDot.classList.add('dotTrue'); con.value = true; } else if (connectorDot) { connectorDot.classList.remove('dotTrue'); con.value = false; } } if (slotIn1) { connectorDot = slotIn1.querySelector('connector-element').shadowRoot.querySelector('.connector').children[0]; con = slotIn1.querySelector('connector-element'); if (connectorDot && this.input1 === true) { connectorDot.classList.add('dotTrue'); con.value = true; } else if (connectorDot) { connectorDot.classList.remove('dotTrue'); con.value = false; } } if (slotIn2) { connectorDot = slotIn2.querySelector('connector-element').shadowRoot.querySelector('.connector').children[0]; con = slotIn2.querySelector('connector-element'); if (connectorDot && this.input2 === true) { connectorDot.classList.add('dotTrue'); con.value = true; } else if (connectorDot) { connectorDot.classList.remove('dotTrue'); con.value = false; } } } } resetConnectorColor() { let connector; let con; const slotIn1 = this.shadowRoot.querySelector('.gate').querySelector("slot[name='con1']"); const slotIn2 = this.shadowRoot.querySelector('.gate').querySelector("slot[name='con2']"); const slotOut = this.shadowRoot.querySelector('.gate').querySelector("slot[name='con3']"); const slotOut2 = this.shadowRoot.querySelector('.gate').querySelector("slot[name='con4']"); if (this.gatetype === 'OUTPUT') { this.shadowRoot.querySelector('.gate').classList.remove('gateTrue'); } if (slotOut) { connector = slotOut.querySelector('connector-element').shadowRoot.querySelector('.connector').children[0]; con = slotOut.querySelector('connector-element'); if (connector) { connector.classList.remove('dotTrue'); connector.classList.remove('dotError'); con.value = false; } } if (slotOut2) { connector = slotOut2.querySelector('connector-element').shadowRoot.querySelector('.connector').children[0]; con = slotOut2.querySelector('connector-element'); if (connector) { connector.classList.remove('dotTrue'); connector.classList.remove('dotError'); con.value = false; } } if (slotIn1) { connector = slotIn1.querySelector('connector-element').shadowRoot.querySelector('.connector').children[0]; con = slotIn1.querySelector('connector-element'); if (connector) { connector.classList.remove('dotTrue'); connector.classList.remove('dotError'); con.value = false; } } if (slotIn2) { connector = slotIn2.querySelector('connector-element').shadowRoot.querySelector('.connector').children[0]; con = slotIn2.querySelector('connector-element'); if (connector) { connector.classList.remove('dotTrue'); connector.classList.remove('dotError'); con.value = false; } } } truthTableHTML() {} flipGate() { if ((this.shadowRoot.getElementById('flipCheckbox') as SlMenuItem).checked) { this.showTruthTable = false; } else { this.showTruthTable = true; } this.hideContextMenu(); } renderTruthTable() { const truthTable = this.truthTableHTML(); const rows = this.shadowRoot.querySelectorAll('tr'); rows.forEach((row) => { row.querySelectorAll('td').forEach((td) => { td.classList.remove('highlight'); }); }); const widget = this.widget const highlightedRow = (Number(this.input1) << 1) | Number(this.input2); if (widget.simulate === true) { if (rows[highlightedRow]) { let row = rows[highlightedRow] as HTMLTableRowElement; row.querySelectorAll('td').forEach((td) => { td.classList.add('highlight'); }); if (this.gatetype === 'SPLITTER') { row = rows[highlightedRow + 1] as HTMLTableRowElement; } row.querySelectorAll('td').forEach((td) => { td.classList.add('highlight'); }); } } return html` <div class="flippedGate">${truthTable}</div> `; } }