<commit_before>'use strict';

import _ from 'lodash';

import {CONFIRMATION_MODAL_SERVICE} from 'core/confirmationModal/confirmationModal.service';
import {VIEW_STATE_CACHE_SERVICE} from 'core/cache/viewStateCache.service';
import {DISPLAYABLE_TASKS_FILTER} from './displayableTasks.filter';
import {SETTINGS} from 'core/config/settings';

let angular = require('angular');

module.exports = angular.module('spinnaker.core.task.controller', [
  require('angular-ui-router').default,
  require('./taskProgressBar.directive.js'),
  VIEW_STATE_CACHE_SERVICE,
  require('./task.write.service.js'),
  CONFIRMATION_MODAL_SERVICE,
  DISPLAYABLE_TASKS_FILTER
])
  .controller('TasksCtrl', function ($scope, $state, $q, app, viewStateCache, taskWriter, confirmationModalService) {

    if (app.notFound) {
      return;
    }

    var controller = this;
    const application = app;

    $scope.$state = $state;

    var tasksViewStateCache = viewStateCache.get('tasks') || viewStateCache.createCache('tasks', { version: 1 });

    function cacheViewState() {
      tasksViewStateCache.put(application.name, $scope.viewState);
      cacheGlobalViewState();
    }

    function cacheGlobalViewState() {
      tasksViewStateCache.put('#common', {
        itemsPerPage: $scope.viewState.itemsPerPage,
      });
    }

    $scope.tasksUrl = [SETTINGS.gateUrl, 'applications', application.name, 'tasks/'].join('/');
    $scope.filterCountOptions = [10, 20, 30, 50, 100, 200];

    function initializeViewState() {
      var viewState = tasksViewStateCache.get(application.name) || {
        taskStateFilter: '',
        nameFilter: '',
        expandedTasks: [],
      };
      viewState.loading = true;
      viewState.itemsPerPage = tasksViewStateCache.get('#common') ? tasksViewStateCache.get('#common').itemsPerPage : 20;

      $scope.viewState = viewState;
    }

    controller.taskStateFilter = 'All';
    controller.application = application;

    controller.sortedTasks = [];

    controller.toggleDetails = function(taskId) {
      var index = $scope.viewState.expandedTasks.indexOf(taskId);
      if (index === -1) {
        $scope.viewState.expandedTasks.push(taskId);
      } else {
        $scope.viewState.expandedTasks.splice(index, 1);
      }
    };

    controller.isExpanded = function(taskId) {
      return taskId && $scope.viewState.expandedTasks.includes(taskId);
    };

    controller.sortTasksAndResetPaginator = function() {
      controller.sortTasks();
      controller.resetPaginator();
    };

    controller.sortTasks = function() {
      var joinedLists = filterRunningTasks().concat(filterNonRunningTasks());
      controller.sortedTasks = joinedLists;
      if ($scope.viewState.nameFilter) {
        var normalizedSearch = $scope.viewState.nameFilter.toLowerCase();
        controller.sortedTasks = _.filter(joinedLists, function(task) {
          return task.name.toLowerCase().includes(normalizedSearch) ||
            task.id.toLowerCase().includes(normalizedSearch) ||
            (task.getValueFor('credentials') || '').toLowerCase().includes(normalizedSearch) ||
            (task.getValueFor('region') || '').toLowerCase().includes(normalizedSearch) ||
            (task.getValueFor('regions') || []).join(' ').toLowerCase().includes(normalizedSearch) ||
            (task.getValueFor('user') || '').toLowerCase().includes(normalizedSearch);
        });
      }
      if ($scope.viewState.taskStateFilter) {
        controller.sortedTasks = _.filter(controller.sortedTasks, { status: $scope.viewState.taskStateFilter });
      }
    };

    controller.clearNameFilter = function() {
      $scope.viewState.nameFilter = '';
      controller.nameFilterUpdated();
    };

    controller.nameFilterUpdated = function() {
      if ($state.includes('**.taskDetails')) {
        $state.go('^');
      }
      controller.sortTasksAndResetPaginator();
    };

    controller.cancelTask = function(taskId) {
      var task = application.tasks.data.filter(function(task) { return task.id === taskId; })[0];
      var submitMethod = function () {
        return taskWriter.cancelTask(application.name, taskId).then(() => application.tasks.refresh());
      };

      confirmationModalService.confirm({
        header: 'Really cancel ' + task.name + '?',
        buttonText: 'Yes',
        cancelButtonText: 'No',
        submitMethod: submitMethod
      });
    };

    controller.deleteTask = function(taskId) {
      var task = application.tasks.data.filter(function(task) { return task.id === taskId; })[0];
      var submitMethod = function () {
        return taskWriter.deleteTask(taskId).then(() => application.tasks.refresh());
      };

      confirmationModalService.confirm({
        header: 'Really delete history for ' + task.name + '?',
        body: '
This will permanently delete the task history.
',
        buttonText: 'Delete',
        submitMethod: submitMethod
      });
    };

    /**
     * Pagination - largely copied from applications.controller.js
     */

    controller.resetPaginator = function resetPaginator() {
      $scope.pagination = {
        currentPage: 1,
        itemsPerPage: $scope.viewState.itemsPerPage,
        maxSize: 12,
      };
    };

    controller.resultPage = function resultPage() {
      var pagination = $scope.pagination,
        allFiltered = controller.sortedTasks,
        start = (pagination.currentPage - 1) * pagination.itemsPerPage,
        end = pagination.currentPage * pagination.itemsPerPage;
      if (!allFiltered || !allFiltered.length) {
        return [];
      }
      if (allFiltered.length < pagination.itemsPerPage) {
        return allFiltered;
      }
      if (allFiltered.length < end) {
        return allFiltered.slice(start);
      }
      return allFiltered.slice(start, end);
    };

    controller.getFirstDeployServerGroupName = function(task) {
      if(task.execution && task.execution.stages) {
        var stage = findStageWithTaskInExecution(task.execution, ['createCopyLastAsg', 'createDeploy', 'cloneServerGroup', 'createServerGroup']);
        return _.chain(stage)
          .get('context')
          .get('deploy.server.groups')
          .values()
          .head()
          .head()
          .value();
      }
    };

    controller.getAccountId = function(task) {
      return _.chain(task.variables)
        .find({'key': 'account'})
        .result('value')
        .value();
    };

    controller.getRegion = function (task) {
      var deployedServerGroups = _.find(task.variables, function(variable) {
        return variable.key === 'deploy.server.groups';
      }).value;

      return _.keys(deployedServerGroups)[0];
    };

    controller.getProviderForServerGroupByTask = function(task) {
      var serverGroupName = controller.getFirstDeployServerGroupName(task);
      return _.chain(application.serverGroups.data)
        .find(function(serverGroup) {
          return serverGroup.name === serverGroupName;
        })
        .result('type')
        .value();
    };

    function findStageWithTaskInExecution(execution, stageName) {
      return _.chain(execution.stages).find(function(stage) {
        return _.some(stage.tasks, function(task) {
          return stageName.includes(task.name);
        });
      }).value();
    }

    function filterRunningTasks() {
      var running = _.chain(application.tasks.data)
        .filter(function(task) {
          return task.name && task.isActive;
        })
        .value();

      return running.sort(taskStartTimeComparator);
    }

    function filterNonRunningTasks() {
      var notRunning = _.chain(application.tasks.data)
        .filter(function(task) {
          return task.name && !task.isActive;
        })
        .value();

      return notRunning.sort(taskStartTimeComparator);
    }

    function taskStartTimeComparator(taskA, taskB) {
      return taskB.startTime > taskA.startTime ? 1 : taskB.startTime < taskA.startTime ? -1 : 0;
    }

    // angular ui btn-radio doesn't support the ng-change or ng-click directives
    $scope.$watch('viewState.taskStateFilter', controller.sortTasksAndResetPaginator);
    $scope.$watch('viewState', cacheViewState, true);

    // The taskId will not be available in the $stateParams that would be passed into this controller
    // because that field belongs to a child state. So we have to watch for a $stateChangeSuccess event, then set
    // the value on the scope
    $scope.$on('$stateChangeSuccess', function(event, toState, toParams) {
      var taskId = toParams.taskId;
      if (!$scope.viewState.expandedTasks.includes(taskId)) {
        controller.toggleDetails(taskId);
      }
      $scope.viewState.nameFilter = taskId;
      $scope.viewState.taskStateFilter = '';
      controller.sortTasksAndResetPaginator();
    });

    initializeViewState();

    application.tasks.activate();

    application.tasks.ready().then(() => {
      $scope.viewState.loading = false;
      $scope.viewState.loadError = app.tasks.loadFailure;
      if (!app.tasks.loadFailure) {
        this.sortTasks();
      }
    });

    application.activeState = application.tasks;
    $scope.$on('$destroy', () => {
      application.activeState = application;
      application.tasks.deactivate();
    });

    this.application.tasks.onRefresh($scope, this.sortTasks);
  }
);
<commit_msg>fix(core/tasks): fix deep linking to tasks<commit_after>'use strict';

import _ from 'lodash';

import {CONFIRMATION_MODAL_SERVICE} from 'core/confirmationModal/confirmationModal.service';
import {VIEW_STATE_CACHE_SERVICE} from 'core/cache/viewStateCache.service';
import {DISPLAYABLE_TASKS_FILTER} from './displayableTasks.filter';
import {SETTINGS} from 'core/config/settings';

let angular = require('angular');

module.exports = angular.module('spinnaker.core.task.controller', [
  require('angular-ui-router').default,
  require('./taskProgressBar.directive.js'),
  VIEW_STATE_CACHE_SERVICE,
  require('./task.write.service.js'),
  CONFIRMATION_MODAL_SERVICE,
  DISPLAYABLE_TASKS_FILTER
])
  .controller('TasksCtrl', function ($scope, $state, $stateParams, $q, app, viewStateCache, taskWriter, confirmationModalService) {

    if (app.notFound) {
      return;
    }

    var controller = this;
    const application = app;

    $scope.$state = $state;

    var tasksViewStateCache = viewStateCache.get('tasks') || viewStateCache.createCache('tasks', { version: 1 });

    function cacheViewState() {
      tasksViewStateCache.put(application.name, $scope.viewState);
      cacheGlobalViewState();
    }

    function cacheGlobalViewState() {
      tasksViewStateCache.put('#common', {
        itemsPerPage: $scope.viewState.itemsPerPage,
      });
    }

    $scope.tasksUrl = [SETTINGS.gateUrl, 'applications', application.name, 'tasks/'].join('/');
    $scope.filterCountOptions = [10, 20, 30, 50, 100, 200];

    function initializeViewState() {
      var viewState = tasksViewStateCache.get(application.name) || {
        taskStateFilter: '',
        nameFilter: '',
        expandedTasks: [],
      };
      viewState.loading = true;
      viewState.itemsPerPage = tasksViewStateCache.get('#common') ? tasksViewStateCache.get('#common').itemsPerPage : 20;

      $scope.viewState = viewState;
      setTaskFilter();
    }

    const setTaskFilter = () => {
      const taskId = $stateParams.taskId;
      if (!$scope.viewState.expandedTasks.includes(taskId)) {
        controller.toggleDetails(taskId);
      }
      $scope.viewState.nameFilter = taskId;
      $scope.viewState.taskStateFilter = '';
      controller.sortTasksAndResetPaginator();
    };

    controller.taskStateFilter = 'All';
    controller.application = application;

    controller.sortedTasks = [];

    controller.toggleDetails = function(taskId) {
      var index = $scope.viewState.expandedTasks.indexOf(taskId);
      if (index === -1) {
        $scope.viewState.expandedTasks.push(taskId);
      } else {
        $scope.viewState.expandedTasks.splice(index, 1);
      }
    };

    controller.isExpanded = function(taskId) {
      return taskId && $scope.viewState.expandedTasks.includes(taskId);
    };

    controller.sortTasksAndResetPaginator = function() {
      controller.sortTasks();
      controller.resetPaginator();
    };

    controller.sortTasks = function() {
      var joinedLists = filterRunningTasks().concat(filterNonRunningTasks());
      controller.sortedTasks = joinedLists;
      if ($scope.viewState.nameFilter) {
        var normalizedSearch = $scope.viewState.nameFilter.toLowerCase();
        controller.sortedTasks = _.filter(joinedLists, function(task) {
          return task.name.toLowerCase().includes(normalizedSearch) ||
            task.id.toLowerCase().includes(normalizedSearch) ||
            (task.getValueFor('credentials') || '').toLowerCase().includes(normalizedSearch) ||
            (task.getValueFor('region') || '').toLowerCase().includes(normalizedSearch) ||
            (task.getValueFor('regions') || []).join(' ').toLowerCase().includes(normalizedSearch) ||
            (task.getValueFor('user') || '').toLowerCase().includes(normalizedSearch);
        });
      }
      if ($scope.viewState.taskStateFilter) {
        controller.sortedTasks = _.filter(controller.sortedTasks, { status: $scope.viewState.taskStateFilter });
      }
    };

    controller.clearNameFilter = function() {
      $scope.viewState.nameFilter = '';
      controller.nameFilterUpdated();
    };

    controller.nameFilterUpdated = function() {
      if ($state.includes('**.taskDetails')) {
        $state.go('^');
      }
      controller.sortTasksAndResetPaginator();
    };

    controller.cancelTask = function(taskId) {
      var task = application.tasks.data.filter(function(task) { return task.id === taskId; })[0];
      var submitMethod = function () {
        return taskWriter.cancelTask(application.name, taskId).then(() => application.tasks.refresh());
      };

      confirmationModalService.confirm({
        header: 'Really cancel ' + task.name + '?',
        buttonText: 'Yes',
        cancelButtonText: 'No',
        submitMethod: submitMethod
      });
    };

    controller.deleteTask = function(taskId) {
      var task = application.tasks.data.filter(function(task) { return task.id === taskId; })[0];
      var submitMethod = function () {
        return taskWriter.deleteTask(taskId).then(() => application.tasks.refresh());
      };

      confirmationModalService.confirm({
        header: 'Really delete history for ' + task.name + '?',
        body: '
This will permanently delete the task history.
',
        buttonText: 'Delete',
        submitMethod: submitMethod
      });
    };

    /**
     * Pagination - largely copied from applications.controller.js
     */

    controller.resetPaginator = function resetPaginator() {
      $scope.pagination = {
        currentPage: 1,
        itemsPerPage: $scope.viewState.itemsPerPage,
        maxSize: 12,
      };
    };

    controller.resultPage = function resultPage() {
      var pagination = $scope.pagination,
        allFiltered = controller.sortedTasks,
        start = (pagination.currentPage - 1) * pagination.itemsPerPage,
        end = pagination.currentPage * pagination.itemsPerPage;
      if (!allFiltered || !allFiltered.length) {
        return [];
      }
      if (allFiltered.length < pagination.itemsPerPage) {
        return allFiltered;
      }
      if (allFiltered.length < end) {
        return allFiltered.slice(start);
      }
      return allFiltered.slice(start, end);
    };

    controller.getFirstDeployServerGroupName = function(task) {
      if(task.execution && task.execution.stages) {
        var stage = findStageWithTaskInExecution(task.execution, ['createCopyLastAsg', 'createDeploy', 'cloneServerGroup', 'createServerGroup']);
        return _.chain(stage)
          .get('context')
          .get('deploy.server.groups')
          .values()
          .head()
          .head()
          .value();
      }
    };

    controller.getAccountId = function(task) {
      return _.chain(task.variables)
        .find({'key': 'account'})
        .result('value')
        .value();
    };

    controller.getRegion = function (task) {
      var deployedServerGroups = _.find(task.variables, function(variable) {
        return variable.key === 'deploy.server.groups';
      }).value;

      return _.keys(deployedServerGroups)[0];
    };

    controller.getProviderForServerGroupByTask = function(task) {
      var serverGroupName = controller.getFirstDeployServerGroupName(task);
      return _.chain(application.serverGroups.data)
        .find(function(serverGroup) {
          return serverGroup.name === serverGroupName;
        })
        .result('type')
        .value();
    };

    function findStageWithTaskInExecution(execution, stageName) {
      return _.chain(execution.stages).find(function(stage) {
        return _.some(stage.tasks, function(task) {
          return stageName.includes(task.name);
        });
      }).value();
    }

    function filterRunningTasks() {
      var running = _.chain(application.tasks.data)
        .filter(function(task) {
          return task.name && task.isActive;
        })
        .value();

      return running.sort(taskStartTimeComparator);
    }

    function filterNonRunningTasks() {
      var notRunning = _.chain(application.tasks.data)
        .filter(function(task) {
          return task.name && !task.isActive;
        })
        .value();

      return notRunning.sort(taskStartTimeComparator);
    }

    function taskStartTimeComparator(taskA, taskB) {
      return taskB.startTime > taskA.startTime ? 1 : taskB.startTime < taskA.startTime ? -1 : 0;
    }

    // angular ui btn-radio doesn't support the ng-change or ng-click directives
    $scope.$watch('viewState.taskStateFilter', controller.sortTasksAndResetPaginator);
    $scope.$watch('viewState', cacheViewState, true);

    initializeViewState();

    application.tasks.activate();

    application.tasks.ready().then(() => {
      $scope.viewState.loading = false;
      $scope.viewState.loadError = app.tasks.loadFailure;
      if (!app.tasks.loadFailure) {
        this.sortTasks();
      }
    });

    application.activeState = application.tasks;
    $scope.$on('$destroy', () => {
      application.activeState = application;
      application.tasks.deactivate();
    });

    this.application.tasks.onRefresh($scope, this.sortTasks);
  }
);
<|endoftext|>


<commit_before>'use strict';

const { join } = require('path');
const fse = require('fs-extra');
const { defaultsDeep, getOr, get } = require('lodash/fp');
const { env } = require('@strapi/utils');
const loadConfigFile = require('../../app-configuration/load-config-file');
const loadFiles = require('../../../load/load-files');
const getEnabledPlugins = require('./get-enabled-plugins');

const defaultPlugin = {
  bootstrap() {},
  destroy() {},
  register() {},
  config: {
    default: {},
    validator() {},
  },
  routes: [],
  controllers: {},
  services: {},
  policies: {},
  middlewares: {},
  contentTypes: {},
};

const applyUserExtension = async plugins => {
  const extensionsDir = strapi.dirs.extensions;
  if (!(await fse.pathExists(extensionsDir))) {
    return;
  }

  const extendedSchemas = await loadFiles(extensionsDir, '**/content-types/**/schema.json');
  const strapiServers = await loadFiles(extensionsDir, '**/strapi-server.js');

  for (const pluginName in plugins) {
    const plugin = plugins[pluginName];
    // first: load json schema
    for (const ctName in plugin.contentTypes) {
      const extendedSchema = get([pluginName, 'content-types', ctName, 'schema'], extendedSchemas);
      if (extendedSchema) {
        plugin.contentTypes[ctName].schema = Object.assign(
          {},
          plugin.contentTypes[ctName].schema,
          extendedSchema
        );
      }
    }
    // second: execute strapi-server extension
    const strapiServer = get([pluginName, 'strapi-server'], strapiServers);
    if (strapiServer) {
      plugins[pluginName] = await strapiServer(plugin);
    }
  }
};

const formatContentTypes = plugins => {
  for (const pluginName in plugins) {
    const plugin = plugins[pluginName];
    for (const contentTypeName in plugin.contentTypes) {
      const ctSchema = plugin.contentTypes[contentTypeName].schema;
      ctSchema.plugin = pluginName;
      ctSchema.uid = `plugin::${pluginName}.${ctSchema.singularName}`;
    }
  }
};

const getUserPluginsConfig = async () => {
  const globalUserConfigPath = join(strapi.dirs.config, 'plugins.js');
  const currentEnvUserConfigPath = join(
    strapi.dirs.config,
    `env/${process.env.NODE_ENV}/plugins.js`
  );

  if (await fse.pathExists(currentEnvUserConfigPath)) {
    return loadConfigFile(currentEnvUserConfigPath);
  } else if (await fse.pathExists(globalUserConfigPath)) {
    return loadConfigFile(globalUserConfigPath);
  } else {
    return {};
  }
};

const applyUserConfig = async plugins => {
  const userPluginsConfig = await getUserPluginsConfig();

  for (const pluginName in plugins) {
    const plugin = plugins[pluginName];
    const userPluginConfig = getOr({}, `${pluginName}.config`, userPluginsConfig);
    const defaultConfig =
      typeof plugin.config.default === 'function'
        ? plugin.config.default({ env })
        : plugin.config.default;

    const config = defaultsDeep(defaultConfig, userPluginConfig);
    try {
      plugin.config.validator(config);
    } catch (e) {
      throw new Error(`Error regarding ${pluginName} config: ${e.message}`);
    }
    plugin.config = config;
  }
};

const loadPlugins = async strapi => {
  const plugins = {};

  const enabledPlugins = await getEnabledPlugins(strapi);

  strapi.config.set('enabledPlugins', enabledPlugins);

  for (const pluginName in enabledPlugins) {
    const enabledPlugin = enabledPlugins[pluginName];

    const serverEntrypointPath = join(enabledPlugin.pathToPlugin, 'strapi-server.js');

    // only load plugins with a server entrypoint
    if (!(await fse.pathExists(serverEntrypointPath))) {
      continue;
    }

    const pluginServer = loadConfigFile(serverEntrypointPath);
    plugins[pluginName] = defaultsDeep(defaultPlugin, pluginServer);
  }

  // TODO: validate plugin format
  await applyUserConfig(plugins);
  await applyUserExtension(plugins);
  formatContentTypes(plugins);

  for (const pluginName in plugins) {
    strapi.container.get('plugins').add(pluginName, plugins[pluginName]);
  }
};

module.exports = loadPlugins;
<commit_msg>apply feedback<commit_after>'use strict';

const { join } = require('path');
const fse = require('fs-extra');
const { defaultsDeep, getOr, get } = require('lodash/fp');
const { merge } = require('lodash');
const { env } = require('@strapi/utils');
const loadConfigFile = require('../../app-configuration/load-config-file');
const loadFiles = require('../../../load/load-files');
const getEnabledPlugins = require('./get-enabled-plugins');

const defaultPlugin = {
  bootstrap() {},
  destroy() {},
  register() {},
  config: {
    default: {},
    validator() {},
  },
  routes: [],
  controllers: {},
  services: {},
  policies: {},
  middlewares: {},
  contentTypes: {},
};

const applyUserExtension = async plugins => {
  const extensionsDir = strapi.dirs.extensions;
  if (!(await fse.pathExists(extensionsDir))) {
    return;
  }

  const extendedSchemas = await loadFiles(extensionsDir, '**/content-types/**/schema.json');
  const strapiServers = await loadFiles(extensionsDir, '**/strapi-server.js');

  for (const pluginName in plugins) {
    const plugin = plugins[pluginName];
    // first: load json schema
    for (const ctName in plugin.contentTypes) {
      const extendedSchema = get([pluginName, 'content-types', ctName, 'schema'], extendedSchemas);
      if (extendedSchema) {
        plugin.contentTypes[ctName].schema = Object.assign(
          {},
          plugin.contentTypes[ctName].schema,
          extendedSchema
        );
      }
    }
    // second: execute strapi-server extension
    const strapiServer = get([pluginName, 'strapi-server'], strapiServers);
    if (strapiServer) {
      plugins[pluginName] = await strapiServer(plugin);
    }
  }
};

const formatContentTypes = plugins => {
  for (const pluginName in plugins) {
    const plugin = plugins[pluginName];
    for (const contentTypeName in plugin.contentTypes) {
      const ctSchema = plugin.contentTypes[contentTypeName].schema;
      ctSchema.plugin = pluginName;
      ctSchema.uid = `plugin::${pluginName}.${ctSchema.singularName}`;
    }
  }
};

const getUserPluginsConfig = async () => {
  const globalUserConfigPath = join(strapi.dirs.config, 'plugins.js');
  const currentEnvUserConfigPath = join(
    strapi.dirs.config,
    `env/${process.env.NODE_ENV}/plugins.js`
  );
  let config = {};

  // assign global user config if exists
  if (await fse.pathExists(globalUserConfigPath)) {
    config = loadConfigFile(globalUserConfigPath);
  }

  // and merge user config by environment if exists
  if (await fse.pathExists(currentEnvUserConfigPath)) {
    config = merge(config, loadConfigFile(currentEnvUserConfigPath));
  }

  return config;
};

