Last active
December 1, 2016 23:34
-
-
Save spion/1c3a945e2afe059d443fcc2800fb553e 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 {render} from 'react-dom' | |
import {observable, computed, action} from 'mobx' | |
import {observer} from 'mobx-react' | |
import * as React from 'react' | |
import {fromPromise, IPromiseBasedObservable} from 'mobx-utils' | |
import 'whatwg-fetch' | |
import {computedAsync, matchAsync} from './computed-async' | |
function delay<T>(n:number) { | |
return function(v:T) { | |
return new Promise<T>(resolve => setTimeout(resolve, n, v)) | |
} | |
} | |
class Model { | |
@observable path = '/' | |
@observable selectedFile:{name: string} = null; | |
@computedAsync get directoryContent() { | |
return fetch('/files' + this.path) | |
.then(delay(1000)) | |
.then(res => res.json()) | |
.then(res => (res as any).list) | |
} | |
@computedAsync get selectedFileContent() { | |
if (this.selectedFile) { | |
return this.directoryContent.then(files => { | |
if (files.indexOf(this.selectedFile) < 0) return null; | |
return fetch('/files' + this.path + '/' + this.selectedFile.name) | |
.then(delay(1000)) | |
.then(res => res.text()) | |
}) | |
} | |
return null; | |
} | |
@action selectFile(f:any) { | |
if (f.dir) { | |
this.path += f.name + '/'; | |
} else { | |
this.selectedFile = f; | |
} | |
} | |
@action upOneLevel() { | |
this.path = this.path.split('/').slice(0, -2).join('/') + '/' | |
} | |
} | |
@observer | |
class View extends React.Component<{model:Model}, {}> { | |
render() { | |
let model = this.props.model; | |
let pleaseSelect = <p>Please click on a file to show it here</p>; | |
return ( | |
<div> | |
<p> | |
Current directory path: {model.path} | |
<button onClick={() => model.upOneLevel()}>..</button> | |
</p> | |
{matchAsync(model.directoryContent, { | |
pending: () => <p>Loading...</p>, | |
fulfilled: (files:any[]) => | |
<div> | |
{files.map(f => <div> | |
<button onClick={() => model.selectFile(f)}>{f.name}</button> | |
</div>)} | |
</div>, | |
rejected: (e:any) => <p>Error loading directory listing</p> | |
})} | |
<div> | |
{matchAsync(model.selectedFileContent, { | |
pending: () => <p>Loading file...</p>, | |
fulfilled: (v:any) => v?<p>Content: {v.toString()}:</p>:pleaseSelect, | |
rejected: (e:any) => <p>Error loading file: {e.message}</p> | |
})} | |
</div> | |
</div> | |
) | |
} | |
} | |
render(<View model={new Model()} />, document.getElementById('app')) | |
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 {Atom, Reaction} from 'mobx' | |
interface Handlers<T, U> { | |
null?: () => U; | |
pending?: () => U; | |
fulfilled?: (t: T) => U; | |
rejected?: (e: any) => U; | |
} | |
type PromiseState = "fulfilled" | "rejected" | "pending" | |
class ObservablePromise<T> implements PromiseLike<T> { | |
private promise: PromiseLike<T>; | |
private state: PromiseState = "fulfilled" | |
private value: T = null; | |
private error: any = null; | |
private atom: Atom; | |
private reaction: Reaction; | |
constructor(private getter:() => PromiseLike<T> | T, thisObj:any) { | |
let getSetter = () => { | |
// cancel previous request, if cancellable | |
if (this.promise && typeof (this.promise as any).cancel == 'function') { | |
(this.promise as any).cancel() | |
} | |
this.promise = getter.call(thisObj) | |
if (this.promise && typeof this.promise.then === 'function') { | |
this.state = "pending" | |
this.promise.then(val => { | |
this.state = "fulfilled" | |
this.value = val; | |
this.atom.reportChanged() | |
}, err => { | |
this.state = "rejected" | |
this.error = err; | |
this.atom.reportChanged() | |
}); | |
} else { | |
this.state = "fulfilled" | |
this.value = this.promise as any; | |
} | |
this.atom.reportChanged() | |
} | |
let stopReactions = () => {} | |
this.atom = new Atom('ComputedAsync', | |
() => { | |
this.reaction = new Reaction("ComputedAsync", function () { | |
this.track(getSetter); | |
}); | |
stopReactions = this.reaction.getDisposer() | |
this.reaction.runReaction() | |
}, | |
() => { stopReactions() }); | |
} | |
get() { | |
this.atom.reportObserved() | |
return this; | |
} | |
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): Promise<TResult> { | |
return this.promise.then(onfulfilled, onrejected) as Promise<TResult> | |
} | |
catch<TResult>(handler:(reason:any) => PromiseLike<TResult> | TResult):Promise<TResult> { | |
return this.promise.then(null, handler) as Promise<TResult> | |
} | |
public [Symbol.toStringTag]:"Promise" | |
} | |
export function computedAsync<T>(target: any, name: string, desc:TypedPropertyDescriptor<Promise<T>>) { | |
let getter:ObservablePromise<T> = null; | |
return { | |
get: function() { | |
if (!getter) { | |
getter = new ObservablePromise<T>(desc.get, this) | |
} | |
return getter.get(); | |
} | |
} as TypedPropertyDescriptor<ObservablePromise<T>> | |
} | |
export function matchAsync<T, U>(p:Promise<T>, handlers: Handlers<T, U>): U { | |
let state = (p as any).state as PromiseState; | |
if (typeof state != 'string') { throw new TypeError("Not an observable promise!"); } | |
switch(state) { | |
case "pending": return handlers.pending && handlers.pending() | |
case "fulfilled": return handlers.fulfilled && handlers.fulfilled((p as any).value) | |
case "rejected": return handlers.rejected && handlers.rejected((p as any).error) | |
} | |
} |
benjamingr
commented
Nov 8, 2016
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment