Last active
May 27, 2020 23:30
-
-
Save rksm/8dbae37ca879219b910334ff9ec35863 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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