const applyUserConfig = async plugins => {
  const userPluginsConfig = await getUserPluginsConfig();

  for (const pluginName in plugins) {
    const plugin = plugins[pluginName];
    const userPluginConfig = getOr({}, `${pluginName}.config`, userPluginsConfig);
    const defaultConfig =
      typeof plugin.config.default === 'function'
        ? plugin.config.default({ env })
        : plugin.config.default;

    const config = defaultsDeep(defaultConfig, userPluginConfig);
    try {
      plugin.config.validator(config);
    } catch (e) {
      throw new Error(`Error regarding ${pluginName} config: ${e.message}`);
    }
    plugin.config = config;
  }
};

const loadPlugins = async strapi => {
  const plugins = {};

  const enabledPlugins = await getEnabledPlugins(strapi);

  strapi.config.set('enabledPlugins', enabledPlugins);

  for (const pluginName in enabledPlugins) {
    const enabledPlugin = enabledPlugins[pluginName];

    const serverEntrypointPath = join(enabledPlugin.pathToPlugin, 'strapi-server.js');

    // only load plugins with a server entrypoint
    if (!(await fse.pathExists(serverEntrypointPath))) {
      continue;
    }

    const pluginServer = loadConfigFile(serverEntrypointPath);
    plugins[pluginName] = defaultsDeep(defaultPlugin, pluginServer);
  }

  // TODO: validate plugin format
  await applyUserConfig(plugins);
  await applyUserExtension(plugins);
  formatContentTypes(plugins);

  for (const pluginName in plugins) {
    strapi.container.get('plugins').add(pluginName, plugins[pluginName]);
  }
};

module.exports = loadPlugins;
<|endoftext|>


<commit_before><commit_msg>Modify components of binding to use module-unit<commit_after><|endoftext|>


<commit_before>/** @ngInject */
export function NavigationController (Text, Navigation, Session, API_BASE, ShoppingCart, $scope, $uibModal, $state,
                                      EventNotifications, ApplianceInfo, CollectionsApi, RBAC) {
  const vm = this
  const destroy = $scope.$on('shoppingCartUpdated', refresh)
  const destroyCart = $scope.$on('shoppingCartUpdated', refreshCart)
  const destroyNotifications = $scope.$watch(
    function () {
      return EventNotifications.state().groups
    },
    refreshNotifications, true)
  const destroyToast = $scope.$watch(
    function () {
      return EventNotifications.state().toastNotifications
    },
    refreshToast, true)
  const applianceInfo = ApplianceInfo.get()

  $scope.$on('destroy', function () {
    destroyCart()
    destroyNotifications()
    destroyToast()
    destroy()
  })
  $scope.$on('$stateChangeSuccess', function () {
    clearActiveItems()
    setActiveItems()
  })

  activate()

  function activate () {
    vm.permissions = {
      help: {
        about: RBAC.has('about'),
        documentation: RBAC.has('documentation')
      },
      suiAppLauncher: RBAC.has(RBAC.FEATURES.CORE.APP_LAUNCHER),
      suiNotifications: RBAC.has(RBAC.FEATURES.CORE.NOTIFICATIONS),
      suiLanguage: RBAC.has(RBAC.FEATURES.CORE.LANGUAGE),
      helpMenu: RBAC.hasAny(['about', 'product', 'documentation'])
    }
    EventNotifications.setToastDisplay(vm.permissions.suiNotifications)
    angular.extend(vm, {
      state: Navigation.state,
      text: Text.app,
      user: Session.currentUser,
      API_BASE: API_BASE,
      switchGroup: switchGroup,
      items: [],
      notificationsDrawerShown: false,
      newNotifications: false,
      titleHtml: 'app/components/notifications/drawer-title.html',
      headingHTML: 'app/components/notifications/heading.html',
      notificationHTML: 'app/components/notifications/notification-body.html',
      notificationFooterHTML: 'app/components/notifications/notification-footer.html',
      handleItemClick: handleItemClick,
      toggleNotificationsList: toggleNotificationsList,
      updateViewingToast: updateViewingToast,
      handleDismissToast: handleDismissToast,
      getNotficationStatusIconClass: getNotficationStatusIconClass,
      markNotificationRead: markNotificationRead,
      clearNotification: clearNotification,
      clearAllNotifications: clearAllNotifications,
      markAllRead: markAllRead,
      shoppingCart: shoppingCart(),
      about: about(),
      sites: sites()
    })
    setupNavigation()
    refresh()
    if (ShoppingCart.allowed()) {
      ShoppingCart.reload()
    }
    setActiveItems()
  }
  function setupNavigation () {
    Navigation.init()
    vm.items = Navigation.get()
  }
  function shoppingCart () {
    return {
      count: 0,
      open: function () {
        return $uibModal.open({
          template: '<shopping-cart modal-instance="vm.modalInstance"></shopping-cart>',
          size: 'lg',
          controller: function ($uibModalInstance) {
            const vm = this
            vm.modalInstance = $uibModalInstance
          },
          controllerAs: 'vm',
          bindToController: true

        }).result
      },
      allowed: ShoppingCart.allowed
    }
  }

  function about () {
    return {
      isOpen: false,
      additionalInfo: '',
      imgAlt: __('Product logo'),
      imgSrc: 'images/login-screen-logo.png',
      title: Text.app.name,
      productInfo: [
        {name: __('Version: '), value: applianceInfo.miqVersion},
        {name: __('SUI Version: '), value: applianceInfo.suiVersion},
        {name: __('Server Name: '), value: applianceInfo.server},
        {name: __('User Name: '), value: applianceInfo.user},
        {name: __('User Role: '), value: applianceInfo.role}
      ],
      copyright: applianceInfo.copyright,
      supportWebsiteText: applianceInfo.supportWebsiteText,
      supportWebsite: applianceInfo.supportWebsite
    }
  }

  function sites () {
    return [{
      title: __('Administration UI'),
      tooltip: __('Log into the full administrative UI'),
      iconClass: 'fa-cogs',
      url: API_BASE
    }]
  }

  function clearActiveItems () {
    angular.forEach(vm.items, function (item) {
      item.isActive = false
      if (item.children) {
        angular.forEach(item.children, function (secondary) {
          secondary.isActive = false
          if (secondary.children) {
            secondary.children.forEach(function (tertiary) {
              tertiary.isActive = false
            })
          }
        })
      }
    })
  }

  function setActiveItems () {
    angular.forEach(vm.items, function (topLevel) {
      if ($state.includes(topLevel.state)) {
        topLevel.isActive = true
      }
      if (topLevel.children) {
        angular.forEach(topLevel.children, function (secondLevel) {
          if ($state.includes(secondLevel.state)) {
            secondLevel.isActive = true
            topLevel.isActive = true
          }
          if (secondLevel.children) {
            angular.forEach(secondLevel.children, function (thirdLevel) {
              if ($state.includes(thirdLevel.state)) {
                thirdLevel.isActive = true
                secondLevel.isActive = true
                topLevel.isActive = true
              }
            })
          }
        })
      }
    })
  }

  function refreshCart () {
    vm.shoppingCart.count = ShoppingCart.count()
  }

  function refreshNotifications () {
    vm.notificationGroups = EventNotifications.state().groups
    vm.newNotifications = EventNotifications.state().unreadNotifications
    vm.unreadNotificationCount = 0
    angular.forEach(vm.notificationGroups, function (group) {
      vm.unreadNotificationCount += group.unreadCount
    })
    vm.notificationsIndicatorTooltip = __(vm.unreadNotificationCount + ' unread notifications')
  }

  function refreshToast () {
    vm.toastNotifications = EventNotifications.state().toastNotifications
  }

  function refresh () {
    refreshCart()
    refreshNotifications()
    refreshToast()
  }

  function handleItemClick (item) {
    $state.transitionTo(item.state)
  }

  function toggleNotificationsList () {
    vm.notificationsDrawerShown = !vm.notificationsDrawerShown
  }

  function getNotficationStatusIconClass (notification) {
    var retClass = ''
    if (notification && notification.type) {
      if (notification.type === 'info') {
        retClass = 'pficon pficon-info'
      } else if (notification.type === 'error') {
        retClass = 'pficon pficon-error-circle-o'
      } else if (notification.type === 'warning') {
        retClass = 'pficon pficon-warning-triangle-o'
      } else if (notification.type === 'success') {
        retClass = 'pficon pficon-ok'
      } else {
        retClass = 'pficon pficon-info'  // default to info
      }
    }

    return retClass
  }

  function markNotificationRead (notification, group) {
    EventNotifications.markRead(notification, group)
  }

  function clearNotification (notification, group) {
    EventNotifications.clear(notification, group)
  }

  function clearAllNotifications (group) {
    EventNotifications.clearAll(group)
  }

  function markAllRead (group) {
    EventNotifications.markAllRead(group)
  }

  function updateViewingToast (viewing, notification) {
    EventNotifications.setViewingToast(notification, viewing)
  }

  function handleDismissToast (notification) {
    EventNotifications.dismissToast(notification)
  }

  function switchGroup (group) {
    if (vm.user().user_href && group.id) {
      CollectionsApi.post('users', vm.user().user_href.split('/').pop(), {}, {
        'action': 'set_current_group',
        'current_group': {'id': group.id}
      }).then(success, failure)
    }

    function success () {
      Session.setGroup(group)
      Session.getUserAuthorizations()
      vm.user = Session.currentUser
      $state.go('dashboard', {}, {reload: true})
    }

    function failure (response) {
      EventNotifications.error(__('Group switching error: ') + response.data.error.message)
    }
  }
}
<commit_msg>Fixed issue with nav menu not changing language on page reload<commit_after>/** @ngInject */
export function NavigationController (Text, Navigation, Session, API_BASE, ShoppingCart, $scope, $uibModal, $state,
                                      EventNotifications, ApplianceInfo, CollectionsApi, RBAC, Language, lodash) {
  const vm = this
  vm.language = ''
  const destroy = $scope.$on('shoppingCartUpdated', refresh)
  const destroyCart = $scope.$on('shoppingCartUpdated', refreshCart)
  vm.$doCheck = () => {
    const currentLanguage = Language.chosen.code
    if (!lodash.isEqual(vm.language, currentLanguage)) {
      vm.language = lodash.cloneDeep(currentLanguage)
      setupNavigation()
    }
  }
  const destroyNotifications = $scope.$watch(
    function () {
      return EventNotifications.state().groups
    },
    refreshNotifications, true)
  const destroyToast = $scope.$watch(
    function () {
      return EventNotifications.state().toastNotifications
    },
    refreshToast, true)
  const applianceInfo = ApplianceInfo.get()
  vm.language = Language.chosen.code

  $scope.$on('destroy', function () {
    destroyCart()
    destroyNotifications()
    destroyToast()
    destroy()
  })
  $scope.$on('$stateChangeSuccess', function () {
    clearActiveItems()
    setActiveItems()
  })

  activate()

  function activate () {
    vm.permissions = {
      help: {
        about: RBAC.has('about'),
        documentation: RBAC.has('documentation')
      },
      suiAppLauncher: RBAC.has(RBAC.FEATURES.CORE.APP_LAUNCHER),
      suiNotifications: RBAC.has(RBAC.FEATURES.CORE.NOTIFICATIONS),
      suiLanguage: RBAC.has(RBAC.FEATURES.CORE.LANGUAGE),
      helpMenu: RBAC.hasAny(['about', 'product', 'documentation'])
    }
    EventNotifications.setToastDisplay(vm.permissions.suiNotifications)
    angular.extend(vm, {
      state: Navigation.state,
      text: Text.app,
      user: Session.currentUser,
      API_BASE: API_BASE,
      switchGroup: switchGroup,
      items: [],
      notificationsDrawerShown: false,
      newNotifications: false,
      titleHtml: 'app/components/notifications/drawer-title.html',
      headingHTML: 'app/components/notifications/heading.html',
      notificationHTML: 'app/components/notifications/notification-body.html',
      notificationFooterHTML: 'app/components/notifications/notification-footer.html',
      handleItemClick: handleItemClick,
      toggleNotificationsList: toggleNotificationsList,
      updateViewingToast: updateViewingToast,
      handleDismissToast: handleDismissToast,
      getNotficationStatusIconClass: getNotficationStatusIconClass,
      markNotificationRead: markNotificationRead,
      clearNotification: clearNotification,
      clearAllNotifications: clearAllNotifications,
      markAllRead: markAllRead,
      shoppingCart: shoppingCart(),
      about: about(),
      sites: sites()
    })
    setupNavigation()
    refresh()
    if (ShoppingCart.allowed()) {
      ShoppingCart.reload()
    }
    setActiveItems()
  }
  function setupNavigation () {
    Navigation.init()
    vm.items = Navigation.get()
  }
  function shoppingCart () {
    return {
      count: 0,
      open: function () {
        return $uibModal.open({
          template: '<shopping-cart modal-instance="vm.modalInstance"></shopping-cart>',
          size: 'lg',
          controller: function ($uibModalInstance) {
            const vm = this
            vm.modalInstance = $uibModalInstance
          },
          controllerAs: 'vm',
          bindToController: true

        }).result
      },
      allowed: ShoppingCart.allowed
    }
  }

  function about () {
    return {
      isOpen: false,
      additionalInfo: '',
      imgAlt: __('Product logo'),
      imgSrc: 'images/login-screen-logo.png',
      title: Text.app.name,
      productInfo: [
        {name: __('Version: '), value: applianceInfo.miqVersion},
        {name: __('SUI Version: '), value: applianceInfo.suiVersion},
        {name: __('Server Name: '), value: applianceInfo.server},
        {name: __('User Name: '), value: applianceInfo.user},
        {name: __('User Role: '), value: applianceInfo.role}
      ],
      copyright: applianceInfo.copyright,
      supportWebsiteText: applianceInfo.supportWebsiteText,
      supportWebsite: applianceInfo.supportWebsite
    }
  }

  function sites () {
    return [{
      title: __('Administration UI'),
      tooltip: __('Log into the full administrative UI'),
      iconClass: 'fa-cogs',
      url: API_BASE
    }]
  }

  function clearActiveItems () {
    angular.forEach(vm.items, function (item) {
      item.isActive = false
      if (item.children) {
        angular.forEach(item.children, function (secondary) {
          secondary.isActive = false
          if (secondary.children) {
            secondary.children.forEach(function (tertiary) {
              tertiary.isActive = false
            })
          }
        })
      }
    })
  }

  function setActiveItems () {
    angular.forEach(vm.items, function (topLevel) {
      if ($state.includes(topLevel.state)) {
        topLevel.isActive = true
      }
      if (topLevel.children) {
        angular.forEach(topLevel.children, function (secondLevel) {
          if ($state.includes(secondLevel.state)) {
            secondLevel.isActive = true
            topLevel.isActive = true
          }
          if (secondLevel.children) {
            angular.forEach(secondLevel.children, function (thirdLevel) {
              if ($state.includes(thirdLevel.state)) {
                thirdLevel.isActive = true
                secondLevel.isActive = true
                topLevel.isActive = true
              }
            })
          }
        })
      }
    })
  }

  function refreshCart () {
    vm.shoppingCart.count = ShoppingCart.count()
  }

  function refreshNotifications () {
    vm.notificationGroups = EventNotifications.state().groups
    vm.newNotifications = EventNotifications.state().unreadNotifications
    vm.unreadNotificationCount = 0
    angular.forEach(vm.notificationGroups, function (group) {
      vm.unreadNotificationCount += group.unreadCount
    })
    vm.notificationsIndicatorTooltip = __(vm.unreadNotificationCount + ' unread notifications')
  }

  function refreshToast () {
    vm.toastNotifications = EventNotifications.state().toastNotifications
  }

  function refresh () {
    refreshCart()
    refreshNotifications()
    refreshToast()
  }

  function handleItemClick (item) {
    $state.transitionTo(item.state)
  }

  function toggleNotificationsList () {
    vm.notificationsDrawerShown = !vm.notificationsDrawerShown
  }

  function getNotficationStatusIconClass (notification) {
    var retClass = ''
    if (notification && notification.type) {
      if (notification.type === 'info') {
        retClass = 'pficon pficon-info'
      } else if (notification.type === 'error') {
        retClass = 'pficon pficon-error-circle-o'
      } else if (notification.type === 'warning') {
        retClass = 'pficon pficon-warning-triangle-o'
      } else if (notification.type === 'success') {
        retClass = 'pficon pficon-ok'
      } else {
        retClass = 'pficon pficon-info'  // default to info
      }
    }

    return retClass
  }

  function markNotificationRead (notification, group) {
    EventNotifications.markRead(notification, group)
  }

  function clearNotification (notification, group) {
    EventNotifications.clear(notification, group)
  }

  function clearAllNotifications (group) {
    EventNotifications.clearAll(group)
  }

  function markAllRead (group) {
    EventNotifications.markAllRead(group)
  }

  function updateViewingToast (viewing, notification) {
    EventNotifications.setViewingToast(notification, viewing)
  }

  function handleDismissToast (notification) {
    EventNotifications.dismissToast(notification)
  }

  function switchGroup (group) {
    if (vm.user().user_href && group.id) {
      CollectionsApi.post('users', vm.user().user_href.split('/').pop(), {}, {
        'action': 'set_current_group',
        'current_group': {'id': group.id}
      }).then(success, failure)
    }

    function success () {
      Session.setGroup(group)
      Session.getUserAuthorizations()
      vm.user = Session.currentUser
      $state.go('dashboard', {}, {reload: true})
    }

    function failure (response) {
      EventNotifications.error(__('Group switching error: ') + response.data.error.message)
    }
  }
}
<|endoftext|>


<commit_before>import BaseValidator from './base';
import {isBlank} from '@ember/utils';

export default BaseValidator.create({
    properties: ['name', 'mobiledoc'],

    name(model) {
        if (isBlank(model.get('name'))) {
            model.errors.add('name', 'Name cannot be blank');
            this.invalidate();
        }
        model.get('hasValidated').addObject('name');
    },

    mobiledoc(model) {
        if (isBlank(model.get('mobiledoc'))) {
            model.errors.add('mobiledoc', 'Content cannot be blank.');
            this.invalidate();
        }
        model.get('hasValidated').addObject('mobiledoc');
    }
});
<commit_msg>Added max length validation to snippet's name field<commit_after>import BaseValidator from './base';
import validator from 'validator';
import {isBlank} from '@ember/utils';

export default BaseValidator.create({
    properties: ['name', 'mobiledoc'],

    name(model) {
        let {name} = model;

        if (!validator.isLength(name || '', 0, 191)) {
            model.errors.add('name', 'Name cannot be longer than 191 characters');
            this.invalidate();
        }

        if (isBlank(name)) {
            model.errors.add('name', 'Name cannot be blank');
            this.invalidate();
        }

        model.get('hasValidated').addObject('name');
    },

    mobiledoc(model) {
        if (isBlank(model.get('mobiledoc'))) {
            model.errors.add('mobiledoc', 'Content cannot be blank.');
            this.invalidate();
        }
        model.get('hasValidated').addObject('mobiledoc');
    }
});
<|endoftext|>


<commit_before>/**
 *
 * Image Box (JS)
 *
 * @author Takuto Yanagida @ Space-Time Inc.
 * @version 2019-06-11
 *
 */

window.ST = window['ST'] || {};

(function (NS) {

	const TARGET_SELECTOR           = '.stile';
	const TARGET_SELECTOR_IMAGE_BOX = '.stile-image-box';
	const STILE_CLS_IMAGE_BOX       = 'image-box';
	const STILE_STATE_OPEN          = 'open';
	const STILE_STATE_VISIBLE       = 'visible';
	const SIZE_BOX_PADDING          = '4rem';

	NS.addInitializer(7, () => {
		const as1 = document.querySelectorAll(TARGET_SELECTOR + ' a');
		modifyImageAnchorStyle(as1);
		const as2 = document.querySelectorAll(TARGET_SELECTOR_IMAGE_BOX + ' a');
		modifyImageAnchorStyle(as2);
	});

	function modifyImageAnchorStyle(as) {
		const fas = filterImageLink(as);
		for (let i = 0; i < fas.length; i += 1) createBox(fas[i]);
	}

	function filterImageLink(as) {
		const ret = [];
		for (let i = 0; i < as.length; i += 1) {
			const a = as[i];
			if (!NS.containStile(a, 'link-image')) continue;
			const href = a.href;
			if (!href || !isImageUrl(href)) continue;
			const imgs = a.getElementsByTagName('img');
			if (imgs.length === 0) continue;
			const img = imgs[0];
			const src = img.dataset.src ? img.dataset.src : img.src;
			if (!src || !isImageUrl(src)) continue;
			ret.push(a);
		}
		return ret;
	}

	function isImageUrl(url) {
		url = url.split('?')[0];
		url = url.toLowerCase();
		const ss = url.split('.');
		const ext = ss[ss.length - 1];
		switch (ext) {
			case 'jpeg':
			case 'jpg':
			case 'png':
			case 'gif':
				return true;
		}
		return false;
	}

	function createBox(a) {
		const frame = document.createElement('div');
		NS.addStile(frame, STILE_CLS_IMAGE_BOX);
		const img = document.createElement('img');
		img.src = a.href;
		frame.appendChild(img);
		const closeBtn = document.createElement('span');
		frame.appendChild(closeBtn);
		document.body.appendChild(frame);

		a.addEventListener('click', (e) => { onOpen(e, frame, img); });
		frame.addEventListener('click', (e) => { onClose(e, frame); });
		enableTouchGesture(frame, img);

		img.addEventListener('click', (e) => { e.stopPropagation(); });
		enableMouseGesture(frame, img);
		return frame;
	}

	function onOpen(e, frame, img) {
		e.preventDefault();
		NS.addStile(frame, STILE_STATE_OPEN);
		const isPhone = NS.MEDIA_WIDTH.indexOf('phone') !== -1;
		if (checkLandscape(frame, img)) {
			img.style.minWidth = '';
			img.style.width = isPhone ? '100%' : 'calc(100% - ' + SIZE_BOX_PADDING + ')';
			img.style.height = 'auto';
		} else {
			img.style.minHeight = '';
			img.style.width = 'auto';
			img.style.maxWidth = 'none';
			img.style.height = isPhone ? '100%' : 'calc(100% - ' + SIZE_BOX_PADDING + ')';
		}
		centeringImage(frame, img);
		const delay = NS.BROWSER === 'ie11' ? 30 : 0;
		setTimeout(() => { NS.addStile(frame, STILE_STATE_VISIBLE); }, delay);
	}

	function onClose(e, frame) {
		e.preventDefault();
		NS.removeStile(frame, STILE_STATE_VISIBLE);
		setTimeout(() => { NS.removeStile(frame, STILE_STATE_OPEN); }, 200);
	}

	function enableTouchGesture(frame, img) {
		let isLandscape = true;
		let baseDist = 0;
		let scale = 1;
		let tid;
		let xS = 0, yS = 0;

		frame.addEventListener('touchstart', (e) => {
			const ts = e.touches;
			isLandscape = checkLandscape(frame, img);
			baseDist = 0;
			if (!img.style.minWidth && !img.style.minHeight) scale = 1;
			if (ts.length === 1) {
				xS = ts[0].pageX - window.pageXOffset;
				yS = ts[0].pageY - window.pageYOffset;
			} else if (ts.length === 2) {
				xS = (ts[0].pageX + ts[1].pageX) / 2 - window.pageXOffset;
				yS = (ts[0].pageY + ts[1].pageY) / 2 - window.pageYOffset;
			}
		}, true);
		frame.addEventListener('touchmove', (e) => {
			e.stopPropagation();
			e.preventDefault();

			frame.style.overflow = 'scroll';
			const ts = e.touches;

			if (ts.length === 1) {
				const cx = ts[0].pageX - window.pageXOffset;
				const cy = ts[0].pageY - window.pageYOffset;
				frame.scrollLeft += xS - cx;
				frame.scrollTop  += yS - cy;
				xS = cx;
				yS = cy;
			} else if (ts.length > 1) {
				const baseSize = isLandscape ? frame.clientWidth : frame.clientHeight;
				const dist = touchDistance(ts);

				const scx = (ts[0].pageX + ts[1].pageX) / 2 - window.pageXOffset;
				const scy = (ts[0].pageY + ts[1].pageY) / 2 - window.pageYOffset;
				frame.scrollLeft += xS - scx;
				frame.scrollTop  += yS - scy;
				xS = scx;
				yS = scy;

				const imgCx = (scx + frame.scrollLeft) / scale;
				const imgCy = (scy + frame.scrollTop)  / scale;
				clearTimeout(tid);

				if (baseDist) {
					const s = dist / (baseDist * scale);
					if (s && s !== Infinity) {
						scale = Math.max(1, Math.min(4, scale * s));
						if (isLandscape) {
							img.style.minWidth = (baseSize * scale) + 'px';
						} else {
							img.style.minHeight = (baseSize * scale) + 'px';
						}
						centeringImage(frame, img);

						frame.scrollLeft = imgCx * scale - scx;
						frame.scrollTop  = imgCy * scale - scy;
					}
					tid = setTimeout(() => { baseDist = 0; }, 100);
				} else {
					baseDist = dist / scale;
				}
			}
		}, true);
	}

	function enableMouseGesture(frame, img) {
		let isLandscape = true;
		let scale = 1;
		let xS = 0, yS = 0;
		let isMoving = false;

		frame.addEventListener('mousedown', (e) => {
			xS = e.pageX - window.pageXOffset;
			yS = e.pageY - window.pageYOffset;
			isMoving = true;
		});
		frame.addEventListener('mousemove', (e) => {
			if (isMoving) {
				e.stopPropagation();
				e.preventDefault();
				frame.style.overflow = 'hidden';
				const cx = e.pageX - window.pageXOffset;
				const cy = e.pageY - window.pageYOffset;
				frame.scrollLeft += xS - cx;
				frame.scrollTop  += yS - cy;
				xS = cx;
				yS = cy;
			}
		});
		frame.addEventListener('mouseup', () => { isMoving = false; });

		frame.addEventListener('mousewheel', (e) => {
			e.stopPropagation();
			e.preventDefault();

			frame.style.overflow = 'hidden';
			isLandscape = checkLandscape(frame, img);
			const baseSize = isLandscape ? frame.clientWidth : frame.clientHeight;
			if (!img.style.minWidth && !img.style.minHeight) scale = 1;

			const scx = e.pageX - window.pageXOffset;
			const scy = e.pageY - window.pageYOffset;
			const imgCx = (scx + frame.scrollLeft) / scale;
			const imgCy = (scy + frame.scrollTop) / scale;

			const s = 0 > e.deltaY ? 1.1 : 0.9;
			scale = Math.max(1, Math.min(4, scale * s));
			if (isLandscape) {
				img.style.minWidth = (baseSize * scale) + 'px';
			} else {
				img.style.minHeight = (baseSize * scale) + 'px';
			}
			centeringImage(frame, img);

			frame.scrollLeft = imgCx * scale - scx;
			frame.scrollTop = imgCy * scale - scy;
		}, true);
	}

	function checkLandscape(frame, img) {
		const winAspect = frame.offsetWidth / frame.offsetHeight;
		const imgAspect = img.offsetWidth / img.offsetHeight;
		return (winAspect < imgAspect);
	}

	function centeringImage(frame, img) {
		if (img.offsetWidth < frame.offsetWidth) {
			img.style.left = ((frame.offsetWidth - img.offsetWidth) / 2) + 'px';
		} else {
			img.style.left = 0;
		}
		if (img.offsetHeight < frame.offsetHeight) {
			img.style.top = ((frame.offsetHeight - img.offsetHeight) / 2) + 'px';
		} else {
			img.style.top = 0;
		}
	}

	function touchDistance(ts) {
		const x1 = ts[0].screenX;
		const y1 = ts[0].screenY;
		const x2 = ts[1].screenX;
		const y2 = ts[1].screenY;
		return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
	}

})(window.ST);
<commit_msg>Improve image box gestures<commit_after>/**
 *
 * Image Box (JS)
 *
 * @author Takuto Yanagida @ Space-Time Inc.
 * @version 2019-06-11
 *
 */

