Implement list import/export
This commit is contained in:
3 files changed
+158
-7
No files matched your search
@@ -0,0 +1,45 @@
|
||||
import styles from './style.css';
|
||||
|
||||
class ContextMenu {
|
||||
constructor (parent) {
|
||||
this.parent = parent;
|
||||
|
||||
this.root = document.createElement('div');
|
||||
this.root.className = styles.contextMenu;
|
||||
}
|
||||
|
||||
_onmousedown (e) {
|
||||
if (!this.root.contains(e.target)) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
add (option) {
|
||||
const item = document.createElement('button');
|
||||
item.className = styles.contextMenuItem;
|
||||
item.textContent = option.text;
|
||||
item.addEventListener('click', () => {
|
||||
this.destroy();
|
||||
option.callback();
|
||||
});
|
||||
item.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
this.root.appendChild(item);
|
||||
}
|
||||
|
||||
show (mouseEvent) {
|
||||
document.addEventListener('mousedown', this._onmousedown.bind(this));
|
||||
const layersRect = this.parent.layersRect;
|
||||
const x = mouseEvent.clientX - layersRect.left + 1;
|
||||
const y = mouseEvent.clientY - layersRect.top + 1;
|
||||
this.root.style.transform = `translate(${x}px, ${y}px)`;
|
||||
this.parent._addLayer(this.root);
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.root.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export default ContextMenu;
|
||||
@@ -1,3 +1,4 @@
|
||||
import ContextMenu from './context-menu';
|
||||
import styles from './style.css';
|
||||
|
||||
class Monitor {
|
||||
@@ -80,6 +81,18 @@ class Monitor {
|
||||
return label;
|
||||
}
|
||||
|
||||
getTarget () {
|
||||
if (this.targetId) {
|
||||
return this.parent.vm.runtime.getTargetById(this.targetId);
|
||||
}
|
||||
return this.parent.vm.runtime.getTargetForStage();
|
||||
}
|
||||
|
||||
getVmVariable () {
|
||||
const target = this.getTarget();
|
||||
return target.variables[this.id];
|
||||
}
|
||||
|
||||
update (monitor) {
|
||||
// TODO: don't touch if not changed
|
||||
this.x = monitor.get('x');
|
||||
@@ -144,13 +157,7 @@ class VariableMonitor extends Monitor {
|
||||
}
|
||||
|
||||
setVariableValue (value) {
|
||||
let target;
|
||||
if (this.targetId) {
|
||||
target = this.parent.vm.runtime.getTargetById(this.targetId);
|
||||
} else {
|
||||
target = this.parent.vm.runtime.getTargetForStage();
|
||||
}
|
||||
const variable = target.variables[this.id];
|
||||
const variable = this.getVmVariable();
|
||||
variable.value = value;
|
||||
this._value = value;
|
||||
this.valueElement.textContent = value;
|
||||
@@ -254,6 +261,10 @@ class ListMonitor extends Monitor {
|
||||
this.root.appendChild(this.label);
|
||||
this.root.appendChild(this.rowsOuter);
|
||||
this.root.appendChild(this.footer);
|
||||
|
||||
this.handleImport = this.handleImport.bind(this);
|
||||
this.handleExport = this.handleExport.bind(this);
|
||||
this.root.addEventListener('contextmenu', this._oncontextmenu.bind(this));
|
||||
}
|
||||
|
||||
_onscroll (e) {
|
||||
@@ -270,6 +281,68 @@ class ListMonitor extends Monitor {
|
||||
this.updateValue(this.value);
|
||||
}
|
||||
|
||||
|
||||
_oncontextmenu (e) {
|
||||
e.preventDefault();
|
||||
const menu = new ContextMenu(this.parent);
|
||||
menu.add({
|
||||
text: 'import',
|
||||
callback: this.handleImport
|
||||
});
|
||||
menu.add({
|
||||
text: 'export',
|
||||
callback: this.handleExport
|
||||
});
|
||||
menu.show(e);
|
||||
}
|
||||
|
||||
handleImport () {
|
||||
const fileSelector = document.createElement('input');
|
||||
fileSelector.type = 'file';
|
||||
fileSelector.accept = '.txt,.csv,.tsv';
|
||||
fileSelector.style.display = 'none';
|
||||
document.body.appendChild(fileSelector);
|
||||
fileSelector.addEventListener('change', (e) => {
|
||||
const files = e.target.files;
|
||||
if (files.length === 0) return;
|
||||
const file = files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const text = reader.result;
|
||||
// TODO: this doesn't exactly match Scratch behavior
|
||||
const lines = text.split(/\r?\n/);
|
||||
this.setValue(lines);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
fileSelector.click();
|
||||
}
|
||||
|
||||
handleExport () {
|
||||
const value = this.getValue();
|
||||
const exported = value.join('\n');
|
||||
const blob = new Blob([exported], {
|
||||
type: 'text/plain'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.download = `${this.getLabel()}.txt`;
|
||||
link.href = url;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
|
||||
getValue () {
|
||||
return this.getVmVariable().value;
|
||||
}
|
||||
|
||||
setValue (value) {
|
||||
const variable = this.getVmVariable();
|
||||
variable.value = value;
|
||||
this.updateValue(value);
|
||||
}
|
||||
|
||||
getRow () {
|
||||
if (this.rows.length) {
|
||||
return this.rows.pop();
|
||||
|
||||
@@ -211,3 +211,36 @@
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
color: black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 0px 5px 1px hsla(0, 0%, 0%, 0.15);
|
||||
min-width: 130px;
|
||||
}
|
||||
.context-menu-item {
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font-family: inherit;
|
||||
font-size: 0.85em;
|
||||
padding: 8px 12px;
|
||||
transition: .1s background-color, .1s color;
|
||||
}
|
||||
.context-menu-item:focus {
|
||||
background-color: #ff4c4c55;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
.context-menu-item:hover,
|
||||
.context-menu-item:active {
|
||||
color: white;
|
||||
background-color: #ff4c4c;
|
||||
}
|
||||
Reference in new issue
Block a user