const minimized = (model, result) => ( result.isOk ? [ merge(model, {isMinimized: true}), Effects.none ] : [ model, Effects.perform(Unknown.error(result.error)) ] );
const close = model => [ model, Effects.receive(Closed) ];
export const init = () => [ Indexed.create([], null), Effects.receive(Pull) ];
model => [ merge(model, { isEditing: false } ), Effects.receive(Submit(model)) ]
const selected = model => [ model , Effects.receive(Selected) ];
(model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => { switch (action.type) { case "NoOp": return [ model, Effects.none ]; case "Select": return select(model); case "Selected": return [ model, Effects.none ]; case "Unselect": return unselect(model); case "Unselected": return [ model, Effects.none ]; case "Activate": return activate(model); case "Activated": return activated(model); case "Deactivate": return deactivate(model); case "Deactivated": return deactivated(model); case "Focus": return focus(model); case "Blur": return updateShell(model, action); case "Load": return load(model, action.uri); // Dispatch case "LoadStart": return startLoad(model, action.time); case "LoadEnd": return endLoad(model, action.time); case "Connect": return connect(model, action.time); case "LocationChanged": return changeLocation(model, action.uri, action.canGoBack, action.canGoForward); case "SecurityChanged": return updateSecurity(model, action); case "TitleChanged": return updatePage(model, action); case "IconChanged": return updatePage(model, action); case "MetaChanged": return updatePage(model, action); case "FirstPaint": return updatePage(model, action); case "DocumentFirstPaint": return updatePage(model, action); case "LoadFail": return [ model , Effects.task(Unknown.warn(action)) .map(NoOp) ]; case "Close": return close(model); // Force push actions. // We forward these up to WebViews. case "PushDown": return [ model, Effects.receive(PushedDown) ]; // Animation case "SelectAnimation": return updateSelectAnimation(model, action.action); // Delegate case "Progress": return updateProgress(model, action.progress); case "Shell": return updateShell(model, action.shell); case "Page": return updatePage(model, action.page); case "Tab": return updateTab(model, action.source); case "Security": return updateSecurity(model, action.security); case "Navigation": return updateNavigation(model, action.navigation); default: return Unknown.update(model, action); } };
const cleanRestart = model => [ model , Effects .perform(Runtime.cleanRestart) .map(Report) ];
const reciveOpenURLNotification = model => [ model , Effects .task(Runtime.receive('mozbrowseropenwindow')) .map(OpenURL) ];
const reloadRuntime = model => [ model , Effects .task(Runtime.reload) .map(Reloaded) ];
const toggleFullscreen = model => [ model , Effects .perform(Runtime.toggleFullscreen) .map(FullscreenToggled) ];
export const appendFX = <model, action> ( extraFX: Effects<action> , [model, fx]:[model, Effects<action>] ):[model, Effects<action>] => [model, Effects.batch([fx, extraFX])];
const minimize = model => [ model , Effects .perform(Runtime.minimize) .map(Minimized) ];
const close = model => [ model , Effects .perform(Runtime.quit) .map(Closed) ];
const closed = (model, result) => ( result.isOk ? [ model, Effects.none ] : [ model, Effects.perform(Unknown.error(result.error)) ] );
import * as Config from '../openag-config.json'; import {start, Effects} from 'reflex'; import {Renderer} from 'reflex-virtual-dom-driver'; import * as App from './app'; // @TODO this a a temporary measure. Later we may want to replace this with // record and replay functionality. const logger = update => (model, action) => { console.log('>> action', action); const next = update(model, action); const [nextModel, nextFx] = next; console.log('<< [model, effect]', nextModel, nextFx); return next; } // Start app const application = start({ init: App.init, // If in debug mode, log all actions and effects. update: Config.debug ? logger(App.update) : App.update, view: App.view }); window.application = application; const renderer = new Renderer({target: document.body}); application.view.subscribe(renderer.address); application.task.subscribe(Effects.driver(application.address));
export const update/*:type.update*/ = (model, action) => ( action.type === 'SubmitInput' ? submitInput(model) : action.type === 'OpenWebView' ? openWebView(model) : action.type === 'OpenURL' ? openURL(model, action.uri) : action.type === 'ReceiveOpenURLNotification' ? reciveOpenURLNotification(model) : action.type === 'ExitInput' ? exitInput(model) : action.type === 'CreateWebView' ? createWebView(model) : action.type === 'EditWebView' ? editWebView(model) : action.type === 'ShowWebView' ? showWebView(model) : action.type === 'ShowTabs' ? showTabs(model) : action.type === 'SelectWebView' ? selectWebView(model) // @TODO Change this to toggle tabs instead. : action.type === 'Escape' ? showTabs(model) : action.type === 'AttachSidebar' ? attachSidebar(model) : action.type === 'DetachSidebar' ? detachSidebar(model) : action.type === 'ReloadRuntime' ? reloadRuntime(model) // Expand / Shrink animations : action.type === "Expand" ? expand(model) : action.type === "Shrink" ? shrink(model) : action.type === "ResizeAnimation" ? updateResizeAnimation(model, action.action) : action.type === "Expanded" ? expanded(model) : action.type === "Shrinked" ? shrinked(model) // Delegate to the appropriate module : action.type === 'Input' ? updateInput(model, action.source) : action.type === 'Suggest' ? updateInput ( model , Input.Suggest ( { query: model.assistant.query , match: action.source.match , hint: action.source.hint } ) ) : action.type === 'BlurInput' ? updateInput(model, action.source) : action.type === 'WebViews' ? updateWebViews(model, action.source) : action.type === 'SelectTab' ? updateWebViews(model, action.source) : action.type === 'ActivateTabByID' ? updateWebViews(model, action.source) : action.type === 'ActivateTab' ? updateWebViews(model, action.source) : action.type === 'Shell' ? updateShell(model, action.source) : action.type === 'Focus' ? updateShell(model, action.source) // Assistant : action.type === 'Assistant' ? updateAssistant(model, action.source) : action.type === 'Query' ? updateQuery(model) : action.type === 'SuggestNext' ? updateAssistant(model, Assistant.SuggestNext) : action.type === 'SuggestPrevious' ? updateAssistant(model, Assistant.SuggestPrevious) : action.type === 'Devtools' ? updateDevtools(model, action.action) : action.type === 'Sidebar' ? updateSidebar(model, action.action) : action.type === 'Overlay' ? updateOverlay(model, action.action) : action.type === 'Updater' ? updateUpdater(model, action.source) : action.type === 'Failure' ? [ model , Effects.task(Unknown.error(action.error)) ] // Ignore some actions. : action.type === 'Reloaded' ? [model, Effects.none] // TODO: Delegate to modules that need to do cleanup. : action.type === 'LiveReload' ? [model, Effects.none] : Unknown.update(model, action) );
if (isReload) { window.application.address(UI.LiveReload); } const application = start({ initial: isReload ? window.application.model.value : UI.init(), step: Config.logging ? logger(UI.update) : UI.update, view: UI.view }); const renderer = new Renderer({target: document.body}); application.view.subscribe(renderer.address); // Mozbrowser API has cerntain events that need to be handler with-in // the same tick otherwise it's not going to work. To handle those events // properly we use `Driver.force` effect that sends in special // `{type: "Driver.Execute"}` action on which we force a render to run in // the same tick. application.task.subscribe(Effects.service(action => { if (action.type === "Driver.Execute") { renderer.execute(); } else { application.address(action); } })); window.application = application;
const sendValidate = model => [ changeMode(model, VALIDATING, ''), Effects.receive(Validate(readValue(model))) ];
const restart = model => [ model , Effects .perform(Runtime.restart) .map(Report) ];
(names/*:Array<Name>*/)/*:[Model, Effects<Action>]*/ => [ null , Effects .perform(fetch(names)) .map(Fetched) ];
const cleanReload = model => [ model , Effects .perform(Runtime.cleanReload) .map(Report) ];
.map(name => Effects.perform(observe(name)).map(Updated))
export const query/*:type.query*/ = (input, limit) => Effects.task(Task.io(deliver => { }))
const updateResponse = (model, result) => ( result.isOk ? [model, Effects.none] : [model, Effects.perform(report(result.error)).map(NoOp)] );
const unselected = model => [ model , Effects.receive(Unselected) ];
(model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => { switch (action.type) { case "CanGoForwardChanged": return ( action.result.isOk ? [ merge(model, {canGoForward: action.result.value}) , Effects.none ] : [ model, Effects.perform(report(action.result.error)) ] ); case "CanGoBackChanged": return ( action.result.isOk ? [ merge(model, {canGoBack: action.result.value}) , Effects.none ] : [ model, Effects.perform(report(action.result.error)) ] ); case "LocationChanged": // In the case where LocationChanged carries information about // canGoBack and canGoForward, we update the model with the new info. // This scenario will be hit in Servo. if (action.canGoBack != null && action.canGoForward != null) { return [ merge(model , { currentURI: action.uri , canGoBack: action.canGoBack , canGoForward: action.canGoForward } ) , Effects.none ]; } // Otherwise, update the currentURI and create a task to read // canGoBack, canGoForward from the iframe. // This scenario will be hit in Gecko. return [ merge(model , { currentURI: action.uri }) , Effects.batch([ Effects .perform(canGoBack(model.id)) .map(CanGoBackChanged) , Effects .perform(canGoForward(model.id)) .map(CanGoForwardChanged) ]) ]; case "Load": return [ merge(model , { initiatedURI: action.uri , currentURI: action.uri } ), Effects.none ]; case "Stop": return [ model , Effects .perform(stop(model.id)) .map(Stopped) ]; case "Reload": return [ model , Effects .perform(reload(model.id)) .map(Reloaded) ]; case "GoBack": return [ model , Effects .perform(goBack(model.id)) .map(WentBack) ]; case "GoForward": return [ model , Effects .perform(goForward(model.id)) .map(WentForward) ]; case "Stopped": return updateResponse(model, action.stopResult); case "Reloaded": return updateResponse(model, action.reloadResult); case "WentBack": return updateResponse(model, action.goBackResult); case "WentForward": return updateResponse(model, action.goForwardResult); default: return Unknown.update(model, action); } };
(model, {id, evaluate: source}) => [ model , Effects .perform(Host.evaluate(id, source.value)) .map(Print(id, source.version)) ];
(model/*:Model*/, action/*:Action*/)/*:[Model, Effects<Action>]*/ => { switch (action.type) { case "ZoomIn": return [ model , Effects .perform ( zoomIn(model.ref, model.zoom) .map(ok) .capture(reason => Task.succeed(error(reason))) ) .map(ZoomChanged) ]; case "ZoomOut": return [ model , Effects .perform ( zoomOut(model.ref, model.zoom) .map(ok) .capture(reason => Task.succeed(error(reason))) ) .map(ZoomChanged) ]; case "ResetZoom": return [ model , Effects .perform ( resetZoom(model.ref) .map(ok) .capture(reason => Task.succeed(error(reason))) ) .map(ZoomChanged) ]; case "MakeVisible": return [ model , Effects .perform ( setVisibility(model.ref, true) .map(ok) .capture(reason => Task.succeed(error(reason))) ) .map(VisibilityChanged) ]; case "MakeNotVisible": return [ model , Effects .perform ( setVisibility(model.ref, false) .map(ok) .capture(reason => Task.succeed(error(reason))) ) .map(VisibilityChanged) ]; case "VisibilityChanged": return ( action.visibilityChanged.isOk ? updateVisibility(model, action.visibilityChanged.value) : [ model , Effects .perform(warn(action.visibilityChanged.error)) ] ); case "ZoomChanged": return ( action.zoomChanged.isOk ? updateZoom(model, action.zoomChanged.value) : [ model , Effects .perform(warn(action.zoomChanged.error)) ] ); // Delegate case "Focus": return updateFocus(model, true); case "Blur": return updateFocus(model, false); case "Panic": return [model, Effects.perform(warn(action.panic))]; default: return [model, Effects.none]; } };
export const schedule = (action, time) => Effects.task(Task.sleep(time)).map(constant(action));
(model:Model, action:Action):[Model, Effects<Action>] => { switch (action.type) { case 'GoBack': return goBack(model); case 'GoForward': return goForward(model); case 'Reload': return reload(model); case 'ZoomIn': return zoomIn(model); case 'ZoomOut': return zoomOut(model); case 'ResetZoom': return resetZoom(model); case 'Close': return close(model); case 'CloseRuntime': return closeRuntime(model); case 'OpenNewTab': return openNewTab(model); case 'EditWebView': return editWebView(model); case 'ShowWebView': return showWebView(model); case 'ShowTabs': return showTabs(model); case 'Escape': return toggleTabs(model); case 'AttachSidebar': return attachSidebar(model); case 'DetachSidebar': return detachSidebar(model); case 'ReloadRuntime': return reloadRuntime(model); case 'SelectNext': return selectNext(model); case 'SelectPrevious': return selectPrevious(model); case 'EndSelection': return endSelection(model); case 'Shell': return updateShell(model, action.source); case 'Focus': return updateShell(model, Shell.Focus); case 'Devtools': return updateDevtools(model, action.action); case 'Sidebar': return updateSidebar(model, action.action); case 'Tabs': return updateNavigators(model, action); case 'Navigators': return updateNavigators(model, action.navigators); case 'Failure': return [ model , Effects .perform(Unknown.error(action.error)) ]; case 'Closed': return closed(model, action.result); // Ignore some actions. case 'Reloaded': return [ model, Effects.none ] case 'PrintSnapshot': return [model, Effects.none]; case 'UploadSnapshot': return [model, Effects.none]; // TODO: Delegate to modules that need to do cleanup. case 'LiveReload': return [model, Effects.none]; default: return Unknown.update(model, action); } };