window.ST = window['ST'] || {};

(function (NS) {

	const TARGET_SELECTOR           = '.stile';
	const TARGET_SELECTOR_IMAGE_BOX = '.stile-image-box';
	const STILE_CLS_IMAGE_BOX       = 'image-box';
	const STILE_STATE_OPEN          = 'open';
	const STILE_STATE_VISIBLE       = 'visible';
	const SIZE_BOX_PADDING          = '4rem';

	NS.addInitializer(7, () => {
		const as1 = document.querySelectorAll(TARGET_SELECTOR + ' a');
		modifyImageAnchorStyle(as1);
		const as2 = document.querySelectorAll(TARGET_SELECTOR_IMAGE_BOX + ' a');
		modifyImageAnchorStyle(as2);
	});

	function modifyImageAnchorStyle(as) {
		const fas = filterImageLink(as);
		for (let i = 0; i < fas.length; i += 1) createBox(fas[i]);
	}

	function filterImageLink(as) {
		const ret = [];
		for (let i = 0; i < as.length; i += 1) {
			const a = as[i];
			if (!NS.containStile(a, 'link-image')) continue;
			const href = a.href;
			if (!href || !isImageUrl(href)) continue;
			const imgs = a.getElementsByTagName('img');
			if (imgs.length === 0) continue;
			const img = imgs[0];
			const src = img.dataset.src ? img.dataset.src : img.src;
			if (!src || !isImageUrl(src)) continue;
			ret.push(a);
		}
		return ret;
	}

	function isImageUrl(url) {
		url = url.split('?')[0];
		url = url.toLowerCase();
		const ss = url.split('.');
		const ext = ss[ss.length - 1];
		switch (ext) {
			case 'jpeg':
			case 'jpg':
			case 'png':
			case 'gif':
				return true;
		}
		return false;
	}

	function createBox(a) {
		const frame = document.createElement('div');
		NS.addStile(frame, STILE_CLS_IMAGE_BOX);
		const img = document.createElement('img');
		img.src = a.href;
		frame.appendChild(img);
		const closeBtn = document.createElement('span');
		frame.appendChild(closeBtn);
		document.body.appendChild(frame);

		a.addEventListener('click', (e) => { onOpen(e, frame, img); });
		frame.addEventListener('click', (e) => { onClose(e, frame); });
		enableTouchGesture(frame, img);

		img.addEventListener('click', (e) => { e.stopPropagation(); });
		enableMouseGesture(frame, img);
		return frame;
	}

	function onOpen(e, frame, img) {
		e.preventDefault();
		NS.addStile(frame, STILE_STATE_OPEN);
		const isPhone = NS.MEDIA_WIDTH.indexOf('phone') !== -1;
		if (checkLandscape(frame, img)) {
			img.style.minWidth = '';
			img.style.width = isPhone ? '100%' : 'calc(100% - ' + SIZE_BOX_PADDING + ')';
			img.style.height = 'auto';
		} else {
			img.style.minHeight = '';
			img.style.width = 'auto';
			img.style.maxWidth = 'none';
			img.style.height = isPhone ? '100%' : 'calc(100% - ' + SIZE_BOX_PADDING + ')';
		}
		centeringImage(frame, img);
		const delay = NS.BROWSER === 'ie11' ? 30 : 0;
		setTimeout(() => { NS.addStile(frame, STILE_STATE_VISIBLE); }, delay);
	}

	function onClose(e, frame) {
		e.preventDefault();
		NS.removeStile(frame, STILE_STATE_VISIBLE);
		setTimeout(() => { NS.removeStile(frame, STILE_STATE_OPEN); }, 200);
	}

	function enableTouchGesture(frame, img) {
		let isLandscape = true;
		let baseDist = 0;
		let scale = 1;
		const isPhone = NS.MEDIA_WIDTH.indexOf('phone') !== -1;

		let xS = 0, yS = 0;
		let lastTouchCount = 0;

		function updatePoint(ts) {
			lastTouchCount = ts.length;
			if (lastTouchCount === 1) {
				xS = ts[0].pageX - window.pageXOffset;
				yS = ts[0].pageY - window.pageYOffset;
			} else if (lastTouchCount === 2) {
				xS = (ts[0].pageX + ts[1].pageX) / 2 - window.pageXOffset;
				yS = (ts[0].pageY + ts[1].pageY) / 2 - window.pageYOffset;
			}
		}

		frame.addEventListener('touchstart', (e) => {
			isLandscape = checkLandscape(frame, img);
			baseDist = 0;
			if (!img.style.minWidth && !img.style.minHeight) scale = 1;

			updatePoint(e.touches);
		});
		frame.addEventListener('touchmove', (e) => {
			e.preventDefault();
			e.stopPropagation();

			frame.style.overflow = 'hidden';
			const ts = e.touches;
			if (lastTouchCount !== ts.length) updatePoint(ts);

			if (ts.length === 1) {
				const cx = ts[0].pageX - window.pageXOffset;
				const cy = ts[0].pageY - window.pageYOffset;
				frame.scrollLeft += xS - cx;
				frame.scrollTop  += yS - cy;
				xS = cx;
				yS = cy;
			} else if (ts.length > 1) {
				const baseSize = isLandscape ? frame.clientWidth : frame.clientHeight;
				const dist = touchDistance(ts);

				const scx = (ts[0].pageX + ts[1].pageX) / 2 - window.pageXOffset;
				const scy = (ts[0].pageY + ts[1].pageY) / 2 - window.pageYOffset;
				frame.scrollLeft += xS - scx;
				frame.scrollTop  += yS - scy;
				xS = scx;
				yS = scy;

				const imgCx = (scx + frame.scrollLeft) / scale;
				const imgCy = (scy + frame.scrollTop)  / scale;

				if (baseDist) {
					const s = dist / (baseDist * scale);
					if (s && s !== Infinity) {
						scale = setImageScale(img, baseSize, scale * s, isLandscape, isPhone);
						centeringImage(frame, img);

						frame.scrollLeft = imgCx * scale - scx;// + dx;
						frame.scrollTop  = imgCy * scale - scy;// + dy;
					}
				}
				baseDist = dist / scale;
			}
		}, { passive: false });

		// for Android
		preventWindowTouchMove(frame);
	}

	function preventWindowTouchMove(f) {
		let isTouching = false;
		window.addEventListener('touchmove', (e) => {
			if (!isTouching) return;
			e.preventDefault();
			e.stopImmediatePropagation();
		}, { passive: false });
		f.addEventListener('touchstart', () => { isTouching = true; });
		f.addEventListener('touchend', () => { isTouching = false; });
	}

	function enableMouseGesture(frame, img) {
		let isLandscape = true;
		let scale = 1;
		let xS = 0, yS = 0;
		let isMoving = false;
		const isPhone = NS.MEDIA_WIDTH.indexOf('phone') !== -1;

		frame.addEventListener('mousedown', (e) => {
			xS = e.pageX - window.pageXOffset;
			yS = e.pageY - window.pageYOffset;
			isMoving = true;
		});
		frame.addEventListener('mousemove', (e) => {
			if (isMoving) {
				e.stopPropagation();
				e.preventDefault();
				frame.style.overflow = 'hidden';
				const cx = e.pageX - window.pageXOffset;
				const cy = e.pageY - window.pageYOffset;
				frame.scrollLeft += xS - cx;
				frame.scrollTop  += yS - cy;
				xS = cx;
				yS = cy;
			}
		});
		frame.addEventListener('mouseup', () => { isMoving = false; });

		frame.addEventListener('mousewheel', (e) => {
			e.stopPropagation();
			e.preventDefault();

			frame.style.overflow = 'hidden';
			isLandscape = checkLandscape(frame, img);
			const baseSize = isLandscape ? frame.clientWidth : frame.clientHeight;
			if (!img.style.minWidth && !img.style.minHeight) scale = 1;

			const scx = e.pageX - window.pageXOffset;
			const scy = e.pageY - window.pageYOffset;
			const imgCx = (scx + frame.scrollLeft) / scale;
			const imgCy = (scy + frame.scrollTop) / scale;

			const s = 0 > e.deltaY ? 1.1 : 0.9;
			scale = setImageScale(img, baseSize, scale * s, isLandscape, isPhone);
			centeringImage(frame, img);

			frame.scrollLeft = imgCx * scale - scx;
			frame.scrollTop  = imgCy * scale - scy;
		}, true);
	}

	function checkLandscape(frame, img) {
		const winAspect = frame.offsetWidth / frame.offsetHeight;
		const imgAspect = img.offsetWidth / img.offsetHeight;
		return (winAspect < imgAspect);
	}

	function setImageScale(img, baseSize, scale, isLandscape, isPhone) {
		scale = Math.max(1, Math.min(4, scale));
		let size = '';
		if (isPhone) {
			size = (baseSize * scale) + 'px';
		} else {
			size = 'calc(' + (baseSize * scale) + 'px - ' + SIZE_BOX_PADDING + ')';
		}
		if (isLandscape) img.style.minWidth = size;
		else img.style.minHeight = size;
		return scale;
	}

	function centeringImage(frame, img) {
		if (img.offsetWidth < frame.offsetWidth) {
			img.style.left = ((frame.offsetWidth - img.offsetWidth) / 2) + 'px';
		} else {
			img.style.left = 0;
		}
		if (img.offsetHeight < frame.offsetHeight) {
			img.style.top = ((frame.offsetHeight - img.offsetHeight) / 2) + 'px';
		} else {
			img.style.top = 0;
		}
	}

	function touchDistance(ts) {
		const x1 = ts[0].screenX;
		const y1 = ts[0].screenY;
		const x2 = ts[1].screenX;
		const y2 = ts[1].screenY;
		return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
	}

})(window.ST);
<|endoftext|>


<commit_before>/*
 *
 *                              ~ BOA WRAPPER ~
 *
 * Execute boa file from javascript and get the results in JSON format. This
 * module exposes one interface (`run`) which will run a script and return a
 * promise. The promise gets resolved with the resulting JSON.
 */
'use strict';

let cp = require('child_process');
let ipc = require('ipc');
let jetpack = require('fs-jetpack');
let im = require('./instance-manager');
let manifest = jetpack.read(`${__dirname}/../../package.json`, 'json');

module.exports = (function() {

  function parseToJSON(raw, fmt) {
    let json = new Object;
    let parsed = [];
    let lines = raw.split('\n');
    // let delims = /\[|\]|\t|\s+|\r/;
    let delims = /\,/;

    for (let line of lines) {
      line = line.replace(/\ = /g, ',');
      let matches = line.split(delims);
      // var exists = (n) => !!n; not implemented in io.js currently

      // remove empty strings/elements
      matches = matches.filter(function(n) { return !!n; });

      // coerce into strings
      matches = matches.map(function(n) { return "" + n });

      if (matches.length > 0) {
        parsed.push(matches);
      }
    }

    if (fmt == 'lta') {
      return parsed;
    }

    for (let chunk of parsed) {
      let pntr = json;
      let val = chunk.pop();
      for (let i = 0; i < chunk.length; i++) {
        let key = chunk[i];
        if (!(key in pntr)) {
          pntr[key] = i == chunk.length - 1 ? [val] : new Object;
        } else {
          if (i == chunk.length - 1) {
            pntr[key].push(val);
            console.log(`[BOA][PARSE] Found duplicate path to different value: ${JSON.stringify(pntr[key])}`);
          }
        }
        pntr = pntr[key];
      }
    }

    return json;
  }

  let run = function run(opts, fmt) {
    let desired = manifest.boa;
    let jarname = `candoia-core-v${desired}.jar`

    // todo sanitize uri so user apps cannot execute random code
    let promise = new Promise(function(resolve, reject) {
      let cmd = `java -jar "${__dirname}/../../boa/${jarname}"`;
      for (let opt in opts) {
        cmd += ` ${opt} ${opts[opt]}`;
      }
      console.log(`[BOA] ${cmd}`);
      let child = cp.exec(cmd, {cwd: `${__dirname}/../../`}, function(error, stdout, stderr) {
        if (stderr) {
          reject(stderr);
        }
      });
      child.on('exit', function(code, signal) {
        let raw = jetpack.read(`${__dirname}}/../../output.txt`);
        if (raw) {
          let res = parseToJSON(raw, fmt);
          resolve(res);
        } else {
          reject(`The boa compiler did not produce any output. Cwd: ${child.process.cwd()}, Look: ${__dirname}/../../, Code: ${code}. Signal: ${signal}`);
        }
      });
    });
    return promise;
  }

  ipc.on('boa-run', function(event, url, fmt) {
    fmt = fmt || 'json';
    let instance = im.get(event.sender.getId());
    let local = instance.repos.local;

    let prog = `"${__dirname}/../../.apps/${instance.app.name}/${url}"`;
    let opts = {
      '-i': prog
    }

    if (local == '') {
      let remote = instance.repos.remote;
      let s = remote.split('/');
      let c = `${s[3]},${s[4]},null,null,null`;
      console.log(c);
      opts['-g'] = c;
    } else {
      opts['-p'] = `"${local}"`;
    }

    run(opts, fmt).then(function(json) {
      event.returnValue = json;
    }).catch(function(e) {
      event.returnValue = {error: e};
      console.log(`[BOA][ERROR] ${e}`);
    });
  });

  ipc.on('boa-exec', function(event, code, fmt) {
    fmt = fmt || 'json';
    let instance = im.get(event.sender.getId());
    let local = instance.repos.local;
    let file = `${__dirname}/../../.tmp/query.boa`;
    jetpack.write(file, code);

    let opts = {
      '-i': `"${file}"`
    }

    if (local == '') {
      let remote = instance.repos.remote;
      let s = remote.split('/');
      let c = `${s[3]},${s[4]},null,null,null`;
      console.log(c);
      opts['-g'] = c;
    } else {
      opts['-p'] = local;
    }

    run(opts, fmt).then(function(json) {
      event.returnValue = json;
    }).catch(function(e) {
      event.returnValue = {error: e};
      console.log(`[BOA][ERROR] ${e}`);
    });
  });

  return { run };
})();
<commit_msg>fixed issue with boa files not being found for locally-installed apps<commit_after>/*
 *
 *                              ~ BOA WRAPPER ~
 *
 * Execute boa file from javascript and get the results in JSON format. This
 * module exposes one interface (`run`) which will run a script and return a
 * promise. The promise gets resolved with the resulting JSON.
 */
'use strict';

let cp = require('child_process');
let ipc = require('ipc');
let jetpack = require('fs-jetpack');
let im = require('./instance-manager');
let manifest = jetpack.read(`${__dirname}/../../package.json`, 'json');
let path = require('path');

module.exports = (function() {

  function parseToJSON(raw, fmt) {
    let json = new Object;
    let parsed = [];
    let lines = raw.split('\n');
    // let delims = /\[|\]|\t|\s+|\r/;
    let delims = /\,/;

    for (let line of lines) {
      line = line.replace(/\ = /g, ',');
      let matches = line.split(delims);
      // var exists = (n) => !!n; not implemented in io.js currently

      // remove empty strings/elements
      matches = matches.filter(function(n) { return !!n; });

      // coerce into strings
      matches = matches.map(function(n) { return "" + n });

      if (matches.length > 0) {
        parsed.push(matches);
      }
    }

    if (fmt == 'lta') {
      return parsed;
    }

    for (let chunk of parsed) {
      let pntr = json;
      let val = chunk.pop();
      for (let i = 0; i < chunk.length; i++) {
        let key = chunk[i];
        if (!(key in pntr)) {
          pntr[key] = i == chunk.length - 1 ? [val] : new Object;
        } else {
          if (i == chunk.length - 1) {
            pntr[key].push(val);
            console.log(`[BOA][PARSE] Found duplicate path to different value: ${JSON.stringify(pntr[key])}`);
          }
        }
        pntr = pntr[key];
      }
    }

    return json;
  }

  let run = function run(opts, fmt) {
    let desired = manifest.boa;
    let jarname = `candoia-core-v${desired}.jar`

    // todo sanitize uri so user apps cannot execute random code
    let promise = new Promise(function(resolve, reject) {
      let cmd = `java -jar "${__dirname}/../../boa/${jarname}"`;
      for (let opt in opts) {
        cmd += ` ${opt} ${opts[opt]}`;
      }
      console.log(`[BOA] ${cmd}`);
      let child = cp.exec(cmd, {cwd: `${__dirname}/../../`}, function(error, stdout, stderr) {
        if (stderr) {
          reject(stderr);
        }
      });
      child.on('exit', function(code, signal) {
        let raw = jetpack.read(`${__dirname}}/../../output.txt`);
        if (raw) {
          let res = parseToJSON(raw, fmt);
          resolve(res);
        } else {
          reject(`The boa compiler did not produce any output. Cwd: ${child.process.cwd()}, Look: ${__dirname}/../../, Code: ${code}. Signal: ${signal}`);
        }
      });
    });
    return promise;
  }

  ipc.on('boa-run', function(event, url, fmt) {
    fmt = fmt || 'json';
    let instance = im.get(event.sender.getId());
    let local = instance.repos.local;

    // let prog = `"${__dirname}/../../.apps/${instance.app.name}/${url}"`;
    let prog = path.join(instance.app.path, url);
    console.log(prog);
    let opts = {
      '-i': prog
    }

    if (local == '') {
      let remote = instance.repos.remote;
      let s = remote.split('/');
      let c = `${s[3]},${s[4]},null,null,null`;
      console.log(c);
      opts['-g'] = c;
    } else {
      opts['-p'] = `"${local}"`;
    }

    run(opts, fmt).then(function(json) {
      event.returnValue = json;
    }).catch(function(e) {
      event.returnValue = {error: e};
      console.log(`[BOA][ERROR] ${e}`);
    });
  });

  ipc.on('boa-exec', function(event, code, fmt) {
    fmt = fmt || 'json';
    let instance = im.get(event.sender.getId());
    let local = instance.repos.local;
    let file = `${__dirname}/../../.tmp/query.boa`;
    jetpack.write(file, code);

    let opts = {
      '-i': `"${file}"`
    }

    if (local == '') {
      let remote = instance.repos.remote;
      let s = remote.split('/');
      let c = `${s[3]},${s[4]},null,null,null`;
      console.log(c);
      opts['-g'] = c;
    } else {
      opts['-p'] = local;
    }

    run(opts, fmt).then(function(json) {
      event.returnValue = json;
    }).catch(function(e) {
      event.returnValue = {error: e};
      console.log(`[BOA][ERROR] ${e}`);
    });
  });

  return { run };
})();
<|endoftext|>


<commit_before>const
  slugify    = require('./slugify')

const
  PRECISION = 3

module.exports = format

