<commit_before>import deepmerge from 'deepmerge';
import isPlainObject from 'is-plain-object';

const hideSpoilers = {
  backgroundColor: 'black',
  color: 'black',
  '& a, & a:hover, & a:focus': {
    color: 'black'
  },
  '& code': {
    backgroundColor: 'black',
  }
}

const spoilerStyles = (theme) => ({
  '& p.spoiler': {
    margin: 0,
  },
  '& .spoiler': {
    padding: 8,
    pointerEvents: 'auto',
    minHeight: theme.typography.commentStyle.fontSize,
    '& .public-DraftStyleDefault-block': {
      margin: 0,
    },
    '&:not(:hover)': { // using ':not(:hover)' means we don't need to manually reset elements with special colors or backgrounds, instead they just automatically stay the same if we're not hovering
      ...hideSpoilers,
    }
  },
  // Note: ".spoiler" is the old class Oli originally used. ".spoilers" is a new class
  // that is applied in make_editable_callbacks.js to groups of adjaecent spoiler paragraphs.
  // (see the make_editable_callbacks.js file for details)
  '& div.spoilers': {
    margin: '1em 0',
    overflow: 'auto',
    '&:not(:hover)': {
      ...hideSpoilers,
    },
    '&:hover': {
      background: theme.palette.grey[100]
    }
  },
  '& p.spoiler-v2': {
    margin: 0,
    padding: '0.5em 8px'
  },
  '& .spoilers:not(:hover)::selection, & .spoilers:not(:hover) ::selection': {
    backgroundColor: 'transparent'
  },
  '& .spoilers > p:hover ~ p': {
    ...hideSpoilers
  }
})

export const postBodyStyles = (theme) => {
  return {
    ...theme.typography.body1,
    ...theme.typography.postStyle,
    wordBreak: "break-word",
    ...spoilerStyles(theme),
    '& pre': {
      ...theme.typography.codeblock
    },
    '& code': {
      ...theme.typography.code
    },
    '& blockquote': {
      ...theme.typography.blockquote,
      ...theme.typography.body1,
      ...theme.typography.postStyle
    },
    '& li': {
      ...theme.typography.body1,
      ...theme.typography.li,
      ...theme.typography.postStyle
    },
    '& h1': {
      ...theme.typography.display2,
      ...theme.typography.postStyle
    },
    '& h2': {
      ...theme.typography.display1,
      ...theme.typography.postStyle,
    },
    '& h3': {
      ...theme.typography.display1,
      ...theme.typography.postStyle,
    },
    '& h4': {
      ...theme.typography.body1,
      ...theme.typography.postStyle,
      fontWeight:600,
    },
    '& img': {
      maxWidth: "100%"
    },
    '& sup': {
      verticalAlign: 'baseline',
      top: '-0.6em',
      fontSize: '65%',
      position: 'relative'
    },
    // Used for R:A-Z imports as well as markdown-it-footnotes
    '& .footnotes': {
      marginTop: 40,
      fontSize: '0.9em',
      paddingTop: 40,
      borderTop: 'solid 1px rgba(0,0,0,0.2)',
      '& sup': {
        marginRight: 10,
      },
      '& ol': {
        marginBlockStart: '1em',
        paddingInlineStart: 0,
        marginInlineStart: '1em'
      },
      '& li': {
        fontSize: '0.9em' // Overwriting default size setting for list items
      },
    },
    // Hiding the footnote-separator that markdown-it adds by default
    '& .footnotes-sep': {
      display: 'none'
    },
    '& a, & a:hover, & a:active': {
      color: theme.palette.primary.main
    },
  }
}

export const commentBodyStyles = theme => {
  const commentBodyStyles = {
    marginTop: ".5em",
    marginBottom: ".25em",
    wordBreak: "break-word",
    ...theme.typography.body2,
    ...theme.typography.commentStyle,

    ...spoilerStyles(theme),
    '& blockquote': {
      ...theme.typography.commentBlockquote,
      ...theme.typography.body2,
      ...theme.typography.commentStyle
    },
    '& li': {
      ...theme.typography.body2,
      ...theme.typography.commentStyle
    },
    '& h1, & h2, & h3': {
      ...theme.typography.commentHeader,
      ...theme.typography.commentStyle
    },
    // spoiler styles
    // HACK FIXME: Playing with pointer events is a horrible idea in general, and probably also in this context
    // but it's the only way I was able to make this weird stuff work.
    pointerEvents: 'none',
    '& *': {
      pointerEvents: 'auto'
    },
    '& > *:hover ~ .spoiler': {
      color: 'black'
    },
    '& > *:hover ~ .spoiler:before': {
      content: '"spoiler (hover/select to reveal)"',
      color: 'white',
    },
  }
  return deepmerge(postBodyStyles(theme), commentBodyStyles, {isMergeableObject:isPlainObject})
}

export const postHighlightStyles = theme => {
  const postHighlightStyles = {
    ...theme.typography.body2,
    ...theme.typography.postStyle,
    '& blockquote': {
      ...theme.typography.body2,
      ...theme.typography.postStyle,
      '& > p': {
        margin:0
      },
    },
    '& ul': {
      paddingInlineStart: 30
    },
    '& li': {
      ...theme.typography.body2,
      ...theme.typography.postStyle,
    },
    '& h1, & h2, & h3': {
      ...theme.typography.commentHeader,
    },
  }
  return deepmerge(postBodyStyles(theme), postHighlightStyles, {isMergeableObject:isPlainObject})
}

export const ckEditorStyles = theme => {
  return {
    '& .ck': {
      '--ck-spacing-standard': `${theme.spacing.unit}px`,
      '&.ck-content': {
        marginLeft: -theme.spacing.unit,
        '--ck-focus-outer-shadow-geometry': "none",
        '--ck-focus-ring': "solid 1px rgba(0,0,0,0)",
        '--ck-focus-outer-shadow': "none",
        '--ck-inner-shadow': "none"
      },
      '&.ck-sidebar, &.ck-presence-list': { //\u25B6
        '& li': {
          // By default ckEditor elements get the styles from postBodyStyles li elements
          marginBottom: 'unset',
          fontFamily: 'unset',
          fontSize: 'unset',
          fontWeight: 'unset',
          lineHeight: 'unset'
        },
        '& .ck-comment:after': {
          display:"none"
        },
        '& .ck-annotation__info-name, & .ck-annotation__info-time, & .ck-comment__input, & .ck-thread__comment-count, & .ck-annotation__main p, & .ck-annotation__info-name, & .ck-annotation__info-time, & .ck-presence-list__counter': {
          ...commentBodyStyles(theme),
        },
        '& .ck-thread__comment-count': {
          paddingLeft: theme.spacing.unit*2,
          color: theme.palette.grey[600],
          margin: 0,
          paddingBottom: ".5em",
          '&:before': {
            content: '"\\25B6"'
          }
        },
        '& .ck-comment__input': {
          paddingLeft: theme.spacing.unit*2
        },
        '& .ck-annotation__main, & .ck-comment__input, & .ck-thread__input': {
          width : "100%"
        },
        '& .ck-comment__wrapper': {
          borderTop: 'solid 1px rgba(0,0,0,.15)',
        },
        '& .ck-annotation__info-name, & .ck-annotation__info-time': {
          color: theme.palette.grey[600],
          fontSize: "1rem"
        },
        '& .ck-annotation__user, & .ck-thread__user': {
          display: "none"
        },
        '--ck-color-comment-count': theme.palette.primary.main,
      }
    }
  }
}

export const editorStyles = (theme, styleFunction) => ({
    '& .public-DraftStyleDefault-block': {
      marginTop: '1em',
      marginBottom: '1em',
    },
    '& code .public-DraftStyleDefault-block': {
      marginTop: 0,
      marginBottom: 0,
    },
    '& blockquote .public-DraftStyleDefault-block': {
      marginTop: 0,
      marginBottom: 0,
    },
    // Using '*' selectors is a bit dangerous, as is using '!important'
    // This is necessary to catch spoiler-selectors on 'code' elemenents, as implemented in draft-js,
    // which involved nested spans with manually set style attributes, which can't be overwritten except via 'important'
    //
    // This selector isn't necessary on rendered posts/comments, just the draft-js editor.
    // To minimize potential damage from */important it's only applied here.
    '& .spoiler:not(:hover) *': {
      backgroundColor: "black !important"
    },
    ...styleFunction(theme),
    ...ckEditorStyles(theme)
})
<commit_msg>update spoiler background partial-opacity<commit_after>import deepmerge from 'deepmerge';
import isPlainObject from 'is-plain-object';

const hideSpoilers = {
  backgroundColor: 'black',
  color: 'black',
  '& a, & a:hover, & a:focus': {
    color: 'black'
  },
  '& code': {
    backgroundColor: 'black',
  }
}

const spoilerStyles = (theme) => ({
  '& p.spoiler': {
    margin: 0,
  },
  '& .spoiler': {
    padding: 8,
    pointerEvents: 'auto',
    minHeight: theme.typography.commentStyle.fontSize,
    '& .public-DraftStyleDefault-block': {
      margin: 0,
    },
    '&:not(:hover)': { // using ':not(:hover)' means we don't need to manually reset elements with special colors or backgrounds, instead they just automatically stay the same if we're not hovering
      ...hideSpoilers,
    }
  },
  // Note: ".spoiler" is the old class Oli originally used. ".spoilers" is a new class
  // that is applied in make_editable_callbacks.js to groups of adjaecent spoiler paragraphs.
  // (see the make_editable_callbacks.js file for details)
  '& div.spoilers': {
    margin: '1em 0',
    overflow: 'auto',
    '&:not(:hover)': {
      ...hideSpoilers,
    },
    '&:hover': {
      background: 'rgba(0,0,0,.12)' // This leaves a light grey background over the revealed-spoiler to make it more obvious where it started.
    }
  },
  '& p.spoiler-v2': {
    margin: 0,
    padding: '0.5em 8px'
  },
  '& .spoilers:not(:hover)::selection, & .spoilers:not(:hover) ::selection': {
    backgroundColor: 'transparent'
  },
  '& .spoilers > p:hover ~ p': {
    ...hideSpoilers
  }
})

export const postBodyStyles = (theme) => {
  return {
    ...theme.typography.body1,
    ...theme.typography.postStyle,
    wordBreak: "break-word",
    ...spoilerStyles(theme),
    '& pre': {
      ...theme.typography.codeblock
    },
    '& code': {
      ...theme.typography.code
    },
    '& blockquote': {
      ...theme.typography.blockquote,
      ...theme.typography.body1,
      ...theme.typography.postStyle
    },
    '& li': {
      ...theme.typography.body1,
      ...theme.typography.li,
      ...theme.typography.postStyle
    },
    '& h1': {
      ...theme.typography.display2,
      ...theme.typography.postStyle
    },
    '& h2': {
      ...theme.typography.display1,
      ...theme.typography.postStyle,
    },
    '& h3': {
      ...theme.typography.display1,
      ...theme.typography.postStyle,
    },
    '& h4': {
      ...theme.typography.body1,
      ...theme.typography.postStyle,
      fontWeight:600,
    },
    '& img': {
      maxWidth: "100%"
    },
    '& sup': {
      verticalAlign: 'baseline',
      top: '-0.6em',
      fontSize: '65%',
      position: 'relative'
    },
    // Used for R:A-Z imports as well as markdown-it-footnotes
    '& .footnotes': {
      marginTop: 40,
      fontSize: '0.9em',
      paddingTop: 40,
      borderTop: 'solid 1px rgba(0,0,0,0.2)',
      '& sup': {
        marginRight: 10,
      },
      '& ol': {
        marginBlockStart: '1em',
        paddingInlineStart: 0,
        marginInlineStart: '1em'
      },
      '& li': {
        fontSize: '0.9em' // Overwriting default size setting for list items
      },
    },
    // Hiding the footnote-separator that markdown-it adds by default
    '& .footnotes-sep': {
      display: 'none'
    },
    '& a, & a:hover, & a:active': {
      color: theme.palette.primary.main
    },
  }
}

export const commentBodyStyles = theme => {
  const commentBodyStyles = {
    marginTop: ".5em",
    marginBottom: ".25em",
    wordBreak: "break-word",
    ...theme.typography.body2,
    ...theme.typography.commentStyle,

    ...spoilerStyles(theme),
    '& blockquote': {
      ...theme.typography.commentBlockquote,
      ...theme.typography.body2,
      ...theme.typography.commentStyle
    },
    '& li': {
      ...theme.typography.body2,
      ...theme.typography.commentStyle
    },
    '& h1, & h2, & h3': {
      ...theme.typography.commentHeader,
      ...theme.typography.commentStyle
    },
    // spoiler styles
    // HACK FIXME: Playing with pointer events is a horrible idea in general, and probably also in this context
    // but it's the only way I was able to make this weird stuff work.
    pointerEvents: 'none',
    '& *': {
      pointerEvents: 'auto'
    },
    '& > *:hover ~ .spoiler': {
      color: 'black'
    },
    '& > *:hover ~ .spoiler:before': {
      content: '"spoiler (hover/select to reveal)"',
      color: 'white',
    },
  }
  return deepmerge(postBodyStyles(theme), commentBodyStyles, {isMergeableObject:isPlainObject})
}

export const postHighlightStyles = theme => {
  const postHighlightStyles = {
    ...theme.typography.body2,
    ...theme.typography.postStyle,
    '& blockquote': {
      ...theme.typography.body2,
      ...theme.typography.postStyle,
      '& > p': {
        margin:0
      },
    },
    '& ul': {
      paddingInlineStart: 30
    },
    '& li': {
      ...theme.typography.body2,
      ...theme.typography.postStyle,
    },
    '& h1, & h2, & h3': {
      ...theme.typography.commentHeader,
    },
  }
  return deepmerge(postBodyStyles(theme), postHighlightStyles, {isMergeableObject:isPlainObject})
}

export const ckEditorStyles = theme => {
  return {
    '& .ck': {
      '--ck-spacing-standard': `${theme.spacing.unit}px`,
      '&.ck-content': {
        marginLeft: -theme.spacing.unit,
        '--ck-focus-outer-shadow-geometry': "none",
        '--ck-focus-ring': "solid 1px rgba(0,0,0,0)",
        '--ck-focus-outer-shadow': "none",
        '--ck-inner-shadow': "none"
      },
      '&.ck-sidebar, &.ck-presence-list': { //\u25B6
        '& li': {
          // By default ckEditor elements get the styles from postBodyStyles li elements
          marginBottom: 'unset',
          fontFamily: 'unset',
          fontSize: 'unset',
          fontWeight: 'unset',
          lineHeight: 'unset'
        },
        '& .ck-comment:after': {
          display:"none"
        },
        '& .ck-annotation__info-name, & .ck-annotation__info-time, & .ck-comment__input, & .ck-thread__comment-count, & .ck-annotation__main p, & .ck-annotation__info-name, & .ck-annotation__info-time, & .ck-presence-list__counter': {
          ...commentBodyStyles(theme),
        },
        '& .ck-thread__comment-count': {
          paddingLeft: theme.spacing.unit*2,
          color: theme.palette.grey[600],
          margin: 0,
          paddingBottom: ".5em",
          '&:before': {
            content: '"\\25B6"'
          }
        },
        '& .ck-comment__input': {
          paddingLeft: theme.spacing.unit*2
        },
        '& .ck-annotation__main, & .ck-comment__input, & .ck-thread__input': {
          width : "100%"
        },
        '& .ck-comment__wrapper': {
          borderTop: 'solid 1px rgba(0,0,0,.15)',
        },
        '& .ck-annotation__info-name, & .ck-annotation__info-time': {
          color: theme.palette.grey[600],
          fontSize: "1rem"
        },
        '& .ck-annotation__user, & .ck-thread__user': {
          display: "none"
        },
        '--ck-color-comment-count': theme.palette.primary.main,
      }
    }
  }
}

export const editorStyles = (theme, styleFunction) => ({
    '& .public-DraftStyleDefault-block': {
      marginTop: '1em',
      marginBottom: '1em',
    },
    '& code .public-DraftStyleDefault-block': {
      marginTop: 0,
      marginBottom: 0,
    },
    '& blockquote .public-DraftStyleDefault-block': {
      marginTop: 0,
      marginBottom: 0,
    },
    // Using '*' selectors is a bit dangerous, as is using '!important'
    // This is necessary to catch spoiler-selectors on 'code' elemenents, as implemented in draft-js,
    // which involved nested spans with manually set style attributes, which can't be overwritten except via 'important'
    //
    // This selector isn't necessary on rendered posts/comments, just the draft-js editor.
    // To minimize potential damage from */important it's only applied here.
    '& .spoiler:not(:hover) *': {
      backgroundColor: "black !important"
    },
    ...styleFunction(theme),
    ...ckEditorStyles(theme)
})
<|endoftext|>


<commit_before>var m = require('mithril');
var $ = require('jquery');
var $osf = require('js/osfHelpers');
var waterbutler = require('js/waterbutler');
var bootbox = require('bootbox');
var makeClient = require('js/clipboard.js');

var util = require('./util.js');

// Helper for filtering
function TRUTHY(item) {
    return !!item; //Force cast to a bool
}

var model = {
    revisions: [],
    loaded: m.prop(false),
    errorMessage: undefined,
    hasUser: false,
    hasDate: false,
    hasHashes: false,
    selectedRevision: 0
};

var FileRevisionsTable = {
    controller: function(file, node, enableEditing, canEdit) {
        var self = {};
        self.node = node;
        self.file = file;
        self.canEdit = canEdit;
        self.enableEditing = enableEditing;
        self.baseUrl = (window.location.href).split('?')[0];

        model.hasHashes = self.file.provider === 'osfstorage';
        model.hasDate = self.file.provider !== 'dataverse';

        self.reload = function() {
            model.loaded(false);
            m.redraw();
            $.ajax({
                dataType: 'json',
                async: true,
                url: self.file.urls.revisions,
                beforeSend: $osf.setXHRAuthorization
            }).done(function(response) {
                m.startComputation();
                var urlParmas = $osf.urlParams();
                model.revisions = response.data.map(function(rev, index) {
                    rev = FileRevisionsTable.postProcessRevision(self.file, self.node, rev, index);
                    if (urlParmas[rev.versionIdentifier] === rev.version) {
                        model.selectedRevision = index;
                    }
                    return rev;
                });
                model.loaded(true);
                // Can only edit the latest version of a file
                if (model.selectedRevision === 0) {
                    self.enableEditing();
                }
                model.hasUser = model.revisions[0] && model.revisions[0].extra && model.revisions[0].extra.user;
                m.endComputation();
            }).fail(function(response) {
                m.startComputation();
                model.loaded(true);
                model.errorMessage = response.responseJSON ?
                    response.responseJSON.message || 'Unable to fetch versions' :
                    'Unable to fetch versions';
                m.endComputation();

                // model.errorMessage(err);

                if (self.file.provider === 'figshare') {
                    // Hack for Figshare
                    // only figshare will error on a revisions request
                    // so dont allow downloads and set a fake current version
                    $.ajax({
                        method: 'GET',
                        url: self.file.urls.metadata,
                        beforeSend: $osf.setXHRAuthorization
                    }).done(function(resp) {
                        self.canEdit(self.canEdit() && resp.data.extra.canDelete);
                        m.redraw();
                    }).fail(function(xhr) {
                        self.canEdit(false);
                        m.redraw();
                    });
                }
            });
        };

        self.showModal = function(hashName, index, hash) {
            var showModal = bootbox.dialog({
                title: 'Version ' + index + ' ' + hashName + ' hash',
                message: '
  ' +
                    '
' +
                    '
' +'
' + hash + '
' +
                    ' Copy '+
                    '

'
            });
            showModal.on('show.bs.modal', function() {
                var $copyBtn = $('#copyBtn');
                new makeClient($copyBtn);
            });
            showModal.modal('show');
        };

        self.getTableHead = function() {
            return m('thead', [
                m('tr', [
                    m('th', 'Version ID'),
                    model.hasDate ? m('th', 'Date') : false,
                    model.hasUser ? m('th', 'User') : false,
                    model.hasHashes ? m('th', 'Hashes') : false,
                    m('th[colspan=2]', 'Download'),
                ].filter(TRUTHY))
            ]);
        };

        self.makeTableRow = function(revision, index) {
            var isSelected = index === model.selectedRevision;

            return m('tr' + (isSelected ? '.active' : ''), [
                m('td',  isSelected ? revision.displayVersion :
                  m('a', {href: parseInt(revision.displayVersion) === model.revisions.length ? self.baseUrl : revision.osfViewUrl}, revision.displayVersion)
                ),
                model.hasDate ? m('td', revision.displayDate) : false,
                model.hasUser ?
                    m('td', revision.extra.user.url ?
                            m('a', {href: revision.extra.user.url}, revision.extra.user.name) :
                            revision.extra.user.name
                    ) : false,
                model.hasHashes ? m('td',
                    m( 'a.btn.btn-primary.btn-xs', {
                        onclick: function() {
                            self.showModal('MD5', revision.displayVersion, revision.displayMd5);
                        }
                    }, m('td', 'MD5') ),
                    m( 'a.btn.btn-primary.btn-xs', {
                        onclick: function() {
                            self.showModal('SHA256', revision.displayVersion, revision.displaySha256);
                        }
                    }, m('td', 'SHA2') )
                ) : false,
                m('td', revision.extra.downloads > -1 ? m('.badge', revision.extra.downloads) : ''),
                m('td',
                    m('a.btn.btn-primary.btn-sm.file-download', {
                        href: revision.osfDownloadUrl,
                        onclick: function() {
                            window.location = revision.waterbutlerDownloadUrl;
                            return false;
                        }
                    }, m('i.fa.fa-download'))
                ),
            ].filter(TRUTHY));
        };

        if (!model.loaded()) {
            self.reload();
        }
        $(document).on('fileviewpage:reload', self.reload);
        return self;
    },
    view: function(ctrl) {
        return m('#revisionsPanel.panel.panel-default', [
                m('.panel-heading.clearfix', m('h3.panel-title', 'Revisions')),
                m('.panel-body', {style:{'padding-right': '0','padding-left':'0', 'padding-bottom' : '0'}}, (function() {
                    if (!model.loaded()) {
                        return util.Spinner;
                    }
                    if (model.errorMessage) {
                        return m('.alert.alert-warning', {style:{margin: '10px'}}, model.errorMessage);
                    }

                    return m('table.table',{style:{'margin-bottom': '0'}}, [
                        ctrl.getTableHead(),
                        m('tbody', model.revisions.map(ctrl.makeTableRow))
                    ]);
                })())
            ]);
    },
    postProcessRevision: function(file, node, revision, index) {
        var options = {};
        var urlParams = $osf.urlParams();

        if (urlParams.branch !== undefined) {
            options.branch = urlParams.branch;
        }
        options[revision.versionIdentifier] = revision.version;

        revision.date = new $osf.FormattableDate(revision.modified);
        revision.displayDate = revision.date.local !== 'Invalid date' ?
            revision.date.local :
            revision.date;

        switch (file.provider) {
            // Note: Google Drive version identifiers often begin with the same sequence
            case 'googledrive':
                revision.displayVersion = revision.version.substring(revision.version.length - 8);
                break;
            // Note: Dataverse internal version names are ugly; Substitute our own
            case 'dataverse':
                var displayMap = {
                    'latest': 'Draft',
                    'latest-published': 'Published'
                };

                revision.displayVersion = revision.version in displayMap ?
                    displayMap[revision.version] : revision.version.substring(0, 8);
                break;
            default:
                revision.displayVersion = revision.version.substring(0, 8);
        }

        if (file.provider === 'osfstorage' && file.name && index !== 0) {
            var parts = file.name.split('.');
            if (parts.length === 1) {
                options.displayName = parts[0] + '-' + revision.modified;
            } else {
                options.displayName = parts.slice(0, parts.length - 1).join('') + '-' + revision.modified + '.' + parts[parts.length - 1];
            }
        }
        revision.displayMd5 = revision.extra.hashes.md5;
        revision.displaySha256 = revision.extra.hashes.sha256;

        revision.osfViewUrl = '?' + $.param(options);
        revision.osfDownloadUrl = '?' + $.param($.extend({action: 'download'}, options));
        revision.waterbutlerDownloadUrl = waterbutler.buildDownloadUrl(file.path, file.provider, node.id, options);

        return revision;
    }
};

module.exports = FileRevisionsTable;
<commit_msg>Change button size and make it a button group<commit_after>var m = require('mithril');
var $ = require('jquery');
var $osf = require('js/osfHelpers');
var waterbutler = require('js/waterbutler');
var bootbox = require('bootbox');
var makeClient = require('js/clipboard.js');

var util = require('./util.js');

// Helper for filtering
function TRUTHY(item) {
    return !!item; //Force cast to a bool
}

var model = {
    revisions: [],
    loaded: m.prop(false),
    errorMessage: undefined,
    hasUser: false,
    hasDate: false,
    hasHashes: false,
    selectedRevision: 0
};

var FileRevisionsTable = {
    controller: function(file, node, enableEditing, canEdit) {
        var self = {};
        self.node = node;
        self.file = file;
        self.canEdit = canEdit;
        self.enableEditing = enableEditing;
        self.baseUrl = (window.location.href).split('?')[0];

        model.hasHashes = self.file.provider === 'osfstorage';
        model.hasDate = self.file.provider !== 'dataverse';

        self.reload = function() {
            model.loaded(false);
            m.redraw();
            $.ajax({
                dataType: 'json',
                async: true,
                url: self.file.urls.revisions,
                beforeSend: $osf.setXHRAuthorization
            }).done(function(response) {
                m.startComputation();
                var urlParmas = $osf.urlParams();
                model.revisions = response.data.map(function(rev, index) {
                    rev = FileRevisionsTable.postProcessRevision(self.file, self.node, rev, index);
                    if (urlParmas[rev.versionIdentifier] === rev.version) {
                        model.selectedRevision = index;
                    }
                    return rev;
                });
                model.loaded(true);
                // Can only edit the latest version of a file
                if (model.selectedRevision === 0) {
                    self.enableEditing();
                }
                model.hasUser = model.revisions[0] && model.revisions[0].extra && model.revisions[0].extra.user;
                m.endComputation();
            }).fail(function(response) {
                m.startComputation();
                model.loaded(true);
                model.errorMessage = response.responseJSON ?
                    response.responseJSON.message || 'Unable to fetch versions' :
                    'Unable to fetch versions';
                m.endComputation();

                // model.errorMessage(err);

                if (self.file.provider === 'figshare') {
                    // Hack for Figshare
                    // only figshare will error on a revisions request
                    // so dont allow downloads and set a fake current version
                    $.ajax({
                        method: 'GET',
                        url: self.file.urls.metadata,
                        beforeSend: $osf.setXHRAuthorization
                    }).done(function(resp) {
                        self.canEdit(self.canEdit() && resp.data.extra.canDelete);
                        m.redraw();
                    }).fail(function(xhr) {
                        self.canEdit(false);
                        m.redraw();
                    });
                }
            });
        };

        self.showModal = function(hashName, index, hash) {
            var showModal = bootbox.dialog({
                title: 'Version ' + index + ' ' + hashName + ' hash',
                message: '
  ' +
                    '
' +
                    '
' +'
' + hash + '
' +
                    ' Copy '+
                    '

'
            });
            showModal.on('show.bs.modal', function() {
                var $copyBtn = $('#copyBtn');
                new makeClient($copyBtn);
            });
            showModal.modal('show');
        };

        self.getTableHead = function() {
            return m('thead', [
                m('tr', [
                    m('th', 'Version ID'),
                    model.hasDate ? m('th', 'Date') : false,
                    model.hasUser ? m('th', 'User') : false,
                    model.hasHashes ? m('th', 'Hashes') : false,
                    m('th[colspan=2]', 'Download'),
                ].filter(TRUTHY))
            ]);
        };

        self.makeTableRow = function(revision, index) {
            var isSelected = index === model.selectedRevision;

            return m('tr' + (isSelected ? '.active' : ''), [
                m('td',  isSelected ? revision.displayVersion :
                  m('a', {href: parseInt(revision.displayVersion) === model.revisions.length ? self.baseUrl : revision.osfViewUrl}, revision.displayVersion)
                ),
                model.hasDate ? m('td', revision.displayDate) : false,
                model.hasUser ?
                    m('td', revision.extra.user.url ?
                            m('a', {href: revision.extra.user.url}, revision.extra.user.name) :
                            revision.extra.user.name
                    ) : false,
                model.hasHashes ? m('td', m('div.btn-group',
                    m( 'a.btn.btn-primary.btn-sm', {
                        onclick: function() {
                            self.showModal('MD5', revision.displayVersion, revision.displayMd5);
                        }
                    }, m('td', 'MD5') ),
                    m( 'a.btn.btn-primary.btn-sm', {
                        onclick: function() {
                            self.showModal('SHA256', revision.displayVersion, revision.displaySha256);
                        }
                    }, m('td', 'SHA2') )
                )) : false,
                m('td', revision.extra.downloads > -1 ? m('.badge', revision.extra.downloads) : ''),
                m('td',
                    m('a.btn.btn-primary.btn-sm.file-download', {
                        href: revision.osfDownloadUrl,
                        onclick: function() {
                            window.location = revision.waterbutlerDownloadUrl;
                            return false;
                        }
                    }, m('i.fa.fa-download'))
                ),
            ].filter(TRUTHY));
        };

        if (!model.loaded()) {
            self.reload();
        }
        $(document).on('fileviewpage:reload', self.reload);
        return self;
    },
    view: function(ctrl) {
        return m('#revisionsPanel.panel.panel-default', [
                m('.panel-heading.clearfix', m('h3.panel-title', 'Revisions')),
                m('.panel-body', {style:{'padding-right': '0','padding-left':'0', 'padding-bottom' : '0'}}, (function() {
                    if (!model.loaded()) {
                        return util.Spinner;
                    }
                    if (model.errorMessage) {
                        return m('.alert.alert-warning', {style:{margin: '10px'}}, model.errorMessage);
                    }

                    return m('table.table',{style:{'margin-bottom': '0'}}, [
                        ctrl.getTableHead(),
                        m('tbody', model.revisions.map(ctrl.makeTableRow))
                    ]);
                })())
            ]);
    },
    postProcessRevision: function(file, node, revision, index) {
        var options = {};
        var urlParams = $osf.urlParams();

        if (urlParams.branch !== undefined) {
            options.branch = urlParams.branch;
        }
        options[revision.versionIdentifier] = revision.version;

        revision.date = new $osf.FormattableDate(revision.modified);
        revision.displayDate = revision.date.local !== 'Invalid date' ?
            revision.date.local :
            revision.date;

        switch (file.provider) {
            // Note: Google Drive version identifiers often begin with the same sequence
            case 'googledrive':
                revision.displayVersion = revision.version.substring(revision.version.length - 8);
                break;
            // Note: Dataverse internal version names are ugly; Substitute our own
            case 'dataverse':
                var displayMap = {
                    'latest': 'Draft',
                    'latest-published': 'Published'
                };

                revision.displayVersion = revision.version in displayMap ?
                    displayMap[revision.version] : revision.version.substring(0, 8);
                break;
            default:
                revision.displayVersion = revision.version.substring(0, 8);
        }

        if (file.provider === 'osfstorage' && file.name && index !== 0) {
            var parts = file.name.split('.');
            if (parts.length === 1) {
                options.displayName = parts[0] + '-' + revision.modified;
            } else {
                options.displayName = parts.slice(0, parts.length - 1).join('') + '-' + revision.modified + '.' + parts[parts.length - 1];
            }
        }
        revision.displayMd5 = revision.extra.hashes.md5;
        revision.displaySha256 = revision.extra.hashes.sha256;

        revision.osfViewUrl = '?' + $.param(options);
        revision.osfDownloadUrl = '?' + $.param($.extend({action: 'download'}, options));
        revision.waterbutlerDownloadUrl = waterbutler.buildDownloadUrl(file.path, file.provider, node.id, options);

        return revision;
    }
};

module.exports = FileRevisionsTable;
<|endoftext|>


<commit_before>define([
  'angular',
  'app/app',
  'lodash',
  'jquery',
  'jquery.flot',
  'jquery.flot.pie',
],
function (angular, app, _, $) {
  'use strict';

  var module = angular.module('grafana.panels.piechart', []);
  app.useModule(module);

  module.directive('piechartPanel', function() {

    return {
      link: function(scope, elem) {
        var data, panel;

        scope.$on('render', function() {
          render();
          scope.panelRenderingComplete();
        });

        function setElementHeight() {
          try {
            var height = scope.height || panel.height || scope.row.height;
            if (_.isString(height)) {
              height = parseInt(height.replace('px', ''), 10);
            }

            height -= 5; // padding
            height -= panel.title ? 24 : 9; // subtract panel title bar

            elem.css('height', height + 'px');

            return true;
          } catch(e) { // IE throws errors sometimes
            return false;
          }
        }

        function addPieChart() {
          var width = elem.width();
          var height = elem.height();

          var size = Math.min(width, height);

          var plotCanvas = $('

');
          var plotCss = {};

          plotCss.top = '10px';
          plotCss.margin = 'auto';
          plotCss.position = 'relative';
          plotCss.height = (size - 20) + 'px';

          plotCanvas.css(plotCss);

          var options = {
            legend: {
              show: false
            },
            series: {
              pie: {
                show: true,
                label: {
                  show: scope.panel.legend.show && scope.panel.legendType === 'On graph'
                }
              }
            //},
            //grid: {
            //  hoverable: true
            }
          };

          if (panel.pieType === 'donut') {
            options.series.pie.innerRadius = 0.5;
          }

          elem.html(plotCanvas);

          $.plot(plotCanvas, scope.data, options);
        }

        function render() {
          if (!scope.data) { return; }

          data = scope.data;
          panel = scope.panel;

          setElementHeight();

          addPieChart();
        }
      }
    };
  });
});
<commit_msg>use bgcolor for stroke color and setup width to have a nicer visual effect<commit_after>define([
  'angular',
  'app/app',
  'lodash',
  'jquery',
  'jquery.flot',
  'jquery.flot.pie',
],
function (angular, app, _, $) {
  'use strict';

  var module = angular.module('grafana.panels.piechart', []);
  app.useModule(module);

  module.directive('piechartPanel', function() {

    return {
      link: function(scope, elem) {
        var data, panel;

        scope.$on('render', function() {
          render();
          scope.panelRenderingComplete();
        });

        function setElementHeight() {
          try {
            var height = scope.height || panel.height || scope.row.height;
            if (_.isString(height)) {
              height = parseInt(height.replace('px', ''), 10);
            }

            height -= 5; // padding
            height -= panel.title ? 24 : 9; // subtract panel title bar

            elem.css('height', height + 'px');

            return true;
          } catch(e) { // IE throws errors sometimes
            return false;
          }
        }

        function addPieChart() {
          var width = elem.width();
          var height = elem.height();

          var size = Math.min(width, height);

          var plotCanvas = $('

');
          var plotCss = {};

          plotCss.top = '10px';
          plotCss.margin = 'auto';
          plotCss.position = 'relative';
          plotCss.height = (size - 20) + 'px';

          plotCanvas.css(plotCss);

          var $panelContainer = elem.parents('.panel-container');
          var strokecolor = $panelContainer.css('background-color');
          var options = {
            legend: {
              show: false
            },
            series: {
              pie: {
                show: true,
                stroke: {
                    color: strokecolor,
                    width: 0.3
                },
                label: {
                  show: scope.panel.legend.show && scope.panel.legendType === 'On graph'
                }
              }
            //},
            //grid: {
            //  hoverable: true
            }
          };

          if (panel.pieType === 'donut') {
            options.series.pie.innerRadius = 0.5;
          }

          elem.html(plotCanvas);

          $.plot(plotCanvas, scope.data, options);
        }

        function render() {
          if (!scope.data) { return; }

          data = scope.data;
          panel = scope.panel;

          setElementHeight();

          addPieChart();
        }
      }
    };
  });
});
<|endoftext|>


<commit_before>(function () {
  var each = [].forEach

  var main = document.querySelector('.js-MainContent')
  var doc = document.documentElement
  var body = document.body
  var header = document.querySelector('.js-MainHeader')
  var menu = document.querySelector('.js-Sidebar')
  var content = document.querySelector('.js-Content')

  var isIndex = body.classList.contains('page-index')

  // On index page
  if (isIndex) {
    IndexPage()
  // On inner pages
  } else {
    InnerPage()
  }

  function InnerPage () {
    var menuButton = document.querySelector('.js-MenuBtn')
    menuButton.addEventListener('click', function () {
      menu.classList.toggle('is-open')
    })

    body.addEventListener('click', function (e) {
      if (e.target !== menuButton && !menu.contains(e.target)) {
        menu.classList.remove('is-open')
      }
    })

    // build sidebar
    var currentPageAnchor = menu.querySelector('.sidebar-link.current')
    var isAPI = document.querySelector('.Content').classList.contains('api')
    if (currentPageAnchor || isAPI) {
      var allLinks = []
      var sectionContainer
      if (false && isAPI) {
        sectionContainer = document.querySelector('.menu-root')
      } else {
        sectionContainer = document.createElement('ul')
        sectionContainer.className = 'menu-sub'
        currentPageAnchor.parentNode.appendChild(sectionContainer)
      }
      var h2s = content.querySelectorAll('h2')
      if (h2s.length) {
        each.call(h2s, function (h) {
          sectionContainer.appendChild(makeLink(h))
          var h3s = collectH3s(h)
          allLinks.push(h)
          allLinks.push.apply(allLinks, h3s)
          if (h3s.length) {
            sectionContainer.appendChild(makeSubLinks(h3s, isAPI))
          }
        })
      } else {
        h2s = content.querySelectorAll('h3')
        each.call(h2s, function (h) {
          sectionContainer.appendChild(makeLink(h))
          allLinks.push(h)
        })
      }

      var animating = false
      sectionContainer.addEventListener('click', function (e) {
        e.preventDefault()
        if (e.target.classList.contains('section-link')) {
          menu.classList.remove('open')
          setActive(e.target)
          animating = true
          setTimeout(function () {
            animating = false
          }, 400)
        }
      }, true)

      // make links clickable
      allLinks.forEach(makeLinkClickable)

      // init smooth scroll
      window.smoothScroll.init({
        speed: 400,
        offset: window.innerWidth > 720
          ? 40
          : 58
      })
    }

    // listen for scroll event to do positioning & highlights
    window.addEventListener('scroll', updateSidebar)
    window.addEventListener('resize', updateSidebar)
  }

  function updateSidebar () {
    var top = doc && doc.scrollTop || body.scrollTop
    var headerHeight = header.offsetHeight
    if (top > headerHeight) {
      addClass(main, 'fix-sidebar')
    } else {
      removeClass(main, 'fix-sidebar')
    }
    if (animating || !allLinks) return
    var last
    for (var i = 0; i < allLinks.length; i++) {
      var link = allLinks[i]
      if (link.offsetTop > top) {
        if (!last) last = link
        break
      } else {
        last = link
      }
    }
    if (last) {
      setActive(last.id)
    }
  }

  function makeLink (h) {
    var link = document.createElement('li')
    var text = h.textContent.replace(/\(.*\)$/, '')
    // make sure the ids are link-able...
    h.id = h.id
      .replace(/\(.*\)$/, '')
      .replace(/\$/, '')
    link.innerHTML =
      '' +
        text +
      ''
    return link
  }

  function collectH3s (h) {
    var h3s = []
    var next = h.nextSibling
    while (next && next.tagName !== 'H2') {
      if (next.tagName === 'H3') {
        h3s.push(next)
      }
      next = next.nextSibling
    }
    return h3s
  }

  function makeSubLinks (h3s, small) {
    var container = document.createElement('ul')
    if (small) {
      container.className = 'menu-sub'
    }
    h3s.forEach(function (h) {
      container.appendChild(makeLink(h))
    })
    return container
  }

  function setActive (id) {
    var previousActive = menu.querySelector('.section-link.active')
    var currentActive = typeof id === 'string'
      ? menu.querySelector('.section-link[href="#' + id + '"]')
      : id
    if (currentActive !== previousActive) {
      if (previousActive) previousActive.classList.remove('active')
      currentActive.classList.add('active')
    }
  }

  function makeLinkClickable (link) {
    var wrapper = document.createElement('a')
    wrapper.href = '#' + link.id
    wrapper.setAttribute('data-scroll', '')
    link.parentNode.insertBefore(wrapper, link)
    wrapper.appendChild(link)
  }

  function addClass (el, className) {
    if (el.classList) {
      el.classList.add(className)
    } else {
      el.className += ' ' + className
    }
  }

  function removeClass (el, className) {
    if (el.classList) {
      el.classList.remove(className)
    } else {
      el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ')
    }
  }

  function qsa (selector, context) {
    return Array.prototype.slice.call((context || document).querySelectorAll(selector) || [])
  }

  // Index page taglines

  // Pick random item
  function pickRandom (items) {
    return items[Math.floor(Math.random() * items.length)]
  }

  function IndexPage () {
    looptagLines()
    setInterval(looptagLines, 3800)
  }

  // Loop and render
  function looptagLines () {
    var taglinePart = document.querySelector('.js-IndexHero-taglinePart')
    var taglines = qsa('.js-IndexHero-taglineItem')

    taglinePart.classList.remove('is-visible')

    setTimeout(function() {
      var randomTagline = pickRandom(taglines)
      var taglineText = randomTagline.textContent
      taglinePart.innerHTML = taglineText

      taglinePart.classList.add('is-visible')
    }, 800)
  }

  // Search with SwiftType
  // @todo get our own swifttype

  // (function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){
  // (w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t);
  // e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e);
  // })(window,document,'script','//s.swiftypecdn.com/install/v2/st.js','_st');

  // _st('install','HgpxvBc7pUaPUWmG9sgv','2.0.0');

  // version select
  // document.querySelector('.version-select').addEventListener('change', function (e) {
  //   var version = e.target.value
  //   if (version.indexOf('1.') !== 0) {
  //     version = version.replace('.', '')
  //     var section = window.location.pathname.match(/\/(\w+?)\//)[1]
  //     window.location.assign('http://' + version + '.uppy.io/' + section + '/')
  //   } else {
  //     // TODO when 1.x is out
  //   }
  // })
})()
<commit_msg>fix linting issues<commit_after>(function () {
  var each = [].forEach

  var main = document.querySelector('.js-MainContent')
  var doc = document.documentElement
  var body = document.body
  var header = document.querySelector('.js-MainHeader')
  var menu = document.querySelector('.js-Sidebar')
  var content = document.querySelector('.js-Content')

  var isIndex = body.classList.contains('page-index')

  var animating = false
  var allLinks = []

  // On index page
  if (isIndex) {
    IndexPage()
  // On inner pages
  } else {
    InnerPage()
  }

  function InnerPage () {
    var menuButton = document.querySelector('.js-MenuBtn')
    menuButton.addEventListener('click', function () {
      menu.classList.toggle('is-open')
    })

    body.addEventListener('click', function (e) {
      if (e.target !== menuButton && !menu.contains(e.target)) {
        menu.classList.remove('is-open')
      }
    })

    // build sidebar
    var currentPageAnchor = menu.querySelector('.sidebar-link.current')
    var isAPI = document.querySelector('.Content').classList.contains('api')
    if (currentPageAnchor || isAPI) {
      var sectionContainer
      if (false && isAPI) {
        sectionContainer = document.querySelector('.menu-root')
      } else {
        sectionContainer = document.createElement('ul')
        sectionContainer.className = 'menu-sub'
        currentPageAnchor.parentNode.appendChild(sectionContainer)
      }
      var h2s = content.querySelectorAll('h2')
      if (h2s.length) {
        each.call(h2s, function (h) {
          sectionContainer.appendChild(makeLink(h))
          var h3s = collectH3s(h)
          allLinks.push(h)
          allLinks.push.apply(allLinks, h3s)
          if (h3s.length) {
            sectionContainer.appendChild(makeSubLinks(h3s, isAPI))
          }
        })
      } else {
        h2s = content.querySelectorAll('h3')
        each.call(h2s, function (h) {
          sectionContainer.appendChild(makeLink(h))
          allLinks.push(h)
        })
      }

      sectionContainer.addEventListener('click', function (e) {
        e.preventDefault()
        if (e.target.classList.contains('section-link')) {
          menu.classList.remove('open')
          setActive(e.target)
          animating = true
          setTimeout(function () {
            animating = false
          }, 400)
        }
      }, true)

      // make links clickable
      allLinks.forEach(makeLinkClickable)

      // init smooth scroll
      window.smoothScroll.init({
        speed: 400,
        offset: window.innerWidth > 720
          ? 40
          : 58
      })
    }

    // listen for scroll event to do positioning & highlights
    window.addEventListener('scroll', updateSidebar)
    window.addEventListener('resize', updateSidebar)
  }

  function updateSidebar () {
    var top = doc && doc.scrollTop || body.scrollTop
    var headerHeight = header.offsetHeight
    if (top > headerHeight) {
      addClass(main, 'fix-sidebar')
    } else {
      removeClass(main, 'fix-sidebar')
    }
    if (animating || !allLinks) return
    var last
    for (var i = 0; i < allLinks.length; i++) {
      var link = allLinks[i]
      if (link.offsetTop > top) {
        if (!last) last = link
        break
      } else {
        last = link
      }
    }
    if (last) {
      setActive(last.id)
    }
  }

  function makeLink (h) {
    var link = document.createElement('li')
    var text = h.textContent.replace(/\(.*\)$/, '')
    // make sure the ids are link-able...
    h.id = h.id
      .replace(/\(.*\)$/, '')
      .replace(/\$/, '')
    link.innerHTML =
      '' +
        text +
      ''
    return link
  }

  function collectH3s (h) {
    var h3s = []
    var next = h.nextSibling
    while (next && next.tagName !== 'H2') {
      if (next.tagName === 'H3') {
        h3s.push(next)
      }
      next = next.nextSibling
    }
    return h3s
  }

  function makeSubLinks (h3s, small) {
    var container = document.createElement('ul')
    if (small) {
      container.className = 'menu-sub'
    }
    h3s.forEach(function (h) {
      container.appendChild(makeLink(h))
    })
    return container
  }

  function setActive (id) {
    var previousActive = menu.querySelector('.section-link.active')
    var currentActive = typeof id === 'string'
      ? menu.querySelector('.section-link[href="#' + id + '"]')
      : id
    if (currentActive !== previousActive) {
      if (previousActive) previousActive.classList.remove('active')
      currentActive.classList.add('active')
    }
  }

  function makeLinkClickable (link) {
    var wrapper = document.createElement('a')
    wrapper.href = '#' + link.id
    wrapper.setAttribute('data-scroll', '')
    link.parentNode.insertBefore(wrapper, link)
    wrapper.appendChild(link)
  }

  function addClass (el, className) {
    if (el.classList) {
      el.classList.add(className)
    } else {
      el.className += ' ' + className
    }
  }

  function removeClass (el, className) {
    if (el.classList) {
      el.classList.remove(className)
    } else {
      el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ')
    }
  }

  function qsa (selector, context) {
    return Array.prototype.slice.call((context || document).querySelectorAll(selector) || [])
  }

  // Index page taglines

  // Pick random item
  function pickRandom (items) {
    return items[Math.floor(Math.random() * items.length)]
  }

  function IndexPage () {
    looptagLines()
    setInterval(looptagLines, 3800)
  }

  // Loop and render
  function looptagLines () {
    var taglinePart = document.querySelector('.js-IndexHero-taglinePart')
    var taglines = qsa('.js-IndexHero-taglineItem')

    taglinePart.classList.remove('is-visible')

    setTimeout(function () {
      var randomTagline = pickRandom(taglines)
      var taglineText = randomTagline.textContent
      taglinePart.innerHTML = taglineText

      taglinePart.classList.add('is-visible')
    }, 800)
  }

  // Search with SwiftType
  // @todo get our own swifttype

  // (function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){
  // (w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t);
  // e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e);
  // })(window,document,'script','//s.swiftypecdn.com/install/v2/st.js','_st');

  // _st('install','HgpxvBc7pUaPUWmG9sgv','2.0.0');

  // version select
  // document.querySelector('.version-select').addEventListener('change', function (e) {
  //   var version = e.target.value
  //   if (version.indexOf('1.') !== 0) {
  //     version = version.replace('.', '')
  //     var section = window.location.pathname.match(/\/(\w+?)\//)[1]
  //     window.location.assign('http://' + version + '.uppy.io/' + section + '/')
  //   } else {
  //     // TODO when 1.x is out
  //   }
  // })
})()
<|endoftext|>


<commit_before>export default function (headHtml) {
  return `
    <!DOCTYPE html>

        <title>React Storybook</title>
        ${headHtml}

  `;
}
<commit_msg>Add React Developer Tools hook (#104)<commit_after>export default function (headHtml) {
  return `
    <!DOCTYPE html>

        <title>React Storybook</title>
        ${headHtml}

  `;
}
<|endoftext|>


<commit_before>/**
 * @copyright Copyright (c) 2020 Marco Ambrosini <marcoambrosini@pm.me>
 *
 * @author Marco Ambrosini <marcoambrosini@pm.me>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

import BrowserStorage from '../services/BrowserStorage'
import {
	CONVERSATION,
} from '../constants'

const state = {
	isGrid: false,
	selectedVideoPeerId: null,
	videoBackgroundBlur: 1,
}

const getters = {
	isGrid: (state) => {
		return state.isGrid
	},
	selectedVideoPeerId: (state) => {
		return state.selectedVideoPeerId
	},
	getBlurFilter: (state) => (width, height) => {
		return `filter: blur(${(width * height * state.videoBackgroundBlur) / 1000}px)`
	},
}

const mutations = {

	isGrid(state, value) {
		state.isGrid = value
	},
	selectedVideoPeerId(state, value) {
		state.selectedVideoPeerId = value
	},
}

const actions = {
	/**
	 * Sets the current grid mode and saves it in preferences.
	 *
	 * @param {object} context default store context;
	 * @param {bool} value true for enabled grid mode, false for speaker view;
	 */
	isGrid(context, value) {
		BrowserStorage.setItem('callprefs-' + context.getters.getToken() + '-isgrid', value)
		context.commit('isGrid', value)
	},
	selectedVideoPeerId(context, value) {
		context.commit('selectedVideoPeerId', value)
	},

	joinCall(context, { token }) {
		let isGrid = BrowserStorage.getItem('callprefs-' + token + '-isgrid')
		if (isGrid === null) {
			const conversationType = context.getters.conversations[token].type
			// default to grid view for group/public calls, otherwise speaker view
			isGrid = (conversationType === CONVERSATION.GROUP
				|| conversationType === CONVERSATION.PUBLIC)
		}
		context.dispatch('isGrid', isGrid)
	},
}

export default { state, mutations, getters, actions }
<commit_msg>Fix calls always started in grid view<commit_after>/**
 * @copyright Copyright (c) 2020 Marco Ambrosini <marcoambrosini@pm.me>
 *
 * @author Marco Ambrosini <marcoambrosini@pm.me>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

import BrowserStorage from '../services/BrowserStorage'
import {
	CONVERSATION,
} from '../constants'

const state = {
	isGrid: false,
	selectedVideoPeerId: null,
	videoBackgroundBlur: 1,
}

const getters = {
	isGrid: (state) => {
		return state.isGrid
	},
	selectedVideoPeerId: (state) => {
		return state.selectedVideoPeerId
	},
	getBlurFilter: (state) => (width, height) => {
		return `filter: blur(${(width * height * state.videoBackgroundBlur) / 1000}px)`
	},
}

const mutations = {

	isGrid(state, value) {
		state.isGrid = value
	},
	selectedVideoPeerId(state, value) {
		state.selectedVideoPeerId = value
	},
}

const actions = {
	/**
	 * Sets the current grid mode and saves it in preferences.
	 *
	 * @param {object} context default store context;
	 * @param {bool} value true for enabled grid mode, false for speaker view;
	 */
	isGrid(context, value) {
		BrowserStorage.setItem('callprefs-' + context.getters.getToken() + '-isgrid', value)
		context.commit('isGrid', value)
	},
	selectedVideoPeerId(context, value) {
		context.commit('selectedVideoPeerId', value)
	},

	joinCall(context, { token }) {
		let isGrid = BrowserStorage.getItem('callprefs-' + token + '-isgrid')
		if (isGrid === null) {
			const conversationType = context.getters.conversations[token].type
			// default to grid view for group/public calls, otherwise speaker view
			isGrid = (conversationType === CONVERSATION.GROUP
				|| conversationType === CONVERSATION.PUBLIC)
		} else {
			// BrowserStorage.getItem returns a string instead of a boolean
			isGrid = (isGrid === 'true')
		}
		context.dispatch('isGrid', isGrid)
	},
}

export default { state, mutations, getters, actions }
<|endoftext|>


<commit_before>define([
	'goo/entities/components/LightComponent',
	'goo/entities/components/CameraComponent',
	'goo/entities/components/MeshRendererComponent',

	'goo/renderer/light/PointLight',
	'goo/renderer/light/DirectionalLight',
	'goo/renderer/light/SpotLight',

	'goo/shapes/debug/LightDebug',
	'goo/shapes/debug/CameraDebug',
	'goo/renderer/Material',
	'goo/renderer/shaders/ShaderLib',
	'goo/math/Transform'
], function(
	LightComponent,
	CameraComponent,
	MeshRendererComponent,

	PointLight,
	DirectionalLight,
	SpotLight,

	LightDebug,
	CameraDebug,
	Material,
	ShaderLib,
	Transform
) {
	'use strict';
	var DebugDrawHelper = {};

	var lightDebug = new LightDebug();
	var cameraDebug = new CameraDebug();

	DebugDrawHelper.getRenderablesFor = function(component) {
		var meshes;
		if(component.type === 'LightComponent') {
			meshes = lightDebug.getMesh(component.light);
		} else if (component.type === 'CameraComponent') {
			meshes = cameraDebug.getMesh(component.camera);
		} else if (component.type === 'MeshRendererComponent') {
			return;
			// Not done yet
		}
		var material = Material.createMaterial(ShaderLib.simpleColored, 'DebugDrawMaterial');
		return [
		 {
			 meshData: meshes[0],
			 transform: new Transform(),
			 materials: [material]
		 },
		 {
			 meshData: meshes[1],
			 transform: new Transform(),
			 materials: [material]
		 }
		];
	};

	DebugDrawHelper.update = function(renderables, component, scale) {
		if(component.camera && component.camera.changedProperties) {
			var camera = component.camera;
			if((camera.far / camera.near) !== renderables[1].farNear) {
				renderables[1].meshData = CameraDebug.buildFrustum(camera.far / camera.near);
				renderables[1].farNear = camera.far / camera.near;
			}
			component.camera.changedProperties = false;
		}
		DebugDrawHelper[component.type].updateMaterial(renderables[0].materials[0], component);
		DebugDrawHelper[component.type].updateTransform(renderables[1].transform, component, scale);

		renderables[0].transform.scale.scale(scale);
		renderables[0].transform.update();
	};

	DebugDrawHelper.LightComponent = {};
	DebugDrawHelper.CameraComponent = {};

	DebugDrawHelper.LightComponent.updateMaterial = function(material, component) {
		var light = component.light;
		var color = material.uniforms.color = material.uniforms.color || [];
		color[0] = light.color.data[0];
		color[1] = light.color.data[1];
		color[2] = light.color.data[2];
	};

	DebugDrawHelper.LightComponent.updateTransform = function(transform, component, scale) {
		var light = component.light;
		if (!(light instanceof DirectionalLight)) {
			var r = light.range;
			transform.scale.setd(r,r,r);
			if (light instanceof SpotLight) {
				var angle = light.angle * Math.PI / 180;
				var tan = Math.tan(angle);
				transform.scale.muld(tan, tan, 1);
			}
		} else {
			transform.scale.scale(scale);
		}
		transform.update();
	};

	DebugDrawHelper.CameraComponent.updateMaterial = function(material/*, component*/) {
		material.uniforms.color = material.uniforms.color || [1, 1, 1];
	};

	DebugDrawHelper.CameraComponent.updateTransform = function(transform, component) {
		var camera = component.camera;
		var z = camera.far;
		var y = z * 2 * Math.tan(camera.fov/2 * Math.PI/180);
		var x = y * camera.aspect;
		transform.scale.setd(x, y, z);
		transform.update();
	};

	return DebugDrawHelper;
});<commit_msg>Bugfix on camera debug draw<commit_after>define([
	'goo/entities/components/LightComponent',
	'goo/entities/components/CameraComponent',
	'goo/entities/components/MeshRendererComponent',

	'goo/renderer/light/PointLight',
	'goo/renderer/light/DirectionalLight',
	'goo/renderer/light/SpotLight',

	'goo/shapes/debug/LightDebug',
	'goo/shapes/debug/CameraDebug',
	'goo/renderer/Material',
	'goo/renderer/shaders/ShaderLib',
	'goo/math/Transform'
], function(
	LightComponent,
	CameraComponent,
	MeshRendererComponent,

	PointLight,
	DirectionalLight,
	SpotLight,

	LightDebug,
	CameraDebug,
	Material,
	ShaderLib,
	Transform
) {
	'use strict';
	var DebugDrawHelper = {};

	var lightDebug = new LightDebug();
	var cameraDebug = new CameraDebug();

	DebugDrawHelper.getRenderablesFor = function(component) {
		var meshes;
		if(component.type === 'LightComponent') {
			meshes = lightDebug.getMesh(component.light);
		} else if (component.type === 'CameraComponent') {
			meshes = cameraDebug.getMesh(component.camera);
		} else if (component.type === 'MeshRendererComponent') {
			return;
			// Not done yet
		}
		var material = Material.createMaterial(ShaderLib.simpleColored, 'DebugDrawMaterial');
		return [
		 {
			 meshData: meshes[0],
			 transform: new Transform(),
			 materials: [material]
		 },
		 {
			 meshData: meshes[1],
			 transform: new Transform(),
			 materials: [material]
		 }
		];
	};

	DebugDrawHelper.update = function(renderables, component, scale) {
		if(component.camera && component.camera.changedProperties) {
			var camera = component.camera;
			if((camera.far / camera.near) !== renderables[1].farNear) {
				renderables[1].meshData = CameraDebug.buildFrustum(camera.far / camera.near);
				renderables[1].farNear = camera.far / camera.near;
			}
			component.camera.changedProperties = false;
		}
		DebugDrawHelper[component.type].updateMaterial(renderables[0].materials[0], component);
		DebugDrawHelper[component.type].updateTransform(renderables[1].transform, component, scale);

		renderables[0].transform.scale.scale(scale);
		renderables[0].transform.update();
	};

	DebugDrawHelper.LightComponent = {};
	DebugDrawHelper.CameraComponent = {};

	DebugDrawHelper.LightComponent.updateMaterial = function(material, component) {
		var light = component.light;
		var color = material.uniforms.color = material.uniforms.color || [];
		color[0] = light.color.data[0];
		color[1] = light.color.data[1];
		color[2] = light.color.data[2];
	};

	DebugDrawHelper.LightComponent.updateTransform = function(transform, component, scale) {
		var light = component.light;
		if (!(light instanceof DirectionalLight)) {
			var r = light.range;
			transform.scale.setd(r,r,r);
			if (light instanceof SpotLight) {
				var angle = light.angle * Math.PI / 180;
				var tan = Math.tan(angle);
				transform.scale.muld(tan, tan, 1);
			}
		} else {
			transform.scale.scale(scale);
		}
		transform.update();
	};

	DebugDrawHelper.CameraComponent.updateMaterial = function(material/*, component*/) {
		material.uniforms.color = material.uniforms.color || [1, 1, 1];
	};

	DebugDrawHelper.CameraComponent.updateTransform = function(transform, component) {
		var camera = component.camera;
		var z = camera.far;
		var y = z * Math.tan(camera.fov/2 * Math.PI/180);
		var x = y * camera.aspect;
		transform.scale.setd(x, y, z);
		transform.update();
	};

	return DebugDrawHelper;
});<|endoftext|>


