<commit_before>/**
 * Component: Headline
 */

import React from 'react';
import { camelCase } from 'lodash';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import css from './Headline.css';

const Headline = ({ size, margin, tag, faded, bold, children }) => {
  /**
   * Available prop values
   */

  const availablePropValues = {
    size: ['large', 'medium', 'small'],
    margin: ['large', 'medium', 'small', 'none'],
    tag: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div'],
  };

  /**
   * Determine prop value helper
   */

  /**
   * Get CSS classes depending on props
   */

  const getCssClasses = () => classNames(
    // Base styling
    css.headline,
    // Size
    css[camelCase(`size ${size}`)],
    // Margin
    css[camelCase(`margin ${margin || size}`)],
    // Faded
    { [css.isFaded]: faded },
    // Bold
    { [css.isBold]: bold },
  );

  /**
   * Tag
   */

  const HeadlineTag = tag;

  /**
   * Content
   */
  const content = children;

  return (
    <HeadlineTag className={getCssClasses()}>{content}</HeadlineTag>
  );
};

Headline.defaultProps = {
  label: '',
  size: 'medium',
  margin: '', // Determined by size if nothing is set
  tag: 'h2',
  faded: false,
  bold: true,
};

Headline.propTypes = {
  children: PropTypes.node,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  margin: PropTypes.oneOf(['small', 'medium', 'large', 'none', '']), // If nothing is set it will default to the value of size
  tag: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div']),
  faded: PropTypes.bool,
  bold: PropTypes.bool,
};

export default Headline;
<commit_msg>Fixed linting issue<commit_after>/**
 * Component: Headline
 */

import React from 'react';
import { camelCase } from 'lodash';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import css from './Headline.css';

const Headline = ({ size, margin, tag, faded, bold, children }) => {
  /**
   * Get CSS classes depending on props
   */

  const getCssClasses = () => classNames(
    // Base styling
    css.headline,
    // Size
    css[camelCase(`size ${size}`)],
    // Margin
    css[camelCase(`margin ${margin || size}`)],
    // Faded
    { [css.isFaded]: faded },
    // Bold
    { [css.isBold]: bold },
  );

  const HeadlineTag = tag;

  return (
    <HeadlineTag className={getCssClasses()}>{children}</HeadlineTag>
  );
};

Headline.defaultProps = {
  label: '',
  size: 'medium',
  margin: '', // Determined by size if nothing is set
  tag: 'h2',
  faded: false,
  bold: true,
};

Headline.propTypes = {
  children: PropTypes.node,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  margin: PropTypes.oneOf(['small', 'medium', 'large', 'none', '']), // If nothing is set it will default to the value of size
  tag: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div']),
  faded: PropTypes.bool,
  bold: PropTypes.bool,
};

export default Headline;
<|endoftext|>


<commit_before>/* eslint-disable no-undefined */
/* eslint-disable no-use-before-define */
import _ from 'lodash';
import {MANY_TO_ONE, MANY_TO_MANY} from 'node-bits';
import pluralize from 'pluralize';
import {foreignKeyRelationshipName} from '../foreign_key_name';
import {READ} from '../../constants';

// boolean functions to define if the relationship makes it in
const searchTermForRelationship = (model, relationship) => {
  if (relationship.as) {
    return foreignKeyRelationshipName(relationship);
  }

  if (relationship.references === model.name) {
    return relationship.model;
  }

  return relationship.references;
};

const notNull = array => _.filter(array, c => !_.isNil(c));

const relationshipNameMatches = (name, relationshipName) =>
  name && relationshipName && (name === relationshipName || name === pluralize(relationshipName) || name === pluralize.singular(relationshipName));

const keysFromNode = node => {
  if (_.isArray(node)) {
    return _.flatMap(notNull(node.map(c => keysFromNode(c))));
  }

  if (_.isString(node)) {
    return null;
  }

  if (_.isObject(node)) {
    const own = _.keys(node);
    const child = notNull(own.map(c => keysFromNode(node[c])));

    return _.flatMap([...own, ...child]);
  }

  return null;
};

const relationshipApplies = (model, relationship, piece, path) => {
  const searchTerm = searchTermForRelationship(model, relationship);
  return _.some(piece, item => {
    const itemComponents = _.take(item.split('.'), path.length + 1);
    const relationshipName = _.last(itemComponents);

    const pathMatches = path.length === 0 || _.every(_.zipWith(path, _.dropRight(itemComponents, 1), relationshipNameMatches));
    return pathMatches && relationshipNameMatches(relationshipName, searchTerm);
  });
};

const compileCheckList = params => {
  const checklist = [];
  if (params.args.select) {
    checklist.push(buildShouldInclude(params.args.select, {overrideDefault: true}));
  }

  if (params.args.expand) {
    checklist.push(buildShouldInclude(params.args.expand, {overrideDefault: true}));
  }

  if (params.args.orderby) {
    checklist.push(buildShouldInclude(params.args.orderby.map(o => o.field)));
  }

  if (params.args.where) {
    checklist.push(buildShouldInclude(keysFromNode(params.args.where)));
  }

  return checklist;
};

const shouldIncludeBySchemaDefinition = (model, relationship, params) => {
  let config = params.mode === READ ? relationship.includeInSelect : relationship.includeInWrite;
  if (config === undefined) {
    return false;
  }

  // include both by default
  if (config === true) {
    config = {model: true, reverse: true};
  }

  // include per config
  if (relationship.model === model.name) {
    return config.model;
  }

  if (relationship.references === model.name) {
    return config.reverse;
  }

  return false;
};

const buildShouldInclude = (models, overrideDefault) =>
  (model, relationship, path) => {
    // nothing specified, so don't imply either way
    if (models === undefined) {
      return undefined;
    }

    const includesRelationship = relationshipApplies(model, relationship, models, path);
    return overrideDefault ? includesRelationship : (includesRelationship || undefined);
  };

const shouldIncludeRelationship = (model, relationship, params, path) => {
  // see if this applies at all to the passed in model (if not it could be brought in elsewhere)
  if (relationship.model !== model.name && relationship.references !== model.name) {
    return false;
  }

  // If some part of our query requires the relationship, it trumps the schema definition
  const checks = compileCheckList(params);

  const results = checks.map(check => check(model, relationship, path));
  const includeByQuery = _.some(results, result => result === true);
  const excludeByQuery = _.some(results, result => result === false);

  if (includeByQuery) {
    return true;
  }

  if (excludeByQuery) {
    return false;
  }

  // follow constraints
  if (params.constraints && !params.constraints.schema) {
    return false;
  }

  // not defined by the query, follow the schema definition
  return shouldIncludeBySchemaDefinition(model, relationship, params);
};

// functions to create the include object per sequelizejs syntax
const findRelatedModel = (model, relationship, params) => {
  if (relationship.model === model.name) {
    return params.models[relationship.references];
  }

  return params.models[relationship.model];
};

const defineSeparate = (relationship, params) => {
  const config = params.mode === READ ? relationship.includeInSelect : relationship.includeInWrite;
  if (!config) {
    return false;
  }

  if (!_.isObject(config)) {
    return false;
  }

  return config.separate && (relationship.type === MANY_TO_MANY || relationship.type === MANY_TO_ONE);
};

const defineAttributes = (related, relationship, params) => {
  if (!params.args.select) {
    return undefined;
  }

  const attributes = [];
  _.forEach(params.args.select, item => {
    const pieces = item.split('.');

    if (pieces.length < 2) {
      return;
    }

    const term = relationship.as ? relationship.as.replace('Id', '') : related.name;

    if (relationshipNameMatches(pieces[pieces.length - 2], term)) {
      attributes.push(pieces[pieces.length - 1]);
    }
  });

  return attributes.length === 0 || attributes.includes('*') ? undefined : attributes;
};

const defineInclude = (model, relationship, params, path) => {
  // find the model
  const related = findRelatedModel(model, relationship, params);
  const relatedName = relationship.as || related.name;

  if (path.includes(relatedName)) {
    return null;
  }

  // build include object
  return {
    as: foreignKeyRelationshipName(relationship),
    model: related,
    include: build(related, params, [...path, relatedName]), // eslint-disable-line
    separate: defineSeparate(relationship, params),
    attributes: defineAttributes(related, relationship, params),
  };
};

// entry point for recursion so we can go down the rabbit hole
const build = (model, params, path) => {

  const appliedRelationships = _.filter(params.relationships,
    relationship => shouldIncludeRelationship(model, relationship, params, _.slice(path, 1)));

  const includes = appliedRelationships.map(
    relationship => defineInclude(model, relationship, params, path)
  );

  return _.filter(includes, x => !_.isNil(x));
};

// public interface
export const buildInclude = (args, mode, model, models, relationships, constraints) =>
  build(model, {mode, models, relationships, args, constraints}, [model.name]);
<commit_msg>add ability to specify separate and perform where clauses on the nested separated queries<commit_after>/* eslint-disable no-undefined */
/* eslint-disable no-use-before-define */
import _ from 'lodash';
import {MANY_TO_ONE, MANY_TO_MANY} from 'node-bits';
import pluralize from 'pluralize';
import {foreignKeyRelationshipName} from '../foreign_key_name';
import {READ} from '../../constants';

// boolean functions to define if the relationship makes it in
const searchTermForRelationship = (model, relationship) => {
  if (relationship.as) {
    return foreignKeyRelationshipName(relationship);
  }

  if (relationship.references === model.name) {
    return relationship.model;
  }

  return relationship.references;
};

const notNull = array => _.filter(array, c => !_.isNil(c));

const relationshipNameMatches = (name, relationshipName) =>
  name && relationshipName && (name === relationshipName || name === pluralize(relationshipName) || name === pluralize.singular(relationshipName));

const keysFromNode = node => {
  if (_.isArray(node)) {
    return _.flatMap(notNull(node.map(c => keysFromNode(c))));
  }

  if (_.isString(node)) {
    return null;
  }

  if (_.isObject(node)) {
    const own = _.keys(node);
    const child = notNull(own.map(c => keysFromNode(node[c])));

    return _.flatMap([...own, ...child]);
  }

  return null;
};

const relationshipApplies = (model, relationship, piece, path) => {
  const searchTerm = searchTermForRelationship(model, relationship);
  return _.some(piece, item => {
    const itemComponents = _.take(item.split('.'), path.length + 1);
    const relationshipName = _.last(itemComponents);

    const pathMatches = path.length === 0 || _.every(_.zipWith(path, _.dropRight(itemComponents, 1), relationshipNameMatches));
    return pathMatches && relationshipNameMatches(relationshipName, searchTerm);
  });
};

const compileCheckList = params => {
  const checklist = [];
  if (params.args.select) {
    checklist.push(buildShouldInclude(params.args.select, {overrideDefault: true}));
  }

  if (params.args.expand) {
    checklist.push(buildShouldInclude(params.args.expand, {overrideDefault: true}));
  }

  if (params.args.orderby) {
    checklist.push(buildShouldInclude(params.args.orderby.map(o => o.field)));
  }

  if (params.args.where) {
    checklist.push(buildShouldInclude(keysFromNode(params.args.where)));
  }

  return checklist;
};

const shouldIncludeBySchemaDefinition = (model, relationship, params) => {
  let config = params.mode === READ ? relationship.includeInSelect : relationship.includeInWrite;
  if (config === undefined) {
    return false;
  }

  // include both by default
  if (config === true) {
    config = {model: true, reverse: true};
  }

  // include per config
  if (relationship.model === model.name) {
    return config.model;
  }

  if (relationship.references === model.name) {
    return config.reverse;
  }

  return false;
};

const buildShouldInclude = (models, overrideDefault) =>
  (model, relationship, path) => {
    // nothing specified, so don't imply either way
    if (models === undefined) {
      return undefined;
    }

    const includesRelationship = relationshipApplies(model, relationship, models, path);
    return overrideDefault ? includesRelationship : (includesRelationship || undefined);
  };

const shouldIncludeRelationship = (model, relationship, params, path) => {
  // see if this applies at all to the passed in model (if not it could be brought in elsewhere)
  if (relationship.model !== model.name && relationship.references !== model.name) {
    return false;
  }

  // If some part of our query requires the relationship, it trumps the schema definition
  const checks = compileCheckList(params);

  const results = checks.map(check => check(model, relationship, path));
  const includeByQuery = _.some(results, result => result === true);
  const excludeByQuery = _.some(results, result => result === false);

  if (includeByQuery) {
    return true;
  }

  if (excludeByQuery) {
    return false;
  }

  // follow constraints
  if (params.constraints && !params.constraints.schema) {
    return false;
  }

  // not defined by the query, follow the schema definition
  return shouldIncludeBySchemaDefinition(model, relationship, params);
};

// functions to create the include object per sequelizejs syntax
const findRelatedModel = (model, relationship, params) => {
  if (relationship.model === model.name) {
    return params.models[relationship.references];
  }

  return params.models[relationship.model];
};

const defineSeparate = (relationship, params) => {
  const config = params.mode === READ ? relationship.includeInSelect : relationship.includeInWrite;
  const separateFromArgs = params.args.separate ? _.some(_.find(params.args.separate, model => _.isObject(model) ? model.model : model === pluralize(relationship.model))) : false;

  if (separateFromArgs) {
    return true;
  }

  if (!config) {
    return false;
  }

  if (!_.isObject(config)) {
    return false;
  }

  return config.separate && (relationship.type === MANY_TO_MANY || relationship.type === MANY_TO_ONE);
};

const defineAttributes = (related, relationship, params) => {
  if (!params.args.select) {
    return undefined;
  }

  const attributes = [];
  _.forEach(params.args.select, item => {
    const pieces = item.split('.');

    if (pieces.length < 2) {
      return;
    }

    const term = relationship.as ? relationship.as.replace('Id', '') : related.name;

    if (relationshipNameMatches(pieces[pieces.length - 2], term)) {
      attributes.push(pieces[pieces.length - 1]);
    }
  });

  return attributes.length === 0 || attributes.includes('*') ? undefined : attributes;
};

const defineNestedWhere = (relatedName, params) => {
  const nestedWhere = params.args.separate ? _.get(_.find(params.args.separate, separate => separate.model === pluralize(relatedName)), 'where', {}) : {};

  return {
    ...nestedWhere,
  };
};

const defineInclude = (model, relationship, params, path) => {
  // find the model
  const related = findRelatedModel(model, relationship, params);
  const relatedName = relationship.as || related.name;

  if (path.includes(relatedName)) {
    return null;
  }

  // build include object
  return {
    as: foreignKeyRelationshipName(relationship),
    model: related,
    include: build(related, params, [...path, relatedName]), // eslint-disable-line
    separate: defineSeparate(relationship, params),
    attributes: defineAttributes(related, relationship, params),
    where: defineNestedWhere(relatedName, params),
  };
};

// entry point for recursion so we can go down the rabbit hole
const build = (model, params, path) => {

  const appliedRelationships = _.filter(params.relationships,
    relationship => shouldIncludeRelationship(model, relationship, params, _.slice(path, 1)));

  const includes = appliedRelationships.map(
    relationship => defineInclude(model, relationship, params, path)
  );

  return _.filter(includes, x => !_.isNil(x));
};

// public interface
export const buildInclude = (args, mode, model, models, relationships, constraints) =>
  build(model, {mode, models, relationships, args, constraints}, [model.name]);
<|endoftext|>


<commit_before>//  The OARN Relief Nursery Database
//  Copyright (C) 2015  Oregon Association of Relief Nurseries
//
//  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/>.

(function (enyo, scope) {

    enyo.kind({
        name: 'oarn.ClassroomManager',

        kind: 'onyx.Popup',

        autoDismiss: false,

        modal: true,

        centered: true,

        scrim: true,

        floating: true,

        style: 'background-color: #EAEAEA; min-width:450px; min-height:300px;',

        published: {
            /**
             * The API auth token, bound to the widget by a parent control.
             *
             * @type {string}
             * @default null
             * @public
             */
            token: null,

            /**
             * The oarn API object, instantiated in create.
             *
             * @type {object}
             * @public
             */
            api: null,

            /**
             * An object bound from the parent with properties: organization_id, name, and short_name
             */
            selectedOrganization: null

        },

        components: [
            {style: 'min-height: 250px;', components: [
                {name: 'classroomRepeaterDisplay', kind: 'oarn.RepeaterDisplay', style: 'padding: 5px 5px 5px 5px;'},

                {name: 'classRepeaterDisplay', kind: 'oarn.RepeaterDisplay', style: 'padding: 5px 5px 5px 5px',
                    showing: false},
            ]},

            {name: 'buttonsRow', style: 'text-align: center; padding-top:5px',
                components: [{name: 'btnClose', kind: 'onyx.Button', content: 'Close',
                        style: 'margin: 5px 5px 5px 5px',	ontap: 'goClose'}]},

            {name: 'selectHelper', kind: 'oarn.SelectHelper'},
            {name: 'popupFactory', kind: 'oarn.PopupFactory'}
        ],

        events: {
            onClassroomManagerClosed: ''
        },

        handlers: {
            onRowSelected: 'rowSelectedHandler',
            onRowDeleted: 'rowDeletedHandler',
            onRepeaterRendered: 'repeaterRenderedHandler'
        },

        create: function () {
            this.inherited(arguments);
            this.defineClassroomRepeaterDisplay();
        },

        defineClassroomRepeaterDisplay: function () {
            this.$.classroomRepeaterDisplay.set('.groupboxHeaderText', 'Physical Classrooms');
            this.$.classroomRepeaterDisplay.set('.newRecordHeaderText', 'New Physical Classroom');
            this.$.classroomRepeaterDisplay.set('.hasSimpleSelectButton', true);

            this.$.classroomRepeaterDisplay.selectEndpoints = [
                {
                    endpoint: 'api/v1/organization/locations/',
                    name: 'locations',
                    parseWith: this.$.selectHelper.parseOrgLocation
                }
            ];

            this.$.classroomRepeaterDisplay.staticPostFields = [
                {fieldName: 'organization', value: this.get('.selectedOrganization.organization_id')}
            ];

            this.$.classroomRepeaterDisplay.fields = [
                {
                    fieldName: 'organization',
                    postBodyOnly: true
                },
                {
                    fieldName: 'classroom_id',
                    postBodyOnly: true,
                    primaryKey: true
                },
                {
                    fieldName: 'name',
                    fieldType: 'TextBox',
                    fieldWidth: 175,
                    headerText: 'Classroom Name',
                    labelText: 'Classroom Name',
                    validator: this.classroomNameIsValid
                },
                {
                    fieldName: 'location',
                    fieldType: 'DataSelect',
                    dataSelectName: 'locations',
                    headerText: 'Location',
                    labelText: 'Location',
                    fieldWidth: 100
                }

            ];

            this.$.classroomRepeaterDisplay.endpoint = 'api/v1/attendance/classrooms/';
            this.$.classroomRepeaterDisplay.patchEndpoint = 'api/v1/attendance/classrooms/';
            this.$.classroomRepeaterDisplay.postEndpoint = 'api/v1/attendance/classrooms/';
            this.$.classroomRepeaterDisplay.deleteEndpoint = 'api/v1/attendance/classrooms/';

            this.$.classroomRepeaterDisplay.allowsNewRecords = true; // this page is accessible only to admins

            this.$.classroomRepeaterDisplay.set('maxWidth', 500);

            this.$.classroomRepeaterDisplay.createRepeater();

            this.set('.classroomRepeaterInitialized', true);
            this.hide();
            this.show();
        },

        goClose: function (inSender, inEvent) {
            this.hide();
            this.doClassroomManagerClosed();
        },

        classroomNameIsValid: function (val) {
            if (val.length > 0) {
                return true;
            }
            else {
                return false;
            }
        },

        defineClassRepeaterDisplay: function (classroomID) {
            this.$.classRepeaterDisplay.set('.groupboxHeaderText', 'Classes in Selected Classroom');
            this.$.classRepeaterDisplay.set('.newRecordHeaderText', 'New Class');
            this.$.classRepeaterDisplay.set('.hasSimpleSelectButton', false);

            this.$.classRepeaterDisplay.selectEndpoints = [];

            this.$.classRepeaterDisplay.staticPostFields = [
                {fieldName: 'classroom', value: classroomID}
            ];

            this.$.classRepeaterDisplay.fields = [
                {
                    fieldName: 'organization',
                    postBodyOnly: true
                },
                {
                    fieldName: 'class_schedule_id',
                    postBodyOnly: true,
                    primaryKey: true
                },
                {
                    fieldName: 'name',
                    fieldType: 'TextBox',
                    fieldWidth: 175,
                    headerText: 'Class Name',
                    labelText: 'Class Name',
                    validator: this.classroomNameIsValid
                },
                {
                    fieldName: 'sunday',
                    fieldType: 'Checkbox',
                    headerText: 'Sun',
                    labelText: 'Sunday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'monday',
                    fieldType: 'Checkbox',
                    headerText: 'Mon',
                    labelText: 'Monday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'tuesday',
                    fieldType: 'Checkbox',
                    headerText: 'Tues',
                    labelText: 'Tuesday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'wednesday',
                    fieldType: 'Checkbox',
                    headerText: 'Wed',
                    labelText: 'Wednesday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'thursday',
                    fieldType: 'Checkbox',
                    headerText: 'Thur',
                    labelText: 'Thursday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'friday',
                    fieldType: 'Checkbox',
                    headerText: 'Fri',
                    labelText: 'Friday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'saturday',
                    fieldType: 'Checkbox',
                    headerText: 'Sat',
                    labelText: 'Saturday',
                    fieldWidth: 30
                }

            ];

            this.$.classRepeaterDisplay.endpoint = 'api/v1/attendance/class-schedules/?classroom_id=' +
                classroomID;
            this.$.classRepeaterDisplay.patchEndpoint = 'api/v1/attendance/class-schedules/';
            this.$.classRepeaterDisplay.postEndpoint = 'api/v1/attendance/class-schedules/';
            this.$.classRepeaterDisplay.deleteEndpoint = 'api/v1/attendance/class-schedules/';

            this.$.classRepeaterDisplay.allowsNewRecords = true; // this page is accessible only to admins

            this.$.classRepeaterDisplay.set('maxWidth', 500);

            this.$.classRepeaterDisplay.show();
            this.$.classRepeaterDisplay.createRepeater();

            this.set('.classRepeaterInitialized', true);
            this.hide();
            this.show();
        },

        rowSelectedHandler: function (inSender, inEvent) {
            if (inEvent.originator.name == 'classroomRepeaterDisplay') {
                this.defineClassRepeaterDisplay(inEvent.primaryKey);
            }
        },

        rowDeletedHandler: function (inSender, inEvent) {
            if (inEvent.originator.name == 'classroomRepeaterDisplay') {
                if (inEvent.primaryKey == this.$.classRepeaterDisplay.staticPostFields[0].value) {
                    this.$.classRepeaterDisplay.hide();
                }
            }
        },

        repeaterRenderedHandler: function (inSender, inEvent) {
            this.hide();
            this.show();
        }
    });

}) (enyo, this);<commit_msg>Fixed undefined variable bug in classroom deletion<commit_after>//  The OARN Relief Nursery Database
//  Copyright (C) 2015  Oregon Association of Relief Nurseries
//
//  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/>.

(function (enyo, scope) {

    enyo.kind({
        name: 'oarn.ClassroomManager',

        kind: 'onyx.Popup',

        autoDismiss: false,

        modal: true,

        centered: true,

        scrim: true,

        floating: true,

        style: 'background-color: #EAEAEA; min-width:450px; min-height:300px;',

        published: {
            /**
             * The API auth token, bound to the widget by a parent control.
             *
             * @type {string}
             * @default null
             * @public
             */
            token: null,

            /**
             * The oarn API object, instantiated in create.
             *
             * @type {object}
             * @public
             */
            api: null,

            /**
             * An object bound from the parent with properties: organization_id, name, and short_name
             */
            selectedOrganization: null

        },

        components: [
            {style: 'min-height: 250px;', components: [
                {name: 'classroomRepeaterDisplay', kind: 'oarn.RepeaterDisplay', style: 'padding: 5px 5px 5px 5px;'},

                {name: 'classRepeaterDisplay', kind: 'oarn.RepeaterDisplay', style: 'padding: 5px 5px 5px 5px',
                    showing: false},
            ]},

            {name: 'buttonsRow', style: 'text-align: center; padding-top:5px',
                components: [{name: 'btnClose', kind: 'onyx.Button', content: 'Close',
                        style: 'margin: 5px 5px 5px 5px',	ontap: 'goClose'}]},

            {name: 'selectHelper', kind: 'oarn.SelectHelper'},
            {name: 'popupFactory', kind: 'oarn.PopupFactory'}
        ],

        events: {
            onClassroomManagerClosed: ''
        },

        handlers: {
            onRowSelected: 'rowSelectedHandler',
            onRowDeleted: 'rowDeletedHandler',
            onRepeaterRendered: 'repeaterRenderedHandler'
        },

        create: function () {
            this.inherited(arguments);
            this.defineClassroomRepeaterDisplay();
        },

        defineClassroomRepeaterDisplay: function () {
            this.$.classroomRepeaterDisplay.set('.groupboxHeaderText', 'Physical Classrooms');
            this.$.classroomRepeaterDisplay.set('.newRecordHeaderText', 'New Physical Classroom');
            this.$.classroomRepeaterDisplay.set('.hasSimpleSelectButton', true);

            this.$.classroomRepeaterDisplay.selectEndpoints = [
                {
                    endpoint: 'api/v1/organization/locations/',
                    name: 'locations',
                    parseWith: this.$.selectHelper.parseOrgLocation
                }
            ];

            this.$.classroomRepeaterDisplay.staticPostFields = [
                {fieldName: 'organization', value: this.get('.selectedOrganization.organization_id')}
            ];

            this.$.classroomRepeaterDisplay.fields = [
                {
                    fieldName: 'organization',
                    postBodyOnly: true
                },
                {
                    fieldName: 'classroom_id',
                    postBodyOnly: true,
                    primaryKey: true
                },
                {
                    fieldName: 'name',
                    fieldType: 'TextBox',
                    fieldWidth: 175,
                    headerText: 'Classroom Name',
                    labelText: 'Classroom Name',
                    validator: this.classroomNameIsValid
                },
                {
                    fieldName: 'location',
                    fieldType: 'DataSelect',
                    dataSelectName: 'locations',
                    headerText: 'Location',
                    labelText: 'Location',
                    fieldWidth: 100
                }

            ];

            this.$.classroomRepeaterDisplay.endpoint = 'api/v1/attendance/classrooms/';
            this.$.classroomRepeaterDisplay.patchEndpoint = 'api/v1/attendance/classrooms/';
            this.$.classroomRepeaterDisplay.postEndpoint = 'api/v1/attendance/classrooms/';
            this.$.classroomRepeaterDisplay.deleteEndpoint = 'api/v1/attendance/classrooms/';

            this.$.classroomRepeaterDisplay.allowsNewRecords = true; // this page is accessible only to admins

            this.$.classroomRepeaterDisplay.set('maxWidth', 500);

            this.$.classroomRepeaterDisplay.createRepeater();

            this.set('.classroomRepeaterInitialized', true);
            this.hide();
            this.show();
        },

        goClose: function (inSender, inEvent) {
            this.hide();
            this.doClassroomManagerClosed();
        },

        classroomNameIsValid: function (val) {
            if (val.length > 0) {
                return true;
            }
            else {
                return false;
            }
        },

        defineClassRepeaterDisplay: function (classroomID) {
            this.$.classRepeaterDisplay.set('.groupboxHeaderText', 'Classes in Selected Classroom');
            this.$.classRepeaterDisplay.set('.newRecordHeaderText', 'New Class');
            this.$.classRepeaterDisplay.set('.hasSimpleSelectButton', false);

            this.$.classRepeaterDisplay.selectEndpoints = [];

            this.$.classRepeaterDisplay.staticPostFields = [
                {fieldName: 'classroom', value: classroomID}
            ];

            this.$.classRepeaterDisplay.fields = [
                {
                    fieldName: 'organization',
                    postBodyOnly: true
                },
                {
                    fieldName: 'class_schedule_id',
                    postBodyOnly: true,
                    primaryKey: true
                },
                {
                    fieldName: 'name',
                    fieldType: 'TextBox',
                    fieldWidth: 175,
                    headerText: 'Class Name',
                    labelText: 'Class Name',
                    validator: this.classroomNameIsValid
                },
                {
                    fieldName: 'sunday',
                    fieldType: 'Checkbox',
                    headerText: 'Sun',
                    labelText: 'Sunday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'monday',
                    fieldType: 'Checkbox',
                    headerText: 'Mon',
                    labelText: 'Monday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'tuesday',
                    fieldType: 'Checkbox',
                    headerText: 'Tues',
                    labelText: 'Tuesday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'wednesday',
                    fieldType: 'Checkbox',
                    headerText: 'Wed',
                    labelText: 'Wednesday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'thursday',
                    fieldType: 'Checkbox',
                    headerText: 'Thur',
                    labelText: 'Thursday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'friday',
                    fieldType: 'Checkbox',
                    headerText: 'Fri',
                    labelText: 'Friday',
                    fieldWidth: 30,
                },
                {
                    fieldName: 'saturday',
                    fieldType: 'Checkbox',
                    headerText: 'Sat',
                    labelText: 'Saturday',
                    fieldWidth: 30
                }

            ];

            this.$.classRepeaterDisplay.endpoint = 'api/v1/attendance/class-schedules/?classroom_id=' +
                classroomID;
            this.$.classRepeaterDisplay.patchEndpoint = 'api/v1/attendance/class-schedules/';
            this.$.classRepeaterDisplay.postEndpoint = 'api/v1/attendance/class-schedules/';
            this.$.classRepeaterDisplay.deleteEndpoint = 'api/v1/attendance/class-schedules/';

            this.$.classRepeaterDisplay.allowsNewRecords = true; // this page is accessible only to admins

            this.$.classRepeaterDisplay.set('maxWidth', 500);

            this.$.classRepeaterDisplay.show();
            this.$.classRepeaterDisplay.createRepeater();

            this.set('.classRepeaterInitialized', true);
            this.hide();
            this.show();
        },

        rowSelectedHandler: function (inSender, inEvent) {
            if (inEvent.originator.name == 'classroomRepeaterDisplay') {
                this.defineClassRepeaterDisplay(inEvent.primaryKey);
            }
        },

        rowDeletedHandler: function (inSender, inEvent) {
            if (inEvent.originator.name == 'classroomRepeaterDisplay') {
                if (typeof this.$.classRepeaterDisplay.staticPostFields[0] !== 'undefined' && inEvent.primaryKey == this.$.classRepeaterDisplay.staticPostFields[0].value) {
                    this.$.classRepeaterDisplay.hide();
                }
            }
        },

        repeaterRenderedHandler: function (inSender, inEvent) {
            this.hide();
            this.show();
        }
    });

}) (enyo, this);
<|endoftext|>


<commit_before>import { h, ref, isRef, computed, watch, provide, onBeforeUnmount, getCurrentInstance } from 'vue'

import QBtn from '../btn/QBtn.js'
import QIcon from '../icon/QIcon.js'
import QSpinner from '../spinner/QSpinner.js'
import QCircularProgress from '../circular-progress/QCircularProgress.js'

import useDark, { useDarkProps } from '../../composables/private/use-dark.js'
import useFile, { useFileProps, useFileEmits } from '../../composables/private/use-file.js'

import { stop } from '../../utils/event.js'
import { humanStorageSize } from '../../utils/format.js'
import { uploaderKey } from '../../utils/private/symbols.js'

function getProgressLabel (p) {
  return (p * 100).toFixed(2) + '%'
}

export const coreProps = {
  ...useDarkProps,
  ...useFileProps,

  label: String,

  color: String,
  textColor: String,

  square: Boolean,
  flat: Boolean,
  bordered: Boolean,

  noThumbnails: Boolean,
  autoUpload: Boolean,
  hideUploadBtn: Boolean,

  disable: Boolean,
  readonly: Boolean
}

export const coreEmits = [
  ...useFileEmits,
  'start', 'finish', 'added', 'removed'
]

export function getRenderer (getPlugin) {
  const vm = getCurrentInstance()
  const { props, slots, emit, proxy } = vm
  const { $q } = proxy

  const isDark = useDark(props, $q)

  function updateFileStatus (file, status, uploadedSize) {
    file.__status = status

    if (status === 'idle') {
      file.__uploaded = 0
      file.__progress = 0
      file.__sizeLabel = humanStorageSize(file.size)
      file.__progressLabel = '0.00%'
      return
    }
    if (status === 'failed') {
      proxy.$forceUpdate()
      return
    }

    file.__uploaded = status === 'uploaded'
      ? file.size
      : uploadedSize

    file.__progress = status === 'uploaded'
      ? 1
      : Math.min(0.9999, file.__uploaded / file.size)

    file.__progressLabel = getProgressLabel(file.__progress)
    proxy.$forceUpdate()
  }

  const state = {
    files: ref([]),
    queuedFiles: ref([]),
    uploadedFiles: ref([]),
    uploadedSize: ref(0),

    updateFileStatus,
    isAlive () {
      return vm.isDeactivated !== true && vm.isUnmounted !== true
    }
  }

  Object.assign(state, getPlugin({ props, slots, emit, helpers: state }))

  const uploadSize = ref(0)
  const editable = computed(() => props.disable !== true && props.readonly !== true)

  if (state.isBusy === void 0) {
    state.isBusy = ref(false)
  }

  const dnd = ref(false)

  const rootRef = ref(null)
  const inputRef = ref(null)

  provide(uploaderKey, renderInput)

  const {
    pickFiles,
    addFiles,
    onDragover,
    onDragleave,
    processFiles,
    getDndNode,
    maxFilesNumber,
    maxTotalSizeNumber
  } = useFile({ editable, dnd, getFileInput, addFilesToQueue })

  const canAddFiles = computed(() =>
    editable.value === true
    && state.isUploading.value !== true
    // if single selection and no files are queued:
    && (props.multiple === true || state.queuedFiles.value.length === 0)
    // if max-files is set and current number of files does not exceeds it:
    && (props.maxFiles === void 0 || state.files.value.length < maxFilesNumber.value)
    // if max-total-size is set and current upload size does not exceeds it:
    && (props.maxTotalSize === void 0 || uploadSize.value < maxTotalSizeNumber.value)
  )

  const canUpload = computed(() =>
    editable.value === true
    && state.isBusy.value !== true
    && state.isUploading.value !== true
    && state.queuedFiles.value.length > 0
  )

  const uploadProgress = computed(() => (
    uploadSize.value === 0
      ? 0
      : state.uploadedSize.value / uploadSize.value
  ))

  const uploadProgressLabel = computed(() => getProgressLabel(uploadProgress.value))
  const uploadSizeLabel = computed(() => humanStorageSize(uploadSize.value))

  const classes = computed(() =>
    'q-uploader column no-wrap'
    + (isDark.value === true ? ' q-uploader--dark q-dark' : '')
    + (props.bordered === true ? ' q-uploader--bordered' : '')
    + (props.square === true ? ' q-uploader--square no-border-radius' : '')
    + (props.flat === true ? ' q-uploader--flat no-shadow' : '')
    + (props.disable === true ? ' disabled q-uploader--disable' : '')
  )

  const colorClass = computed(() =>
    'q-uploader__header'
    + (props.color !== void 0 ? ` bg-${ props.color }` : '')
    + (props.textColor !== void 0 ? ` text-${ props.textColor }` : '')
  )

  watch(state.isUploading, (newVal, oldVal) => {
    if (oldVal === false && newVal === true) {
      emit('start')
    }
    else if (oldVal === true && newVal === false) {
      emit('finish')
    }
  })

  function reset () {
    if (props.disable === false) {
      state.abort()
      state.uploadedSize.value = 0
      uploadSize.value = 0
      revokeImgURLs()
      state.files.value = []
      state.queuedFiles.value = []
      state.uploadedFiles.value = []
    }
  }

  function removeUploadedFiles () {
    if (props.disable === false) {
      batchRemoveFiles([ 'uploaded' ], () => {
        state.uploadedFiles.value = []
      })
    }
  }

  function removeQueuedFiles () {
    batchRemoveFiles([ 'idle', 'failed' ], ({ size }) => {
      uploadSize.value -= size
      state.queuedFiles.value = []
    })
  }

  function batchRemoveFiles (statusList, cb) {
    if (props.disable === true) {
      return
    }

    const removed = {
      files: [],
      size: 0
    }

    const localFiles = state.files.value.filter(f => {
      if (statusList.indexOf(f.__status) === -1) {
        return true
      }

      removed.size += f.size
      removed.files.push(f)

      f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src)

      return false
    })

    if (removed.files.length > 0) {
      state.files.value = localFiles
      cb(removed)
      emit('removed', removed.files)
    }
  }

  function removeFile (file) {
    if (props.disable) { return }

    if (file.__status === 'uploaded') {
      state.uploadedFiles.value = state.uploadedFiles.value.filter(f => f.__key !== file.__key)
    }
    else if (file.__status === 'uploading') {
      file.__abort()
    }
    else {
      uploadSize.value -= file.size
    }

    state.files.value = state.files.value.filter(f => {
      if (f.__key !== file.__key) {
        return true
      }

      f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src)

      return false
    })

    state.queuedFiles.value = state.queuedFiles.value.filter(f => f.__key !== file.__key)
    emit('removed', [ file ])
  }

  function revokeImgURLs () {
    state.files.value.forEach(f => {
      f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src)
    })
  }

  function getFileInput () {
    return inputRef.value
      || rootRef.value.getElementsByClassName('q-uploader__input')[ 0 ]
  }

  function addFilesToQueue (e, fileList) {
    const localFiles = processFiles(e, fileList, state.files.value, true)

    if (localFiles === void 0) { return }

    const fileInput = getFileInput()
    if (fileInput !== void 0 && fileInput !== null) {
      fileInput.value = ''
    }

    localFiles.forEach(file => {
      state.updateFileStatus(file, 'idle')
      uploadSize.value += file.size

      if (props.noThumbnails !== true && file.type.toUpperCase().startsWith('IMAGE')) {
        const img = new Image()
        img.src = window.URL.createObjectURL(file)
        file.__img = img
      }
    })

    state.files.value = state.files.value.concat(localFiles)
    state.queuedFiles.value = state.queuedFiles.value.concat(localFiles)
    emit('added', localFiles)
    props.autoUpload === true && state.upload()
  }

  function upload () {
    canUpload.value === true && state.upload()
  }

  function getBtn (show, icon, fn) {
    if (show === true) {
      const data = {
        type: 'a',
        key: icon,
        icon: $q.iconSet.uploader[ icon ],
        flat: true,
        dense: true
      }

      let child = void 0

      if (icon === 'add') {
        data.onClick = pickFiles
        child = renderInput
      }
      else {
        data.onClick = fn
      }

      return h(QBtn, data, child)
    }
  }

  function renderInput () {
    return h('input', {
      ref: inputRef,
      class: 'q-uploader__input overflow-hidden absolute-full',
      tabindex: -1,
      type: 'file',
      title: '', // try to remove default tooltip
      accept: props.accept,
      multiple: props.multiple === true ? 'multiple' : void 0,
      capture: props.capture,
      onMousedown: stop, // need to stop refocus from QBtn
      onClick: pickFiles,
      onChange: addFilesToQueue
    })
  }

  function getHeader () {
    if (slots.header !== void 0) {
      return slots.header(slotScope.value)
    }

    return [
      h('div', {
        class: 'q-uploader__header-content flex flex-center no-wrap q-gutter-xs'
      }, [
        getBtn(state.queuedFiles.value.length > 0, 'removeQueue', removeQueuedFiles),
        getBtn(state.uploadedFiles.value.length > 0, 'removeUploaded', removeUploadedFiles),

        state.isUploading.value === true
          ? h(QSpinner, { class: 'q-uploader__spinner' })
          : null,

        h('div', { class: 'col column justify-center' }, [
          props.label !== void 0
            ? h('div', { class: 'q-uploader__title' }, [ props.label ])
            : null,

          h('div', { class: 'q-uploader__subtitle' }, [
            uploadSizeLabel.value + ' / ' + uploadProgressLabel.value
          ])
        ]),

        getBtn(canAddFiles.value, 'add'),
        getBtn(props.hideUploadBtn === false && canUpload.value === true, 'upload', state.upload),
        getBtn(state.isUploading.value, 'clear', state.abort)
      ])
    ]
  }

  function getList () {
    if (slots.list !== void 0) {
      return slots.list(slotScope.value)
    }

    return state.files.value.map(file => h('div', {
      key: file.__key,
      class: 'q-uploader__file relative-position'
        + (props.noThumbnails !== true && file.__img !== void 0 ? ' q-uploader__file--img' : '')
        + (
          file.__status === 'failed'
            ? ' q-uploader__file--failed'
            : (file.__status === 'uploaded' ? ' q-uploader__file--uploaded' : '')
        ),
      style: props.noThumbnails !== true && file.__img !== void 0
        ? { backgroundImage: 'url("' + file.__img.src + '")' }
        : null
    }, [
      h('div', {
        class: 'q-uploader__file-header row flex-center no-wrap'
      }, [
        file.__status === 'failed'
          ? h(QIcon, {
            class: 'q-uploader__file-status',
            name: $q.iconSet.type.negative,
            color: 'negative'
          })
          : null,

        h('div', { class: 'q-uploader__file-header-content col' }, [
          h('div', { class: 'q-uploader__title' }, [ file.name ]),
          h('div', {
            class: 'q-uploader__subtitle row items-center no-wrap'
          }, [
            file.__sizeLabel + ' / ' + file.__progressLabel
          ])
        ]),

        file.__status === 'uploading'
          ? h(QCircularProgress, {
            value: file.__progress,
            min: 0,
            max: 1,
            indeterminate: file.__progress === 0
          })
          : h(QBtn, {
            round: true,
            dense: true,
            flat: true,
            icon: $q.iconSet.uploader[ file.__status === 'uploaded' ? 'done' : 'clear' ],
            onClick: () => { removeFile(file) }
          })
      ])
    ]))
  }

  onBeforeUnmount(() => {
    state.isUploading.value === true && state.abort()
    state.files.value.length > 0 && revokeImgURLs()
  })

  const publicMethods = {
    pickFiles,
    addFiles,
    reset,
    removeUploadedFiles,
    removeQueuedFiles,
    removeFile,
    upload,
    abort: state.abort
  }

  // TODO: the result of this computed, especially the dynamic part, isn't currently typed
  // This result in an error with Volar when accessing the state (eg. files array)
  const slotScope = computed(() => {
    const acc = {
      canAddFiles: canAddFiles.value,
      canUpload: canUpload.value,
      uploadSizeLabel: uploadSizeLabel.value,
      uploadProgressLabel: uploadProgressLabel.value
    }

    for (const key in state) {
      acc[ key ] = isRef(state[ key ]) === true
        ? state[ key ].value
        : state[ key ]
    }

    // TODO: (Qv3) Put the QUploader instance under `ref`
    // property for consistency and flexibility
    // return { ref: { ...acc, ...publicMethods } }
    return { ...acc, ...publicMethods }
  })

  // expose public methods
  Object.assign(proxy, publicMethods)

  return () => {
    const children = [
      h('div', { class: colorClass.value }, getHeader()),
      h('div', { class: 'q-uploader__list scroll' }, getList()),
      getDndNode('uploader')
    ]

    state.isBusy.value === true && children.push(
      h('div', {
        class: 'q-uploader__overlay absolute-full flex flex-center'
      }, [ h(QSpinner) ])
    )

    const data = { ref: rootRef, class: classes.value }

    if (canAddFiles.value === true) {
      Object.assign(data, { onDragover, onDragleave })
    }

    return h('div', data, children)
  }
}
<commit_msg>fix(QUploader): prevent flicker when dragging out (#13273)<commit_after>import { h, ref, isRef, computed, watch, provide, onBeforeUnmount, getCurrentInstance } from 'vue'

import QBtn from '../btn/QBtn.js'
import QIcon from '../icon/QIcon.js'
import QSpinner from '../spinner/QSpinner.js'
import QCircularProgress from '../circular-progress/QCircularProgress.js'

import useDark, { useDarkProps } from '../../composables/private/use-dark.js'
import useFile, { useFileProps, useFileEmits } from '../../composables/private/use-file.js'

import { stop } from '../../utils/event.js'
import { humanStorageSize } from '../../utils/format.js'
import { uploaderKey } from '../../utils/private/symbols.js'

function getProgressLabel (p) {
  return (p * 100).toFixed(2) + '%'
}

export const coreProps = {
  ...useDarkProps,
  ...useFileProps,

  label: String,

  color: String,
  textColor: String,

  square: Boolean,
  flat: Boolean,
  bordered: Boolean,

  noThumbnails: Boolean,
  autoUpload: Boolean,
  hideUploadBtn: Boolean,

  disable: Boolean,
  readonly: Boolean
}

export const coreEmits = [
  ...useFileEmits,
  'start', 'finish', 'added', 'removed'
]

export function getRenderer (getPlugin) {
  const vm = getCurrentInstance()
  const { props, slots, emit, proxy } = vm
  const { $q } = proxy

  const isDark = useDark(props, $q)

  function updateFileStatus (file, status, uploadedSize) {
    file.__status = status

    if (status === 'idle') {
      file.__uploaded = 0
      file.__progress = 0
      file.__sizeLabel = humanStorageSize(file.size)
      file.__progressLabel = '0.00%'
      return
    }
    if (status === 'failed') {
      proxy.$forceUpdate()
      return
    }

    file.__uploaded = status === 'uploaded'
      ? file.size
      : uploadedSize

    file.__progress = status === 'uploaded'
      ? 1
      : Math.min(0.9999, file.__uploaded / file.size)

    file.__progressLabel = getProgressLabel(file.__progress)
    proxy.$forceUpdate()
  }

  const state = {
    files: ref([]),
    queuedFiles: ref([]),
    uploadedFiles: ref([]),
    uploadedSize: ref(0),

    updateFileStatus,
    isAlive () {
      return vm.isDeactivated !== true && vm.isUnmounted !== true
    }
  }

  Object.assign(state, getPlugin({ props, slots, emit, helpers: state }))

  const uploadSize = ref(0)
  const editable = computed(() => props.disable !== true && props.readonly !== true)

  if (state.isBusy === void 0) {
    state.isBusy = ref(false)
  }

  const dnd = ref(false)

  const rootRef = ref(null)
  const inputRef = ref(null)

  provide(uploaderKey, renderInput)

  const {
    pickFiles,
    addFiles,
    onDragover,
    onDragleave,
    processFiles,
    getDndNode,
    maxFilesNumber,
    maxTotalSizeNumber
  } = useFile({ editable, dnd, getFileInput, addFilesToQueue })

  const canAddFiles = computed(() =>
    editable.value === true
    && state.isUploading.value !== true
    // if single selection and no files are queued:
    && (props.multiple === true || state.queuedFiles.value.length === 0)
    // if max-files is set and current number of files does not exceeds it:
    && (props.maxFiles === void 0 || state.files.value.length < maxFilesNumber.value)
    // if max-total-size is set and current upload size does not exceeds it:
    && (props.maxTotalSize === void 0 || uploadSize.value < maxTotalSizeNumber.value)
  )

  const canUpload = computed(() =>
    editable.value === true
    && state.isBusy.value !== true
    && state.isUploading.value !== true
    && state.queuedFiles.value.length > 0
  )

  const uploadProgress = computed(() => (
    uploadSize.value === 0
      ? 0
      : state.uploadedSize.value / uploadSize.value
  ))

  const uploadProgressLabel = computed(() => getProgressLabel(uploadProgress.value))
  const uploadSizeLabel = computed(() => humanStorageSize(uploadSize.value))

  const classes = computed(() =>
    'q-uploader column no-wrap'
    + (isDark.value === true ? ' q-uploader--dark q-dark' : '')
    + (props.bordered === true ? ' q-uploader--bordered' : '')
    + (props.square === true ? ' q-uploader--square no-border-radius' : '')
    + (props.flat === true ? ' q-uploader--flat no-shadow' : '')
    + (props.disable === true ? ' disabled q-uploader--disable' : '')
  )

  const colorClass = computed(() =>
    'q-uploader__header'
    + (props.color !== void 0 ? ` bg-${ props.color }` : '')
    + (props.textColor !== void 0 ? ` text-${ props.textColor }` : '')
  )

  watch(state.isUploading, (newVal, oldVal) => {
    if (oldVal === false && newVal === true) {
      emit('start')
    }
    else if (oldVal === true && newVal === false) {
      emit('finish')
    }
  })

  function reset () {
    if (props.disable === false) {
      state.abort()
      state.uploadedSize.value = 0
      uploadSize.value = 0
      revokeImgURLs()
      state.files.value = []
      state.queuedFiles.value = []
      state.uploadedFiles.value = []
    }
  }

  function removeUploadedFiles () {
    if (props.disable === false) {
      batchRemoveFiles([ 'uploaded' ], () => {
        state.uploadedFiles.value = []
      })
    }
  }

  function removeQueuedFiles () {
    batchRemoveFiles([ 'idle', 'failed' ], ({ size }) => {
      uploadSize.value -= size
      state.queuedFiles.value = []
    })
  }

  function batchRemoveFiles (statusList, cb) {
    if (props.disable === true) {
      return
    }

    const removed = {
      files: [],
      size: 0
    }

    const localFiles = state.files.value.filter(f => {
      if (statusList.indexOf(f.__status) === -1) {
        return true
      }

      removed.size += f.size
      removed.files.push(f)

      f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src)

      return false
    })

    if (removed.files.length > 0) {
      state.files.value = localFiles
      cb(removed)
      emit('removed', removed.files)
    }
  }

  function removeFile (file) {
    if (props.disable) { return }

    if (file.__status === 'uploaded') {
      state.uploadedFiles.value = state.uploadedFiles.value.filter(f => f.__key !== file.__key)
    }
    else if (file.__status === 'uploading') {
      file.__abort()
    }
    else {
      uploadSize.value -= file.size
    }

    state.files.value = state.files.value.filter(f => {
      if (f.__key !== file.__key) {
        return true
      }

      f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src)

      return false
    })

    state.queuedFiles.value = state.queuedFiles.value.filter(f => f.__key !== file.__key)
    emit('removed', [ file ])
  }

  function revokeImgURLs () {
    state.files.value.forEach(f => {
      f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src)
    })
  }

  function getFileInput () {
    return inputRef.value
      || rootRef.value.getElementsByClassName('q-uploader__input')[ 0 ]
  }

  function addFilesToQueue (e, fileList) {
    const localFiles = processFiles(e, fileList, state.files.value, true)

    if (localFiles === void 0) { return }

    const fileInput = getFileInput()
    if (fileInput !== void 0 && fileInput !== null) {
      fileInput.value = ''
    }

    localFiles.forEach(file => {
      state.updateFileStatus(file, 'idle')
      uploadSize.value += file.size

      if (props.noThumbnails !== true && file.type.toUpperCase().startsWith('IMAGE')) {
        const img = new Image()
        img.src = window.URL.createObjectURL(file)
        file.__img = img
      }
    })

    state.files.value = state.files.value.concat(localFiles)
    state.queuedFiles.value = state.queuedFiles.value.concat(localFiles)
    emit('added', localFiles)
    props.autoUpload === true && state.upload()
  }

  function upload () {
    canUpload.value === true && state.upload()
  }

  function getBtn (show, icon, fn) {
    if (show === true) {
      const data = {
        type: 'a',
        key: icon,
        icon: $q.iconSet.uploader[ icon ],
        flat: true,
        dense: true
      }

      let child = void 0

      if (icon === 'add') {
        data.onClick = pickFiles
        child = renderInput
      }
      else {
        data.onClick = fn
      }

      return h(QBtn, data, child)
    }
  }

  function renderInput () {
    return h('input', {
      ref: inputRef,
      class: 'q-uploader__input overflow-hidden absolute-full',
      tabindex: -1,
      type: 'file',
      title: '', // try to remove default tooltip
      accept: props.accept,
      multiple: props.multiple === true ? 'multiple' : void 0,
      capture: props.capture,
      onMousedown: stop, // need to stop refocus from QBtn
      onClick: pickFiles,
      onChange: addFilesToQueue
    })
  }

  function getHeader () {
    if (slots.header !== void 0) {
      return slots.header(slotScope.value)
    }

    return [
      h('div', {
        class: 'q-uploader__header-content column'
      }, [
        h('div', {
          class: 'flex flex-center no-wrap q-gutter-xs'
        }, [
          getBtn(state.queuedFiles.value.length > 0, 'removeQueue', removeQueuedFiles),
          getBtn(state.uploadedFiles.value.length > 0, 'removeUploaded', removeUploadedFiles),

          state.isUploading.value === true
            ? h(QSpinner, { class: 'q-uploader__spinner' })
            : null,

          h('div', { class: 'col column justify-center' }, [
            props.label !== void 0
              ? h('div', { class: 'q-uploader__title' }, [ props.label ])
              : null,

            h('div', { class: 'q-uploader__subtitle' }, [
              uploadSizeLabel.value + ' / ' + uploadProgressLabel.value
            ])
          ]),

          getBtn(canAddFiles.value, 'add'),
          getBtn(props.hideUploadBtn === false && canUpload.value === true, 'upload', state.upload),
          getBtn(state.isUploading.value, 'clear', state.abort)
        ])
      ])
    ]
  }

  function getList () {
    if (slots.list !== void 0) {
      return slots.list(slotScope.value)
    }

    return state.files.value.map(file => h('div', {
      key: file.__key,
      class: 'q-uploader__file relative-position'
        + (props.noThumbnails !== true && file.__img !== void 0 ? ' q-uploader__file--img' : '')
        + (
          file.__status === 'failed'
            ? ' q-uploader__file--failed'
            : (file.__status === 'uploaded' ? ' q-uploader__file--uploaded' : '')
        ),
      style: props.noThumbnails !== true && file.__img !== void 0
        ? { backgroundImage: 'url("' + file.__img.src + '")' }
        : null
    }, [
      h('div', {
        class: 'q-uploader__file-header row flex-center no-wrap'
      }, [
        file.__status === 'failed'
          ? h(QIcon, {
            class: 'q-uploader__file-status',
            name: $q.iconSet.type.negative,
            color: 'negative'
          })
          : null,

        h('div', { class: 'q-uploader__file-header-content col' }, [
          h('div', { class: 'q-uploader__title' }, [ file.name ]),
          h('div', {
            class: 'q-uploader__subtitle row items-center no-wrap'
          }, [
            file.__sizeLabel + ' / ' + file.__progressLabel
          ])
        ]),

        file.__status === 'uploading'
          ? h(QCircularProgress, {
            value: file.__progress,
            min: 0,
            max: 1,
            indeterminate: file.__progress === 0
          })
          : h(QBtn, {
            round: true,
            dense: true,
            flat: true,
            icon: $q.iconSet.uploader[ file.__status === 'uploaded' ? 'done' : 'clear' ],
            onClick: () => { removeFile(file) }
          })
      ])
    ]))
  }

  onBeforeUnmount(() => {
    state.isUploading.value === true && state.abort()
    state.files.value.length > 0 && revokeImgURLs()
  })

  const publicMethods = {
    pickFiles,
    addFiles,
    reset,
    removeUploadedFiles,
    removeQueuedFiles,
    removeFile,
    upload,
    abort: state.abort
  }

  // TODO: the result of this computed, especially the dynamic part, isn't currently typed
  // This result in an error with Volar when accessing the state (eg. files array)
  const slotScope = computed(() => {
    const acc = {
      canAddFiles: canAddFiles.value,
      canUpload: canUpload.value,
      uploadSizeLabel: uploadSizeLabel.value,
      uploadProgressLabel: uploadProgressLabel.value
    }

    for (const key in state) {
      acc[ key ] = isRef(state[ key ]) === true
        ? state[ key ].value
        : state[ key ]
    }

    // TODO: (Qv3) Put the QUploader instance under `ref`
    // property for consistency and flexibility
    // return { ref: { ...acc, ...publicMethods } }
    return { ...acc, ...publicMethods }
  })

  // expose public methods
  Object.assign(proxy, publicMethods)

  return () => {
    const children = [
      h('div', { class: colorClass.value }, getHeader()),
      h('div', { class: 'q-uploader__list scroll' }, getList()),
      getDndNode('uploader')
    ]

    state.isBusy.value === true && children.push(
      h('div', {
        class: 'q-uploader__overlay absolute-full flex flex-center'
      }, [ h(QSpinner) ])
    )

    const data = { ref: rootRef, class: classes.value }

    if (canAddFiles.value === true) {
      Object.assign(data, { onDragover, onDragleave })
    }

    return h('div', data, children)
  }
}
<|endoftext|>


<commit_before>var q = require('bluebird');
var registry = require("npm-registry");
var _ = require('underscore');
var npm = new registry();

var database = require("./lib/database.js");

// crawlers
var keywords = require("npm-keywords");
var ghClient = require("./lib/github.js");
var npmClient = require("./lib/npm.js");
var npmHistory = require("./lib/npmHistory.js");

// helpers
var postProcessor = require("./lib/util/postProcessor.js");

var workflow = function(opts) {
  this.keys = opts.keys || [];
  this.refreshTime = opts.refreshTime || 3600;
  this.updateInterval = opts.updateInterval || 60;
  this.db = new database();
  this.dbLoad = this.db.load();
};

workflow.prototype.start = function() {
  return this.run().then(function() {
    this.reloadCron = setInterval(this.run.bind(this), this.refreshTime * 1000);
    this.updateCron = setInterval(this.updateCronJob.bind(this), this.updateInterval * 1000);
  }.bind(this));
};

// 1) download package list
workflow.prototype.run = function run() {
  var self = this;
  return keywords(this.keys, npm).map(this.downloadPkg.bind(this)).then(function(pkgs) {
    // on succes:  save to DB
    console.log("workflow: trying to save into DB.");
    self.pkgs = pkgs.map(function(el){
     return {name: el.name, version: el.version};
    });
    self.dbLoad
      .then(self.db.loadCache.bind(self.db))
      .then(function() {
        return self.db.save(pkgs);
      });
  }).then(function() {
    console.log("workflow: load complete.");
  });
};

// 2) crawl the package from NPM
workflow.prototype.downloadPkg = function(name) {
  // also support pkg object with name attribute
  if (name.name !== undefined) {
    name = name.name;
  }
  return new npmClient(name, npm).then(this.postDownload.bind(this));
};

// 3) other info
workflow.prototype.postDownload = function(pkg) {
  var postOpts = {
    keys: this.keys
  };
  var ps = [];
  ps.push(new ghClient(pkg));
  ps.push(new npmHistory(pkg));
  return q.all(ps).then(function() {
    return postProcessor(pkg, postOpts);
  }).catch(function(err) {
    if (err.name !== "queryEvents") {
      console.log("downloaderr", err);
    }
    return pkg;
  });
};

// update a single pkg
workflow.prototype.updatePkg = function(pkg) {
  this.downloadPkg(pkg).then(function(newPkg) {
    var index = _.indexOf(_.pluck(this.pkgs, "name"), newPkg.name);
    this.pkgs[index] = newPkg;
    this.db.updatePkg(newPkg);
    console.log("package update saved: ", newPkg.name);
  }.bind(this)).catch(function(err) {
    console.log("err during package update: ", pkg.name);
    console.log(err);
  });
};

// crons the npm registry manually and checks for package updates
workflow.prototype.updateCronJob = function updateCronJob() {
  var self = this;
  this.pkgs.forEach(function(oldPkg) {
    return new npmClient(oldPkg.name + "@latest", npm).then(function(newPkg) {
      // we have only the latest version -> download entire package
      if (oldPkg.version !== newPkg.version) {
        console.log("new package uploaded: ", newPkg.name);
        self.updatePkg(newPkg.name);
      }
    }.bind(this));
  });
};

workflow.prototype.stop = function() {
  clearInterval(this.updateCron);
  clearInterval(this.reloadCron);
};

module.exports = workflow;
<commit_msg>check for empty package names<commit_after>var q = require('bluebird');
var registry = require("npm-registry");
var _ = require('underscore');
var npm = new registry();

var database = require("./lib/database.js");

// crawlers
var keywords = require("npm-keywords");
var ghClient = require("./lib/github.js");
var npmClient = require("./lib/npm.js");
var npmHistory = require("./lib/npmHistory.js");

// helpers
var postProcessor = require("./lib/util/postProcessor.js");

var workflow = function(opts) {
  this.keys = opts.keys || [];
  this.refreshTime = opts.refreshTime || 3600;
  this.updateInterval = opts.updateInterval || 60;
  this.db = new database();
  this.dbLoad = this.db.load();
};

workflow.prototype.start = function() {
  return this.run().then(function() {
    this.reloadCron = setInterval(this.run.bind(this), this.refreshTime * 1000);
    this.updateCron = setInterval(this.updateCronJob.bind(this), this.updateInterval * 1000);
  }.bind(this));
};

// 1) download package list
workflow.prototype.run = function run() {
  var self = this;
  return keywords(this.keys, npm).map(this.downloadPkg.bind(this)).then(function(pkgs) {
    // on succes:  save to DB
    console.log("workflow: trying to save into DB.");
    self.pkgs = pkgs.map(function(el){
     return {name: el.name, version: el.version};
    });
    self.dbLoad
      .then(self.db.loadCache.bind(self.db))
      .then(function() {
        return self.db.save(pkgs);
      });
  }).then(function() {
    console.log("workflow: load complete.");
  });
};

// 2) crawl the package from NPM
workflow.prototype.downloadPkg = function(name) {
  // also support pkg object with name attribute
  if (name.name !== undefined) {
    name = name.name;
  }
  return new npmClient(name, npm).then(this.postDownload.bind(this));
};

// 3) other info
workflow.prototype.postDownload = function(pkg) {
  var postOpts = {
    keys: this.keys
  };
  var ps = [];
  ps.push(new ghClient(pkg));
  ps.push(new npmHistory(pkg));
  return q.all(ps).then(function() {
    return postProcessor(pkg, postOpts);
  }).catch(function(err) {
    if (err.name !== "queryEvents") {
      console.log("downloaderr", err);
    }
    return pkg;
  });
};

// update a single pkg
workflow.prototype.updatePkg = function(pkg) {
  this.downloadPkg(pkg).then(function(newPkg) {
    var index = _.indexOf(_.pluck(this.pkgs, "name"), newPkg.name);
    this.pkgs[index] = newPkg;
    this.db.updatePkg(newPkg);
    console.log("package update saved: ", newPkg.name);
  }.bind(this)).catch(function(err) {
    console.log("err during package update: ", pkg.name);
    console.log(err);
  });
};

// crons the npm registry manually and checks for package updates
workflow.prototype.updateCronJob = function updateCronJob() {
  var self = this;
  this.pkgs.forEach(function(oldPkg) {
    return new npmClient(oldPkg.name + "@latest", npm).then(function(newPkg) {
      // we have only the latest version -> download entire package
      if (oldPkg.version !== newPkg.version && newPkg.name !== undefined) {
        console.log("new package uploaded: ", newPkg.name);
        self.updatePkg(newPkg.name);
      }
    }.bind(this));
  });
};

workflow.prototype.stop = function() {
  clearInterval(this.updateCron);
  clearInterval(this.reloadCron);
};

module.exports = workflow;
<|endoftext|>


<commit_before>/* ShuffleManager
 *
 * Manages navigation throughout a list which is:
 * - Sourced from another provided list
 * - In random order (except to avoid consecutive duplicates)
 * - Extended endlessly on-the-fly, as needed
 * - Able to have future history overwritten by non-random choices
 * - Able to swap source lists and maintain shuffle order for common members
 */

class ShuffleManager {
  constructor (list) {
    this.list = list;
    this.backStack = [];
    this.forwardStack = [];
    this.currentItem = null;
  }

  findNextItem () {
    if (!this.list.length) {
      return null;
    }
    for (let i = 1; i <= this.forwardStack.length; i++) {
      if (this.list.indexOf(this.forwardStack[this.forwardStack.length - i]) !== -1) {
        goForward(this, i);
        return this.currentItem;
      }
    }
    if (allItemsMatch(this.list, this.currentItem)) {
      // we can serve this as our "next" item but we
      // won't modify our history since it's the same.
      return this.currentItem;
    }
    let nextItem;
    do {
        nextItem = this.list[Math.floor(Math.random() * this.list.length)];
    } while (this.currentItem === nextItem || nextItem === undefined);
    // if we're skipping items that aren't in our current list we may
    // have some items in our forwardStack - make sure we move to the front.
    goForward(this, this.forwardStack.length);
    if (this.currentItem !== undefined) {
      this.backStack.push(this.currentItem);
    }
    this.currentItem = nextItem;
    return this.currentItem;
  }

  findPreviousItem () {
    if (!this.list.length) {
      return null;
    }
    for (let i = 1; i <= this.backStack.length; i++) {
      if (this.list.indexOf(this.backStack[this.backStack.length - i]) !== -1) {
        goBack(this, i);
        return this.currentItem;
      }
    }
    return null;
  }

  pickNextItem (index) {
    if (this.list[index] === undefined) {
      return null;
    }
    if (this.currentItem !== undefined) {
      this.backStack.push(this.currentItem);
    }
    this.forwardStack.length = 0;
    this.currentItem = this.list[index];
    return this.currentItem;
  }

  setList (list) {
    this.list = list;
  }

  clear () {
    this.backStack.length = 0;
    this.forwardStack.length = 0;
    this.currentItem = null;
  }
}

function allItemsMatch (list, item) {
  if (!list.length) {
    return false;
  }
  for (let i = 0; i < list.length; i++) {
    if (item !== list[i]) {
      return false;
    }
  }
  return true;
}

function goForward (sm, n) {
  for (let i = 0; i < n; i++) {
    if (!sm.forwardStack.length) {
      sm.goBack(i); // rollback before erroring
      throw `Could not go forward ${n} times!`;
    }
    sm.backStack.push(sm.currentItem);
    sm.currentItem = sm.forwardStack.pop();
  }
}

function goBack (sm, n) {
  for (let i = 0; i < n; i++) {
    if (!sm.backStack.length) {
      sm.goForward(i); // rollback before erroring
      throw `Could not go back ${n} times!`;
    }
    sm.forwardStack.push(sm.currentItem);
    sm.currentItem = sm.backStack.pop();
  }
}

module.exports = ShuffleManager;
<commit_msg>allowBackShuffle option can be specified for ShuffleManager.<commit_after>/* ShuffleManager
 *
 * Manages navigation throughout a list which is:
 * - Sourced from another provided list
 * - In random order (except to avoid consecutive duplicates)
 * - Extended endlessly on-the-fly, as needed
 * - Able to have future history overwritten by non-random choices
 * - Able to swap source lists and maintain shuffle order for common members
 */

class ShuffleManager {
  constructor (list, options = {}) {
    this.list = list;
    this.backStack = [];
    this.forwardStack = [];
    this.currentItem = null;

    this.setOptions(options);
  }

  findNextItem () {
    if (!this.list.length) {
      return null;
    }
    for (let i = 1; i <= this.forwardStack.length; i++) {
      if (this.list.indexOf(this.forwardStack[this.forwardStack.length - i]) !== -1) {
        goForward(this, i);
        return this.currentItem;
      }
    }
    if (allItemsMatch(this.list, this.currentItem)) {
      // we can serve this as our "next" item but we
      // won't modify our history since it's the same.
      return this.currentItem;
    }
    let nextItem;
    do {
        nextItem = this.list[Math.floor(Math.random() * this.list.length)];
    } while (this.currentItem === nextItem || nextItem === undefined);
    // if we're skipping items that aren't in our current list we may
    // have some items in our forwardStack - make sure we move to the front.
    goForward(this, this.forwardStack.length);
    if (this.currentItem !== undefined) {
      this.backStack.push(this.currentItem);
    }
    this.currentItem = nextItem;
    return this.currentItem;
  }

  findPreviousItem () {
    if (!this.list.length) {
      return null;
    }
    for (let i = 1; i <= this.backStack.length; i++) {
      if (this.list.indexOf(this.backStack[this.backStack.length - i]) !== -1) {
        goBack(this, i);
        return this.currentItem;
      }
    }
    if (!this.allowBackShuffle)
      return null;
    }
    if (allItemsMatch(this.list, this.currentItem)) {
      // we can serve this as our "next" item but we
      // won't modify our history since it's the same.
      return this.currentItem;
    }
    let previousItem;
    do {
        previousItem = this.list[Math.floor(Math.random() * this.list.length)];
    } while (this.currentItem === previousItem || previousItem === undefined);
    // if we're skipping items that aren't in our current list we may
    // have some items in our backStack - make sure we move to the back.
    goBack(this, this.backStack.length);
    if (this.currentItem !== undefined) {
      this.forwardStack.push(this.currentItem);
    }
    this.currentItem = previousItem;
    return this.currentItem;
  }

  pickNextItem (index) {
    if (this.list[index] === undefined) {
      return null;
    }
    if (this.currentItem !== undefined) {
      this.backStack.push(this.currentItem);
    }
    this.forwardStack.length = 0;
    this.currentItem = this.list[index];
    return this.currentItem;
  }

  setList (list) {
    this.list = list;
  }

  setOptions (options) {
    this.allowBackShuffle = Boolean(options.allowBackShuffle);
  }

  clear () {
    this.backStack.length = 0;
    this.forwardStack.length = 0;
    this.currentItem = null;
  }
}

function allItemsMatch (list, item) {
  if (!list.length) {
    return false;
  }
  for (let i = 0; i < list.length; i++) {
    if (item !== list[i]) {
      return false;
    }
  }
  return true;
}

function goForward (sm, n) {
  for (let i = 0; i < n; i++) {
    if (!sm.forwardStack.length) {
      sm.goBack(i); // rollback before erroring
      throw `Could not go forward ${n} times!`;
    }
    sm.backStack.push(sm.currentItem);
    sm.currentItem = sm.forwardStack.pop();
  }
}

function goBack (sm, n) {
  for (let i = 0; i < n; i++) {
    if (!sm.backStack.length) {
      sm.goForward(i); // rollback before erroring
      throw `Could not go back ${n} times!`;
    }
    sm.forwardStack.push(sm.currentItem);
    sm.currentItem = sm.backStack.pop();
  }
}

module.exports = ShuffleManager;
<|endoftext|>


