Skip to content

Instantly share code, notes, and snippets.

@rksm
Last active May 27, 2020 23:30
Show Gist options
  • Save rksm/8dbae37ca879219b910334ff9ec35863 to your computer and use it in GitHub Desktop.
Save rksm/8dbae37ca879219b910334ff9ec35863 to your computer and use it in GitHub Desktop.
import { Color, Line, Point, pt, rect, Rectangle, Transform } from "lively.graphics";
import { string, fun, promise, tree, Path as PropertyPath } from "lively.lang";
import { signal } from "lively.bindings";
import { copy, deserializeSpec, ExpressionSerializer } from "lively.serializer2";
export class Morph {
static get properties() {
return {
name: { group: "core" },
draggable: {
group: "interaction",
isStyleProp: true,
defaultValue: true,
},
grabbable: {
group: "interaction",
isStyleProp: true,
defaultValue: false,
set(bool) {
// Since grabbing is implemented via dragging we also need to make
// this morph draggable
if (bool && !this.draggable) this.draggable = true;
this.setProperty("grabbable", bool);
},
},
};
}
addMorph(submorph, insertBeforeMorph) {
// insert at right position in submorph list, according to insertBeforeMorph
const submorphs = this.submorphs;
const insertBeforeMorphIndex = insertBeforeMorph ? submorphs.indexOf(insertBeforeMorph) : -1;
const insertionIndex =
insertBeforeMorphIndex === -1 ? submorphs.length : insertBeforeMorphIndex;
return this.addMorphAt(submorph, insertionIndex);
}
addMorphAt(submorph, index) {
// ensure it's a morph or a spec
if (!submorph || typeof submorph !== "object")
throw new Error(`${submorph} cannot be added as a submorph to ${this}`);
// sanity check
if (submorph.isMorph) {
if (submorph.isAncestorOf(this)) {
this.env.world.logError(
new Error(
`addMorph: Circular relationships between morphs not allowed\ntried to add ${submorph} to ${this}`
)
);
return null;
}
if (submorph === this) {
this.env.world.logError(new Error(`addMorph: Trying to add itself as a submorph: ${this}`));
return null;
}
}
if (!submorph.isMorph) submorph = morph(submorph);
var existingIndex = this.submorphs.indexOf(submorph);
if (existingIndex > -1 && existingIndex === index) return;
this.addMethodCallChangeDoing(
{
target: this,
selector: "addMorphAt",
args: [submorph, index],
undo: {
target: this,
selector: "removeMorph",
args: [submorph],
},
},
() => {
var prevOwner = submorph.owner,
submorphs = this.submorphs,
tfm;
if (prevOwner && prevOwner !== this) {
// since morph transforms are local to a morphs owner we need to
// compute a new transform for the morph inside the new owner so that the
// morphs does not appear to change its position / rotation / scale
tfm = submorph.transformForNewOwner(this);
submorph.remove();
}
if (submorph._env !== this._env) submorph._env = this._env;
// modify the submorphs array
index = Math.min(submorphs.length, Math.max(0, index));
// is the morph already in submorphs? Remove it and fix index
if (existingIndex > -1) {
submorphs.splice(existingIndex, 1);
if (existingIndex < index) index--;
}
submorphs.splice(index, 0, submorph);
// set new owner
submorph._owner = this;
submorph._cachedPaths = {};
if (tfm) submorph.setTransform(tfm);
submorph.requestStyling();
this._morphicState["submorphs"] = submorphs;
this._submorphOrderChanged = true;
this.makeDirty();
submorph.resumeSteppingAll();
submorph.withAllSubmorphsDo((ea) => ea.onOwnerChanged(this));
}
);
return submorph;
}
}
/*global: global, System*/
import { arr, obj, string, Path, promise } from "lively.lang";
import { evalCodeTransform } from "./eval-support.js";
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// eval
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function _eval(__lvEvalStatement, __lvVarRecorder /*needed as arg for capturing*/, __lvOriginalCode) {
return eval(__lvEvalStatement);
}
/**
* The main function where all eval options are configured.
* options can be: {
* runtime: {
* modules: {[MODULENAME: PerModuleOptions]}
* }
* }
*/
function runEval(code, options, thenDo) {
// 1. In case we rewrite the code with on-start and on-end calls we prepare
// the environment with actual function handlers that will get called once
// the code is evaluated
const evalDone = promise.deferred();
const recorder = options.topLevelVarRecorder || getGlobal();
const originalSource = code;
let returnedValue, returnedError;
// 2. Transform the code to capture top-level variables, inject function calls, ...
try {
code = evalCodeTransform(code, options);
} catch (e) {
console.warn(result.addWarning("lively.vm evalCodeTransform not working: " + e));
}
// 3. Now really run eval!
try {
returnedValue = _eval.call(
options.context,
code,
options.topLevelVarRecorder,
options.originalSource || originalSource
);
} catch (e) {
returnedError = e;
}
// 4. Wrapping up: if we inject a on-eval-end call we let it handle the
// wrap-up, otherwise we firectly call finishEval()
if (options.wrapInStartEndCall) {
if (returnedError && !onEvalEndCalled) recorder[endEvalFunctionName](returnedError, undefined);
} else {
finishEval(returnedError, returnedError || returnedValue, result, options, recorder, evalDone, thenDo);
}
return options.sync ? result : evalDone.promise;
}
import { arr, obj, graph } from "lively.lang";
import { parse, query } from "lively.ast";
import { computeRequireMap } from "./dependencies.js";
import { moduleSourceChange } from "./change.js";
import { scheduleModuleExportsChange, runScheduledExportChanges } from "./import-export.js";
import { livelySystemEnv } from "./system.js";
import { Package } from "./packages/package.js";
import { isURL } from "./url-helpers.js";
import { emit, subscribe } from "lively.notifications";
import { defaultClassToFunctionConverterName } from "lively.vm";
import { runtime as classRuntime } from "lively.classes";
import { ImportInjector, GlobalInjector, ImportRemover } from "./import-modification.js";
import { _require, _resolve } from "./nodejs.js";
export var detectModuleFormat = (function () {
const esmFormatCommentRegExp = /['"]format (esm|es6)['"];/,
cjsFormatCommentRegExp = /['"]format cjs['"];/,
// Stolen from SystemJS
esmRegEx = /(^\s*|[}\);\n]\s*)(import\s+(['"]|(\*\s+as\s+)?[^"'\(\)\n;]+\s+from\s+['"]|\{)|export\s+\*\s+from\s+["']|export\s+(\{|default|function|class|var|const|let|async\s+function))/;
return (source, metadata) => {
if (metadata && metadata.format) {
if (metadata.format == "es6") metadata.format == "esm";
return metadata.format;
}
if (
esmFormatCommentRegExp.test(source.slice(0, 5000)) ||
(!cjsFormatCommentRegExp.test(source.slice(0, 5000)) && esmRegEx.test(source))
)
return "esm";
return "global";
};
})();
export default function module(System, moduleName, parent) {
var sysEnv = livelySystemEnv(System),
id = System.decanonicalize(moduleName, parent);
return sysEnv.loadedModules[id] || (sysEnv.loadedModules[id] = new ModuleInterface(System, id));
}
// ModuleInterface is primarily used to provide an API that integrates the System
// loader state with lively.modules extensions.
// It does not hold any mutable state.
class ModuleInterface {
constructor(System, id) {
// We assume module ids to be a URL with a scheme
if (!isURL(id) && !/^@/.test(id))
throw new Error(
`ModuleInterface constructor called with ${id} that does not seem to be a fully normalized module id.`
);
this.System = System;
this.id = id;
// Under what variable name the recorder becomes available during module
// execution and eval
this.recorderName = "__lvVarRecorder";
this.sourceAccessorName = "__lvOriginalCode";
this._recorder = null;
// cached values
this._source = null;
this._ast = null;
this._scope = null;
this._observersOfTopLevelState = [];
this._evaluationsInProgress = 0;
this._evalId = 1;
this.createdAt = this.lastModifiedAt = new Date();
subscribe("lively.modules/modulechanged", (data) => {
if (data.module === this.id) this.reset();
});
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// properties
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
source() {
// returns Promise<string>
// rk 2016-06-24:
// We should consider using lively.resource here. Unfortunately
// System.fetch (at least with the current systemjs release) will not work in
// all cases b/c modules once loaded by the loaded get cached and System.fetch
// returns "" in those cases
//
// cs 2016-08-06:
// Changed implementation, so it uses System.resource to be consistent
// with module loading
if (this.id === "@empty") return Promise.resolve("");
if (this._source) return Promise.resolve(this._source);
return this.System.resource(this.id)
.read()
.then((source) => (this._source = source));
}
setSource(source) {
/* rms 3.1.17: There appear to be situations where the module system tries to
set the source to the transformed form instead of the original ES6 format.
I have not traced down the reason for this, but this check will prevent that
a module's transformed source will be set to its original source by scanning
the source string for the recorder and sourceAccessor variables. */
if (
this.sourceAccessorName &&
this.recorderName &&
source.includes("var " + this.recorderName) &&
source.includes("var " + this.sourceAccessorName)
)
return;
if (this._source === source) return;
this.reset();
this._source = source;
}
async ast() {
if (this._ast) return this._ast;
return (this._ast = parse(await this.source()));
}
async scope() {
if (this._scope) return this._scope;
const ast = await this.ast();
return (this._scope = query.topLevelDeclsAndRefs(ast).scope);
}
async resolvedScope() {
return (this._scope = query.resolveReferences(await this.scope()));
}
metadata() {
var load = this.System.loads ? this.System.loads[this.id] : null;
return load ? load.metadata : null;
}
addMetadata(addedMeta) {
let { System, id } = this,
oldMeta = this.metadata(),
meta = oldMeta ? Object.assign(oldMeta, addedMeta) : addedMeta;
System.config({ meta: { [id]: meta } });
return System.meta[id];
}
format() {
// assume esm by default
var meta = this.metadata();
if (meta && meta.format) return meta.format;
if (this._source) return detectModuleFormat(this._source);
return "global";
}
setFormat(format) {
// assume esm by default
return this.addMetadata({ format });
}
reset() {
this._source = null;
this._ast = null;
this._scope = null;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// loading
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
get() {
// opts = {format, instrument}
var { id, System } = this;
return System.get(id);
}
async load(opts) {
// opts = {format, instrument}
var { id, System } = this;
opts && this.addMetadata(opts);
return System.get(id) || (await System.import(id));
}
isLoaded() {
return !!this.System.get(this.id);
}
unloadEnv() {
this._recorder = null;
this._observersOfTopLevelState = [];
// FIXME this shouldn't be necessary anymore....
delete livelySystemEnv(this.System).loadedModules[this.id];
}
unloadDeps(opts) {
opts = obj.merge({ forgetDeps: true, forgetEnv: true }, opts);
this.dependents().forEach((ea) => {
this.System.delete(ea.id);
if (this.System.loads) delete this.System.loads[ea.id];
if (opts.forgetEnv) ea.unloadEnv();
});
}
async unload(opts) {
opts = { reset: true, forgetDeps: true, forgetEnv: true, ...opts };
let { System, id } = this;
if (opts.reset) this.reset();
if (opts.forgetDeps) this.unloadDeps(opts);
this.System.delete(id);
if (System.loads) {
delete System.loads[id];
}
if (System.meta) delete System.meta[id];
if (opts.forgetEnv) this.unloadEnv();
let cache = System._livelyModulesTranslationCache;
if (cache) await cache.deleteCachedData(id);
emit("lively.modules/moduleunloaded", { module: this.id }, Date.now(), this.System);
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// imports and exports
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
async imports() {
return query.imports(await this.scope());
}
async exports() {
return query.exports(await this.scope());
}
async addImports(specs) {
var source = await this.source();
for (let spec of specs) {
var fromModule = module(this.System, spec.from || spec.moduleId),
fromPackage = fromModule.package(),
importData = {
exported: spec.exported || spec.local,
moduleId: fromModule.id,
packageName: fromPackage.name,
packageURL: fromPackage.url,
pathInPackage: fromModule.pathInPackage(),
},
alias = spec.local,
{ newSource: source, standAloneImport } = ImportInjector.run(
this.System,
this.id,
this.package(),
source,
importData,
alias
);
}
await this.changeSource(source);
}
async bindingPathFor(nameOfRef) {
let decl = await this._localDeclForName(nameOfRef);
if (decl) return await this._resolveImportedDecl(decl);
}
async bindingPathForExport(name) {
await this.resolvedScope();
const exports = await this.exports(),
ex = exports.find((e) => e.exported === name);
if (ex.fromModule) {
const imM = module(this.System, ex.fromModule, this.id);
const decl = { decl: ex.node, id: ex.declId };
decl.declModule = this;
return [decl].concat(await imM.bindingPathForExport(ex.imported));
} else {
return this._resolveImportedDecl({
decl: ex.decl,
id: ex.declId,
declModule: ex && ex.decl ? this : null,
});
}
}
async bindingPathForRefAt(pos) {
const decl = await this._localDeclForRefAt(pos);
if (decl) return await this._resolveImportedDecl(decl);
const [imDecl, id, name] = await this._importForNSRefAt(pos);
if (!imDecl) return [];
const imM = module(this.System, imDecl.source.value, this.id);
return [{ decl: imDecl, declModule: this, id }].concat(await imM.bindingPathForExport(name));
}
async definitionForRefAt(pos) {
const path = await this.bindingPathForRefAt(pos);
return path.length < 1 ? null : path[path.length - 1].decl;
}
}
var gui = global.window.nwDispatcher.requireNwGui();
var createPeer = require('./peer');
var receiver = require('./receiver');
function captureScreen(context) {
gui.Screen.chooseDesktopMedia(
["window","screen"],
function(streamId) {
var vid_constraint = {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: streamId,
maxWidth: 1920,
maxHeight: 1080,
minFrameRate: 5,
maxFrameRate: 24
},
optional:[]
};
navigator.webkitGetUserMedia(
{audio:false, video: vid_constraint},
function(stream) {
receiver.whenCreated(function(receiverPeer) {
self.peer.call(receiverPeer.id, stream);
})
// var vid = document.getElementById('video_1');
// vid.src = URL.createObjectURL(stream);
// vid.play();
// stream.onended = function() { console.log("Ended"); };
},
function(error) { console.log('failure',error); });
});
}
var self = module.exports = {
peer: null,
create: function(context) {
if (self.peer) self.peer.close();
self.peer = createPeer(context.Peer, "sender");
// unload
function unload() { self.peer.close(); }
self.peer.on("close", function() {
window.removeEventListener('unload', unload);
});
window.addEventListener('unload', unload);
return self.peer;
},
captureScreen: captureScreen
};
var createPeer = require('./peer');
var whenCreatedCallbacks = [];
var self = module.exports = {
window: null,
peer: null,
mark: function(evt) {
var t = evt.target;
if (!t) return;
var marker = t.ownerDocument.createElement("div");
marker.style.width = "10px";
marker.style.height = "10px";
marker.style.backgroundColor = "red";
marker.style.position = "absolute";
var relX = evt.pageX - evt.layerX;
var relY = evt.pageY - evt.layerY;
var x = t.getBoundingClientRect().left + evt.layerX
var y = t.getBoundingClientRect().top + evt.layerY
var x = evt.layerX
var y = evt.layerY
var x = relX
var y = relY
marker.style.left = x + "px";
marker.style.top = y + "px";
t.parentNode.appendChild(marker);
setTimeout(function() {
t.parentNode.removeChild(marker);
}, 800);
},
create: function(context) {
if (self.peer) self.peer.close();
self.peer = createPeer(context.Peer, "receiver");
self.peer.on("call", function(call) { call.answer(); });
self.peer.on("stream", function(stream, call) {
if (!self.window) {
console.warn("cannot display call b/c no receiver window!");
return;
}
var doc = context.document;
var vid = doc.createElement("video");
vid.className = "video-screen";
vid.src = context.URL.createObjectURL(stream);
doc.querySelector("#video-root").appendChild(vid);
vid.width = doc.documentElement.clientWidth - 100;
vid.play();
vid.addEventListener("click", function(evt) {
self.mark(evt);
console.log("clicked");
})
});
// unload
function unload() { self.peer.close(); }
self.peer.on("close", function() {
self.peer.removeAllListeners("call");
self.peer.removeAllListeners("stream");
window.removeEventListener('unload', unload);
})
window.addEventListener('unload', unload);
var cbs = whenCreatedCallbacks;
whenCreatedCallbacks = [];
setTimeout(function() {
cbs.forEach(function(ea) {
try { ea.call(null, self.peer); } catch (e) { console.error(e); }
});
}, 0);
return self.peer;
},
whenCreated: function(callback) {
if (self.peer) callback(self.peer);
else whenCreatedCallbacks.push(callback);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment