Beispiel #1
0
  const navigateTo = location => viewers => {
    const uri = readInputURL(location);
    const navigate = !isPinned(active(viewers)) ? loadURI(uri) :
                     compose(openTab(uri), clearActiveInput);

    return navigate(viewers);
  };
Beispiel #2
0
 }, items.sortBy(id).map(webView => WebView.render(webView.id, webView, {
     onOpen, onOpenBg, onClose,
     edit: compose(edit, In(items.indexOf(webView)))
 })))
Beispiel #3
0
define((require, exports, module) => {

  'use strict';

  const Component = require('omniscient');
  const {DOM} = require('react');
  const {compose, throttle, curry} = require('lang/functional');
  const {Editable} = require('common/editable');
  const {KeyBindings} = require('common/keyboard');
  const ClassSet = require('common/class-set');
  const os = require('common/os');
  const {WindowBar} = require('./window-bar');
  const {LocationBar} = require('./location-bar');
  const {Suggestions} = require('./suggestion-box');
  const {Previews} = require('./preview-box');
  const {WebViewBox, WebView} = require('./web-view');
  const {Dashboard} = require('./dashboard');
  const {readDashboardNavigationTheme} = require('./dashboard/actions');
  const {activate: activateStrip, readInputURL, sendEventToChrome,
         deactivate, writeSession, resetSession, resetSelected} = require('./actions');
  const {indexOfSelected, indexOfActive, isActive, active, selected,
         selectNext, selectPrevious, select, activate,
         reorder, reset, remove, insertBefore,
         isntPinned, isPinned} = require('./deck/actions');
  const {readTheme} = require('./theme');
  const {Main} = require('./main');

  const editWith = edit => {
    if (typeof(edit) !== "function") {
      throw TypeError("Must be a function")
    }
    return submit => submit(edit);
  }

  const onNavigation = KeyBindings({
    'accel l': editWith(LocationBar.enter),
    'accel t': editWith(Editable.focus)
  });

  const onTabStripKeyDown = KeyBindings({
    'control tab': editWith(activateStrip),
    'control shift tab': editWith(activateStrip),
    'meta shift ]': editWith(activateStrip),
    'meta shift [': editWith(activateStrip),
    'meta t': editWith(activateStrip),
  });
  const onTabStripKeyUp = KeyBindings({
    'control': editWith(deactivate),
    'meta': editWith(deactivate)
  });

  let onViewerBinding;
  {
    const modifier = os.platform() == 'linux' ? 'alt' : 'accel';

    onViewerBinding = KeyBindings({
      'accel =': editWith(WebView.zoomIn),
      'accel -': editWith(WebView.zoomOut),
      'accel 0': editWith(WebView.zoomReset),
      [`${modifier} left`]: editWith(WebView.goBack),
      [`${modifier} right`]: editWith(WebView.goForward),
      'escape': editWith(WebView.stop),
      'accel r': editWith(WebView.reload),
      'F5': editWith(WebView.reload),
    });
  };

  const loadURI = (uri, viewer) => viewers => {
    const target = viewer || active(viewers);
    return viewers.mergeIn([viewers.indexOf(target)],
                           {uri, isFocused: true});
  }

  const openTab = uri => items =>
    insertBefore(items,
                 WebView.open({uri,
                               isSelected: true,
                               isFocused: true,
                               isActive: true}),
                 isntPinned);

  const openTabBg = uri => items =>
    insertBefore(items, WebView.open({uri}), isntPinned);

  const clearActiveInput = viewers =>
    viewers.setIn([indexOfActive(viewers), 'userInput'], '');

  const navigateTo = location => viewers => {
    const uri = readInputURL(location);
    const navigate = !isPinned(active(viewers)) ? loadURI(uri) :
                     compose(openTab(uri), clearActiveInput);

    return navigate(viewers);
  };

  // If closing viewer, replace it with a fresh one & select it.
  // This avoids code branching down the pipe that otherwise will
  // need to deal with 0 viewer & no active viewer case.
  const close = p => items =>
    !isPinned(items.find(p)) ? remove(items, p) : items;

  const closeTab = id =>
    close(x => x.get('id') == id);


  const switchTab = (items, to) =>
    to ? activate(select(items, tab => tab === to)) : items;

  switchTab.toIndex = index => items => switchTab(items, items.get(index));
  switchTab.toLast = items => switchTab(items, items.last());
  switchTab.toDashboard = switchTab.toIndex(0);


  let onTabSwitch;
  {
    const modifier = os.platform() == 'darwin' ? 'meta' : 'alt';

    onTabSwitch = KeyBindings({
      [`${modifier} 1`]: editWith(switchTab.toIndex(1)),
      [`${modifier} 2`]: editWith(switchTab.toIndex(2)),
      [`${modifier} 3`]: editWith(switchTab.toIndex(3)),
      [`${modifier} 4`]: editWith(switchTab.toIndex(4)),
      [`${modifier} 5`]: editWith(switchTab.toIndex(5)),
      [`${modifier} 6`]: editWith(switchTab.toIndex(6)),
      [`${modifier} 7`]: editWith(switchTab.toIndex(7)),
      [`${modifier} 8`]: editWith(switchTab.toIndex(8)),
      [`${modifier} 9`]: editWith(switchTab.toLast),
    });
  };

  const onDeckBinding = KeyBindings({
    'accel t': editWith(switchTab.toDashboard),
    'accel w': editWith(close(isActive)),
    'control tab': editWith(selectNext),
    'control shift tab': editWith(selectPrevious),
    'meta shift ]': editWith(selectNext),
    'meta shift [': editWith(selectPrevious),
    'ctrl pagedown': editWith(selectNext),
    'ctrl pageup': editWith(selectPrevious),
  });

  const onDeckBindingRelease = KeyBindings({
    'control': editWith(compose(reorder, activate)),
    'meta': editWith(compose(reorder, activate))
  });

  const onBrowserBinding = KeyBindings({
    'accel shift backspace': editWith(resetSession),
    'accel shift s': editWith(writeSession),
    'accel u': edit => edit(state =>
      state.updateIn('webViews', openTab(`data:application/json,${JSON.stringify(root, null, 2)}`)))
  });

  const In = (...path) => edit => state =>
    state.updateIn(path, edit);

  // Browser is a root component for our application that just delegates
  // to a core sub-components here.
  const Browser = Component('Browser', (state, {step: edit}) => {
    const webViews = state.get('webViews');

    const editWebViews = compose(edit, In('webViews'));
    const editSelectedWebView = compose(edit, In('webViews',
                                                indexOfSelected(webViews)));
    const editTabStrip = compose(edit, In('tabStrip'));
    const editInput = compose(edit, In('input'));
    const editRfa = compose(edit, In('rfa'));
    const editDashboard = compose(edit, In('dashboard'));
    const editSuggestions = compose(edit, In('suggestions'));

    const selectedWebView = selected(webViews);
    const activeWebView = active(webViews);
    const tabStrip = state.get('tabStrip');
    const input = state.get('input');
    const rfa = state.get('rfa');
    const dashboard = state.get('dashboard');
    const suggestions = state.get('suggestions');
    const isDocumentFocused = state.get('isDocumentFocused');

    const isDashboardActive = activeWebView.get('uri') === null;
    const isLocationBarActive = input.get('isFocused');
    const isTabStripActive = tabStrip.get('isActive');

    const isTabStripVisible = isDashboardActive ||
                              (isTabStripActive && !isLocationBarActive);

    const isTabstripkillzoneVisible = (
      // Show when tabstrip is visible, except on dashboard
      (isTabStripActive && !isDashboardActive) ||
      // Also show when Awesomebar is active
      isLocationBarActive
    );

    const theme = isDashboardActive ?
      readDashboardNavigationTheme(dashboard) :
      Browser.readTheme(activeWebView);


    return DOM.div({
      key: 'root',
    }, [Main({
      key: 'main',
      windowTitle: selectedWebView.title || selectedWebView.uri,
      scrollGrab: true,
      className: ClassSet({
        'moz-noscrollbars': true,
        isdark: theme.isDark,
        windowFocused: isDocumentFocused,
        showtabstrip: isTabStripVisible,
        scrollable: !input.get('isFocused') && !isTabStripVisible
      }),
      onDocumentUnload: event => writeSession(state),
      onDocumentFocus: event => edit(state => state.set('isDocumentFocused', true)),
      onDocumentBlur: event => edit(state => state.set('isDocumentFocused', false)),
      onDocumentKeyDown: compose(onNavigation(editInput),
                                 onTabStripKeyDown(editTabStrip),
                                 onViewerBinding(editSelectedWebView),
                                 onDeckBinding(editWebViews),
                                 onTabSwitch(editWebViews),
                                 onBrowserBinding(edit)),
      onDocumentKeyUp: compose(onTabStripKeyUp(editTabStrip),
                               onDeckBindingRelease(editWebViews)),
      onAppUpdateAvailable: event =>
        edit(state => state.set('appUpdateAvailable', true)),
      onRuntimeUpdateAvailable: event =>
        edit(state => state.set('runtimeUpdateAvailable', true)),
    }, [
      WindowBar({
        key: 'navigation',
        input,
        tabStrip,
        theme,
        rfa,
        suggestions,
        isDocumentFocused,
        webView: selectedWebView,
      }, {
        onNavigate: location => editWebViews(navigateTo(location)),
        editTabStrip,
        editSelectedWebView,
        editRfa,
        editInput,
        editSuggestions
      }),
      Previews.render(Previews({
        items: webViews,
        style: theme.tabstrip
      }), {
        onMouseLeave: event => editWebViews(compose(reorder, reset)),
        onSelect: id => editWebViews(items => select(items, item => item.get('id') == id)),
        onActivate: id => editWebViews(items => activate(items, item => item.get('id') == id)),
        onClose: id => editWebViews(closeTab(id)),
        edit: editWebViews
      }),
      Suggestions.render({
        key: 'awesomebar',
        isLocationBarActive,
        suggestions,
        theme
      }, {
        onOpen: uri => editWebViews(navigateTo(uri))
      }),
      DOM.div({
        key: 'tabstripkillzone',
        className: ClassSet({
          tabstripkillzone: true,
          'tabstripkillzone-hidden': !isTabstripkillzoneVisible
        }),
        onMouseEnter: event => {
          editWebViews(reset);
          editTabStrip(deactivate);
        }
      }),
      Dashboard({
        key: 'dashboard',
        dashboard,
        hidden: !isDashboardActive
      }, {
        onOpen: uri => editWebViews(openTab(uri)),
        edit: editDashboard
      }),
      WebViewBox.render('web-view-box', WebViewBox({
        isActive: !isDashboardActive,
        items: webViews,
      }), {
        onClose: id => editWebViews(closeTab(id)),
        onOpen: uri => editWebViews(openTab(uri)),
        onOpenBg: uri => editWebViews(openTabBg(uri)),
        edit: editWebViews
      })
    ]),
    DOM.div({
      key: 'appUpdateBanner',
      className: ClassSet({
        appupdatebanner: true,
        active: state.get('appUpdateAvailable') ||
                state.get('runtimeUpdateAvailable')
      }),
    }, [
      'Hey! An update just for you!',
      DOM.div({
        key: 'appUpdateButton',
        className: 'appupdatebutton',
        onClick: e => {
          if (state.get('runtimeUpdateAvailable') && state.get('appUpdateAvailable')) {
            // FIXME: Not supported yet
            sendEventToChrome('clear-cache-and-restart')
          }
          if (state.get('runtimeUpdateAvailable') && !state.get('appUpdateAvailable')) {
            // FIXME: Not supported yet
            sendEventToChrome('restart')
          }
          if (!state.get('runtimeUpdateAvailable') && state.get('appUpdateAvailable')) {
            sendEventToChrome('clear-cache-and-reload')
          }
        }
      }, 'Apply' + (state.get('runtimeUpdateAvailable') ? ' (restart required)' : ''))
    ])]);
  })
  // Create a version of readTheme that will return from cache
  // on repeating calls with an equal cursor.
  Browser.readTheme = Component.cached(readTheme);

  // Exports:

  exports.Browser = Browser;

});
Beispiel #4
0
  const Browser = Component('Browser', (state, {step: edit}) => {
    const webViews = state.get('webViews');

    const editWebViews = compose(edit, In('webViews'));
    const editSelectedWebView = compose(edit, In('webViews',
                                                indexOfSelected(webViews)));
    const editTabStrip = compose(edit, In('tabStrip'));
    const editInput = compose(edit, In('input'));
    const editRfa = compose(edit, In('rfa'));
    const editDashboard = compose(edit, In('dashboard'));
    const editSuggestions = compose(edit, In('suggestions'));

    const selectedWebView = selected(webViews);
    const activeWebView = active(webViews);
    const tabStrip = state.get('tabStrip');
    const input = state.get('input');
    const rfa = state.get('rfa');
    const dashboard = state.get('dashboard');
    const suggestions = state.get('suggestions');
    const isDocumentFocused = state.get('isDocumentFocused');

    const isDashboardActive = activeWebView.get('uri') === null;
    const isLocationBarActive = input.get('isFocused');
    const isTabStripActive = tabStrip.get('isActive');

    const isTabStripVisible = isDashboardActive ||
                              (isTabStripActive && !isLocationBarActive);

    const isTabstripkillzoneVisible = (
      // Show when tabstrip is visible, except on dashboard
      (isTabStripActive && !isDashboardActive) ||
      // Also show when Awesomebar is active
      isLocationBarActive
    );

    const theme = isDashboardActive ?
      readDashboardNavigationTheme(dashboard) :
      Browser.readTheme(activeWebView);


    return DOM.div({
      key: 'root',
    }, [Main({
      key: 'main',
      windowTitle: selectedWebView.title || selectedWebView.uri,
      scrollGrab: true,
      className: ClassSet({
        'moz-noscrollbars': true,
        isdark: theme.isDark,
        windowFocused: isDocumentFocused,
        showtabstrip: isTabStripVisible,
        scrollable: !input.get('isFocused') && !isTabStripVisible
      }),
      onDocumentUnload: event => writeSession(state),
      onDocumentFocus: event => edit(state => state.set('isDocumentFocused', true)),
      onDocumentBlur: event => edit(state => state.set('isDocumentFocused', false)),
      onDocumentKeyDown: compose(onNavigation(editInput),
                                 onTabStripKeyDown(editTabStrip),
                                 onViewerBinding(editSelectedWebView),
                                 onDeckBinding(editWebViews),
                                 onTabSwitch(editWebViews),
                                 onBrowserBinding(edit)),
      onDocumentKeyUp: compose(onTabStripKeyUp(editTabStrip),
                               onDeckBindingRelease(editWebViews)),
      onAppUpdateAvailable: event =>
        edit(state => state.set('appUpdateAvailable', true)),
      onRuntimeUpdateAvailable: event =>
        edit(state => state.set('runtimeUpdateAvailable', true)),
    }, [
      WindowBar({
        key: 'navigation',
        input,
        tabStrip,
        theme,
        rfa,
        suggestions,
        isDocumentFocused,
        webView: selectedWebView,
      }, {
        onNavigate: location => editWebViews(navigateTo(location)),
        editTabStrip,
        editSelectedWebView,
        editRfa,
        editInput,
        editSuggestions
      }),
      Previews.render(Previews({
        items: webViews,
        style: theme.tabstrip
      }), {
        onMouseLeave: event => editWebViews(compose(reorder, reset)),
        onSelect: id => editWebViews(items => select(items, item => item.get('id') == id)),
        onActivate: id => editWebViews(items => activate(items, item => item.get('id') == id)),
        onClose: id => editWebViews(closeTab(id)),
        edit: editWebViews
      }),
      Suggestions.render({
        key: 'awesomebar',
        isLocationBarActive,
        suggestions,
        theme
      }, {
        onOpen: uri => editWebViews(navigateTo(uri))
      }),
      DOM.div({
        key: 'tabstripkillzone',
        className: ClassSet({
          tabstripkillzone: true,
          'tabstripkillzone-hidden': !isTabstripkillzoneVisible
        }),
        onMouseEnter: event => {
          editWebViews(reset);
          editTabStrip(deactivate);
        }
      }),
      Dashboard({
        key: 'dashboard',
        dashboard,
        hidden: !isDashboardActive
      }, {
        onOpen: uri => editWebViews(openTab(uri)),
        edit: editDashboard
      }),
      WebViewBox.render('web-view-box', WebViewBox({
        isActive: !isDashboardActive,
        items: webViews,
      }), {
        onClose: id => editWebViews(closeTab(id)),
        onOpen: uri => editWebViews(openTab(uri)),
        onOpenBg: uri => editWebViews(openTabBg(uri)),
        edit: editWebViews
      })
    ]),
    DOM.div({
      key: 'appUpdateBanner',
      className: ClassSet({
        appupdatebanner: true,
        active: state.get('appUpdateAvailable') ||
                state.get('runtimeUpdateAvailable')
      }),
    }, [
      'Hey! An update just for you!',
      DOM.div({
        key: 'appUpdateButton',
        className: 'appupdatebutton',
        onClick: e => {
          if (state.get('runtimeUpdateAvailable') && state.get('appUpdateAvailable')) {
            // FIXME: Not supported yet
            sendEventToChrome('clear-cache-and-restart')
          }
          if (state.get('runtimeUpdateAvailable') && !state.get('appUpdateAvailable')) {
            // FIXME: Not supported yet
            sendEventToChrome('restart')
          }
          if (!state.get('runtimeUpdateAvailable') && state.get('appUpdateAvailable')) {
            sendEventToChrome('clear-cache-and-reload')
          }
        }
      }, 'Apply' + (state.get('runtimeUpdateAvailable') ? ' (restart required)' : ''))
    ])]);
  })