<commit_before>/* Milesian Clock and converter functions - initiate part
Character set is UTF-8.
These functions are associated with the Milesian clock and converter html page:
They use the basic Milesian calendar functions, and the conversion functions of other calendar,
in order to display the Milesian on-line clock and to perform calendar conversion.
Associated with:
*	MilesianClock.html
1. Common functions
*	CBCCE (the Cycle-Based Calendar Computation Engine)
*	MilesianAlertMsg
*	getRealTZmsOffset method
2. For conversions and clock operation
*	MilesianClockOperations
*	LunarClockOperation
*	MilesianDateProperties
*	JulianDateProperties
*	FrenchRevDateProperties
*	IsoWeekCalendarDateProperties
*	LunarDateProperties
*	ChronologicalCountConversion
3. For display, using Unicode standards
*	UnicodeBasic
*	UnicodeMilesianFormat (used to be: toMilesianLocaleDateString then UnicodeMilesian)
*	MilesianMonthNameString (indirectly - or access to the name base in XML)
*/
/* Version notes
	This .js file is highly related to the corresponding html code.
	This part is especially for initialisation.
*/
/* Version	M2021-08-07
		Created from milesianclockdisplay.js

*/
/* Copyright Miletus 2017-2021 - Louis A. de Fouquières
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sub-license, or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
1. The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.
2. Changes with respect to any former version shall be documented.

The software is provided "as is", without warranty of any kind,
express of implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose and non-infringement.
In no event shall the authors of copyright holders be liable for any
claim, damages or other liability, whether in an action of contract,
tort or otherwise, arising from, out of or in connection with the software
or the use or other dealings in the software.

Inquiries: www.calendriermilesien.org
*/
"use strict";
var	// global variables at document level.
	modules,		// all modules here once imported
	milesian,	// the Milesian calendar (plays a special role)
	calendars = [],	// an array (pointers to) calendar objects
	targetDate = undefined, 	// new Date(),	// Reference UTC date
	customCalIndex = 0,	// initialised and later changed.
	switchingDate = { year : 1582, month : 12, day : 20 },
	TZ = "", 	// time zone for specifying a date; "" means "system", only alternative value is "UTC".
	TZOffset = 0,
	// TZSettings : {mode : "", msoffset : 0},	// deprecate
	Options = {weekday : "long", day : "numeric", month: "long", year : "numeric",
			hour : "numeric", minute : "numeric", second : "numeric"},
	askedOptions,	// for custom calendar
	userOptions,	//
	unicodeFormat,	// for Unicode selected calendar
	unicodeOptions,
	clockFormat,
	astroCalend,
	weekFormat,		// formater for day of week for current custom calendars
	DOWFormat,		// formater for the traditional week day, used for the yearly figures
	romanDateFormat,	// monthDay for julian and gregorian calendar
	lunarDateFormat, // monthDay to indicate a general astronomical event
	dracoDateFormat,	// year month day
	seasonDateFormat,	// year month day hours minutes
	milesianClock;

(async function () {	// Fetching modules and XML data, initialising
	modules = await import ('./aggregate-all.js');
	let pldrString = await import ('https://louis-aime.github.io/calendrical-javascript/pldr.js');
	let	pldrDOM = await modules.fetchDOM ("https://louis-aime.github.io/calendrical-javascript/pldr.xml")
			.then ( (pldrDOM) => pldrDOM ) // The pldr data used by the Milesian calendar (and possibly others).
			.catch ( (error) => { return pldrString.default() } );	// if error (no XML file) take default pldr
	milesian = new modules.MilesianCalendar ("milesian",pldrDOM);
	calendars.push (milesian);
	calendars.push (new modules.GregorianCalendar ("gregorian"));
	calendars.push (new modules.JulianCalendar ("julian"));
	calendars.push (new modules.WesternCalendar ("historic", modules.ExtDate.fullUTC(switchingDate.year, switchingDate.month, switchingDate.day)));
	calendars.push (new modules.FrenchRevCalendar ("frenchrev"));
	targetDate = new modules.ExtDate(milesian);
	milesianClock = new modules.SolarClock( document.querySelector("#mclock") );
	customCalIndex = calendars.findIndex (item => item.id == document.custom.calend.value);  // change custom calendar
	compLocalePresentationCalendar();
	clockAnimate.clockRun(1);	// start clock
})();

window.onload = function () {	// Initiate fields and set event listeners

	document.gregorianswitch.day.value = switchingDate.day;
	document.gregorianswitch.month.value = switchingDate.month;
	document.gregorianswitch.year.value = switchingDate.year;

	document.gregorianswitch.addEventListener("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		let
			day =  Math.round (event.srcElement.elements.day.value),
			month = event.srcElement.elements.month.value,
			year =  Math.round (event.srcElement.elements.year.value),
			testDate = new modules.ExtDate (calendars.find(item => item.id == "gregorian"),modules.ExtDate.fullUTC(year, month, day)),
			index = calendars.findIndex (item => item.id == "historic");
		if ( (testDate.valueOf() >= Date.UTC(1582,9,15,0,0,0,0)) && (testDate.day() == day) )
			calendars[index] = new modules.WesternCalendar("historic", testDate.valueOf())
		else alert ("Invalid switching date to Gregorian calendar: " + day + '/' + month + '/' + year );
		// confirm current switching date
		[document.gregorianswitch.day.value, document.gregorianswitch.month.value, document.gregorianswitch.year.value ]
			= [ switchingDate.day, switchingDate.month, switchingDate.year ] = [ day, month, year ];
		compLocalePresentationCalendar();	// because we changed one calendar, disseminate change.
		if (calendars[customCalIndex].id == "historic") targetDate = new modules.ExtDate (calendars[index], targetDate.valueOf());
			// sweep former historic calendar out of current data
		setDisplay();
	})

	document.custom.addEventListener("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		clockAnimate.clockRun(0);
		calcCustomDate()
	})
	document.custom.calend.addEventListener("blur", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		customCalIndex = calendars.findIndex (item => item.id == document.custom.calend.value);  // change custom calendar
		targetDate = new modules.ExtDate(calendars[customCalIndex], targetDate.valueOf());	// set custom calendar if changed, and set date.
		compLocalePresentationCalendar();
		setDisplay();
	})

	document.week.addEventListener("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		clockAnimate.clockRun(0);
		calcWeek()
	})
	document.daycounter.addEventListener("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		clockAnimate.clockRun(0);
		calcJulianDay()
	})

	document.control.addEventListener("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		clockAnimate.changeDayOffset()
	})
	document.control.now.addEventListener("click", function (event) {
		clockAnimate.clockRun(0);
		setDateToNow()
	})
	document.control.minus.addEventListener("click", function (event) {
		clockAnimate.clockRun(0);
		clockAnimate.setDayOffset(-1)
	})
	document.control.plus.addEventListener("click", function (event) {
		clockAnimate.clockRun(0);
		clockAnimate.setDayOffset(+1)
	})

	document.time.addEventListener("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		clockAnimate.clockRun(0);
		calcTime()
	})

	document.timeShift.addEventListener("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		myTimeShift.changeAddTime()
	})
	document.timeShift.minus.addEventListener("click", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		clockAnimate.clockRun(0);
		myTimeShift.changeAddTime()
		myTimeShift.addTime(-1)
	})
	document.timeShift.plus.addEventListener("click", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		clockAnimate.clockRun(0);
		myTimeShift.changeAddTime()
		myTimeShift.addTime(+1)
	})

	document.TZmode.addEventListener("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		TZ = document.TZmode.TZcontrol.value;
		setDisplay();
	})

	document.getElementById("h0").addEventListener("click", function (event) {
		clockAnimate.clockRun(0);
		setUTCHoursFixed(0)
	})
	document.getElementById("h12").addEventListener("click", function (event) {
		clockAnimate.clockRun(0);
		setUTCHoursFixed(12)
	})

	document.LocaleOptions.addEventListener ("submit", function (event) {
		event.preventDefault();		// necessary to avoid re-loading with multi-fields forms especially when fields are fetched from event.
		compLocalePresentationCalendar();
		setDisplay()
	})
}
<commit_msg>Use promises<commit_after>/* Milesian Clock and converter functions - initiate part
Character set is UTF-8.
These functions are associated with the Milesian clock and converter html page:
They use the basic Milesian calendar functions, and the conversion functions of other calendar,
in order to display the Milesian on-line clock and to perform calendar conversion.
Associated with:
*	MilesianClock.html
*/
/* Version notes (follow GitHub)
	This file aims ar created an adequate environment for several milesian clock applications..
	This part is especially for initialisation.
*/
/* Version	M2021-08-07
		Created from milesianclockdisplay.js

*/
/* Copyright Miletus 2017-2021 - Louis A. de Fouquières
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sub-license, or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
1. The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.
2. Changes with respect to any former version shall be documented.

The software is provided "as is", without warranty of any kind,
express of implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose and non-infringement.
In no event shall the authors of copyright holders be liable for any
claim, damages or other liability, whether in an action of contract,
tort or otherwise, arising from, out of or in connection with the software
or the use or other dealings in the software.

Inquiries: www.calendriermilesien.org
*/
"use strict";
var	// global variables at document level.
	// all modules here once imported
	// pldrDOM,		// imported PLDR
	modules,	// imported modules
	pldrDOM;	// pldrDOM,

const // Promises of loading initial files.
	modulesload = import ("./aggregate-all.js")
		.then ( (modulesload) =>  { modules = modulesload } ),
	pldrload  = import ("./calendrical-javascript/fetchdom.js").then	// should add a fallback with pldr.js
		( (value) => value.default ('https://louis-aime.github.io/calendrical-javascript/pldr.xml', 1000) ) .then (
			(value) => { pldrDOM = value },			// fetching XML file has succeeded.
			(error) => {							// fetching XML has failed, we use the fallback value
				console.log ('pldrDOM not fetched from its repository, fetching pldr.js');
				return import ("./calendrical-javascript/pldr.js").then
					( (value) => pldrDOM = value.default () )
					}
			),
	loadComplete = Promise.all([modulesload, pldrload]);

<|endoftext|>


<commit_before>define('logger', function() {
    function logger(name, color) {
        return function() {
            if (!logger.enabled) {
                return;
            }
            var args = Array.prototype.slice.call(arguments);
            var tabs = '';
            args[0].replace(/^\t+/, function(m) {
                tabs = m;
                return '';
            });
            args[0] = "%c" + tabs + name + "::" + args[0];
            args.splice(1, 0, "color:" + color +";");
            console.log.apply(console, args);
        };
    }
    logger.enabled = true;
    return logger;
});<commit_msg>fix logger for handling if objects are passed to the logger.<commit_after>define('logger', function() {
    function logger(name, color) {
        return function() {
            if (!logger.enabled) {
                return;
            }
            var args = Array.prototype.slice.call(arguments);
            var tabs = '';
            (args[0] + '').replace(/^\t+/, function(m) {
                tabs = m;
                return '';
            });
            args[0] = "%c" + tabs + name + "::" + args[0];
            args.splice(1, 0, "color:" + color +";");
            console.log.apply(console, args);
        };
    }
    logger.enabled = true;
    return logger;
});<|endoftext|>