-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #193 from Strilanc/dev
Dev
- Loading branch information
Showing
13 changed files
with
824 additions
and
317 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
import DetailedError from "src/base/DetailedError.js" | ||
|
||
/** | ||
* An observable sequence of events. | ||
*/ | ||
class Observable { | ||
/** | ||
* @param {!function(!function(T):void): (!function(void):void)} subscribe | ||
* @template T | ||
*/ | ||
constructor(subscribe) { | ||
/** | ||
* @type {!(function(!(function(T): void)): !(function(void): void))} | ||
* @template T | ||
* @private | ||
*/ | ||
this._subscribe = subscribe; | ||
} | ||
|
||
/** | ||
* @param {!function(T):void} observer | ||
* @returns {!function(void):void} unsubscriber | ||
* @template T | ||
*/ | ||
subscribe(observer) { | ||
return this._subscribe(observer); | ||
} | ||
|
||
/** | ||
* @param {T} items | ||
* @returns {!Observable.<T>} An observable that immediately forwards all the given items to any new subscriber. | ||
* @template T | ||
*/ | ||
static of(...items) { | ||
return new Observable(observer => { | ||
for (let item of items) { | ||
observer(item); | ||
} | ||
return () => {}; | ||
}); | ||
} | ||
|
||
/** | ||
* Subscribes to the receiving observable for a moment and returns any collected items. | ||
* @returns {!Array.<T>} | ||
* @template T | ||
*/ | ||
snapshot() { | ||
let result = []; | ||
let unsub = this.subscribe(e => result.push(e)); | ||
unsub(); | ||
return result; | ||
} | ||
|
||
/** | ||
* @param {!function(TIn) : TOut} transformFunc | ||
* @returns {!Observable.<TOut>} An observable with the same items, but transformed by the given function. | ||
* @template TIn, TOut | ||
*/ | ||
map(transformFunc) { | ||
return new Observable(observer => this.subscribe(item => observer(transformFunc(item)))); | ||
} | ||
|
||
/** | ||
* @returns {!Observable.<T>} An observable that forwards all the items from all the observables observed by the | ||
* receiving observable of observables. | ||
* @template T | ||
*/ | ||
flatten() { | ||
return new Observable(observer => { | ||
let unsubs = []; | ||
unsubs.push(this.subscribe(observable => unsubs.push(observable.subscribe(observer)))); | ||
return () => { | ||
for (let unsub of unsubs) { | ||
unsub() | ||
} | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* @param {!HTMLElement|!HTMLDocument} element | ||
* @param {!string} eventKey | ||
* @returns {!Observable.<*>} An observable corresponding to an event fired from an element. | ||
*/ | ||
static elementEvent(element, eventKey) { | ||
return new Observable(observer => { | ||
element.addEventListener(eventKey, observer); | ||
return () => element.removeEventListener(eventKey, observer); | ||
}); | ||
} | ||
|
||
/** | ||
* | ||
* @param {!int} count | ||
* @returns {!Observable.<T> | ||
* @template T | ||
*/ | ||
skip(count) { | ||
return new Observable(observer => { | ||
let remaining = count; | ||
return this.subscribe(item => { | ||
if (remaining > 0) { | ||
remaining -= 1; | ||
} else { | ||
observer(item); | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
/** | ||
* @returns {!Observable.<T>} An observable with the same events, but filtering out any event value that's the same | ||
* as the previous one. | ||
* @template T | ||
*/ | ||
whenDifferent(equater = undefined) { | ||
let eq = equater || ((e1, e2) => e1 === e2); | ||
return new Observable(observer => { | ||
let hasLast = false; | ||
let last = undefined; | ||
return this.subscribe(item => { | ||
if (!hasLast || !eq(last, item)) { | ||
last = item; | ||
hasLast = true; | ||
observer(item); | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
class ObservableSource { | ||
constructor() { | ||
/** | ||
* @type {!Array.<!function(T):void>} | ||
* @private | ||
* @template T | ||
*/ | ||
this._observers = []; | ||
/** | ||
* @type {!Observable.<T>} | ||
* @private | ||
* @template T | ||
*/ | ||
this._observable = new Observable(observer => { | ||
// HACK: not re-entrant safe! | ||
this._observers.push(observer); | ||
let didRun = false; | ||
return () => { | ||
if (!didRun) { | ||
didRun = true; | ||
this._observers.splice(this._observers.indexOf(observer), 1); | ||
} | ||
}; | ||
}); | ||
} | ||
|
||
/** | ||
* @returns {!Observable.<T>} | ||
* @template T | ||
*/ | ||
observable() { | ||
return this._observable; | ||
} | ||
|
||
/** | ||
* @param {T} eventValue | ||
* @template T | ||
*/ | ||
send(eventValue) { | ||
// HACK: not re-entrant safe! | ||
for (let obs of this._observers) { | ||
obs(eventValue); | ||
} | ||
} | ||
} | ||
|
||
class ObservableValue { | ||
/** | ||
* @param {T=undefined} initialValue | ||
* @template T | ||
*/ | ||
constructor(initialValue=undefined) { | ||
this._value = initialValue; | ||
this._source = new ObservableSource(); | ||
this._observable = new Observable(observer => { | ||
// HACK: not re-entrant safe! | ||
observer(this._value); | ||
return this._source.observable().subscribe(observer); | ||
}); | ||
} | ||
|
||
/** | ||
* @returns {!Observable} | ||
*/ | ||
observable() { | ||
return this._observable; | ||
} | ||
|
||
/** | ||
* @param {T} newValue | ||
* @template T | ||
*/ | ||
set(newValue) { | ||
this._value = newValue; | ||
this._source.send(newValue); | ||
} | ||
|
||
/** | ||
* @returns {T} The current value. | ||
* @template T | ||
*/ | ||
get() { | ||
return this._value; | ||
} | ||
} | ||
|
||
export { Observable, ObservableSource, ObservableValue } |
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** | ||
* @param {!string} name | ||
* @param {!string} content | ||
*/ | ||
function saveFile(name, content) { | ||
//noinspection JSUnresolvedVariable | ||
if (navigator.msSaveBlob) { | ||
//noinspection JSUnresolvedFunction | ||
navigator.msSaveBlob(new Blob([content], {type: 'text/html;charset=UTF-8'}), name); | ||
return; | ||
} | ||
|
||
let anchor = document.createElement("a"); | ||
//noinspection JSUnresolvedVariable,JSUnresolvedFunction | ||
anchor.href = window.URL !== undefined ? | ||
window.URL.createObjectURL(new Blob([content], {type: 'text/html;charset=UTF-8'})) : | ||
'data:application/octet-stream,' + encodeURI(moddedHtml); | ||
anchor.download = name; | ||
try { | ||
//noinspection XHTMLIncompatabilitiesJS | ||
document.body.appendChild(anchor); | ||
anchor.click(); | ||
} finally { | ||
//noinspection XHTMLIncompatabilitiesJS | ||
document.body.removeChild(anchor); | ||
} | ||
} | ||
|
||
export { saveFile }; |
Oops, something went wrong.