UNPKG

agentscape

Version:

Agentscape is a library for creating agent-based simulations. It provides a simple API for defining agents and their behavior, and for defining the environment in which the agents interact. Agentscape is designed to be flexible and extensible, allowing

312 lines (264 loc) 9.91 kB
const DRAG_HANDLE_ICON = ` <svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="4.5" cy="2.5" r=".6" fill="#000000" /> <circle cx="4.5" cy="4.5" r=".6" fill="#000000" /> <circle cx="4.5" cy="6.499" r=".6" fill="#000000" /> <circle cx="4.5" cy="8.499" r=".6" fill="#000000" /> <circle cx="4.5" cy="10.498" r=".6" fill="#000000" /> <circle cx="4.5" cy="12.498" r=".6" fill="#000000" /> <circle cx="6.5" cy="2.5" r=".6" fill="#000000" /> <circle cx="6.5" cy="4.5" r=".6" fill="#000000" /> <circle cx="6.5" cy="6.499" r=".6" fill="#000000" /> <circle cx="6.5" cy="8.499" r=".6" fill="#000000" /> <circle cx="6.5" cy="10.498" r=".6" fill="#000000" /> <circle cx="6.5" cy="12.498" r=".6" fill="#000000" /> <circle cx="8.499" cy="2.5" r=".6" fill="#000000" /> <circle cx="8.499" cy="4.5" r=".6" fill="#000000" /> <circle cx="8.499" cy="6.499" r=".6" fill="#000000" /> <circle cx="8.499" cy="8.499" r=".6" fill="#000000" /> <circle cx="8.499" cy="10.498" r=".6" fill="#000000" /> <circle cx="8.499" cy="12.498" r=".6" fill="#000000" /> <circle cx="10.499" cy="2.5" r=".6" fill="#000000" /> <circle cx="10.499" cy="4.5" r=".6" fill="#000000" /> <circle cx="10.499" cy="6.499" r=".6" fill="#000000" /> <circle cx="10.499" cy="8.499" r=".6" fill="#000000" /> <circle cx="10.499" cy="10.498" r=".6" fill="#000000" /> <circle cx="10.499" cy="12.498" r=".6" fill="#000000" /> </svg> ` class DragPage extends HTMLElement { public headerRef: HTMLDivElement public titleRef: HTMLDivElement public dragHandleRef: HTMLDivElement private initWidth: number = 400 static get observedAttributes() { return ['heading', 'key', 'minimized', 'color'] } constructor() { super() } public connectedCallback() { const style = ` :root { width: auto; } :host { position: absolute; z-index: 9; background-color: #ffffff; text-align: center; border: 1px solid #000000; width: auto; } :host([active]) { z-index: 999; } :host([disabled]) #header { background-color: #9E9E9E; cursor: unset; } #header { display: flex; justify-content: space-between; padding: 10px; z-index: 10; background-color: #2196F3; color: #fff; } #drag-handle { width: 20px; height: 20px; cursor: move; } #drag-handle svg { width: 20px; height: 20px; } #heading { margin: 0 auto; } #controls { display: inline-flex; margin-left: 10px; } .btn { line-height: 9px; width: 10px; height: 10px; border: 1px solid black; border-radius: 3px; box-shadow: 1px 1px black; cursor: pointer; color: white; } .btn:not(:last-of-type) { margin: 0 5px 0 0; } .btn:hover { box-shadow: -1px -1px black; border: 1px solid black; color: white; } :host([disabled]) .btn, :host([disabled]) .btn:hover { border: 1px solid #616161; box-shadow: inset 1px 1px black; color: #424242; cursor: unset; } .btn:active { box-shadow: inset 1px 1px black; } ` const template = document.createElement('template') template.innerHTML = ` <style>${style}</style> <div id="header"> <div id="drag-handle">${DRAG_HANDLE_ICON}</div> <div id="heading">${this.heading}</div> <div id="controls"> <div id="minimize" class="btn">-</div> </div> </div> <slot id="content"></slot> ` const shadowRoot = this.attachShadow({mode: 'open'}) shadowRoot.appendChild(template.content.cloneNode(true)) this.headerRef = this.shadowRoot!.querySelector<HTMLDivElement>('#header')! this.titleRef = this.shadowRoot!.querySelector<HTMLDivElement>('#heading')! this.dragHandleRef = this.shadowRoot!.querySelector<HTMLDivElement>('#drag-handle')! this.restorePosition() this.dragHandleRef.addEventListener('mousedown', this.beginElementDrag) this.dragHandleRef.addEventListener('dblclick', this.handleDblCLick) this.headerRef.querySelectorAll<HTMLDivElement>('.btn').forEach( (btnEl) => btnEl.addEventListener('click', this.handleBtnClick)) // set the initial width to the width of the content this.initWidth = this.getBoundingClientRect().width // update the z-index when the element is clicked this.addEventListener('click', () => { if (this.id !== 'canvas_main') { this.style.zIndex = '3' const otherPanes = Array.from(document.getElementsByTagName('drag-pane')).filter( (el: HTMLElement) => el !== this && el.id !== 'canvas_main') otherPanes.forEach( (el: HTMLElement) => el.style.zIndex = '2') } }) } public get heading(): string { return this.getAttribute('heading') || '' } public set heading(newState: string) { this.setAttribute('heading', newState) } public get minimized(): boolean { return this.getAttribute('minimized') === 'true' || this.hasAttribute('minimized') && this.getAttribute('minimized') === '' } public set minimized(newState: boolean) { if (newState) { this.setAttribute('minimized', 'true') } else { this.removeAttribute('minimized') } } public set color(newColor: string) { this.setAttribute('color', newColor) } public get color(): string { return this.getAttribute('color') || this.defaultHeaderColor } public get key(): string|undefined { return this.getAttribute('key') || undefined } public set key(newValue: string|undefined){ if (newValue) { this.setAttribute('key', newValue) } else { this.removeAttribute('key') } } attributeChangedCallback(_name: string) { if (_name === 'minimized') { if (this.minimized) { // this.style.height = '30px' this.shadowRoot!.querySelector<HTMLDivElement>('#content')!.style.display = 'none' this.style.width = this.initWidth + 'px' } else { // this.style.height = 'auto' this.shadowRoot!.querySelector<HTMLDivElement>('#content')!.style.display = 'block' this.style.width = this.initWidth + 'px' } } } private defaultHeaderColor: string = '#2196F3' private handleBtnClick = (event: MouseEvent) => { const el = event!.target as HTMLDivElement switch (el.id) { case 'close': this.dispatchEvent(new Event('remove')) this.remove() break case 'minimize': this.minimized = !this.minimized this.dispatchEvent(new Event('toggleminimize', {bubbles: true, composed: true})) break default: break } } private handleDblCLick = () => { this.minimized = !this.minimized this.dispatchEvent(new Event('toggleminimize', {bubbles: true, composed: true})) } private pos: {x: number, y: number} = {x:0, y:0} private storePosition(){ if (this.key) { window.localStorage.setItem(`drag-pane-${this.key}`, JSON.stringify(this.pos)) } } private restorePosition(){ if (this.key) { const storeValue = window.localStorage.getItem(`drag-pane-${this.key}`) if (storeValue) { this.pos = JSON.parse(storeValue) this.style.top = this.pos.y + 'px' this.style.left = this.pos.x + 'px' } } } private didMove: boolean = false // private moveThreshold: number = 5; private beginElementDrag = (event: MouseEvent) => { event.preventDefault() this.setAttribute('active', 'true') // get the mouse cursor position at startup: this.pos.x = event.clientX this.pos.y = event.clientY document.onmouseup = this.endElementDrag // call a function whenever the cursor moves: document.onmousemove = this.elementDrag } private elementDrag = (event: MouseEvent) => { event.preventDefault() // calculate the new cursor position: const pos1 = this.pos.x - event.clientX const pos2 = this.pos.y - event.clientY this.pos.x = event.clientX this.pos.y = event.clientY this.didMove = true this.dispatchEvent(new Event('dragstart', {bubbles: true, composed: true})) // set the element's new position: const newPosition = {top: this.offsetTop - pos2, left: this.offsetLeft - pos1} this.style.top = newPosition.top + 'px' this.style.left = newPosition.left + 'px' } private endElementDrag = () => { // release element document.onmouseup = null document.onmousemove = null this.setAttribute('active', 'false') if (this.didMove) { this.storePosition() this.dispatchEvent(new Event('dragend', {bubbles: true, composed: true})) this.didMove = false } } } window.customElements.define('drag-pane', DragPage) export default DragPage