<commit_before>import { observable, action } from 'mobx';
import defaultLocale from '../../locales/en_US';

const localeStores = new Map();
let localeData = defaultLocale;

export default class LocaleStore {
	static ensure(name) {
		if (localeStores.has(name)) return localeStores.get(name);
		const data = localeData[name];
		const locale = new LocaleStore(data);
		localeStores.set(name, locale);
		return locale;
	}

	static test(name) {
		return localeData.hasOwnProperty(name);
	}

	static setLocale(newLocale) {
		if (localeData !== newLocale) {
			localeData = newLocale;
			for (const [name, localeStore] of localeStores) {
				localeStore._setData(localeData[name]);
			}
		}
	}

	constructor(data) {
		this.data = observable(data);
	}

	localizeProp(prop, key) {
		return prop === undefined ? this.data[key] : prop;
	}

	localize(props, exclude) {
		return Object.keys(this.data).reduce((acc, key) => {
			const propValue = props[key];
			if (!exclude || !~exclude.indexOf(key)) {
				acc[key] = propValue === undefined ? this.data[key] : propValue;
			}
			return acc;
		}, {});
	}

	@action
	_setData(data) {
		this.data = data;
	}
}
<commit_msg>fix LocaleStore localize() bug<commit_after>import { observable, action } from 'mobx';
import defaultLocale from '../../locales/en_US';

const localeStores = new Map();
let localeData = defaultLocale;

export default class LocaleStore {
	static ensure(name) {
		if (localeStores.has(name)) return localeStores.get(name);
		const data = localeData[name];
		const locale = new LocaleStore(data);
		localeStores.set(name, locale);
		return locale;
	}

	static test(name) {
		return localeData.hasOwnProperty(name);
	}

	static setLocale(newLocale) {
		if (localeData !== newLocale) {
			localeData = newLocale;
			for (const [name, localeStore] of localeStores) {
				localeStore._setData(localeData[name]);
			}
		}
	}

	constructor(data) {
		this.data = observable(data);
	}

	localizeProp(prop, key) {
		return prop === undefined ? this.data[key] : prop;
	}

	localize(props, exclude) {
		return Object.keys(this.data)
			.concat(Object.keys(props))
			.reduce((acc, key) => {
				const propValue = props[key];
				if (!exclude || !~exclude.indexOf(key)) {
					acc[key] = propValue === undefined ? this.data[key] : propValue;
				}
				return acc;
			}, {});
	}

	@action
	_setData(data) {
		this.data = data;
	}
}
<|endoftext|>


<commit_before>import { GraphQLObjectType, GraphQLString } from 'graphql';

/**
 *
 * @param {string} field
 * @returns {resolveFn} Resolves URL entry using url or normalizedUrl
 */
function resolveUrl(field) {
  return async function({ url, normalizedUrl }, args, { loaders }) {
    const urls = [url];
    if (normalizedUrl) {
      // Only consider normalizedUrl when there is one
      urls.push(normalizedUrl);
    }
    const urlEnties = await loaders.urlLoader.loadMany(urls);
    const firstEntry = urlEnties.find(urlEntry => urlEntry) || {};
    return firstEntry[field];
  };
}

const Hyperlink = new GraphQLObjectType({
  name: 'Hyperlink',
  description: 'Data behind a hyperlink',
  fields: {
    url: { type: GraphQLString },
    title: { type: GraphQLString },
    summary: { type: GraphQLString },
    topImageUrl: { type: GraphQLString, resolve: resolveUrl('topImageUrl') },
    fetchedAt: { type: GraphQLString, resolve: resolveUrl('fetchedAt') },
    status: { type: GraphQLString, resolve: resolveUrl('status') },
    error: { type: GraphQLString, resolve: resolveUrl('error') },
  },
});

export default Hyperlink;
<commit_msg>Add normalizedUrl field in Hyperlink<commit_after>import { GraphQLObjectType, GraphQLString } from 'graphql';

/**
 *
 * @param {string} field
 * @returns {resolveFn} Resolves URL entry using url or normalizedUrl
 */
function resolveUrl(field) {
  return async function({ url, normalizedUrl }, args, { loaders }) {
    const urls = [url];
    if (normalizedUrl) {
      // Only consider normalizedUrl when there is one
      urls.push(normalizedUrl);
    }
    const urlEnties = await loaders.urlLoader.loadMany(urls);
    const firstEntry = urlEnties.find(urlEntry => urlEntry) || {};
    return firstEntry[field];
  };
}

const Hyperlink = new GraphQLObjectType({
  name: 'Hyperlink',
  description: 'Data behind a hyperlink',
  fields: {
    url: { type: GraphQLString, description: 'URL in text' },
    normalizedUrl: {
      type: GraphQLString,
      description: 'URL normalized by scrapUrl',
    },
    title: { type: GraphQLString },
    summary: { type: GraphQLString },
    topImageUrl: { type: GraphQLString, resolve: resolveUrl('topImageUrl') },
    fetchedAt: { type: GraphQLString, resolve: resolveUrl('fetchedAt') },
    status: { type: GraphQLString, resolve: resolveUrl('status') },
    error: { type: GraphQLString, resolve: resolveUrl('error') },
  },
});

export default Hyperlink;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { ScrollView, View, Platform } from 'react-native';
import { compose, mapProps, pure } from 'recompose';

import withGroupInfo from '@data/withGroupInfo';
import Header from '@ui/Header';
import ProgressiveImage from '@ui/ProgressiveImage';
import PaddedView from '@ui/PaddedView';
import BackgroundView from '@ui/BackgroundView';
import Card from '@ui/Card';
import { H3, H4, H5, H6, H7, BodyText } from '@ui/typography';
import styled from '@ui/styled';
import Avatar, { AvatarList } from '@ui/Avatar';
import Button from '@ui/Button';
import Chip, { ChipList } from '@ui/Chip';
import SideBySideView, { ResponsiveSideBySideView, Left, Right } from '@ui/SideBySideView';
import { Link } from '@ui/NativeWebRouter';
import Settings from '@utils/Settings';
import MediaQuery from '@ui/MediaQuery';
import SecondaryNav, { Like, Share } from '@ui/SecondaryNav';
import Meta from '@ui/Meta';
import WebBrowser from '@ui/WebBrowser';
import { track, events, categories } from '@utils/analytics';

import Map from './Map';

const FlexedSideBySideView = styled({ flex: 1 })(SideBySideView);
const FlexedLeft = styled({ flex: 1 })(Left);

const rockUrl = Settings.APP_ROCK_URL || 'https://rock.newspring.cc/'; // todo

const enhance = compose(
  pure,
  mapProps(({ match: { params: { id } } }) => ({ id })),
  withGroupInfo,
);

const ShareLink = compose(
  withGroupInfo,
  mapProps(({ group } = {}) => ({ content: group })),
)(Share);

const StyledImage = styled({
  width: '100%',
  aspectRatio: 2,
  ...Platform.select({
    web: {
      height: 0,
      paddingTop: '50%',
    },
  }),
})(ProgressiveImage);

const Label = styled(({ theme }) => ({ color: theme.colors.text.tertiary }))(H7);
const Info = styled(({ theme }) => ({ color: theme.colors.text.secondary }))(H6);
const AdTitle = styled({ textAlign: 'center' })(H4);

const GroupInfoContainer = styled(({ theme }) => ({
  paddingVertical: theme.sizing.baseUnit / 2,
}))(View);

const CenteredSideBySideView = styled({
  alignItems: 'center',
})(ResponsiveSideBySideView);

const GroupFindCTA = styled({
  alignItems: 'center',
})(PaddedView);

const leadersLoadingState = [
  {
    person: {
      id: 'loading',
      photo: null,
    },
  },
];

const handleGroupContact = ({ guid, loginParam }) => {
  let newLoginParam = loginParam;

  if (loginParam) {
    newLoginParam = `&${loginParam}`;
  }

  WebBrowser.openBrowserAsync(`${rockUrl}Workflows/304?Group=${guid}${newLoginParam}`);
  track(events.ContactedGroup, categories.Account);
};

const GroupInfo = ({ label, info }) => (
  <GroupInfoContainer>
    {label}
    <Info>{info}</Info>
  </GroupInfoContainer>
);

GroupInfo.propTypes = {
  label: PropTypes.string,
  info: PropTypes.string,
};

const isCurrentPersonLeader = (person, leaders) =>
  person && Array.isArray(leaders) && leaders.filter(x => x.person.id === person.id).length;

const GroupSingle = enhance(
  ({
    id,
    group: {
      photo = null,
      name, // name
      guid,
      entityId,
      type,
      demographic,
      description,
      kidFriendly,
      ageRange,
      campus = {},
      tags = [],
      locations = [], // schedule { description }
      // { location { city, state, latitude, longitude } }
      members = [], //   role // {
      //   person { id, photo, firstName, nickName, lastName }
      // }
      // groupType
      isLiked,
    } = {},
    person,
    isLoading,
  }) => {
    const leaders = members.filter(x => x.role.toLowerCase() === 'leader');
    const loginParam = person ? person.impersonationParameter : '';
    const isLeader = isCurrentPersonLeader(person, leaders);
    return (
      <BackgroundView>
        <Meta title={name} image={photo} description={description} id={id} />

        <FlexedSideBySideView>
          <FlexedLeft>
            <ScrollView>