function format( template, filename, iteration, time, opt ) {
  opt = opt || {}

  var
    path     = opt.path || require('path'),
    extname  = path.extname( filename ),
    basename = path.basename( filename, extname ),
    dirname  = path.dirname( filename )

  iteration = parseInt( iteration ) || 0

  if ( dirname == '.' )
    dirname = ''

  // Ensure trailing slash on dirname, if it exists
  if ( dirname && dirname.substr( -1 ) != '/' )
    dirname = dirname + '/'

  var slug = slugify
  var useUTC = false

  return template.replace(
    /\%([0])?(\d*?)(\.\d*)?([irBbFfEeYMDhmsztT])/g,
    function ( tag, flags, width, precision,  specifier ) {
      var radix

      if ( precision )
        precision = parseInt( precision.substr(1) ) || PRECISION

      width = parseInt( width )

      switch ( specifier ) {
        case 'F':
          return trim( basename + extname )

        case 'f':
          return trim( slug( 'base', basename ) + slug( 'ext', extname )  )

        case 'B':
          return trim( basename )

        case 'b':
          return trim( slug( 'base', basename ) )

        case 'E':
          return trim( extname )

        case 'e':
          return trim( slug( 'ext', extname ) )

        case 'P':
          if ( dirname && dirname != '.' ) {
            return trim( slug( 'dir', dirname ) ) + path.sep
          } else {
            return ''
          }

        case 'p':
          if ( dirname && dirname != '.' ) {
            return trim( slug( 'dir', dirname ) ) + path.sep
          } else {
            return ''
          }

        case 'i':
          radix = radix || 10
          // falls through
        case 'z':
          radix = radix || 16

          if ( width )
            return pad( iteration.toString( radix ) )

          if ( !iteration )
            if ( flags == '0' )
              return '0'
            else
              return ''

          return iteration.toString( radix )

        case 'r':
          return random( width )

        case 't':
          return trimFloat(
            time instanceof Date ?
              time.getTime()
            :
              parseFloat( time ) || 0
          )
      }

      //
      // Resolve `time` to be a Date object.
      //
      if ( time === undefined ) {
        // Default is now.
        time = new Date()
      } else if ( 'number' == typeof time ) {
        // Convert from a Number to a Date.
        // A few liberties are taken here to allow the library to process
        // timecode style input. By default, Date will take UTC
        // milliseconds the epoch numbers as input. If output is in local
        // time zone ( default ), this will produce unexpected output for
        // small numbers. Instead, we treat Number conversion as being in
        // the output time zone so that 'zero' for Number conversion
        // always starts at midnight.

        if ( !useUTC )
          time += new Date( time ).getTimezoneOffset() * 60 * 1000

        time = new Date( time )
      } else if ( !(time instanceof Date ) ) {
        time = new Date( time )
      }

      if ( !precision && !width )
        width = 2

      switch ( specifier ) {
        case 's':
          if ( useUTC )
            return trimFloat( time.getUTCSeconds() + time.getUTCMilliseconds() / 1000 )
          else
            return trimFloat( time.getSeconds() + time.getMilliseconds() / 1000 )

        case 'T':
          return time.toJSON()
      }

      width = width || 2

      if ( useUTC ) {
        switch ( specifier ) {
          case 'Y': return padTrim( time.getUTCFullYear() )
          case 'M': return padTrim( time.getUTCMonth() + 1 )
          case 'D': return padTrim( time.getUTCDate() )
          case 'h': return padTrim( time.getUTCHours() )
          case 'm': return padTrim( time.getUTCMinutes() )
        }
      } else {
        switch ( specifier ) {
          case 'Y': return padTrim( time.getFullYear() )
          case 'M': return padTrim( time.getMonth() + 1 )
          case 'D': return padTrim( time.getDate() )
          case 'h': return padTrim( time.getHours() )
          case 'm': return padTrim( time.getMinutes() )
        }
      }

      function trim ( str ) {
        str = String(str)
        return width ? str.substr( 0, width ) : str
      }

      function trimFloat( num ) {
        var
          str = num.toFixed( precision ),
          split = str.split('.'),
          integer = split[0],
          decimal = split[1]

        str = width ?
            flags == '0' ?
                padTrim( integer )
              :
                pad( integer )
          :
            integer

        if ( decimal )
          str += '.'+decimal

        return str
      }

      function pad ( num ) {
        num = String( num )
        while ( width && num.length < width ) {
          num = '0'+num
        }
        return num
      }

      function padTrim ( num ) {
        num = pad( num )

        if ( width && num.length > width )
          num = num.substr( num.length - width )

        return num
      }
    }
  )
}<commit_msg>Add UTC option<commit_after>const
  slug    = require('./slugify')

const
  PRECISION = 3

module.exports = format

function format( template, filename, iteration, time, opt ) {
  opt = opt || {}

  var
    path     = opt.path || require('path'),
    extname  = path.extname( filename ),
    basename = path.basename( filename, extname ),
    dirname  = path.dirname( filename )

  iteration = parseInt( iteration ) || 0

  if ( dirname == '.' )
    dirname = ''

  // Ensure trailing slash on dirname, if it exists
  if ( dirname && dirname.substr( -1 ) != '/' )
    dirname = dirname + '/'

  return template.replace(
    /\%([0])?(\d*?)(\.\d*)?([irBbFfEeYMDhmsztT])/g,
    function ( tag, flags, width, precision,  specifier ) {
      var radix

      if ( precision )
        precision = parseInt( precision.substr(1) ) || PRECISION

      width = parseInt( width )

      switch ( specifier ) {
        case 'F':
          return trim( basename + extname )

        case 'f':
          return trim( slug( 'base', basename ) + slug( 'ext', extname )  )

        case 'B':
          return trim( basename )

        case 'b':
          return trim( slug( 'base', basename ) )

        case 'E':
          return trim( extname )

        case 'e':
          return trim( slug( 'ext', extname ) )

        case 'P':
          if ( dirname && dirname != '.' ) {
            return trim( slug( 'dir', dirname ) ) + path.sep
          } else {
            return ''
          }

        case 'p':
          if ( dirname && dirname != '.' ) {
            return trim( slug( 'dir', dirname ) ) + path.sep
          } else {
            return ''
          }

        case 'i':
          radix = radix || 10
          // falls through
        case 'z':
          radix = radix || 16

          if ( width )
            return pad( iteration.toString( radix ) )

          if ( !iteration )
            if ( flags == '0' )
              return '0'
            else
              return ''

          return iteration.toString( radix )

        case 'r':
          return random( width )

        case 't':
          return trimFloat(
            time instanceof Date ?
              time.getTime()
            :
              parseFloat( time ) || 0
          )
      }

      //
      // Resolve `time` to be a Date object.
      //
      if ( time === undefined ) {
        // Default is now.
        time = new Date()
      } else if ( 'number' == typeof time ) {
        // Convert from a Number to a Date.
        // A few liberties are taken here to allow the library to process
        // timecode style input. By default, Date will take UTC
        // milliseconds the epoch numbers as input. If output is in local
        // time zone ( default ), this will produce unexpected output for
        // small numbers. Instead, we treat Number conversion as being in
        // the output time zone so that 'zero' for Number conversion
        // always starts at midnight.

        if ( !opt.UTC )
          time += new Date( time ).getTimezoneOffset() * 60 * 1000

        time = new Date( time )
      } else if ( !(time instanceof Date ) ) {
        time = new Date( time )
      }

      if ( !precision && !width )
        width = 2

      switch ( specifier ) {
        case 's':
          if ( opt.UTC )
            return trimFloat( time.getUTCSeconds() + time.getUTCMilliseconds() / 1000 )
          else
            return trimFloat( time.getSeconds() + time.getMilliseconds() / 1000 )

        case 'T':
          return time.toJSON()
      }

      width = width || 2

      if ( opt.UTC ) {
        switch ( specifier ) {
          case 'Y': return padTrim( time.getUTCFullYear() )
          case 'M': return padTrim( time.getUTCMonth() + 1 )
          case 'D': return padTrim( time.getUTCDate() )
          case 'h': return padTrim( time.getUTCHours() )
          case 'm': return padTrim( time.getUTCMinutes() )
        }
      } else {
        switch ( specifier ) {
          case 'Y': return padTrim( time.getFullYear() )
          case 'M': return padTrim( time.getMonth() + 1 )
          case 'D': return padTrim( time.getDate() )
          case 'h': return padTrim( time.getHours() )
          case 'm': return padTrim( time.getMinutes() )
        }
      }

      function trim ( str ) {
        str = String(str)
        return width ? str.substr( 0, width ) : str
      }

      function trimFloat( num ) {
        var
          str = num.toFixed( precision ),
          split = str.split('.'),
          integer = split[0],
          decimal = split[1]

        str = width ?
            flags == '0' ?
                padTrim( integer )
              :
                pad( integer )
          :
            integer

        if ( decimal )
          str += '.'+decimal

        return str
      }

      function pad ( num ) {
        num = String( num )
        while ( width && num.length < width ) {
          num = '0'+num
        }
        return num
      }

      function padTrim ( num ) {
        num = pad( num )

        if ( width && num.length > width )
          num = num.substr( num.length - width )

        return num
      }
    }
  )
}<|endoftext|>


<commit_before>import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import PodcastChannel from './podcast-channel.js';
import PodcastSettings from './podcast-settings.js';
import PodcastItem from './podcast-item.js';
import podcastParser from './podcast-parser.js';
import podcastRender from './podcast-render.js';
var fs = require('fs');
var path = require('path');
var moment = require('moment');

export default class Podcast extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      podcast: {
        title: '',
        link: '',
        subtitle: '',
        description: '',
        language: 'en-us',
        image: '',
        author: '',
        copyright: '',
        ownerName: '',
        ownerEmail: '',
        explicit: false,
        categories: [],
        items: []
      },
      settings: {
        showOptionalFields: false,
        deploytype: 'none',
        prefixUrl: '',
        sftphost: '',
        sftpuser: '',
        sftppass: '',
        sftppath: '',
        s3bucketname: '',
        s3accesskey: '',
        s3secretkey: ''
      },
      activeItem: -1,
      settingsDialogOpen: false
    };
    this.updateSubState = this.updateSubState.bind(this);
    this.handleItemChange = this.handleItemChange.bind(this);
    this.newItem = this.newItem.bind(this);
    this.deleteItem = this.deleteItem.bind(this);
    this.saveChanges = this.saveChanges.bind(this);
    this.setActiveItem = this.setActiveItem.bind(this);
    this.openSettingsDialog = this.openSettingsDialog.bind(this);
    this.closeSettingsDialog = this.closeSettingsDialog.bind(this);
    this.merge = this.merge.bind(this);
  }
  merge(stateKey, newStateObject) {
    var newObject = this.state[stateKey];
    var k;
    for (k in newStateObject) {
      if (newStateObject.hasOwnProperty(k) === false) {
        continue;
      }
      newObject[k] = newStateObject[k];
    }
    return newObject;
  }
  componentDidMount() {
    var localPath = path.join(this.props.directory, '.podcast-local.xml');
    var xmlPath = path.join(this.props.directory, 'podcast.xml');
    var outerThis = this;
    try {
      fs.accessSync(localPath, fs.F_OK);
      podcastParser(localPath, function(parsed) {
        outerThis.setState({podcast: outerThis.merge('podcast', parsed)});
      });
    } catch (e) {
      console.log('no .podcast-local.xml file - trying podcast.xml');
      try {
        fs.accessSync(xmlPath, fs.F_OK);
        podcastParser(xmlPath, function(parsed) {
          outerThis.setState({podcast: outerThis.merge('podcast', parsed)});
        });
      } catch (e) {
        console.log('no podcast.xml - starting from scratch');
      }
    }
    var settingsPath = path.join(this.props.directory, '.settings.json');
    try {
      fs.accessSync(settingsPath, fs.F_OK);
      fs.readFile(settingsPath, 'utf8', function(err, data) {
        if (err) throw err;
        outerThis.setState({settings: JSON.parse(data)});
      });
    } catch (e) {
      console.log('no .settings.json file found');
    }
  }
  updateSubState(outerKey, innerKey, value) {
    var newState = {};
    newState[outerKey] = this.state[outerKey];
    newState[outerKey][innerKey] = value;
    this.setState(newState);
  }
  handleItemChange(i, k, v) {
    var tmpPodcast = this.state.podcast;
    tmpPodcast.items[i][k] = v;
    this.setState({podcast: tmpPodcast});
  }
  newItem() {
    var DATE_RFC2822 = "ddd, DD MMM YYYY HH:mm:ss ZZ";
    var newItem = {
      title: '',
      description: '',
      author: '',
      duration: '0:00:00',
      subtitle: '',
      pubdate: moment().format(DATE_RFC2822),
      filesize: 0,
      filetype: '',
      fileurl: ''
    };
    var tmpPodcast = this.state.podcast;
    tmpPodcast.items = [newItem].concat(tmpPodcast.items);
    this.setState({
      podcast: tmpPodcast,
      activeItem: 0
    });
  }
  deleteItem(i) {
    var tmpPodcast = this.state.podcast;
    tmpPodcast.items.splice(i, 1);
    this.setState({podcast: tmpPodcast});
  }
  setActiveItem(i) {
    this.setState({activeItem: i});
  }
  openSettingsDialog() {
    this.setState({settingsDialogOpen: true});
  }
  closeSettingsDialog() {
    this.setState({settingsDialogOpen: false});
  }
  saveChanges() {
    podcastRender(this.props.directory,
      this.state.podcast,
      this.state.settings.prefixUrl);
  }
  render() {
    var outerThis = this;
    return (

            <RaisedButton
              label="Settings"
              onTouchTap={this.openSettingsDialog}
            />

            <RaisedButton
              label="Go Home"
              onTouchTap={this.props.goHome}
            />

            <RaisedButton
              label="Save Changes"
              onTouchTap={this.saveChanges}
            />

        <PodcastChannel {...this.props}
          podcast={this.state.podcast}
          settings={this.state.settings}
          handleChange={(k, v) => this.updateSubState('podcast', k, v)}
        />
        <PodcastSettings {...this.props}
          open={this.state.settingsDialogOpen}
          close={this.closeSettingsDialog}
          settings={this.state.settings}
          handleChange={(k, v) => this.updateSubState('settings', k, v)}
        />

Episodes:

              <RaisedButton
                label="New Episode"
                onTouchTap={this.newItem}
              />

          {
            this.state.podcast.items.map(function(item, i) {
              return (
                <PodcastItem key={i} {...outerThis.props}
                  episode={item}
                  settings={outerThis.state.settings}
                  handleChange={(k, v) => outerThis.handleItemChange(i, k, v)}
                  deleteItem={() => outerThis.deleteItem(i)}
                  expanded={outerThis.state.activeItem === i}
                  expand={() => outerThis.setActiveItem(i)}
                  collapse={() => outerThis.setActiveItem(-1)}
                />
              );
            })
          }

    );
  }
}
<commit_msg>reorder buttons, incorporate publish functionality<commit_after>import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import PodcastChannel from './podcast-channel.js';
import PodcastSettings from './podcast-settings.js';
import PodcastItem from './podcast-item.js';
import podcastParser from './podcast-parser.js';
import podcastRender from './podcast-render.js';
import syncRemote from './sync-remote.js';
var fs = require('fs');
var path = require('path');
var moment = require('moment');

