<commit_before>import macro from 'vtk.js/Sources/macro';

import vtkAnnotatedCubeActor from 'vtk.js/Sources/Rendering/Core/AnnotatedCubeActor';
import vtkCornerAnnotation from 'vtk.js/Sources/Interaction/UI/CornerAnnotation';
import vtkInteractorStyleManipulator from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator';
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';
import vtkOpenGLRenderWindow from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow';
import vtkOrientationMarkerWidget from 'vtk.js/Sources/Interaction/Widgets/OrientationMarkerWidget';
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow';
import vtkRenderWindowInteractor from 'vtk.js/Sources/Rendering/Core/RenderWindowInteractor';

import InteractionPresets from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator/Presets';
import AnnotatedCubePresets from 'vtk.js/Sources/Rendering/Core/AnnotatedCubeActor/Presets';

// ----------------------------------------------------------------------------
// vtkViewProxy methods
// ----------------------------------------------------------------------------

function vtkViewProxy(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkViewProxy');

  // Private --------------------------------------------------------------------

  function updateAnnotationColor() {
    const [r, g, b] = model.renderer.getBackground();
    model.cornerAnnotation.getAnnotationContainer().style.color =
      r + g + b > 1.5 ? 'black' : 'white';
  }

  // Setup --------------------------------------------------------------------
  model.renderWindow = vtkRenderWindow.newInstance();
  model.renderer = vtkRenderer.newInstance({ background: [0, 0, 0] });
  model.renderWindow.addRenderer(model.renderer);

  model.openglRenderWindow = vtkOpenGLRenderWindow.newInstance();
  model.renderWindow.addView(model.openglRenderWindow);

  model.interactor = vtkRenderWindowInteractor.newInstance();
  model.interactor.setView(model.openglRenderWindow);

  model.interactorStyle3D = vtkInteractorStyleManipulator.newInstance();
  model.interactorStyle2D = vtkInteractorStyleManipulator.newInstance();

  // Apply default interaction styles
  InteractionPresets.applyPreset('3D', model.interactorStyle3D);
  InteractionPresets.applyPreset('2D', model.interactorStyle2D);

  model.cornerAnnotation = vtkCornerAnnotation.newInstance();

  // Setup interaction
  model.interactor.setInteractorStyle(
    model.useParallelRendering
      ? model.interactorStyle2D
      : model.interactorStyle3D
  );
  model.camera = model.renderer.getActiveCamera();
  model.camera.setParallelProjection(!!model.useParallelRendering);

  // Orientation a cube setup -------------------------------------------------

  model.orientationAxes = vtkAnnotatedCubeActor.newInstance();
  AnnotatedCubePresets.applyPreset('default', model.orientationAxes);
  model.orientationWidget = vtkOrientationMarkerWidget.newInstance({
    actor: model.orientationAxes,
    interactor: model.renderWindow.getInteractor(),
  });
  model.orientationWidget.setEnabled(true);
  model.orientationWidget.setViewportCorner(
    vtkOrientationMarkerWidget.Corners.BOTTOM_LEFT
  );
  model.orientationWidget.setViewportSize(0.1);

  // API ----------------------------------------------------------------------

  publicAPI.setPresetToInteractor3D = (nameOrDefinitions) => {
    if (Array.isArray(nameOrDefinitions)) {
      return InteractionPresets.applyDefinitions(
        nameOrDefinitions,
        model.interactorStyle3D
      );
    }
    return InteractionPresets.applyPreset(
      nameOrDefinitions,
      model.interactorStyle3D
    );
  };

  // --------------------------------------------------------------------------

  publicAPI.setPresetToInteractor2D = (nameOrDefinitions) => {
    if (Array.isArray(nameOrDefinitions)) {
      return InteractionPresets.applyDefinitions(
        nameOrDefinitions,
        model.interactorStyle2D
      );
    }
    return InteractionPresets.applyPreset(
      nameOrDefinitions,
      model.interactorStyle2D
    );
  };

  // --------------------------------------------------------------------------

  publicAPI.setPresetToOrientationAxes = (nameOrDefinitions) => {
    if (typeof nameOrDefinitions === 'string') {
      return AnnotatedCubePresets.applyPreset(
        nameOrDefinitions,
        model.orientationAxes
      );
    }
    return AnnotatedCubePresets.applyDefinitions(
      nameOrDefinitions,
      model.orientationAxes
    );
  };

  // --------------------------------------------------------------------------

  publicAPI.setContainer = (container) => {
    if (model.container) {
      model.interactor.unbindEvents(model.container);
      model.openglRenderWindow.setContainer(null);
      model.cornerAnnotation.setContainer(null);
    }

    model.container = container;

    if (container) {
      model.openglRenderWindow.setContainer(container);
      model.cornerAnnotation.setContainer(container);
      model.interactor.initialize();
      model.interactor.bindEvents(container);
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.resize = () => {
    if (model.container) {
      const dims = model.container.getBoundingClientRect();
      model.openglRenderWindow.setSize(
        Math.max(10, Math.floor(dims.width)),
        Math.max(10, Math.floor(dims.height))
      );
      publicAPI.renderLater();
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.renderLater = () => {
    if (model.representations.length > 0 && model.resetCameraOnFirstRender) {
      model.resetCameraOnFirstRender = false;
      // console.log('==> resetCamera before renderLater', model.proxyId);
      publicAPI.resetCamera();
    }
    model.orientationWidget.updateMarkerOrientation();
    setTimeout(model.renderWindow.render, 0);
  };

  // --------------------------------------------------------------------------

  publicAPI.addRepresentation = (representation) => {
    if (!representation) {
      return;
    }
    if (model.representations.indexOf(representation) === -1) {
      model.representations.push(representation);
      representation.getActors().forEach(model.renderer.addActor);
      representation.getVolumes().forEach(model.renderer.addVolume);
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.removeRepresentation = (representation) => {
    if (!representation) {
      return;
    }
    if (model.representations.indexOf(representation) !== -1) {
      model.representations = model.representations.filter(
        (r) => r !== representation
      );
      representation.getActors().forEach(model.renderer.removeActor);
      representation.getVolumes().forEach(model.renderer.removeVolume);
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.resetCamera = () => {
    model.renderer.resetCamera();
    model.renderer.resetCameraClippingRange();
    model.interactorStyle2D.setCenterOfRotation(model.camera.getFocalPoint());
    model.interactorStyle3D.setCenterOfRotation(model.camera.getFocalPoint());
    publicAPI.renderLater();
  };

  // --------------------------------------------------------------------------

  publicAPI.captureImage = () => model.renderWindow.captureImages()[0];

  // --------------------------------------------------------------------------

  publicAPI.openCaptureImage = (target = '_blank') => {
    const image = new Image();
    image.src = publicAPI.captureImage();
    const w = window.open('', target);
    w.document.write(image.outerHTML);
    w.document.title = 'vtk.js Image Capture';
    window.focus();
  };

  // --------------------------------------------------------------------------

  publicAPI.setCornerAnnotation = (corner, templateString) => {
    model.cornerAnnotation.updateTemplates({
      [corner]: (meta) =>
        vtkCornerAnnotation.applyTemplate(templateString, meta),
    });
  };

  // --------------------------------------------------------------------------

  publicAPI.updateCornerAnnotation = (meta) =>
    model.cornerAnnotation.updateMetadata(meta);

  // --------------------------------------------------------------------------

  publicAPI.setAnnotationOpacity = (opacity) => {
    if (model.annotationOpacity !== Number(opacity)) {
      model.annotationOpacity = Number(opacity);
      model.cornerAnnotation.getAnnotationContainer().style.opacity = opacity;
      publicAPI.modified();
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.setBackground = macro.chain(
    model.renderer.setBackground,
    updateAnnotationColor
  );

  // --------------------------------------------------------------------------

  publicAPI.getBackground = model.renderer.getBackground;

  // --------------------------------------------------------------------------

  publicAPI.setAnimation = (enable) => {
    if (enable) {
      model.renderWindow.getInteractor().requestAnimation('proxy');
    } else {
      model.renderWindow.getInteractor().cancelAnimation('proxy');
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.updateOrientation = (axisIndex, orientation, viewUp) => {
    if (axisIndex === undefined) {
      return;
    }
    model.axis = axisIndex;
    model.orientation = orientation;
    model.viewUp = viewUp;
    const position = model.camera.getFocalPoint();
    position[model.axis] += model.orientation;
    model.camera.setPosition(...position);
    model.camera.setViewUp(...viewUp);
  };

  // --------------------------------------------------------------------------

  publicAPI.resetOrientation = () => {
    publicAPI.updateOrientation(model.axis, model.orientation, model.viewUp);
  };

  // --------------------------------------------------------------------------

  publicAPI.rotate = (angle) => {
    const { viewUp, focalPoint, position } = model.camera.get(
      'viewUp',
      'focalPoint',
      'position'
    );
    const axis = [
      focalPoint[0] - position[0],
      focalPoint[1] - position[1],
      focalPoint[2] - position[2],
    ];

    vtkMatrixBuilder
      .buildFromDegree()
      .rotate(Number.isNaN(angle) ? 90 : angle, axis)
      .apply(viewUp);

    model.camera.setViewUp(...viewUp);
    model.camera.modified();
    model.renderWindow.render();
  };

  // --------------------------------------------------------------------------
  // Initialization from state or input
  // --------------------------------------------------------------------------

  publicAPI.updateOrientation(model.axis, model.orientation, model.viewUp);
  updateAnnotationColor();
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  representations: [],
  sectionName: 'view',
  annotationOpacity: 1,
  resetCameraOnFirstRender: true,
};

// ----------------------------------------------------------------------------

function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  macro.obj(publicAPI, model);
  macro.get(publicAPI, model, [
    'representations',
    'renderer',
    'renderWindow',
    'openglRenderWindow',
    'interactor',
    'interactorStyle2D',
    'interactorStyle3D',
    'container',
    'useParallelRendering',
    'camera',
    'cornerAnnotation',
    'annotationOpacity',
  ]);

  // Object specific methods
  vtkViewProxy(publicAPI, model);

  // Proxy handling
  macro.proxy(publicAPI, model);
  macro.proxyPropertyMapping(publicAPI, model, {
    orientationAxes: { modelKey: 'orientationWidget', property: 'enabled' },
    cameraViewUp: { modelKey: 'camera', property: 'viewUp' },
    cameraPosition: { modelKey: 'camera', property: 'position' },
    cameraFocalPoint: { modelKey: 'camera', property: 'focalPoint' },
  });
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkViewProxy');

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<commit_msg>fix(ViewProxy): Add setCornerAnnotations method<commit_after>import macro from 'vtk.js/Sources/macro';

import vtkAnnotatedCubeActor from 'vtk.js/Sources/Rendering/Core/AnnotatedCubeActor';
import vtkCornerAnnotation from 'vtk.js/Sources/Interaction/UI/CornerAnnotation';
import vtkInteractorStyleManipulator from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator';
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';
import vtkOpenGLRenderWindow from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow';
import vtkOrientationMarkerWidget from 'vtk.js/Sources/Interaction/Widgets/OrientationMarkerWidget';
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow';
import vtkRenderWindowInteractor from 'vtk.js/Sources/Rendering/Core/RenderWindowInteractor';

import InteractionPresets from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator/Presets';
import AnnotatedCubePresets from 'vtk.js/Sources/Rendering/Core/AnnotatedCubeActor/Presets';

// ----------------------------------------------------------------------------
// vtkViewProxy methods
// ----------------------------------------------------------------------------

function vtkViewProxy(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkViewProxy');

  // Private --------------------------------------------------------------------

  function updateAnnotationColor() {
    const [r, g, b] = model.renderer.getBackground();
    model.cornerAnnotation.getAnnotationContainer().style.color =
      r + g + b > 1.5 ? 'black' : 'white';
  }

  // Setup --------------------------------------------------------------------
  model.renderWindow = vtkRenderWindow.newInstance();
  model.renderer = vtkRenderer.newInstance({ background: [0, 0, 0] });
  model.renderWindow.addRenderer(model.renderer);

  model.openglRenderWindow = vtkOpenGLRenderWindow.newInstance();
  model.renderWindow.addView(model.openglRenderWindow);

  model.interactor = vtkRenderWindowInteractor.newInstance();
  model.interactor.setView(model.openglRenderWindow);

  model.interactorStyle3D = vtkInteractorStyleManipulator.newInstance();
  model.interactorStyle2D = vtkInteractorStyleManipulator.newInstance();

  // Apply default interaction styles
  InteractionPresets.applyPreset('3D', model.interactorStyle3D);
  InteractionPresets.applyPreset('2D', model.interactorStyle2D);

  model.cornerAnnotation = vtkCornerAnnotation.newInstance();

  // Setup interaction
  model.interactor.setInteractorStyle(
    model.useParallelRendering
      ? model.interactorStyle2D
      : model.interactorStyle3D
  );
  model.camera = model.renderer.getActiveCamera();
  model.camera.setParallelProjection(!!model.useParallelRendering);

  // Orientation a cube setup -------------------------------------------------

  model.orientationAxes = vtkAnnotatedCubeActor.newInstance();
  AnnotatedCubePresets.applyPreset('default', model.orientationAxes);
  model.orientationWidget = vtkOrientationMarkerWidget.newInstance({
    actor: model.orientationAxes,
    interactor: model.renderWindow.getInteractor(),
  });
  model.orientationWidget.setEnabled(true);
  model.orientationWidget.setViewportCorner(
    vtkOrientationMarkerWidget.Corners.BOTTOM_LEFT
  );
  model.orientationWidget.setViewportSize(0.1);

  // API ----------------------------------------------------------------------

  publicAPI.setPresetToInteractor3D = (nameOrDefinitions) => {
    if (Array.isArray(nameOrDefinitions)) {
      return InteractionPresets.applyDefinitions(
        nameOrDefinitions,
        model.interactorStyle3D
      );
    }
    return InteractionPresets.applyPreset(
      nameOrDefinitions,
      model.interactorStyle3D
    );
  };

  // --------------------------------------------------------------------------

  publicAPI.setPresetToInteractor2D = (nameOrDefinitions) => {
    if (Array.isArray(nameOrDefinitions)) {
      return InteractionPresets.applyDefinitions(
        nameOrDefinitions,
        model.interactorStyle2D
      );
    }
    return InteractionPresets.applyPreset(
      nameOrDefinitions,
      model.interactorStyle2D
    );
  };

  // --------------------------------------------------------------------------

  publicAPI.setPresetToOrientationAxes = (nameOrDefinitions) => {
    if (typeof nameOrDefinitions === 'string') {
      return AnnotatedCubePresets.applyPreset(
        nameOrDefinitions,
        model.orientationAxes
      );
    }
    return AnnotatedCubePresets.applyDefinitions(
      nameOrDefinitions,
      model.orientationAxes
    );
  };

  // --------------------------------------------------------------------------

  publicAPI.setContainer = (container) => {
    if (model.container) {
      model.interactor.unbindEvents(model.container);
      model.openglRenderWindow.setContainer(null);
      model.cornerAnnotation.setContainer(null);
    }

    model.container = container;

    if (container) {
      model.openglRenderWindow.setContainer(container);
      model.cornerAnnotation.setContainer(container);
      model.interactor.initialize();
      model.interactor.bindEvents(container);
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.resize = () => {
    if (model.container) {
      const dims = model.container.getBoundingClientRect();
      model.openglRenderWindow.setSize(
        Math.max(10, Math.floor(dims.width)),
        Math.max(10, Math.floor(dims.height))
      );
      publicAPI.renderLater();
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.renderLater = () => {
    if (model.representations.length > 0 && model.resetCameraOnFirstRender) {
      model.resetCameraOnFirstRender = false;
      // console.log('==> resetCamera before renderLater', model.proxyId);
      publicAPI.resetCamera();
    }
    model.orientationWidget.updateMarkerOrientation();
    setTimeout(model.renderWindow.render, 0);
  };

  // --------------------------------------------------------------------------

  publicAPI.addRepresentation = (representation) => {
    if (!representation) {
      return;
    }
    if (model.representations.indexOf(representation) === -1) {
      model.representations.push(representation);
      representation.getActors().forEach(model.renderer.addActor);
      representation.getVolumes().forEach(model.renderer.addVolume);
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.removeRepresentation = (representation) => {
    if (!representation) {
      return;
    }
    if (model.representations.indexOf(representation) !== -1) {
      model.representations = model.representations.filter(
        (r) => r !== representation
      );
      representation.getActors().forEach(model.renderer.removeActor);
      representation.getVolumes().forEach(model.renderer.removeVolume);
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.resetCamera = () => {
    model.renderer.resetCamera();
    model.renderer.resetCameraClippingRange();
    model.interactorStyle2D.setCenterOfRotation(model.camera.getFocalPoint());
    model.interactorStyle3D.setCenterOfRotation(model.camera.getFocalPoint());
    publicAPI.renderLater();
  };

  // --------------------------------------------------------------------------

  publicAPI.captureImage = () => model.renderWindow.captureImages()[0];

  // --------------------------------------------------------------------------

  publicAPI.openCaptureImage = (target = '_blank') => {
    const image = new Image();
    image.src = publicAPI.captureImage();
    const w = window.open('', target);
    w.document.write(image.outerHTML);
    w.document.title = 'vtk.js Image Capture';
    window.focus();
  };

  // --------------------------------------------------------------------------

  publicAPI.setCornerAnnotation = (corner, templateString) => {
    model.cornerAnnotation.updateTemplates({
      [corner]: (meta) =>
        vtkCornerAnnotation.applyTemplate(templateString, meta),
    });
  };

  // --------------------------------------------------------------------------

  publicAPI.setCornerAnnotations = (annotations, useTemplateString = false) => {
    if (useTemplateString) {
      Object.keys(annotations).forEach((key) => {
        publicAPI.setCornerAnnotation(key, annotations[key]);
      });
    } else {
      model.cornerAnnotation.updateTemplates(annotations);
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.updateCornerAnnotation = (meta) =>
    model.cornerAnnotation.updateMetadata(meta);

  // --------------------------------------------------------------------------

  publicAPI.setAnnotationOpacity = (opacity) => {
    if (model.annotationOpacity !== Number(opacity)) {
      model.annotationOpacity = Number(opacity);
      model.cornerAnnotation.getAnnotationContainer().style.opacity = opacity;
      publicAPI.modified();
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.setBackground = macro.chain(
    model.renderer.setBackground,
    updateAnnotationColor
  );

  // --------------------------------------------------------------------------

  publicAPI.getBackground = model.renderer.getBackground;

  // --------------------------------------------------------------------------

  publicAPI.setAnimation = (enable) => {
    if (enable) {
      model.renderWindow.getInteractor().requestAnimation('proxy');
    } else {
      model.renderWindow.getInteractor().cancelAnimation('proxy');
    }
  };

  // --------------------------------------------------------------------------

  publicAPI.updateOrientation = (axisIndex, orientation, viewUp) => {
    if (axisIndex === undefined) {
      return;
    }
    model.axis = axisIndex;
    model.orientation = orientation;
    model.viewUp = viewUp;
    const position = model.camera.getFocalPoint();
    position[model.axis] += model.orientation;
    model.camera.setPosition(...position);
    model.camera.setViewUp(...viewUp);
  };

  // --------------------------------------------------------------------------

  publicAPI.resetOrientation = () => {
    publicAPI.updateOrientation(model.axis, model.orientation, model.viewUp);
  };

  // --------------------------------------------------------------------------

  publicAPI.rotate = (angle) => {
    const { viewUp, focalPoint, position } = model.camera.get(
      'viewUp',
      'focalPoint',
      'position'
    );
    const axis = [
      focalPoint[0] - position[0],
      focalPoint[1] - position[1],
      focalPoint[2] - position[2],
    ];

    vtkMatrixBuilder
      .buildFromDegree()
      .rotate(Number.isNaN(angle) ? 90 : angle, axis)
      .apply(viewUp);

    model.camera.setViewUp(...viewUp);
    model.camera.modified();
    model.renderWindow.render();
  };

  // --------------------------------------------------------------------------
  // Initialization from state or input
  // --------------------------------------------------------------------------

  publicAPI.updateOrientation(model.axis, model.orientation, model.viewUp);
  updateAnnotationColor();
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  representations: [],
  sectionName: 'view',
  annotationOpacity: 1,
  resetCameraOnFirstRender: true,
};

// ----------------------------------------------------------------------------

function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  macro.obj(publicAPI, model);
  macro.get(publicAPI, model, [
    'representations',
    'renderer',
    'renderWindow',
    'openglRenderWindow',
    'interactor',
    'interactorStyle2D',
    'interactorStyle3D',
    'container',
    'useParallelRendering',
    'camera',
    'cornerAnnotation',
    'annotationOpacity',
  ]);

  // Object specific methods
  vtkViewProxy(publicAPI, model);

  // Proxy handling
  macro.proxy(publicAPI, model);
  macro.proxyPropertyMapping(publicAPI, model, {
    orientationAxes: { modelKey: 'orientationWidget', property: 'enabled' },
    cameraViewUp: { modelKey: 'camera', property: 'viewUp' },
    cameraPosition: { modelKey: 'camera', property: 'position' },
    cameraFocalPoint: { modelKey: 'camera', property: 'focalPoint' },
  });
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkViewProxy');

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<|endoftext|>


<commit_before>/*
 *  The MIT License (MIT)
 *
 *  Copyright (c) 2014-2016 Niklas Vangerow
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 */

// -- Constants are loaded from constants.js in the manifest.

// Reference to storage system so that custom css can be retrieved.
// The .sync allows this data to be synchronised with Google's servers
const storage = chrome.storage.sync

// Same as minimal theme, but with a set color. This is the default.
let defaultCSSToInject = ".pace {\n-webkit-pointer-events: none;\npointer-events: none;\n-webkit-user-select: none;\n-moz-user-select: none;\nuser-select: none;\n}\n.pace-inactive {\ndisplay: none;\n}\n.pace .pace-progress {\nbackground: #2299dd;\nposition: fixed;\nz-index: 2000;\ntop: 0;\nright: 100%;\nwidth: 100%;\nheight: 2px;\n}"

// String that keeps this session's CSS string for easy access
let paceCSSCacheString = defaultCSSToInject

// Load this session's CSS so that the paceCSSCacheString string is populated
reloadCachedCSS()
reloadCachedBlacklist()

// Load the provided string into the CSS cache and in permanent storage
function setCustomCSS(newCSS) {
	paceCSSCacheString = newCSS

	let storageObject = {}
	storageObject[storageKeys.css] = newCSS
	storage.set(storageObject)
}

// Reload the CSS into the cache
function reloadCachedCSS() {
	storage.get(storageKeys.css, function(data) {
		if (data.hasOwnProperty(storageKeys.css)) {
			// Load saved CSS string into cache
			paceCSSCacheString = data[storageKeys.css]
		} else {
			// Otherwise, store whatever css we might be caching right now
			let storageObject = {}
			storageObject[storageKeys.css] = paceCSSCacheString

			storage.set(storageObject)
		}
	})
}

// Inject pace.js into the current page
function injectJS() {
	chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
		chrome.tabs.executeScript(null, {
			"file": "js/pace.js",
			"runAt": "document_start"
		})
	})
}

// Inject the cached CSS string into the current page
function injectCSS() {
	chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
		chrome.tabs.insertCSS(tabs[0].id, {
			"code": paceCSSCacheString,
			"runAt": "document_start",
			"allFrames": false
		})
	})
}

// --- Blacklist ---
// Array of regular expressions for the blacklist
let blacklisted = []

// Get the blacklist as an array of strings
function getBlacklistString(callback) {
	storage.get(storageKeys.blacklist, function(data) {
		if (data.hasOwnProperty(storageKeys.blacklist)) {
			const blacklist = data[storageKeys.blacklist]
			callback(blacklist)
		} else {
			callback("")
		}
	})
}

function getBlacklist(callback) {
	getBlacklistString(function(blacklistString) {
		callback(blacklistString.split("\n").filter(s => s.trim().length !== 0))
	})
}

// Set the blacklist string, each item separated by a newline
function setBlacklistString(blacklistString, callback) {
	let storageObject = {}
	storageObject[storageKeys.blacklist] = blacklistString

	storage.set(storageObject, callback)
}

// Set blacklist using an array of STRINGS
function setBlacklist(blacklistArray, callback) {
	setBlacklistString(blacklistArray.join("\n"), callback)
}

// Reload the cached blacklist as an array of
// regular expressions
function reloadCachedBlacklist() {
	getBlacklist(function (blacklist) {
		blacklisted = blacklist.map(function(value) {
			return [(() => {
				// ignore invalid regular expressions
				try {
					return new RegExp(value, 'i')
				} catch (e) {
					return null
				}
			})(), value]
		})
	})
}

// Check whether a href is blacklisted by testing
// it against the blacklist regular expressions.
function isBlacklisted(href) {
	for (let potential of blacklisted) {
		if (potential !== null && potential[0].test(href)) {
			return true
		}
	}
	return false
}

// --- Messages ---
chrome.runtime.onMessage.addListener(function(request, sender, response) {
	switch (request.id) {
	case messageKeys.inject:
		injectJS()
		injectCSS()
		response({"id": request.id})
		return

	case messageKeys.refresh:
		reloadCachedCSS()
		response({"id": request.id})
		return

	case messageKeys.set:
		setCustomCSS(request.newCSS)
		response({"id": request.id})
		return

	case messageKeys.get:
		response({"id": request.id, "css": paceCSSCacheString})
		return

	case messageKeys.reset:
		setCustomCSS(defaultCSSToInject)
		response({"id": request.id})
		return

	case messageKeys.resetAndGet:
		response({"id": request.id, "css": defaultCSSToInject})
		return

	case messageKeys.checkBlacklisted:
		response(isBlacklisted(request.href))
		return

	case messageKeys.getBlacklist:
		response(blacklisted.map((value) => {
			return value[1]
		}))
		return

	case messageKeys.getBlacklistString:
		response(blacklisted.map((value) => {
			return value[1]
		}).join("\n"))
		return

	case messageKeys.setBlacklist:
		// Also reload the cache when the blacklist is changed
		setBlacklist(request.blacklist, reloadCachedBlacklist)
		return

	case messageKeys.setBlacklistString:
		// Also reload the cache when the blacklist is changed
		setBlacklistString(request.blacklistString, reloadCachedBlacklist)
		return
	}
})<commit_msg>Wasn't checking for null regex correctly.<commit_after>/*
 *  The MIT License (MIT)
 *
 *  Copyright (c) 2014-2016 Niklas Vangerow
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 */

// -- Constants are loaded from constants.js in the manifest.

// Reference to storage system so that custom css can be retrieved.
// The .sync allows this data to be synchronised with Google's servers
const storage = chrome.storage.sync

// Same as minimal theme, but with a set color. This is the default.
let defaultCSSToInject = ".pace {\n-webkit-pointer-events: none;\npointer-events: none;\n-webkit-user-select: none;\n-moz-user-select: none;\nuser-select: none;\n}\n.pace-inactive {\ndisplay: none;\n}\n.pace .pace-progress {\nbackground: #2299dd;\nposition: fixed;\nz-index: 2000;\ntop: 0;\nright: 100%;\nwidth: 100%;\nheight: 2px;\n}"

// String that keeps this session's CSS string for easy access
let paceCSSCacheString = defaultCSSToInject

// Load this session's CSS so that the paceCSSCacheString string is populated
reloadCachedCSS()
reloadCachedBlacklist()

// Load the provided string into the CSS cache and in permanent storage
function setCustomCSS(newCSS) {
	paceCSSCacheString = newCSS

	let storageObject = {}
	storageObject[storageKeys.css] = newCSS
	storage.set(storageObject)
}

// Reload the CSS into the cache
function reloadCachedCSS() {
	storage.get(storageKeys.css, function(data) {
		if (data.hasOwnProperty(storageKeys.css)) {
			// Load saved CSS string into cache
			paceCSSCacheString = data[storageKeys.css]
		} else {
			// Otherwise, store whatever css we might be caching right now
			let storageObject = {}
			storageObject[storageKeys.css] = paceCSSCacheString

			storage.set(storageObject)
		}
	})
}

// Inject pace.js into the current page
function injectJS() {
	chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
		chrome.tabs.executeScript(tabs[0].id, {
			"file": "js/pace.js",
			"runAt": "document_start"
		})
	})
}

// Inject the cached CSS string into the current page
function injectCSS() {
	chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
		chrome.tabs.insertCSS(tabs[0].id, {
			"code": paceCSSCacheString,
			"runAt": "document_start",
			"allFrames": false
		})
	})
}

// --- Blacklist ---
// Array of regular expressions for the blacklist
let blacklisted = []

// Get the blacklist as an array of strings
function getBlacklistString(callback) {
	storage.get(storageKeys.blacklist, function(data) {
		if (data.hasOwnProperty(storageKeys.blacklist)) {
			const blacklist = data[storageKeys.blacklist]
			callback(blacklist)
		} else {
			callback("")
		}
	})
}

function getBlacklist(callback) {
	getBlacklistString(function(blacklistString) {
		callback(blacklistString.split("\n").filter(s => s.trim().length !== 0))
	})
}

// Set the blacklist string, each item separated by a newline
function setBlacklistString(blacklistString, callback) {
	let storageObject = {}
	storageObject[storageKeys.blacklist] = blacklistString

	storage.set(storageObject, callback)
}

// Set blacklist using an array of STRINGS
function setBlacklist(blacklistArray, callback) {
	setBlacklistString(blacklistArray.join("\n"), callback)
}

// Reload the cached blacklist as an array of
// regular expressions
function reloadCachedBlacklist() {
	getBlacklist(function (blacklist) {
		blacklisted = blacklist.map(function(value) {
			return [(() => {
				// ignore invalid regular expressions
				try {
					return new RegExp(value, 'i')
				} catch (e) {
					return null
				}
			})(), value]
		})
	})
}

// Check whether a href is blacklisted by testing
// it against the blacklist regular expressions.
function isBlacklisted(href) {
	for (let potential of blacklisted) {
		if (potential[0] !== null && potential[0].test(href)) {
			return true
		}
	}
	return false
}

// --- Messages ---
chrome.runtime.onMessage.addListener(function(request, sender, response) {
	switch (request.id) {
	case messageKeys.inject:
		injectJS()
		injectCSS()
		response({"id": request.id})
		return

	case messageKeys.refresh:
		reloadCachedCSS()
		response({"id": request.id})
		return

	case messageKeys.set:
		setCustomCSS(request.newCSS)
		response({"id": request.id})
		return

	case messageKeys.get:
		response({"id": request.id, "css": paceCSSCacheString})
		return

	case messageKeys.reset:
		setCustomCSS(defaultCSSToInject)
		response({"id": request.id})
		return

	case messageKeys.resetAndGet:
		response({"id": request.id, "css": defaultCSSToInject})
		return

	case messageKeys.checkBlacklisted:
		response(isBlacklisted(request.href))
		return

	case messageKeys.getBlacklist:
		response(blacklisted.map((value) => {
			return value[1]
		}))
		return

	case messageKeys.getBlacklistString:
		response(blacklisted.map((value) => {
			return value[1]
		}).join("\n"))
		return

	case messageKeys.setBlacklist:
		// Also reload the cache when the blacklist is changed
		setBlacklist(request.blacklist, reloadCachedBlacklist)
		return

	case messageKeys.setBlacklistString:
		// Also reload the cache when the blacklist is changed
		setBlacklistString(request.blacklistString, reloadCachedBlacklist)
		return
	}
})<|endoftext|>


<commit_before>//promise constructor
function MyPromise (executor) {
  var value
  var state = 'pending'
  var resolveCallback, rejectCallback

  function resolve (val) {
    if (resolveCallback) {
      resolveCallback(val)
    }
    else {
      value = val
      state = 'resolved'
    }
  }

  function reject (val) {
    if (rejectCallback) {
      rejectCallback(val)
    }
    else {
      value = val
      state = 'rejected'
    }
  }

  this.then = function (res, rej) {
    if (state = 'pending') {
      resolveCallback = res
      rejectCallback = rej
    }
    else if (state === 'resolved') {
      res(value)
    }
    else {
      rej(value)
    }
  }

  executor(resolve, reject)
}

var promise = new MyPromise( function (resolve, reject) {
  var val = Math.random()
  console.log(val);

  if (val > 0.5) {
    setTimeout( function() {
      resolve("Resolved:" + val)
    }, Math.random() * 1000)
  }
  else {
    setTimeout( function() {
      reject("Rejected:" + val)
    }, Math.random() * 1000)
  }
})

promise.then( function (val) {
  setTimeout( function () {
    console.log(val)
  }, Math.random() * 2000)
}, function (val) {
  setTimeout( function () {
    console.log(val, "...Sorry!")
  }, Math.random() * 2000)
})
<commit_msg>rewrote many anonymous functions as ES6 arrow functions<commit_after>//promise constructor
function MyPromise (executor) {
  var value;
  var state = 'pending';
  var resolveCallback, rejectCallback;

  function resolve (val) {
    if (resolveCallback) {
      resolveCallback(val);
    }
    else {
      value = val;
      state = 'resolved';
    }
  }

  function reject (val) {
    if (rejectCallback) {
      rejectCallback(val);
    }
    else {
      value = val;
      state = 'rejected';
    }
  }

  this.then = function(res, rej) {
    if (state = 'pending') {
      resolveCallback = res;
      rejectCallback = rej;
    }
    else if (state === 'resolved') {
      res(value);
    }
    else {
      rej(value);
    }
  }

  executor(resolve, reject);
}

var promise = new MyPromise( function(resolve, reject) {
  var val = Math.random();
  console.log(val);

  if (val > 0.5) {
    setTimeout(
      () => resolve("Resolved:" + val),
      Math.random() * 1000);
  }
  else {
    setTimeout(
      () => reject("Rejected:" + val),
      Math.random() * 1000);
  }
})

promise.then(
  val => {
    setTimeout(
      () => console.log(val),
      Math.random() * 2000);
  },
  val => {
    setTimeout(
      () => console.log(val),
      Math.random() * 2000);
  }
);
<|endoftext|>


<commit_before>const pify = require('pify');
const fs = require('fs');
const path = require('path');
const Registry = require('winreg');

const {cliScriptPath} = require('../config/paths');

const lstat = pify(fs.lstat);
const readlink = pify(fs.readlink);
const unlink = pify(fs.unlink);
const symlink = pify(fs.symlink);

const target = '/usr/local/bin/hyper';
const source = cliScriptPath;

const checkInstall = () => {
  return lstat(target)
    .then(stat => stat.isSymbolicLink())
    .then(() => readlink(target))
    .then(link => link === source)
    .catch(err => {
      if (err.code === 'ENOENT') {
        return false;
      }
      throw err;
    });
};

const createSymlink = () => {
  return unlink(target)
    .catch(err => {
      if (err.code === 'ENOENT') {
        return;
      }
      throw err;
    })
    .then(() => symlink(source, target));
};

exports.addSymlink = () => {
  return checkInstall().then(isInstalled => {
    if (isInstalled) {
      return Promise.resolve();
    }
    return createSymlink();
  });
};

exports.addBinToUserPath = () => {
  // Can't use pify because of param order of Registry.values callback
  return new Promise((resolve, reject) => {
    const envKey = new Registry({hive: 'HKCU', key: '\\Environment'});
    envKey.values((err, items) => {
      if (err) {
        reject(err);
        return;
      }
      // C:\Users\<user>\AppData\Local\hyper\app-<version>\resources\bin
      const binPath = path.dirname(cliScriptPath);
      // C:\Users\<user>\AppData\Local\hyper
      const basePath = path.resolve(binPath, '../../..');

      const pathItem = items.find(item => item.name === 'Path');
      const pathParts = pathItem.value.split(';');
      const existingPath = pathParts.find(pathPart => pathPart === binPath);
      if (existingPath) {
        resolve();
        return;
      }

      // Because version is in path we need to remove old path if present and add current path
      const newPathValue = pathParts
        .filter(pathPart => !pathPart.startsWith(basePath))
        .concat([binPath])
        .join(';');

      envKey.set(pathItem.name, Registry.REG_SZ, newPathValue, error => {
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    });
  });
};
<commit_msg>Fix CLI installation on Windows (#2600)<commit_after>const pify = require('pify');
const fs = require('fs');
const path = require('path');
const Registry = require('winreg');

const {cliScriptPath} = require('../config/paths');

const lstat = pify(fs.lstat);
const readlink = pify(fs.readlink);
const unlink = pify(fs.unlink);
const symlink = pify(fs.symlink);

const target = '/usr/local/bin/hyper';
const source = cliScriptPath;

const checkInstall = () => {
  return lstat(target)
    .then(stat => stat.isSymbolicLink())
    .then(() => readlink(target))
    .then(link => link === source)
    .catch(err => {
      if (err.code === 'ENOENT') {
        return false;
      }
      throw err;
    });
};

const createSymlink = () => {
  return unlink(target)
    .catch(err => {
      if (err.code === 'ENOENT') {
        return;
      }
      throw err;
    })
    .then(() => symlink(source, target));
};

exports.addSymlink = () => {
  return checkInstall().then(isInstalled => {
    if (isInstalled) {
      return Promise.resolve();
    }
    return createSymlink();
  });
};

exports.addBinToUserPath = () => {
  // Can't use pify because of param order of Registry.values callback
  return new Promise((resolve, reject) => {
    const envKey = new Registry({hive: 'HKCU', key: '\\Environment'});
    envKey.values((err, items) => {
      if (err) {
        reject(err);
        return;
      }
      // C:\Users\<user>\AppData\Local\hyper\app-<version>\resources\bin
      const binPath = path.dirname(cliScriptPath);
      // C:\Users\<user>\AppData\Local\hyper
      const basePath = path.resolve(binPath, '../../..');

      const pathItem = items.find(item => item.name.toUpperCase() === 'PATH');

      let newPathValue = binPath;
      const pathItemName = pathItem ? pathItem.name : 'PATH';
      if (pathItem) {
        const pathParts = pathItem.value.split(';');
        const existingPath = pathParts.find(pathPart => pathPart === binPath);
        if (existingPath) {
          resolve();
          return;
        }

        // Because version is in path we need to remove old path if present and add current path
        newPathValue = pathParts
          .filter(pathPart => !pathPart.startsWith(basePath))
          .concat([binPath])
          .join(';');
      }

      envKey.set(pathItemName, Registry.REG_SZ, newPathValue, error => {
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    });
  });
};
<|endoftext|>


<commit_before>'use strict';

angular.module('forms').directive('onFinishRender', function ($rootScope, $timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {

            //Don't do anything if we don't have a ng-repeat on the current element
            if(!element.attr('ng-repeat') && !element.attr('data-ng-repeat')){
                return;
            }

            var broadcastMessage = attrs.onFinishRender || 'ngRepeat';

            if(scope.$first && !scope.$last) {
                scope.$evalAsync(function () {
                    $rootScope.$broadcast(broadcastMessage+' Started');
                });
            }else if(scope.$last) {
            	scope.$evalAsync(function () {
                    // console.log(broadcastMessage+'Finished');
            	    $rootScope.$broadcast(broadcastMessage+' Finished');
                });
            }
        }
    };
});
<commit_msg>Removed unused $timeout dependency from on-finish-render.client.directive.js<commit_after>'use strict';

angular.module('forms').directive('onFinishRender', function ($rootScope) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {

            //Don't do anything if we don't have a ng-repeat on the current element
            if(!element.attr('ng-repeat') && !element.attr('data-ng-repeat')){
                return;
            }

            var broadcastMessage = attrs.onFinishRender || 'ngRepeat';

            if(scope.$first && !scope.$last) {
                scope.$evalAsync(function () {
                    $rootScope.$broadcast(broadcastMessage+' Started');
                });
            } else if(scope.$last) {
            	scope.$evalAsync(function () {
            	    $rootScope.$broadcast(broadcastMessage+' Finished');
                });
            }
        }
    };
});
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import serialize from 'form-serialize';
import types from '../../utils/types';
import renderOption from '../../utils/renderOption';
import Page from '../../containers/Page';
import TitleBar from '../../components/TitleBar';
import Button from '../../components/Button';
import Input from '../../components/Input';
import Aside from '../../containers/Aside';
import { deleteEntry, updateEntry, entryDetails } from '../../actions/entryActions';

export default class Entry extends Component {
  static propTypes = {
    ...types.entries,
    params: PropTypes.shape({
      id: PropTypes.string.isRequired,
    }).isRequired,
  }

  static defaultProps = {
    title: '',
  }

  constructor(props) {
    super(props);
    this.renderFields = this.renderFields.bind(this);
    this.deleteEntry = this.deleteEntry.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  state = { status: null }

  componentDidMount() {
    const { dispatch, params } = this.props;
    dispatch(entryDetails(params.id));
  }

  onSubmit(e) {
    e.preventDefault();
    const { params, dispatch } = this.props;
    const data = serialize(this.page.form, { hash: true });
    dispatch(updateEntry(params.id, data));
  }

  deleteEntry() {
    const { params, dispatch } = this.props;
    dispatch(deleteEntry(params.id));
  }

  renderFields(entryFields, fieldId) {
    const { fields } = this.props.fields;
    const foundField = fields.find(f => f._id === fieldId);
    const entryField = entryFields.find(f => f.fieldId === fieldId);
    return renderOption(foundField, entryField ? entryField.value : null);
  }

  render() {
    const { sections, params, entries } = this.props;

    const {
      section,
      title,
      _id,
      full,
      status,
      fields,
      dateCreated,
    } = entries.entries.find(e => e._id === params.id);

    // TODO: Render loader
    if (full === undefined) return null;

    const sectionObj = sections.sections.find(s => s._id === section);

    const links = [
      { label: 'Entries', path: '/admin/entries' },
      { label: sectionObj.title, path: `/admin/entries/${sectionObj.slug}` },
      { label: title, path: `/admin/entries/${sectionObj.slug}/${_id}` },
    ];

    return (
      <Page name="entry" links={links} onSubmit={this.onSubmit} ref={(r) => { this.page = r; }}>
        <TitleBar title={title}>
          <Button small onClick={this.Submit} type="submit">Save Entry</Button>
          <Button small onClick={this.deleteEntry}>Delete Entry</Button>
        </TitleBar>

            <Input label="Title" defaultValue={title} name="title" full required />
            {sectionObj.fields.map(fieldId => this.renderFields(fields, fieldId))}

      </Page>
    );
  }
}
<commit_msg>:scroll: Only fetch entry details when necessary<commit_after>import React, { Component, PropTypes } from 'react';
import serialize from 'form-serialize';
import types from '../../utils/types';
import renderOption from '../../utils/renderOption';
import Page from '../../containers/Page';
import TitleBar from '../../components/TitleBar';
import Button from '../../components/Button';
import Input from '../../components/Input';
import Aside from '../../containers/Aside';
import { deleteEntry, updateEntry, entryDetails } from '../../actions/entryActions';

export default class Entry extends Component {
  static propTypes = {
    ...types.entries,
    params: PropTypes.shape({
      id: PropTypes.string.isRequired,
    }).isRequired,
  }

  static defaultProps = {
    title: '',
  }

  constructor(props) {
    super(props);
    this.renderFields = this.renderFields.bind(this);
    this.deleteEntry = this.deleteEntry.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  state = { status: null }

  componentDidMount() {
    const { dispatch, params, entries } = this.props;
    const { full } = entries.entries.find(e => e._id === params.id);
    if (!full || full === undefined) {
      dispatch(entryDetails(params.id));
    }
  }

  onSubmit(e) {
    e.preventDefault();
    const { params, dispatch } = this.props;
    const data = serialize(this.page.form, { hash: true });
    dispatch(updateEntry(params.id, data));
  }

  deleteEntry() {
    const { params, dispatch } = this.props;
    dispatch(deleteEntry(params.id));
  }

  renderFields(entryFields, fieldId) {
    const { fields } = this.props.fields;
    const foundField = fields.find(f => f._id === fieldId);
    const entryField = entryFields.find(f => f.fieldId === fieldId);
    return renderOption(foundField, entryField ? entryField.value : null);
  }

  render() {
    const { sections, params, entries } = this.props;

    const {
      section,
      title,
      _id,
      full,
      status,
      fields,
      dateCreated,
    } = entries.entries.find(e => e._id === params.id);

    // TODO: Render loader
    if (full === undefined) return null;

    const sectionObj = sections.sections.find(s => s._id === section);

    const links = [
      { label: 'Entries', path: '/admin/entries' },
      { label: sectionObj.title, path: `/admin/entries/${sectionObj.slug}` },
      { label: title, path: `/admin/entries/${sectionObj.slug}/${_id}` },
    ];

    return (
      <Page name="entry" links={links} onSubmit={this.onSubmit} ref={(r) => { this.page = r; }}>
        <TitleBar title={title}>
          <Button small onClick={this.Submit} type="submit">Save Entry</Button>
          <Button small onClick={this.deleteEntry}>Delete Entry</Button>
        </TitleBar>

            <Input label="Title" defaultValue={title} name="title" full required />
            {sectionObj.fields.map(fieldId => this.renderFields(fields, fieldId))}

      </Page>
    );
  }
}
<|endoftext|>


<commit_before>// 2017-02-07
// 2017-02-05
define([
	'Df_Payment/card'
], function(parent) {'use strict'; return parent.extend({
   /**
	* 2017-02-07
	* @returns {String}
 	*/
	publicKey: function() {return this.config('publicKey');},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @param {Object|Number} status
	 * @returns {Boolean}
	 */
	tokenCheckStatus: function(status) {return null;},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @param {Object} params
	 * @param {Function} callback
	 * @returns {Function}
	 */
	tokenCreate: function(params, callback) {return null;},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @param {Object|Number} status
	 * @param {Object} resp
	 * @returns {String}
	 */
	tokenErrorMessage: function(status, resp) {return null;},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @param {Object} resp
	 * @returns {String}
	 */
	tokenFromResponse: function(resp) {return null;},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @returns {Object}
	 */
	tokenParams: function() {return null;},

	/**
	 * 2017-02-16
	 * @final
	 * @override
	 * @see https://github.com/magento/magento2/blob/2.1.0/app/code/Magento/Checkout/view/frontend/web/js/view/payment/default.js#L127-L159
	 * @used-by https://github.com/magento/magento2/blob/2.1.0/lib/web/knockoutjs/knockout.js#L3863
	 * @param {this} _this
	*/
	placeOrder: function(_this) {
		if (this.validate()) {
			if (!this.isNewCardChosen()) {
				/**
				 * 2016-08-23
				 * Комментарий для Stripe:
				 * Идентификаторы карт начинаются с приставки «card_»
				 * (например: «card_18lGFRFzKb8aMux1Bmcjsa5L»),
				 * а идентификаторы токенов — с приставки «tok_»
				 * (например: «tok_18lWSWFzKb8aMux1viSqpL5X»),
				 * тем самым по приставке мы можем отличить карты от токенов,
				 * и поэтому для карт и токенов мы можем использовать одну и ту же переменную.
				 */
				this.token = this.currentCard();
				this.placeOrderInternal();
			}
			else {
				this.tokenCreate(this.tokenParams(), function(status, resp) {
					if (!_this.tokenCheckStatus(status)) {
						_this.showErrorMessage(_this.tokenErrorMessage(status, resp));
					}
					else {
						_this.token = _this.tokenFromResponse(resp);
						_this.placeOrderInternal();
					}
				});
			}
		}
	},
});});<commit_msg>no message<commit_after>// 2017-02-07
// 2017-02-05
define([
	'Df_Payment/card'
], function(parent) {'use strict'; return parent.extend({
   /**
	* 2017-02-07
	* @returns {String}
 	*/
	publicKey: function() {return this.config('publicKey');},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @param {Object|Number} status
	 * @returns {Boolean}
	 */
	tokenCheckStatus: function(status) {return null;},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @param {Object} params
	 * @param {Function} callback
	 * @returns {Function}
	 */
	tokenCreate: function(params, callback) {return null;},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @param {Object|Number} status
	 * @param {Object} resp
	 * @returns {String}
	 */
	tokenErrorMessage: function(status, resp) {return null;},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @param {Object} resp
	 * @returns {String}
	 */
	tokenFromResponse: function(resp) {return null;},

    /**
	 * 2017-02-16
	 * @abstract
	 * @used-by placeOrder()
	 * @returns {Object}
	 */
	tokenParams: function() {return null;},

	/**
	 * 2017-02-16
	 * @override
	 * @see https://github.com/magento/magento2/blob/2.1.0/app/code/Magento/Checkout/view/frontend/web/js/view/payment/default.js#L127-L159
	 * @used-by https://github.com/magento/magento2/blob/2.1.0/lib/web/knockoutjs/knockout.js#L3863
	 * @param {this} _this
	*/
	placeOrder: function(_this) {
		if (this.validate()) {
			if (!this.isNewCardChosen()) {
				/**
				 * 2016-08-23
				 * Комментарий для Stripe:
				 * Идентификаторы карт начинаются с приставки «card_»
				 * (например: «card_18lGFRFzKb8aMux1Bmcjsa5L»),
				 * а идентификаторы токенов — с приставки «tok_»
				 * (например: «tok_18lWSWFzKb8aMux1viSqpL5X»),
				 * тем самым по приставке мы можем отличить карты от токенов,
				 * и поэтому для карт и токенов мы можем использовать одну и ту же переменную.
				 */
				this.token = this.currentCard();
				this.placeOrderInternal();
			}
			else {
				this.tokenCreate(this.tokenParams(), function(status, resp) {
					if (!_this.tokenCheckStatus(status)) {
						_this.showErrorMessage(_this.tokenErrorMessage(status, resp));
					}
					else {
						_this.token = _this.tokenFromResponse(resp);
						_this.placeOrderInternal();
					}
				});
			}
		}
	},
});});<|endoftext|>


<commit_before>const fs = require('fs')
const execa = require('execa')
const path = require('path')
const semverInc = require('semver/functions/inc')
const semverSatisfies = require('semver/functions/satisfies')
const semverCoerce = require('semver/functions/coerce')
const merge = require('lodash/merge')
const { themes } = require('./consts')
const { getVsfPackageJSON } = require('./helpers')

const createThemeTasks = (installationDir = 'vue-storefront') => ({
  installDeps: {
    title: 'Installing dependencies',
    task: () => execa.shell('cd ' + installationDir + ' && yarn')
  },
  cloneTheme: {
    title: 'Copying Vue Storefront theme',
    task: answers => execa.shell([
      `git clone --quiet --single-branch --branch ${answers.themeBranch} https://github.com/DivanteLtd/vsf-${answers.themeName}.git ${answers.vsf_dir || installationDir}/src/themes/${answers.themeName}`,
      `cd ${installationDir}/src/themes/${answers.themeName}`,
      `git remote rm origin`
    ].join(' && ')),
    skip: answers => {
      if (fs.existsSync(`${installationDir}/src/themes/${answers.themeName}`)) {
        return `Chosen theme already exists in Vue Storefront installation directory ${installationDir}/src/themes/`
      }
    }
  },
  configureTheme: {
    title: 'Configuring Vue Storefront theme',
    task: answers => {
      const _installationDir = answers.vsf_dir || installationDir
      const configurationFiles = ['local.config.js', 'local.json']
      const [themeLocalConfigJsPath, themeLocalJsonPath] = configurationFiles.map(
        file => `${_installationDir}/src/themes/${answers.themeName}/${file}`
      )
      const vsfLocalJsonPath = path.join(_installationDir, '/config/local.json')
      const vsfPackageJsonPath = path.join(_installationDir, '/package.json')

      try {
        const isVsfVersionAsBranch = ['master', 'develop'].includes(answers.specificVersion || getVsfPackageJSON(_installationDir).version)
        const vsfVersionFromPackageJson = JSON.parse(fs.readFileSync(vsfPackageJsonPath)).version
        const vsfVersion = isVsfVersionAsBranch
          ? semverInc(vsfVersionFromPackageJson, 'minor')
          : vsfVersionFromPackageJson

        const vsfLocalJson = fs.existsSync(vsfLocalJsonPath)
          ? JSON.parse(fs.readFileSync(vsfLocalJsonPath))
          : {}

        const themeLocalJson = fs.existsSync(themeLocalConfigJsPath)
          ? require(fs.realpathSync(themeLocalConfigJsPath))(vsfVersion)
          : fs.existsSync(themeLocalJsonPath)
            ? JSON.parse(fs.readFileSync(themeLocalJsonPath))
            : null

        if (themeLocalJson) {
          fs.writeFileSync(vsfLocalJsonPath, JSON.stringify(merge(vsfLocalJson, themeLocalJson), null, 2))
        }
      } catch (e) {
        console.error(`Problem with parsing or merging configurations (${configurationFiles})\n`, e)
      }
    },
    skip: answers => {
      const _installationDir = answers.vsf_dir || installationDir
      const configurationFiles = ['local.config.js', 'local.json', 'local.config']
      const themePath = `${_installationDir}/src/themes/${answers.themeName}`

      if (configurationFiles.every(file => !fs.existsSync(`${themePath}/${file}`))) {
        return `
          Missing configuration file in theme folder - nothing to configure.
          Theme path: ${themePath}.
          Configuration files: ${configurationFiles}.
        `
      }
    }
  }
})

const createThemePrompt = (installationDir = 'vue-storefront') => [
  {
    type: 'list',
    name: 'themeName',
    message: 'Select theme for Vue Storefront',
    choices: answers => {
      const _installationDir = answers.vsf_dir || installationDir
      const isVsfVersionAsBranch = ['master', 'develop'].includes(answers.specificVersion || getVsfPackageJSON(_installationDir).version)
      const selectedVsfVersion = semverCoerce(answers.specificVersion || getVsfPackageJSON(_installationDir).version)

      return Object.entries(themes)
        .filter(([, themeConfig]) => isVsfVersionAsBranch || semverSatisfies(selectedVsfVersion, themeConfig.minVsfVersion, { includePrerelease: true }))
        .map(([themeName, themeConfig]) => ({
          name: themeConfig.label,
          value: themeName
        }))
    }
  },
  {
    type: 'list',
    name: 'themeBranch',
    message: 'Select theme version',
    choices: answers => Object.entries(themes[answers.themeName].branches)
      .map(([branchName, branchLabel]) => ({
        name: branchLabel,
        value: branchName
      }))
  }
]

module.exports = {
  createThemeTasks,
  createThemePrompt
}
<commit_msg>use _installationDir<commit_after>const fs = require('fs')
const execa = require('execa')
const path = require('path')
const semverInc = require('semver/functions/inc')
const semverSatisfies = require('semver/functions/satisfies')
const semverCoerce = require('semver/functions/coerce')
const merge = require('lodash/merge')
const { themes } = require('./consts')
const { getVsfPackageJSON } = require('./helpers')

const createThemeTasks = (installationDir = 'vue-storefront') => ({
  installDeps: {
    title: 'Installing dependencies',
    task: () => execa.shell('cd ' + installationDir + ' && yarn')
  },
  cloneTheme: {
    title: 'Copying Vue Storefront theme',
    task: answers => {
      const _installationDir = answers.vsf_dir || installationDir
      return execa.shell([
        `git clone --quiet --single-branch --branch ${answers.themeBranch} https://github.com/DivanteLtd/vsf-${answers.themeName}.git ${_installationDir}/src/themes/${answers.themeName}`,
        `cd ${_installationDir}/src/themes/${answers.themeName}`,
        `git remote rm origin`
      ].join(' && '))
    },
    skip: answers => {
      const _installationDir = answers.vsf_dir || installationDir
      if (fs.existsSync(`${_installationDir}/src/themes/${answers.themeName}`)) {
        return `Chosen theme already exists in Vue Storefront installation directory ${_installationDir}/src/themes/`
      }
    }
  },
  configureTheme: {
    title: 'Configuring Vue Storefront theme',
    task: answers => {
      const _installationDir = answers.vsf_dir || installationDir
      const configurationFiles = ['local.config.js', 'local.json']
      const [themeLocalConfigJsPath, themeLocalJsonPath] = configurationFiles.map(
        file => `${_installationDir}/src/themes/${answers.themeName}/${file}`
      )
      const vsfLocalJsonPath = path.join(_installationDir, '/config/local.json')
      const vsfPackageJsonPath = path.join(_installationDir, '/package.json')

      try {
        const isVsfVersionAsBranch = ['master', 'develop'].includes(answers.specificVersion || getVsfPackageJSON(_installationDir).version)
        const vsfVersionFromPackageJson = JSON.parse(fs.readFileSync(vsfPackageJsonPath)).version
        const vsfVersion = isVsfVersionAsBranch
          ? semverInc(vsfVersionFromPackageJson, 'minor')
          : vsfVersionFromPackageJson

        const vsfLocalJson = fs.existsSync(vsfLocalJsonPath)
          ? JSON.parse(fs.readFileSync(vsfLocalJsonPath))
          : {}

        const themeLocalJson = fs.existsSync(themeLocalConfigJsPath)
          ? require(fs.realpathSync(themeLocalConfigJsPath))(vsfVersion)
          : fs.existsSync(themeLocalJsonPath)
            ? JSON.parse(fs.readFileSync(themeLocalJsonPath))
            : null

        if (themeLocalJson) {
          fs.writeFileSync(vsfLocalJsonPath, JSON.stringify(merge(vsfLocalJson, themeLocalJson), null, 2))
        }
      } catch (e) {
        console.error(`Problem with parsing or merging configurations (${configurationFiles})\n`, e)
      }
    },
    skip: answers => {
      const _installationDir = answers.vsf_dir || installationDir
      const configurationFiles = ['local.config.js', 'local.json', 'local.config']
      const themePath = `${_installationDir}/src/themes/${answers.themeName}`

      if (configurationFiles.every(file => !fs.existsSync(`${themePath}/${file}`))) {
        return `
          Missing configuration file in theme folder - nothing to configure.
          Theme path: ${themePath}.
          Configuration files: ${configurationFiles}.
        `
      }
    }
  }
})

const createThemePrompt = (installationDir = 'vue-storefront') => [
  {
    type: 'list',
    name: 'themeName',
    message: 'Select theme for Vue Storefront',
    choices: answers => {
      const _installationDir = answers.vsf_dir || installationDir
      const isVsfVersionAsBranch = ['master', 'develop'].includes(answers.specificVersion || getVsfPackageJSON(_installationDir).version)
      const selectedVsfVersion = semverCoerce(answers.specificVersion || getVsfPackageJSON(_installationDir).version)

      return Object.entries(themes)
        .filter(([, themeConfig]) => isVsfVersionAsBranch || semverSatisfies(selectedVsfVersion, themeConfig.minVsfVersion, { includePrerelease: true }))
        .map(([themeName, themeConfig]) => ({
          name: themeConfig.label,
          value: themeName
        }))
    }
  },
  {
    type: 'list',
    name: 'themeBranch',
    message: 'Select theme version',
    choices: answers => Object.entries(themes[answers.themeName].branches)
      .map(([branchName, branchLabel]) => ({
        name: branchLabel,
        value: branchName
      }))
  }
]

module.exports = {
  createThemeTasks,
  createThemePrompt
}
<|endoftext|>


<commit_before>(function() {
    'use strict';

    let _randomShot = () => {
            return Math.floor(Math.random() * (10 - 1) + 1);
        },
        _setBoxParent = () => {
            let num = _randomShot();
        },
        _setBox = () => {
            let num = _randomShot();
        },
        _handleShots = (arg) => {
            if (arg === 'empty') {
                _setBoxParent();
                _setBox();
            } else {
                console.log(arguments);
            }
        };

    postMessage('module loaded');
    onmessage = (e) => {
        switch (e.data) {
            case 'response':
                _handleShots('empty');
                break;
            default:
                _handleShots(e.data);
                break;
        }
    };
}());
<commit_msg>Working on userEnemyShots worker<commit_after>(function() {
    'use strict';

    let _randomShot = () => {
            return Math.floor(Math.random() * (10 - 1) + 1);
        },
        _setBoxParent = (parent) => {
            if (!parent) {
                let num = _randomShot(),
                    prefix = 'tr';
                return prefix + num;
            } else {
                // TODO: pending...
            }
        },
        _setBox = (box) => {
            if (!parent) {
                let num = _randomShot(),
                    prefix = 'td';
                return prefix + num;
            } else {
                // TODO: pending...
            }
        },
        _handleShots = (arg) => {
            let parent = null,
                box = null;
            if (arg === 'empty') {
                parent = _setBoxParent();
                box = _setBox();
            } else {
                parent = _setBoxParent(arg.parent);
                box = _setBox(arg.box);
            }

            return {
                parent: parent,
                box: box
            }
        };

    postMessage('module loaded');
    onmessage = (e) => {
        switch (e.data) {
            case 'response':
                _handleShots('empty');
                break;
            default:
                let prom = new Promise((resolve) => {
                    resolve(_handleShots(JSON.parse(e.data)));
                });
                prom.then((result) => {
                    postMessage(JSON.stringify(result));
                });
                break;
        }
    };
}());
<|endoftext|>


<commit_before>/**
 * modules/analytics data store: properties.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { actions as profileActions } from './profiles';
import { isValidAccountID, isValidPropertyID } from '../util';
import { PROPERTY_CREATE, PROFILE_CREATE } from './constants';

// Actions
const FETCH_CREATE_PROPERTY = 'FETCH_CREATE_PROPERTY';
const FETCH_PROPERTIES_PROFILES = 'FETCH_PROPERTIES_PROFILES';
const RECEIVE_CREATE_PROPERTY = 'RECEIVE_CREATE_PROPERTY';
const RECEIVE_CREATE_PROPERTY_FAILED = 'RECEIVE_CREATE_PROPERTY_FAILED';
const RECEIVE_MATCHED_PROPERTY = 'RECEIVE_MATCHED_PROPERTY';
const RECEIVE_PROPERTIES = 'RECEIVE_PROPERTIES';
const RECEIVE_PROPERTIES_PROFILES_COMPLETED = 'RECEIVE_PROPERTIES_PROFILES_COMPLETED';
const RECEIVE_PROPERTIES_PROFILES_FAILED = 'RECEIVE_PROPERTIES_PROFILES_FAILED';

export const INITIAL_STATE = {
	isFetchingCreateProperty: {},
	isFetchingPropertiesProfiles: {},
	properties: {},
	matchedProperty: undefined,
};

export const actions = {
	/**
	 * Creates a new Analytics property.
	 *
	 * Creates a new Analytics property for an existing Google Analytics account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} accountID Google Analytics account ID.
	 * @return {Object} Redux-style action.
	 */
	*createProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		try {
			const property = yield actions.fetchCreateProperty( accountID );

			const registry = yield Data.commonActions.getRegistry();
			registry.dispatch( STORE_NAME ).setPropertyID( property.id );

			return actions.receiveCreateProperty( { accountID, property } );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreatePropertyFailed( { accountID, error } );
		}
	},

	fetchCreateProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: FETCH_CREATE_PROPERTY,
		};
	},

	fetchPropertiesProfiles( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: FETCH_PROPERTIES_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.property  Google Analytics property object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProperty( { accountID, property } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( property, 'property is required.' );

		return {
			payload: { accountID, property },
			type: RECEIVE_CREATE_PROPERTY,
		};
	},

	/**
	 * Logs an error with property creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.error     Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreatePropertyFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_CREATE_PROPERTY_FAILED,
		};
	},

	/**
	 * Adds a matchedProperty to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} matchedProperty Property object.
	 * @return {Object} Redux-style action.
	 */
	receiveMatchedProperty( matchedProperty ) {
		invariant( matchedProperty, 'matchedProperty is required.' );

		return {
			payload: { matchedProperty },
			type: RECEIVE_MATCHED_PROPERTY,
		};
	},

	/**
	 * Applies the given property over current selections.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args Arguments.
	 * @param {?string} args.propertyID Property ID.
	 * @param {?string} [args.accountID] Account ID.
	 * @param {?string} [args.internalWebPropertyID] Internal web property ID.
	 */
	*applyProperty( { propertyID, accountID = '', internalWebPropertyID = '' } = {} ) {
		invariant( isValidPropertyID( propertyID ), 'A valid propertyID is required.' );

		const registry = yield Data.commonActions.getRegistry();

		registry.dispatch( STORE_NAME ).setPropertyID( propertyID );

		if ( isValidAccountID( accountID ) ) {
			registry.dispatch( STORE_NAME ).setAccountID( accountID );
		}

		if ( ! internalWebPropertyID ) {
			const properties = registry.select( STORE_NAME ).getProperties( accountID ) || [];
			const property = properties.find( ( p ) => p.id === propertyID ) || {};
			internalWebPropertyID = property.internalWebPropertyId;
		}

		registry.dispatch( STORE_NAME ).setInternalWebPropertyID( internalWebPropertyID || '' );
		registry.dispatch( STORE_NAME ).setProfileForProperty( { accountID, propertyID } );
	},

	/**
	 * Adds properties to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Array} properties Properties to add.
	 * @return {Object} Redux-style action.
	 */
	receiveProperties( properties ) {
		invariant( Array.isArray( properties ), 'properties must be an array.' );

		return {
			payload: { properties },
			type: RECEIVE_PROPERTIES,
		};
	},

	receivePropertiesProfilesCompleted( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: RECEIVE_PROPERTIES_PROFILES_COMPLETED,
		};
	},

	receivePropertiesProfilesFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_PROPERTIES_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_CREATE_PROPERTY ]: ( { payload: { accountID } } ) => {
		return API.set( 'modules', 'analytics', 'create-property', { accountID } );
	},
	[ FETCH_PROPERTIES_PROFILES ]: ( { payload: { accountID } } ) => {
		return API.get( 'modules', 'analytics', 'properties-profiles', { accountID }, {
			useCache: false,
		} );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROPERTY: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: true,
				},
			};
		}

		case FETCH_PROPERTIES_PROFILES: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY: {
			const { accountID, property } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
				properties: {
					...state.properties || {},
					[ accountID ]: [
						...( state.properties || {} )[ accountID ] || [],
						property,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_MATCHED_PROPERTY: {
			const { matchedProperty } = payload;

			return {
				...state,
				matchedProperty,
			};
		}

		case RECEIVE_PROPERTIES: {
			const { properties } = payload;

			return {
				...state,
				properties: {
					...state.properties,
					...groupBy( properties, 'accountId' ), // Capitalization rule exception: `accountId` is a property of an API returned value.
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_COMPLETED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProperties( accountID ) {
		if ( ! isValidAccountID( accountID ) ) {
			return undefined;
		}
		try {
			const registry = yield Data.commonActions.getRegistry();

			const existingProperties = registry.select( STORE_NAME ).getProperties( accountID );

			// If there are already properties loaded in state for this request; consider it fulfilled
			// and don't make an API request.
			if ( existingProperties ) {
				return;
			}

			const { properties, profiles, matchedProperty } = yield actions.fetchPropertiesProfiles( accountID );

			yield actions.receiveProperties( properties );
			yield profileActions.receiveProfiles( profiles );

			if ( matchedProperty ) {
				yield actions.receiveMatchedProperty( matchedProperty );
			}

			const propertyID = registry.select( STORE_NAME ).getPropertyID();
			if ( ! propertyID ) {
				const property = matchedProperty || properties[ 0 ] || { id: PROPERTY_CREATE };
				registry.dispatch( STORE_NAME ).setPropertyID( property.id );
				registry.dispatch( STORE_NAME ).setInternalWebPropertyID( property.internalWebPropertyId || '' );
				const profile = profiles[ 0 ] || { id: PROFILE_CREATE };
				registry.dispatch( STORE_NAME ).setProfileID( profile.id );
			}

			return actions.receivePropertiesProfilesCompleted( accountID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receivePropertiesProfilesFailed( { accountID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Gets the matched property, if any.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {?Object} Matched property if set, otherwise `undefined`.
	 */
	getMatchedProperty( state ) {
		return state.matchedProperty;
	},
	/**
	 * Get all Google Analytics properties this account can access.
	 *
	 * Returns an array of all analytics properties.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to fetch properties for.
	 * @return {?Array.<Object>} An array of Analytics properties; `undefined` if not loaded.
	 */
	getProperties( state, accountID ) {
		const { properties } = state;

		return properties[ accountID ];
	},

	/**
	 * Checks if a property is being created for an account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if creating a property, `false` if not.
	 */
	isDoingCreateProperty( state, accountID ) {
		const { isFetchingCreateProperty } = state;

		return !! isFetchingCreateProperty[ accountID ];
	},

	/**
	 * Checks if properties are being fetched for the given account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if fetching a properties, `false` if not.
	 */
	isDoingGetProperties( state, accountID ) {
		const { isFetchingPropertiesProfiles } = state;

		return !! isFetchingPropertiesProfiles[ accountID ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Parse account ID in applyProperty.<commit_after>/**
 * modules/analytics data store: properties.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { actions as profileActions } from './profiles';
import { isValidAccountID, isValidPropertyID, parsePropertyID } from '../util';
import { PROPERTY_CREATE, PROFILE_CREATE } from './constants';

// Actions
const FETCH_CREATE_PROPERTY = 'FETCH_CREATE_PROPERTY';
const FETCH_PROPERTIES_PROFILES = 'FETCH_PROPERTIES_PROFILES';
const RECEIVE_CREATE_PROPERTY = 'RECEIVE_CREATE_PROPERTY';
const RECEIVE_CREATE_PROPERTY_FAILED = 'RECEIVE_CREATE_PROPERTY_FAILED';
const RECEIVE_MATCHED_PROPERTY = 'RECEIVE_MATCHED_PROPERTY';
const RECEIVE_PROPERTIES = 'RECEIVE_PROPERTIES';
const RECEIVE_PROPERTIES_PROFILES_COMPLETED = 'RECEIVE_PROPERTIES_PROFILES_COMPLETED';
const RECEIVE_PROPERTIES_PROFILES_FAILED = 'RECEIVE_PROPERTIES_PROFILES_FAILED';

export const INITIAL_STATE = {
	isFetchingCreateProperty: {},
	isFetchingPropertiesProfiles: {},
	properties: {},
	matchedProperty: undefined,
};

export const actions = {
	/**
	 * Creates a new Analytics property.
	 *
	 * Creates a new Analytics property for an existing Google Analytics account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} accountID Google Analytics account ID.
	 * @return {Object} Redux-style action.
	 */
	*createProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		try {
			const property = yield actions.fetchCreateProperty( accountID );

			const registry = yield Data.commonActions.getRegistry();
			registry.dispatch( STORE_NAME ).setPropertyID( property.id );

			return actions.receiveCreateProperty( { accountID, property } );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreatePropertyFailed( { accountID, error } );
		}
	},

	fetchCreateProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: FETCH_CREATE_PROPERTY,
		};
	},

	fetchPropertiesProfiles( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: FETCH_PROPERTIES_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.property  Google Analytics property object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProperty( { accountID, property } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( property, 'property is required.' );

		return {
			payload: { accountID, property },
			type: RECEIVE_CREATE_PROPERTY,
		};
	},

	/**
	 * Logs an error with property creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.error     Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreatePropertyFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_CREATE_PROPERTY_FAILED,
		};
	},

	/**
	 * Adds a matchedProperty to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} matchedProperty Property object.
	 * @return {Object} Redux-style action.
	 */
	receiveMatchedProperty( matchedProperty ) {
		invariant( matchedProperty, 'matchedProperty is required.' );

		return {
			payload: { matchedProperty },
			type: RECEIVE_MATCHED_PROPERTY,
		};
	},

	/**
	 * Applies the given property over current selections.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args Arguments.
	 * @param {?string} args.propertyID Property ID.
	 * @param {?string} [args.internalWebPropertyID] Internal web property ID.
	 */
	*applyProperty( { propertyID, internalWebPropertyID = '' } = {} ) {
		invariant( isValidPropertyID( propertyID ), 'A valid propertyID is required.' );

		const { accountID } = parsePropertyID( propertyID );
		const registry = yield Data.commonActions.getRegistry();

		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).setPropertyID( propertyID );

		if ( ! internalWebPropertyID ) {
			const properties = registry.select( STORE_NAME ).getProperties( accountID ) || [];
			const property = properties.find( ( p ) => p.id === propertyID ) || {};
			internalWebPropertyID = property.internalWebPropertyId;
		}

		registry.dispatch( STORE_NAME ).setInternalWebPropertyID( internalWebPropertyID || '' );
		registry.dispatch( STORE_NAME ).setProfileForProperty( { accountID, propertyID } );
	},

	/**
	 * Adds properties to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Array} properties Properties to add.
	 * @return {Object} Redux-style action.
	 */
	receiveProperties( properties ) {
		invariant( Array.isArray( properties ), 'properties must be an array.' );

		return {
			payload: { properties },
			type: RECEIVE_PROPERTIES,
		};
	},

	receivePropertiesProfilesCompleted( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: RECEIVE_PROPERTIES_PROFILES_COMPLETED,
		};
	},

	receivePropertiesProfilesFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_PROPERTIES_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_CREATE_PROPERTY ]: ( { payload: { accountID } } ) => {
		return API.set( 'modules', 'analytics', 'create-property', { accountID } );
	},
	[ FETCH_PROPERTIES_PROFILES ]: ( { payload: { accountID } } ) => {
		return API.get( 'modules', 'analytics', 'properties-profiles', { accountID }, {
			useCache: false,
		} );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROPERTY: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: true,
				},
			};
		}

		case FETCH_PROPERTIES_PROFILES: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY: {
			const { accountID, property } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
				properties: {
					...state.properties || {},
					[ accountID ]: [
						...( state.properties || {} )[ accountID ] || [],
						property,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_MATCHED_PROPERTY: {
			const { matchedProperty } = payload;

			return {
				...state,
				matchedProperty,
			};
		}

		case RECEIVE_PROPERTIES: {
			const { properties } = payload;

			return {
				...state,
				properties: {
					...state.properties,
					...groupBy( properties, 'accountId' ), // Capitalization rule exception: `accountId` is a property of an API returned value.
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_COMPLETED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProperties( accountID ) {
		if ( ! isValidAccountID( accountID ) ) {
			return undefined;
		}
		try {
			const registry = yield Data.commonActions.getRegistry();

			const existingProperties = registry.select( STORE_NAME ).getProperties( accountID );

			// If there are already properties loaded in state for this request; consider it fulfilled
			// and don't make an API request.
			if ( existingProperties ) {
				return;
			}

			const { properties, profiles, matchedProperty } = yield actions.fetchPropertiesProfiles( accountID );

			yield actions.receiveProperties( properties );
			yield profileActions.receiveProfiles( profiles );

			if ( matchedProperty ) {
				yield actions.receiveMatchedProperty( matchedProperty );
			}

			const propertyID = registry.select( STORE_NAME ).getPropertyID();
			if ( ! propertyID ) {
				const property = matchedProperty || properties[ 0 ] || { id: PROPERTY_CREATE };
				registry.dispatch( STORE_NAME ).setPropertyID( property.id );
				registry.dispatch( STORE_NAME ).setInternalWebPropertyID( property.internalWebPropertyId || '' );
				const profile = profiles[ 0 ] || { id: PROFILE_CREATE };
				registry.dispatch( STORE_NAME ).setProfileID( profile.id );
			}

			return actions.receivePropertiesProfilesCompleted( accountID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receivePropertiesProfilesFailed( { accountID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Gets the matched property, if any.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {?Object} Matched property if set, otherwise `undefined`.
	 */
	getMatchedProperty( state ) {
		return state.matchedProperty;
	},
	/**
	 * Get all Google Analytics properties this account can access.
	 *
	 * Returns an array of all analytics properties.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to fetch properties for.
	 * @return {?Array.<Object>} An array of Analytics properties; `undefined` if not loaded.
	 */
	getProperties( state, accountID ) {
		const { properties } = state;

		return properties[ accountID ];
	},

	/**
	 * Checks if a property is being created for an account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if creating a property, `false` if not.
	 */
	isDoingCreateProperty( state, accountID ) {
		const { isFetchingCreateProperty } = state;

		return !! isFetchingCreateProperty[ accountID ];
	},

	/**
	 * Checks if properties are being fetched for the given account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if fetching a properties, `false` if not.
	 */
	isDoingGetProperties( state, accountID ) {
		const { isFetchingPropertiesProfiles } = state;

		return !! isFetchingPropertiesProfiles[ accountID ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<|endoftext|>