Beispiel #5
0
 onMouseLeave: event => editWebViews(compose(reorder, reset)),
Beispiel #6
0
 return DOM.div(options, items.sortBy(id).map(item => WebViewer({
   key: item.get('id'),
   state: item,
 }, {onOpen, onClose, edit: compose(edit, In(items.indexOf(item)))})));
 }, items.sortBy(id).map(webView => WebView.render(webView.id, webView, {
   onOpen, onOpenBg, onClose,
   beginVisit, endVisit, changeIcon, changeTitle, changeImage,
   edit: compose(edit, In(items.indexOf(webView)))
 })))
Beispiel #8
0
 onClick: event => editInput(compose(Editable.selectAll, Editable.focus))}, [
Beispiel #9
0
LocationBar.render = Component(function LocationBarView(state, handlers) {
  const {input, tabStrip, webView, suggestions, theme} = state;
  const {onNavigate, editTabStrip, onGoBack, editSelectedWebView,
         editInput, editSuggestions} = handlers;

  const isInputFocused = input.isFocused;

  return DOM.div({
    style: mix(styleLocationBar, theme.locationBar),
    onMouseEnter: event => editTabStrip(Previews.activate)
  }, [
    DOM.div({
      style: mix(styleButton,
                 {left: 0},
                 theme.backButton,
                 !webView.canGoBack && styleDisabledButton),
      key: 'back',
      onClick: event => editSelectedWebView(WebView.goBack)
    }, '\uf053'), // UTF8 "back" icon
    Editable.renderField({
      key: 'input',
      style: mix(styleUrlInput,
                 theme.urlInput,
                 !isInputFocused && styleCollapse),
      placeholder: 'Search or enter address',
      value: Suggestions.selected(suggestions) || webView.userInput,
      type: 'text',
      submitKey: 'Enter',
      isFocused: input.isFocused,
      selectionStart: input.selectionStart,
      selectionEnd: input.selectionEnd,
      selectionDirection: input.selectionDirection,
      onFocus: event => {
        LocationBar.suggest(event.target.value, editSuggestions);
        editInput(Editable.focus);
      },
      onBlur: event => {
        editSuggestions(Suggestions.reset);
        editInput(Editable.blur);
      },
      onSelect: event => editInput(Editable.select(event.target)),
      onChange: event => {
        // Reset suggestions & compute new ones from the changed input value.
        editSuggestions(Suggestions.unselect);
        LocationBar.suggest(event.target.value, editSuggestions);
        // Also reflect changed value onto webViews useInput.
        editSelectedWebView(viewer => viewer.set('userInput', event.target.value));
      },
      onSubmit: event => {
        editSuggestions(Suggestions.reset);
        onNavigate(event.target.value);
      },
      onKeyDown: compose(onInputNavigation(editInput, editSelectedWebView),
                         onSuggetionNavigation(editSuggestions))
    }),
    DOM.p({key: 'page-info',
           style: mix(stylePageSummary,
                      theme.pageInfoText,
                      isInputFocused && styleCollapse),
           onClick: event => editInput(compose(Editable.selectAll, Editable.focus))}, [
      DOM.span({
        key: 'securityicon',
        style: {
          fontFamily: 'FontAwesome',
          fontWeight: 'normal',
          marginRight: 6,
          verticalAlign: 'middle'
        }
      }, isPrivileged(webView.uri) ? '\uf013' :  // Gear
         webView.securityState == 'secure' ? '\uf023' : ''), // Lock
      DOM.span({
        key: 'location',
        style: theme.locationText,
        style: {
          fontWeight: 'bold'
        }
      },
      webView.uri ? getDomainName(webView.uri) : ''),
      DOM.span({
        key: 'title',
        style: mix(theme.titleText, {padding: 5})
      },
      webView.title ? webView.title :
             webView.isLoading ? 'Loading...' :
             webView.uri ? webView.uri :
             'New Tab')
    ]),
    DOM.div({key: 'reload-button',
             style: mix(styleButton,
                        theme.reloadButton,
                        {right: 0},
                        webView.isLoading && {display:'none'},
                        !webView.uri && styleDisabledButton),
             onClick: event => editSelectedWebView(WebView.reload)
    }, '\uf01e'), // UTF8 "reload" icon
    DOM.div({key: 'stop-button',
             style: mix(styleButton,
                        theme.stopButton,
                        {right: 0},
                        !webView.isLoading && {display:'none'}),
             onClick: event => editSelectedWebView(WebView.stop)
    }, '\uf00d') // UTF8 "stop" icon
])});
Beispiel #10
0
const Suggestions = require('./Suggestions');
const {Editable} = require('common/editable');
const WebView = require('./WebView');
const Previews = require('./Previews');
const {getDomainName} = require('common/url-helper');
const {KeyBindings} = require('common/keyboard');
const {mix} = require('common/style');
const {isPrivileged} = require('common/url-helper');

// Model
const LocationBar = function(state) {
  return Object.assign({}, state, {input: Editable(state.input)})
}

// Actions
LocationBar.enter = compose(Editable.selectAll, Editable.focus)
LocationBar.suggest = throttle(Suggestions.compute, 200)
LocationBar.select = Editable.select
LocationBar.selectAll = Editable.selectAll
LocationBar.focus = Editable.focus
LocationBar.blur = Editable.blur

// Style

const styleLocationBar = {
  display: 'inline-block',
  position: 'relative',
  MozWindowDragging: 'no-drag',
  borderRadius: 3,
  lineHeight: '30px',
  width: 460, // FIXME :Doesn't shrink when window is narrow
Beispiel #11
0
const insert = (items, item, n) => {
  const edit = compose(items => include(items, item, n),
                       isActive(item) ? deactivate : identity,
                       isSelected(item) ? deselect : identity);
  return edit(items);
}
Beispiel #12
0
                               previous);

// Make an item following a `selected` item `selected`.
const selectNext = advance(switchSelected,
                           selected,
                           next);

// Makes `active` item `selected`.
const reset = items => select(items, isActive);

// Makes `selected` item `active`.
const activate = (items, p=isSelected) =>
  switchActive(items, active(items), items.find(p));

// Makes an item following a `selected` item both `selected` & `active`.
const activateNext = compose(activate, selectNext);
// Makes an item leading a `selected` item both `selected` & `active`.
const activatePrevious = compose(activate, selectPrevious);


// Item is considered pinned if it's `isPinned` is logical `true`.
const isPinned = item => item.get('isPinned');
const isntPinned = item => !item.get('isPinned');

// Updates `items` such that most recently active item will
// follow the last pinned item.
const reorder = items => {
  const item = active(items);
  return isPinned(item) ? items :
         include(items.remove(items.indexOf(item)),
                 item,