export default class Podcast extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      podcast: {
        title: '',
        link: '',
        subtitle: '',
        description: '',
        language: 'en-us',
        image: '',
        author: '',
        copyright: '',
        ownerName: '',
        ownerEmail: '',
        explicit: false,
        categories: [],
        items: []
      },
      settings: {
        showOptionalFields: false,
        deploytype: 'none',
        prefixUrl: '',
        sftphost: '',
        sftpuser: '',
        sftppass: '',
        sftppath: '',
        s3bucketname: '',
        s3accesskey: '',
        s3secretkey: ''
      },
      activeItem: -1,
      settingsDialogOpen: false
    };
    this.updateSubState = this.updateSubState.bind(this);
    this.handleItemChange = this.handleItemChange.bind(this);
    this.newItem = this.newItem.bind(this);
    this.deleteItem = this.deleteItem.bind(this);
    this.saveChanges = this.saveChanges.bind(this);
    this.setActiveItem = this.setActiveItem.bind(this);
    this.openSettingsDialog = this.openSettingsDialog.bind(this);
    this.closeSettingsDialog = this.closeSettingsDialog.bind(this);
    this.publish = this.publish.bind(this);
    this.merge = this.merge.bind(this);
  }
  merge(stateKey, newStateObject) {
    var newObject = this.state[stateKey];
    var k;
    for (k in newStateObject) {
      if (newStateObject.hasOwnProperty(k) === false) {
        continue;
      }
      newObject[k] = newStateObject[k];
    }
    return newObject;
  }
  componentDidMount() {
    var localPath = path.join(this.props.directory, '.podcast-local.xml');
    var xmlPath = path.join(this.props.directory, 'podcast.xml');
    var outerThis = this;
    try {
      fs.accessSync(localPath, fs.F_OK);
      podcastParser(localPath, function(parsed) {
        outerThis.setState({podcast: outerThis.merge('podcast', parsed)});
      });
    } catch (e) {
      console.log('no .podcast-local.xml file - trying podcast.xml');
      try {
        fs.accessSync(xmlPath, fs.F_OK);
        podcastParser(xmlPath, function(parsed) {
          outerThis.setState({podcast: outerThis.merge('podcast', parsed)});
        });
      } catch (e) {
        console.log('no podcast.xml - starting from scratch');
      }
    }
    var settingsPath = path.join(this.props.directory, '.settings.json');
    try {
      fs.accessSync(settingsPath, fs.F_OK);
      fs.readFile(settingsPath, 'utf8', function(err, data) {
        if (err) throw err;
        outerThis.setState({settings: JSON.parse(data)});
      });
    } catch (e) {
      console.log('no .settings.json file found');
    }
  }
  updateSubState(outerKey, innerKey, value) {
    var newState = {};
    newState[outerKey] = this.state[outerKey];
    newState[outerKey][innerKey] = value;
    this.setState(newState);
  }
  handleItemChange(i, k, v) {
    var tmpPodcast = this.state.podcast;
    tmpPodcast.items[i][k] = v;
    this.setState({podcast: tmpPodcast});
  }
  newItem() {
    var DATE_RFC2822 = "ddd, DD MMM YYYY HH:mm:ss ZZ";
    var newItem = {
      title: '',
      description: '',
      author: '',
      duration: 0.0,
      subtitle: '',
      pubdate: moment().format(DATE_RFC2822),
      filesize: 0,
      filetype: '',
      fileurl: ''
    };
    var tmpPodcast = this.state.podcast;
    tmpPodcast.items = [newItem].concat(tmpPodcast.items);
    this.setState({
      podcast: tmpPodcast,
      activeItem: 0
    });
  }
  deleteItem(i) {
    var tmpPodcast = this.state.podcast;
    tmpPodcast.items.splice(i, 1);
    this.setState({podcast: tmpPodcast});
  }
  setActiveItem(i) {
    this.setState({activeItem: i});
  }
  openSettingsDialog() {
    this.setState({settingsDialogOpen: true});
  }
  closeSettingsDialog() {
    this.setState({settingsDialogOpen: false});
  }
  publish() {
    console.log('syncing...');
    syncRemote(this.props.directory, this.state.settings);
  }
  saveChanges() {
    podcastRender(this.props.directory,
      this.state.podcast,
      this.state.settings.prefixUrl);
  }
  render() {
    var outerThis = this;
    return (

            <RaisedButton
              label="Go Home"
              onTouchTap={this.props.goHome}
            />

            <RaisedButton
              label="Settings"
              onTouchTap={this.openSettingsDialog}
            />

            <RaisedButton
              label="Publish"
              onTouchTap={this.publish}
            />

            <RaisedButton
              label="Save Changes"
              onTouchTap={this.saveChanges}
            />

        <PodcastChannel {...this.props}
          podcast={this.state.podcast}
          settings={this.state.settings}
          handleChange={(k, v) => this.updateSubState('podcast', k, v)}
        />
        <PodcastSettings {...this.props}
          open={this.state.settingsDialogOpen}
          close={this.closeSettingsDialog}
          settings={this.state.settings}
          handleChange={(k, v) => this.updateSubState('settings', k, v)}
        />

Episodes:

              <RaisedButton
                label="New Episode"
                onTouchTap={this.newItem}
              />

          {
            this.state.podcast.items.map(function(item, i) {
              return (
                <PodcastItem key={i} {...outerThis.props}
                  episode={item}
                  settings={outerThis.state.settings}
                  handleChange={(k, v) => outerThis.handleItemChange(i, k, v)}
                  deleteItem={() => outerThis.deleteItem(i)}
                  expanded={outerThis.state.activeItem === i}
                  expand={() => outerThis.setActiveItem(i)}
                  collapse={() => outerThis.setActiveItem(-1)}
                />
              );
            })
          }

    );
  }
}
<|endoftext|>


<commit_before>import React from 'react'
import echarts from 'echarts'
import _ from 'lodash'
import fetch from 'isomorphic-fetch'
import styles from './Main.css'
import Loading from '../Loading'

import { autobind } from 'react-decoration'
import { checkStatus, apiAddress } from '../../util'

const refName = 'RelativeTrend'

const getOption = ({ topic, topicAmountList }) => ({
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'line',
      lineStyle: {
        color: 'rgba(0,0,0,0.2)',
        width: 1,
        type: 'solid'
      }
    }
  },
  legend: {
    data: topic
  },
  singleAxis: {
    top: 50,
    bottom: 50,
    axisTick: {},
    axisLabel: {},
    type: 'time',
    axisPointer: {
      animation: true,
      label: {
        show: true
      }
    },
    splitLine: {
      show: true,
      lineStyle: {
        type: 'dashed',
        opacity: 0.2
      }
    }
  },
  series: [
    {
      type: 'themeRiver',
      itemStyle: {
        emphasis: {
          shadowBlur: 20,
          shadowColor: 'rgba(0, 0, 0, 0.8)'
        }
      },
      data: _.flatMap(topicAmountList.data, (topicAmountWithDate, index) =>
        (topicAmountWithDate.map((topicAmount, dateIndex) => ([
          topicAmountList.date[dateIndex],
          topicAmount,
          topic[index]
        ]))))}]})

export default class RelativeTrend extends React.Component {
  constructor () {
    super()
    this.state = {
      topic: []
    }
    fetch(`${apiAddress}/tot-topic`, { method: 'GET' })
      .then(checkStatus)
      .then((res) => (res.json()))
      .then((topic) => this.setState({...this.state, topic}))
    fetch(`${apiAddress}/tot-topic-amount`, { method: 'GET' })
      .then(checkStatus)
      .then((res) => (res.json()))
      .then((topicAmountList) => this.setState({...this.state, topicAmountList}))
  }

  @autobind
  isDataLoaded () {
    return !_.isEmpty(this.state.topic) && !_.isEmpty(this.state.topicAmountList)
  }

  componentDidMount () {
    this.myChart = echarts.init(this.refs[refName])
    const option = getOption(this.state)
    this.myChart.setOption(option)
  }

  componentDidUpdate () {
    if (this.isDataLoaded()) {
      const option = getOption(this.state)
      this.myChart.setOption(option)
    }
  }

  render () {
    return

      {this.isDataLoaded()
        ?

        : <Loading />}

  }
}
<commit_msg>fix some bug<commit_after>import React from 'react'
import echarts from 'echarts'
import _ from 'lodash'
import fetch from 'isomorphic-fetch'
import styles from './Main.css'
import Loading from '../Loading'

import { autobind } from 'react-decoration'
import { checkStatus, apiAddress } from '../../util'

const refName = 'RelativeTrend'

const getOption = ({ topic, topicAmountList }) => ({
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'line',
      lineStyle: {
        color: 'rgba(0,0,0,0.2)',
        width: 1,
        type: 'solid'
      }
    }
  },
  legend: {
    data: topic
  },
  singleAxis: {
    top: 50,
    bottom: 50,
    axisTick: {},
    axisLabel: {},
    type: 'time',
    axisPointer: {
      animation: true,
      label: {
        show: true
      }
    },
    splitLine: {
      show: true,
      lineStyle: {
        type: 'dashed',
        opacity: 0.2
      }
    }
  },
  series: [
    {
      type: 'themeRiver',
      itemStyle: {
        emphasis: {
          shadowBlur: 20,
          shadowColor: 'rgba(0, 0, 0, 0.8)'
        }
      },
      data: _.flatMap(topicAmountList.data, (topicAmountWithDate, index) =>
        (topicAmountWithDate.map((topicAmount, dateIndex) => ([
          topicAmountList.date[dateIndex],
          topicAmount,
          topic[index]
        ])))
      )
    }
  ]
})

const transformData = (topicAbsAmountList) => {
  const totalAmount = _.zip(...topicAbsAmountList.data).map(_.sum)
  return {
    data: topicAbsAmountList.data.map(topicAbsAmount => (
      topicAbsAmount.map((amount, index) => amount / totalAmount[index])
    )),
    date: topicAbsAmountList.date
  }
}

export default class RelativeTrend extends React.Component {
  constructor () {
    super()
    this.state = {
      topic: []
    }
    fetch(`${apiAddress}/tot-topic`, { method: 'GET' })
      .then(checkStatus)
      .then((res) => (res.json()))
      .then((topic) => this.setState({...this.state, topic}))
    fetch(`${apiAddress}/tot-topic-amount`, { method: 'GET' })
      .then(checkStatus)
      .then((res) => (res.json()))
      .then((topicAbsAmountList) => this.setState({
        ...this.state,
        topicAmountList: transformData(topicAbsAmountList)
      }))
  }

  @autobind
  isDataLoaded () {
    return !_.isEmpty(this.state.topic) && !_.isEmpty(this.state.topicAmountList)
  }

  componentDidMount () {
    this.myChart = echarts.init(this.refs[refName])
  }

  componentDidUpdate () {
    if (this.isDataLoaded()) {
      const option = getOption(this.state)
      this.myChart.setOption(option)
    }
  }

  render () {
    return

      {this.isDataLoaded()
        ?

        : <Loading />}

  }
}
<|endoftext|>


<commit_before><commit_msg>lg_pano: change place where target is set<commit_after><|endoftext|>