Skip to content

Commit

Permalink
Merge pull request #193 from Strilanc/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Strilanc authored Jun 29, 2016
2 parents fb423d0 + 384a5c9 commit 91a2a81
Show file tree
Hide file tree
Showing 13 changed files with 824 additions and 317 deletions.
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@
"url": "https://github.com/Strilanc/Quirk/issues"
},
"devDependencies": {
"grunt": "~1.0.0",
"grunt-cli": "~0.1.13",
"grunt": "~1.0.1",
"grunt-cli": "~1.2.0",
"grunt-contrib-clean": "~1.0.0",
"grunt-contrib-concat": "~1.0.0",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-uglify": "~1.0.1",
"grunt-karma": "~0.12.2",
"grunt-karma": "~2.0.0",
"grunt-traceur": "~0.5.5",
"karma": "~0.13.22",
"karma-chrome-launcher": "~0.2.2",
"karma-firefox-launcher": "~0.1.7",
"traceur": "0.0.104"
"karma": "~1.1.0",
"karma-chrome-launcher": "~1.0.1",
"karma-firefox-launcher": "~1.0.0",
"traceur": "0.0.111"
},
"scripts": {
"build": "grunt build-src",
Expand Down
219 changes: 219 additions & 0 deletions src/base/Obs.js
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 }
45 changes: 41 additions & 4 deletions src/base/Revision.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import describe from "src/base/Describe.js"
import equate from "src/base/Equate.js"
import DetailedError from "src/base/DetailedError.js"
import { ObservableSource, ObservableValue } from "src/base/Obs.js"

/**
* A simple linear revision history tracker, for supporting undo and redo functionality.
Expand All @@ -15,13 +16,34 @@ class Revision {
if (index < 0 || index >= history.length) {
throw new DetailedError("Bad index", {history, index, isWorkingOnCommit});
}
if (!Array.isArray(history)) {
throw new DetailedError("Bad history", {history, index, isWorkingOnCommit});
}

/** @type {!Array.<*>} */
this.history = history;
/** @type {!int} */
this.index = index;
/** @type {!boolean} */
this.isWorkingOnCommit = isWorkingOnCommit;
/** @type {!ObservableSource */
this._changes = new ObservableSource();
/** @type {!ObservableSource */
this._latestActiveCommit = new ObservableValue(this.history[this.index]);
}

/**
* @returns {!Observable.<*>}
*/
changes() {
return this._changes.observable();
}

/**
* @returns {!Observable.<*>}
*/
latestActiveCommit() {
return this._latestActiveCommit.observable();
}

/**
Expand Down Expand Up @@ -55,6 +77,8 @@ class Revision {
this.history = [state];
this.index = 0;
this.isWorkingOnCommit = false;
this._changes.send(state);
this._latestActiveCommit.set(state);
}

/**
Expand All @@ -64,6 +88,7 @@ class Revision {
*/
startedWorkingOnCommit() {
this.isWorkingOnCommit = true;
this._changes.send(undefined);
}

/**
Expand All @@ -73,7 +98,10 @@ class Revision {
*/
cancelCommitBeingWorkedOn() {
this.isWorkingOnCommit = false;
return this.history[this.index];
let result = this.history[this.index];
this._changes.send(result);
this._latestActiveCommit.set(result);
return result;
}

/**
Expand All @@ -82,13 +110,16 @@ class Revision {
* @returns {void}
*/
commit(newCheckpoint) {
this.isWorkingOnCommit = false;
if (newCheckpoint === this.history[this.index]) {
this.cancelCommitBeingWorkedOn();
return;
}
this.isWorkingOnCommit = false;
this.index += 1;
this.history.splice(this.index, this.history.length - this.index);
this.history.push(newCheckpoint);
this._changes.send(newCheckpoint);
this._latestActiveCommit.set(newCheckpoint);
}

/**
Expand All @@ -104,7 +135,10 @@ class Revision {
this.index -= 1;
}
this.isWorkingOnCommit = false;
return this.history[this.index];
let result = this.history[this.index];
this._changes.send(result);
this._latestActiveCommit.set(result);
return result;
}

/**
Expand All @@ -117,7 +151,10 @@ class Revision {
}
this.index += 1;
this.isWorkingOnCommit = false;
return this.history[this.index];
let result = this.history[this.index];
this._changes.send(result);
this._latestActiveCommit.set(result);
return result;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/browser/Clipboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ function selectAndCopyToClipboard(element) {
}
}

export default selectAndCopyToClipboard;
export { selectAndCopyToClipboard }
29 changes: 29 additions & 0 deletions src/browser/SaveFile.js
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 };
Loading

0 comments on commit 91a2a81

Please sign in to comment.