<commit_before>frappe.pages['workspace'].on_page_load = function(wrapper) {
	frappe.ui.make_app_page({
		parent: wrapper,
		name: 'workspace',
		title: __("Workspace"),
	});

	frappe.workspace = new Workspace(wrapper);
	$(wrapper).bind('show', function() {
		frappe.workspace.show();
	});
}

class Workspace {
	constructor(wrapper) {
		this.wrapper = $(wrapper);
		this.page = wrapper.page;
		this.prepare_container();
		this.pages = {};
		this.sidebar_items = {};
		this.sidebar_categories = [
			"Modules",
			"Domains",
			"Places",
			"Administration"
		];

		this.fetch_desktop_settings().then(() => {
			this.make_sidebar();
		})
	}

	show() {
		let page = this.get_page_to_show();
		this.page.set_title(`${__(page)} Workspace`);
		frappe.set_route('workspace', page);
		this.show_page(page);
	}

	prepare_container() {
		let list_sidebar = $(`

`).appendTo(this.wrapper.find(".layout-side-section"));
		this.sidebar = list_sidebar.find(".desk-sidebar");

		this.body = this.wrapper.find(".layout-main-section");
	}

	get_page_to_show() {
		let default_page;

		if (localStorage.current_workspace) {
			default_page = localStorage.current_workspace;
		} else if (this.desktop_settings) {
			default_page = this.desktop_settings["Modules"][0].name;
		} else if (frappe.boot.allowed_workspaces) {
			default_page = frappe.boot.allowed_workspaces[0].name;
		} else {
			default_page = "Website";
		}

		let page = frappe.get_route()[1] || default_page;

		return page;
	}

	fetch_desktop_settings() {
		return frappe
			.call("frappe.desk.desktop.get_desk_sidebar_items")
			.then(response => {
				if (response.message) {
					this.desktop_settings = response.message;
				} else {
					frappe.throw({
						title: __("Couldn't Load Desk"),
						message:
							__("Something went wrong while loading Desk. Please relaod the page. If the problem persists, contact the Administrator"),
						indicator: "red",
						primary_action: {
							label: __("Reload"),
							action: () => location.reload()
						}
					});
				}
			});
	}

	make_sidebar() {
		this.sidebar_categories.forEach(category => {
			if (this.desktop_settings[category]) {
				this.build_sidebar_section(category, this.desktop_settings[category])
			}
		});
	}

	build_sidebar_section(title, items) {
		let sidebar_section = $(`

`)

		// DO NOT REMOVE: Comment to load translation
		// __("Modules") __("Domains") __("Places") __("Administration")
		$(`<div class="list-sidebar-label">${__(title)}</div>`)
			.appendTo(sidebar_section);

		const get_sidebar_item = function(item) {
			return $(`

 ${frappe.utils.icon(item.icon || "folder-normal", "md")}

 ${item.label || item.name}

			`);
		};

		const make_sidebar_category_item = item => {
			if (item.name == this.get_page_to_show()) {
				item.selected = true;
				this.current_page = item.name;
			}

			let $item = get_sidebar_item(item);

			$item.clone().appendTo(sidebar_section);
			this.sidebar_items[item.name] = $item;
		};

		items.forEach(item => make_sidebar_category_item(item));

		sidebar_section.appendTo(this.sidebar)
	}

	show_page(page) {
		if (this.current_page && this.pages[this.current_page]) {
			this.pages[this.current_page].hide();
		}

		if (this.sidebar_items && this.sidebar_items[this.current_page]) {
			this.sidebar_items[this.current_page].removeClass("selected");
			this.sidebar_items[page].addClass("selected");
		}
		this.current_page = page;
		localStorage.current_desk_page = page;

		this.pages[page] ? this.pages[page].show() : this.make_page(page);
	}

	make_page(page) {
		const $page = new DesktopPage({
			container: this.body,
			page_name: page
		});

		this.pages[page] = $page;
		return $page;
	}
}

class DesktopPage {
	constructor({ container, page_name }) {
		frappe.desk_page = this;
		this.container = container;
		this.page_name = page_name;
		this.sections = {};
		this.allow_customization = false;
		this.reload();
	}

	show() {
		frappe.desk_page = this;
		this.page.show();
		if (this.sections.shortcuts) {
			this.sections.shortcuts.widgets_list.forEach(wid => {
				wid.set_actions();
			});
		}
	}

	hide() {
		this.page.hide();
	}

	reload() {
		this.in_customize_mode = false;
		this.page && this.page.remove();
		this.make();
		this.setup_events();
	}

	make() {
		this.page = $(`<div class="desk-page" data-page-name=${this.page_name}></div>`);
		this.page.appendTo(this.container);

		this.get_data().then(res => {
			this.data = res.message;
			if (!this.data) {
				delete localStorage.current_desk_page;
				frappe.set_route("workspace");
				return;
			}

			this.refresh();
		});
	}

	refresh() {
		this.page.empty();
		this.allow_customization = this.data.allow_customization || false;

		if (frappe.is_mobile()) {
			this.allow_customization = false;
		}

		this.data.onboarding && this.data.onboarding.items.length && this.make_onboarding();
		this.make_charts().then(() => {
			this.make_shortcuts();
			this.make_cards();
		});
	}

	get_data() {
		return frappe.call("frappe.desk.desktop.get_desktop_page", {
			page: this.page_name
		});
	}

	setup_events() {
		$(document.body).on('toggleFullWidth', () => this.refresh());
	}

	customize() {
		if (this.in_customize_mode) {
			return;
		}

		// We need to remove this as the  chart group will be visible during customization
		$('.widget.onboarding-widget-box').hide();

		this.customize_link.hide();
		this.save_or_discard_link.show();

		Object.keys(this.sections).forEach(section => {
			this.sections[section].customize();
		});
		this.in_customize_mode = true;

	}

	save_customization() {
		const config = {};

		if (this.sections.charts) config.charts = this.sections.charts.get_widget_config();
		if (this.sections.shortcuts) config.shortcuts = this.sections.shortcuts.get_widget_config();
		if (this.sections.cards) config.cards = this.sections.cards.get_widget_config();

		frappe.call('frappe.desk.desktop.save_customization', {
			page: this.page_name,
			config: config
		}).then(res => {
			if (res.message) {
				frappe.msgprint({ message: __("Customizations Saved Successfully"), title: __("Success")});
				this.reload();
			} else {
				frappe.throw({message: __("Something went wrong while saving customizations"), title: __("Failed")});
				this.reload();
			}
		});
	}

	make_onboarding() {
		this.onboarding_widget = frappe.widget.make_widget({
			label: this.data.onboarding.label || __(`Let's Get Started`),
			subtitle: this.data.onboarding.subtitle,
			steps: this.data.onboarding.items,
			success: this.data.onboarding.success,
			docs_url: this.data.onboarding.docs_url,
			user_can_dismiss: this.data.onboarding.user_can_dismiss,
			widget_type: 'onboarding',
			container: this.page,
			options: {
				allow_sorting: false,
				allow_create: false,
				allow_delete: false,
				allow_hiding: false,
				allow_edit: false,
				max_widget_count: 2,
			}
		});
	}

	make_charts() {
		return frappe.dashboard_utils.get_dashboard_settings().then(settings => {
			let chart_config = settings.chart_config ? JSON.parse(settings.chart_config): {};
			if (this.data.charts.items) {
				this.data.charts.items.map(chart => {
					chart.chart_settings = chart_config[chart.chart_name] || {};
				});
			}

			this.sections["charts"] = new frappe.widget.WidgetGroup({
				container: this.page,
				type: "chart",
				columns: 1,
				hidden: Boolean(this.onboarding_widget),
				options: {
					allow_sorting: this.allow_customization,
					allow_create: this.allow_customization,
					allow_delete: this.allow_customization,
					allow_hiding: false,
					allow_edit: true,
					max_widget_count: 2,
				},
				widgets: this.data.charts.items
			});
		});
	}

	make_shortcuts() {
		this.sections["shortcuts"] = new frappe.widget.WidgetGroup({
			title: this.data.shortcuts.label || __('Your Shortcuts'),
			container: this.page,
			type: "shortcut",
			columns: 3,
			options: {
				allow_sorting: this.allow_customization,
				allow_create: this.allow_customization,
				allow_delete: this.allow_customization,
				allow_hiding: false,
				allow_edit: true,
			},
			widgets: this.data.shortcuts.items
		});
	}

	make_cards() {
		let cards = new frappe.widget.WidgetGroup({
			title: this.data.cards.label || __(`Reports & Masters`),
			container: this.page,
			type: "links",
			columns: 3,
			options: {
				allow_sorting: this.allow_customization,
				allow_create: false,
				allow_delete: false,
				allow_hiding: this.allow_customization,
				allow_edit: false,
			},
			widgets: this.data.cards.items
		});

		this.sections["cards"] = cards;

		// const legend = [
		// 	{
		// 		color: "blue",
		// 		description: __("Important")
		// 	},
		// 	{
		// 		color: "yellow",
		// 		description: __("No Records Created")
		// 	}
		// ].map(item => {
		// 	return `

		//
		// 		${item.description}
		//
`;
		// });

		// $(`

		// 	${legend.join("\n")}
		//
`).insertAfter(cards.body);
	}
}

<commit_msg>feat: add customize options to workspace<commit_after>frappe.pages['workspace'].on_page_load = function(wrapper) {
	frappe.ui.make_app_page({
		parent: wrapper,
		name: 'workspace',
		title: __("Workspace"),
	});

	frappe.workspace = new Workspace(wrapper);
	$(wrapper).bind('show', function() {
		frappe.workspace.show();
	});
}

class Workspace {
	constructor(wrapper) {
		this.wrapper = $(wrapper);
		this.page = wrapper.page;
		this.prepare_container();
		this.setup_dropdown();
		this.pages = {};
		this.sidebar_items = {};
		this.sidebar_categories = [
			"Modules",
			"Domains",
			"Places",
			"Administration"
		];

		this.fetch_desktop_settings().then(() => {
			this.make_sidebar();
		})
	}

	show() {
		let page = this.get_page_to_show();
		this.page.set_title(`${__(page)} Workspace`);
		frappe.set_route('workspace', page);
		this.show_page(page);
	}

	prepare_container() {
		let list_sidebar = $(`

`).appendTo(this.wrapper.find(".layout-side-section"));
		this.sidebar = list_sidebar.find(".desk-sidebar");

		this.body = this.wrapper.find(".layout-main-section");
	}

	get_page_to_show() {
		let default_page;

		if (localStorage.current_workspace) {
			default_page = localStorage.current_workspace;
		} else if (this.desktop_settings) {
			default_page = this.desktop_settings["Modules"][0].name;
		} else if (frappe.boot.allowed_workspaces) {
			default_page = frappe.boot.allowed_workspaces[0].name;
		} else {
			default_page = "Website";
		}

		let page = frappe.get_route()[1] || default_page;

		return page;
	}

	fetch_desktop_settings() {
		return frappe
			.call("frappe.desk.desktop.get_desk_sidebar_items")
			.then(response => {
				if (response.message) {
					this.desktop_settings = response.message;
				} else {
					frappe.throw({
						title: __("Couldn't Load Desk"),
						message:
							__("Something went wrong while loading Desk. Please relaod the page. If the problem persists, contact the Administrator"),
						indicator: "red",
						primary_action: {
							label: __("Reload"),
							action: () => location.reload()
						}
					});
				}
			});
	}

	make_sidebar() {
		this.sidebar_categories.forEach(category => {
			if (this.desktop_settings[category]) {
				this.build_sidebar_section(category, this.desktop_settings[category])
			}
		});
	}

	build_sidebar_section(title, items) {
		let sidebar_section = $(`

`)

		// DO NOT REMOVE: Comment to load translation
		// __("Modules") __("Domains") __("Places") __("Administration")
		$(`<div class="list-sidebar-label">${__(title)}</div>`)
			.appendTo(sidebar_section);

		const get_sidebar_item = function(item) {
			return $(`

 ${frappe.utils.icon(item.icon || "folder-normal", "md")}

 ${item.label || item.name}

			`);
		};

		const make_sidebar_category_item = item => {
			if (item.name == this.get_page_to_show()) {
				item.selected = true;
				this.current_page_name = item.name;
			}

			let $item = get_sidebar_item(item);

			$item.clone().appendTo(sidebar_section);
			this.sidebar_items[item.name] = $item;
		};

		items.forEach(item => make_sidebar_category_item(item));

		sidebar_section.appendTo(this.sidebar)
	}

	show_page(page) {
		if (this.current_page_name && this.pages[this.current_page_name]) {
			this.pages[this.current_page_name].hide();
		}

		if (this.sidebar_items && this.sidebar_items[this.current_page_name]) {
			this.sidebar_items[this.current_page_name].removeClass("selected");
			this.sidebar_items[page].addClass("selected");
		}
		this.current_page_name = page;
		localStorage.current_desk_page = page;

		this.pages[page] ? this.pages[page].show() : this.make_page(page);
		this.current_page = this.pages[page];
		this.setup_dropdown();
	}

	make_page(page) {
		const $page = new DesktopPage({
			container: this.body,
			page_name: page
		});

		this.pages[page] = $page;
		return $page;
	}

	customize() {
		if (this.current_page && this.current_page.allow_customization) {
			this.page.clear_menu()
			this.current_page.customize();

			this.page.set_primary_action(
				__("Save Customizations"),
				() => {
					this.current_page.save_customization();
					this.page.clear_primary_action();
					this.setup_dropdown();
				},
				null,
				__("Saving")
			)

			this.page.set_secondary_action(
				__("Discard"),
				() => {
					this.current_page.reload();
					this.page.clear_secondary_action();
					this.setup_dropdown();
				}
			)
		}
	}

	setup_dropdown() {
		this.page.clear_menu();

		this.page.add_menu_item('Customize', () => {
			this.customize();
		}, 1);

		// this.page.add_menu_item('Reset', () => {
		// }, 1);

		// this.page.add_menu_item('Hide Page', () => {
		// }, 1)
	}
}

class DesktopPage {
	constructor({ container, page_name }) {
		frappe.desk_page = this;
		this.container = container;
		this.page_name = page_name;
		this.sections = {};
		this.allow_customization = false;
		this.reload();
	}

	show() {
		frappe.desk_page = this;
		this.page.show();
		if (this.sections.shortcuts) {
			this.sections.shortcuts.widgets_list.forEach(wid => {
				wid.set_actions();
			});
		}
	}

	hide() {
		this.page.hide();
	}

	reload() {
		this.in_customize_mode = false;
		this.page && this.page.remove();
		this.make();
		this.setup_events();
	}

	make() {
		this.page = $(`<div class="desk-page" data-page-name=${this.page_name}></div>`);
		this.page.appendTo(this.container);

		this.get_data().then(res => {
			this.data = res.message;
			if (!this.data) {
				delete localStorage.current_desk_page;
				frappe.set_route("workspace");
				return;
			}

			this.refresh();
		});
	}

	refresh() {
		this.page.empty();
		this.allow_customization = this.data.allow_customization || false;

		if (frappe.is_mobile()) {
			this.allow_customization = false;
		}

		this.data.onboarding && this.data.onboarding.items.length && this.make_onboarding();
		this.make_charts().then(() => {
			this.make_shortcuts();
			this.make_cards();
		});
	}

	get_data() {
		return frappe.call("frappe.desk.desktop.get_desktop_page", {
			page: this.page_name
		});
	}

	setup_events() {
		$(document.body).on('toggleFullWidth', () => this.refresh());
	}

	customize() {
		if (this.in_customize_mode) {
			return;
		}

		// We need to remove this as the  chart group will be visible during customization
		$('.widget.onboarding-widget-box').hide();

		Object.keys(this.sections).forEach(section => {
			this.sections[section].customize();
		});
		this.in_customize_mode = true;

	}

	save_customization() {
		const config = {};

		if (this.sections.charts) config.charts = this.sections.charts.get_widget_config();
		if (this.sections.shortcuts) config.shortcuts = this.sections.shortcuts.get_widget_config();
		if (this.sections.cards) config.cards = this.sections.cards.get_widget_config();

		frappe.call('frappe.desk.desktop.save_customization', {
			page: this.page_name,
			config: config
		}).then(res => {
			if (res.message) {
				frappe.msgprint({ message: __("Customizations Saved Successfully"), title: __("Success")});
				this.reload();
			} else {
				frappe.throw({message: __("Something went wrong while saving customizations"), title: __("Failed")});
				this.reload();
			}
		});
	}

	make_onboarding() {
		this.onboarding_widget = frappe.widget.make_widget({
			label: this.data.onboarding.label || __(`Let's Get Started`),
			subtitle: this.data.onboarding.subtitle,
			steps: this.data.onboarding.items,
			success: this.data.onboarding.success,
			docs_url: this.data.onboarding.docs_url,
			user_can_dismiss: this.data.onboarding.user_can_dismiss,
			widget_type: 'onboarding',
			container: this.page,
			options: {
				allow_sorting: false,
				allow_create: false,
				allow_delete: false,
				allow_hiding: false,
				allow_edit: false,
				max_widget_count: 2,
			}
		});
	}

	make_charts() {
		return frappe.dashboard_utils.get_dashboard_settings().then(settings => {
			let chart_config = settings.chart_config ? JSON.parse(settings.chart_config): {};
			if (this.data.charts.items) {
				this.data.charts.items.map(chart => {
					chart.chart_settings = chart_config[chart.chart_name] || {};
				});
			}

			this.sections["charts"] = new frappe.widget.WidgetGroup({
				container: this.page,
				type: "chart",
				columns: 1,
				hidden: Boolean(this.onboarding_widget),
				options: {
					allow_sorting: this.allow_customization,
					allow_create: this.allow_customization,
					allow_delete: this.allow_customization,
					allow_hiding: false,
					allow_edit: true,
					max_widget_count: 2,
				},
				widgets: this.data.charts.items
			});
		});
	}

	make_shortcuts() {
		this.sections["shortcuts"] = new frappe.widget.WidgetGroup({
			title: this.data.shortcuts.label || __('Your Shortcuts'),
			container: this.page,
			type: "shortcut",
			columns: 3,
			options: {
				allow_sorting: this.allow_customization,
				allow_create: this.allow_customization,
				allow_delete: this.allow_customization,
				allow_hiding: false,
				allow_edit: true,
			},
			widgets: this.data.shortcuts.items
		});
	}

	make_cards() {
		let cards = new frappe.widget.WidgetGroup({
			title: this.data.cards.label || __(`Reports & Masters`),
			container: this.page,
			type: "links",
			columns: 3,
			options: {
				allow_sorting: this.allow_customization,
				allow_create: false,
				allow_delete: false,
				allow_hiding: this.allow_customization,
				allow_edit: false,
			},
			widgets: this.data.cards.items
		});

		this.sections["cards"] = cards;

		// const legend = [
		// 	{
		// 		color: "blue",
		// 		description: __("Important")
		// 	},
		// 	{
		// 		color: "yellow",
		// 		description: __("No Records Created")
		// 	}
		// ].map(item => {
		// 	return `

		//
		// 		${item.description}
		//
`;
		// });

		// $(`

		// 	${legend.join("\n")}
		//
`).insertAfter(cards.body);
	}
}

<|endoftext|>


<commit_before>/**
 * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary
 * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error
 * interface} alerts users to the trouble, permitting the user to dismiss the error and try again
 * when relevant. The ProcessDialog class is always extended and customized with the actions and
 * content required for each process.
 *
 * The process dialog box consists of a header that visually represents the ‘working’ state of long
 * processes with an animation. The header contains the dialog title as well as
 * two {@link OO.ui.ActionWidget action widgets}:  a ‘safe’ action on the left (e.g., ‘Cancel’) and
 * a ‘primary’ action on the right (e.g., ‘Done’).
 *
 * Like other windows, the process dialog is managed by a
 * {@link OO.ui.WindowManager window manager}.
 * Please see the [OOUI documentation on MediaWiki][1] for more information and examples.
 *
 *     @example
 *     // Example: Creating and opening a process dialog window.
 *     function MyProcessDialog( config ) {
 *         MyProcessDialog.parent.call( this, config );
 *     }
 *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
 *
 *     MyProcessDialog.static.name = 'myProcessDialog';
 *     MyProcessDialog.static.title = 'Process dialog';
 *     MyProcessDialog.static.actions = [
 *         { action: 'save', label: 'Done', flags: 'primary' },
 *         { label: 'Cancel', flags: 'safe' }
 *     ];
 *
 *     MyProcessDialog.prototype.initialize = function () {
 *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
 *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
 *         this.content.$element.append( '
This is a process dialog window. The header ' +
 *             'contains the title and two buttons: \'Cancel\' (a safe action) on the left and ' +
 *             '\'Done\' (a primary action)  on the right.
' );
 *         this.$body.append( this.content.$element );
 *     };
 *     MyProcessDialog.prototype.getActionProcess = function ( action ) {
 *         var dialog = this;
 *         if ( action ) {
 *             return new OO.ui.Process( function () {
 *                 dialog.close( { action: action } );
 *             } );
 *         }
 *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
 *     };
 *
 *     var windowManager = new OO.ui.WindowManager();
 *     $( document.body ).append( windowManager.$element );
 *
 *     var dialog = new MyProcessDialog();
 *     windowManager.addWindows( [ dialog ] );
 *     windowManager.openWindow( dialog );
 *
 * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs
 *
 * @abstract
 * @class
 * @extends OO.ui.Dialog
 *
 * @constructor
 * @param {Object} [config] Configuration options
 */
OO.ui.ProcessDialog = function OoUiProcessDialog( config ) {
	// Parent constructor
	OO.ui.ProcessDialog.parent.call( this, config );

	// Properties
	this.fitOnOpen = false;

	// Initialization
	this.$element.addClass( 'oo-ui-processDialog' );
	if ( OO.ui.isMobile() ) {
		this.$element.addClass( 'oo-ui-isMobile' );
	}
};

/* Setup */

OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );

/* Methods */

/**
 * Handle dismiss button click events.
 *
 * Hides errors.
 *
 * @private
 */
OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {
	this.hideErrors();
};

/**
 * Handle retry button click events.
 *
 * Hides errors and then tries again.
 *
 * @private
 */
OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {
	this.hideErrors();
	this.executeAction( this.currentAction );
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.initialize = function () {
	// Parent method
	OO.ui.ProcessDialog.parent.prototype.initialize.call( this );

	// Properties
	this.$navigation = $( '
' );
	this.$location = $( '
' );
	this.$safeActions = $( '
' );
	this.$primaryActions = $( '
' );
	this.$otherActions = $( '
' );
	this.dismissButton = new OO.ui.ButtonWidget( {
		label: OO.ui.msg( 'ooui-dialog-process-dismiss' )
	} );
	this.retryButton = new OO.ui.ButtonWidget();
	this.$errors = $( '
' );
	this.$errorsTitle = $( '
' );

	// Events
	this.dismissButton.connect( this, {
		click: 'onDismissErrorButtonClick'
	} );
	this.retryButton.connect( this, {
		click: 'onRetryButtonClick'
	} );
	this.title.connect( this, {
		labelChange: 'fitLabel'
	} );

	// Initialization
	this.title.$element.addClass( 'oo-ui-processDialog-title' );
	this.$location
		.append( this.title.$element )
		.addClass( 'oo-ui-processDialog-location' );
	this.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );
	this.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );
	this.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );
	this.$errorsTitle
		.addClass( 'oo-ui-processDialog-errors-title' )
		.text( OO.ui.msg( 'ooui-dialog-process-error' ) );
	this.$errors
		.addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )
		.append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );
	this.$content
		.addClass( 'oo-ui-processDialog-content' )
		.append( this.$errors );
	this.$navigation
		.addClass( 'oo-ui-processDialog-navigation' )
		// Note: Order of appends below is important. These are in the order
		// we want tab to go through them. Display-order is handled entirely
		// by CSS absolute-positioning. As such, primary actions like "done"
		// should go first.
		.append( this.$primaryActions, this.$location, this.$safeActions );
	this.$head.append( this.$navigation );
	this.$foot.append( this.$otherActions );
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.getActionWidgetConfig = function ( config ) {
	function checkFlag( flag ) {
		return config.flags === flag ||
			( Array.isArray( config.flags ) && config.flags.indexOf( flag ) !== -1 );
	}

	// Default to unframed.
	config = $.extend( { framed: true }, config );
	if ( checkFlag( 'close' ) ) {
		// Change close buttons to icon only.
		$.extend( config, {
			icon: 'close',
			invisibleLabel: true
		} );
	} else if ( OO.ui.isMobile() && checkFlag( 'back' ) ) {
		// Change back buttons to icon only.
		$.extend( config, {
			icon: 'previous',
			invisibleLabel: true
		} );
	}

	return config;
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.attachActions = function () {
	var i, len, other, special, others;

	// Parent method
	OO.ui.ProcessDialog.parent.prototype.attachActions.call( this );

	special = this.actions.getSpecial();
	others = this.actions.getOthers();
	if ( special.primary ) {
		this.$primaryActions.append( special.primary.$element );
	}
	for ( i = 0, len = others.length; i < len; i++ ) {
		other = others[ i ];
		this.$otherActions.append( other.$element );
	}
	if ( special.safe ) {
		this.$safeActions.append( special.safe.$element );
	}
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.executeAction = function ( action ) {
	var process = this;
	return OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action )
		.fail( function ( errors ) {
			process.showErrors( errors || [] );
		} );
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.setDimensions = function () {
	var dialog = this;

	// Parent method
	OO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );

	this.fitLabel();

	// If there are many actions, they might be shown on multiple lines. Their layout can change
	// when resizing the dialog and when changing the actions. Adjust the height of the footer to
	// fit them.
	dialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) );
	// Wait for CSS transition to finish and do it again :(
	setTimeout( function () {
		dialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) );
	}, 300 );
};

/**
 * Fit label between actions.
 *
 * @private
 * @chainable
 * @return {OO.ui.MessageDialog} The dialog, for chaining
 */
OO.ui.ProcessDialog.prototype.fitLabel = function () {
	var safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth,
		size = this.getSizeProperties();

	if ( typeof size.width !== 'number' ) {
		if ( this.isOpened() ) {
			navigationWidth = this.$head.width() - 20;
		} else if ( this.isOpening() ) {
			if ( !this.fitOnOpen ) {
				// Size is relative and the dialog isn't open yet, so wait.
				// FIXME: This should ideally be handled by setup somehow.
				this.manager.lifecycle.opened.done( this.fitLabel.bind( this ) );
				this.fitOnOpen = true;
			}
			return;
		} else {
			return;
		}
	} else {
		navigationWidth = size.width - 20;
	}

	safeWidth = this.$safeActions.width();
	primaryWidth = this.$primaryActions.width();
	biggerWidth = Math.max( safeWidth, primaryWidth );

	labelWidth = this.title.$element.width();

	if ( !OO.ui.isMobile() && 2 * biggerWidth + labelWidth < navigationWidth ) {
		// We have enough space to center the label
		leftWidth = rightWidth = biggerWidth;
	} else {
		// Let's hope we at least have enough space not to overlap, because we can't wrap
		// the label.
		if ( this.getDir() === 'ltr' ) {
			leftWidth = safeWidth;
			rightWidth = primaryWidth;
		} else {
			leftWidth = primaryWidth;
			rightWidth = safeWidth;
		}
	}

	this.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } );

	return this;
};

/**
 * Handle errors that occurred during accept or reject processes.
 *
 * @private
 * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled
 */
OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {
	var i, len, $item, actions,
		items = [],
		abilities = {},
		recoverable = true,
		warning = false;

	if ( errors instanceof OO.ui.Error ) {
		errors = [ errors ];
	}

	for ( i = 0, len = errors.length; i < len; i++ ) {
		if ( !errors[ i ].isRecoverable() ) {
			recoverable = false;
		}
		if ( errors[ i ].isWarning() ) {
			warning = true;
		}
		$item = $( '
' )
			.addClass( 'oo-ui-processDialog-error' )
			.append( errors[ i ].getMessage() );
		items.push( $item[ 0 ] );
	}
	this.$errorItems = $( items );
	if ( recoverable ) {
		abilities[ this.currentAction ] = true;
		// Copy the flags from the first matching action.
		actions = this.actions.get( { actions: this.currentAction } );
		if ( actions.length ) {
			this.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() );
		}
	} else {
		abilities[ this.currentAction ] = false;
		this.actions.setAbilities( abilities );
	}
	if ( warning ) {
		this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );
	} else {
		this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );
	}
	this.retryButton.toggle( recoverable );
	this.$errorsTitle.after( this.$errorItems );
	this.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );
};

/**
 * Hide errors.
 *
 * @private
 */
OO.ui.ProcessDialog.prototype.hideErrors = function () {
	this.$errors.addClass( 'oo-ui-element-hidden' );
	if ( this.$errorItems ) {
		this.$errorItems.remove();
		this.$errorItems = null;
	}
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {
	// Parent method
	return OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data )
		.first( function () {
			// Make sure to hide errors.
			this.hideErrors();
			this.fitOnOpen = false;
		}, this );
};
<commit_msg>ProcessDialog: Make "back" buttons icon-only on desktop too<commit_after>/**
 * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary
 * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error
 * interface} alerts users to the trouble, permitting the user to dismiss the error and try again
 * when relevant. The ProcessDialog class is always extended and customized with the actions and
 * content required for each process.
 *
 * The process dialog box consists of a header that visually represents the ‘working’ state of long
 * processes with an animation. The header contains the dialog title as well as
 * two {@link OO.ui.ActionWidget action widgets}:  a ‘safe’ action on the left (e.g., ‘Cancel’) and
 * a ‘primary’ action on the right (e.g., ‘Done’).
 *
 * Like other windows, the process dialog is managed by a
 * {@link OO.ui.WindowManager window manager}.
 * Please see the [OOUI documentation on MediaWiki][1] for more information and examples.
 *
 *     @example
 *     // Example: Creating and opening a process dialog window.
 *     function MyProcessDialog( config ) {
 *         MyProcessDialog.parent.call( this, config );
 *     }
 *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
 *
 *     MyProcessDialog.static.name = 'myProcessDialog';
 *     MyProcessDialog.static.title = 'Process dialog';
 *     MyProcessDialog.static.actions = [
 *         { action: 'save', label: 'Done', flags: 'primary' },
 *         { label: 'Cancel', flags: 'safe' }
 *     ];
 *
 *     MyProcessDialog.prototype.initialize = function () {
 *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
 *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
 *         this.content.$element.append( '
This is a process dialog window. The header ' +
 *             'contains the title and two buttons: \'Cancel\' (a safe action) on the left and ' +
 *             '\'Done\' (a primary action)  on the right.
' );
 *         this.$body.append( this.content.$element );
 *     };
 *     MyProcessDialog.prototype.getActionProcess = function ( action ) {
 *         var dialog = this;
 *         if ( action ) {
 *             return new OO.ui.Process( function () {
 *                 dialog.close( { action: action } );
 *             } );
 *         }
 *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
 *     };
 *
 *     var windowManager = new OO.ui.WindowManager();
 *     $( document.body ).append( windowManager.$element );
 *
 *     var dialog = new MyProcessDialog();
 *     windowManager.addWindows( [ dialog ] );
 *     windowManager.openWindow( dialog );
 *
 * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs
 *
 * @abstract
 * @class
 * @extends OO.ui.Dialog
 *
 * @constructor
 * @param {Object} [config] Configuration options
 */
OO.ui.ProcessDialog = function OoUiProcessDialog( config ) {
	// Parent constructor
	OO.ui.ProcessDialog.parent.call( this, config );

	// Properties
	this.fitOnOpen = false;

	// Initialization
	this.$element.addClass( 'oo-ui-processDialog' );
	if ( OO.ui.isMobile() ) {
		this.$element.addClass( 'oo-ui-isMobile' );
	}
};

/* Setup */

OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );

/* Methods */

/**
 * Handle dismiss button click events.
 *
 * Hides errors.
 *
 * @private
 */
OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {
	this.hideErrors();
};

/**
 * Handle retry button click events.
 *
 * Hides errors and then tries again.
 *
 * @private
 */
OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {
	this.hideErrors();
	this.executeAction( this.currentAction );
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.initialize = function () {
	// Parent method
	OO.ui.ProcessDialog.parent.prototype.initialize.call( this );

	// Properties
	this.$navigation = $( '
' );
	this.$location = $( '
' );
	this.$safeActions = $( '
' );
	this.$primaryActions = $( '
' );
	this.$otherActions = $( '
' );
	this.dismissButton = new OO.ui.ButtonWidget( {
		label: OO.ui.msg( 'ooui-dialog-process-dismiss' )
	} );
	this.retryButton = new OO.ui.ButtonWidget();
	this.$errors = $( '
' );
	this.$errorsTitle = $( '
' );

	// Events
	this.dismissButton.connect( this, {
		click: 'onDismissErrorButtonClick'
	} );
	this.retryButton.connect( this, {
		click: 'onRetryButtonClick'
	} );
	this.title.connect( this, {
		labelChange: 'fitLabel'
	} );

	// Initialization
	this.title.$element.addClass( 'oo-ui-processDialog-title' );
	this.$location
		.append( this.title.$element )
		.addClass( 'oo-ui-processDialog-location' );
	this.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );
	this.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );
	this.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );
	this.$errorsTitle
		.addClass( 'oo-ui-processDialog-errors-title' )
		.text( OO.ui.msg( 'ooui-dialog-process-error' ) );
	this.$errors
		.addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )
		.append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );
	this.$content
		.addClass( 'oo-ui-processDialog-content' )
		.append( this.$errors );
	this.$navigation
		.addClass( 'oo-ui-processDialog-navigation' )
		// Note: Order of appends below is important. These are in the order
		// we want tab to go through them. Display-order is handled entirely
		// by CSS absolute-positioning. As such, primary actions like "done"
		// should go first.
		.append( this.$primaryActions, this.$location, this.$safeActions );
	this.$head.append( this.$navigation );
	this.$foot.append( this.$otherActions );
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.getActionWidgetConfig = function ( config ) {
	function checkFlag( flag ) {
		return config.flags === flag ||
			( Array.isArray( config.flags ) && config.flags.indexOf( flag ) !== -1 );
	}

	config = $.extend( { framed: true }, config );
	if ( checkFlag( 'close' ) ) {
		// Change close buttons to icon only.
		$.extend( config, {
			icon: 'close',
			invisibleLabel: true
		} );
	} else if ( checkFlag( 'back' ) ) {
		// Change back buttons to icon only.
		$.extend( config, {
			icon: 'previous',
			invisibleLabel: true
		} );
	}

	return config;
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.attachActions = function () {
	var i, len, other, special, others;

	// Parent method
	OO.ui.ProcessDialog.parent.prototype.attachActions.call( this );

	special = this.actions.getSpecial();
	others = this.actions.getOthers();
	if ( special.primary ) {
		this.$primaryActions.append( special.primary.$element );
	}
	for ( i = 0, len = others.length; i < len; i++ ) {
		other = others[ i ];
		this.$otherActions.append( other.$element );
	}
	if ( special.safe ) {
		this.$safeActions.append( special.safe.$element );
	}
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.executeAction = function ( action ) {
	var process = this;
	return OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action )
		.fail( function ( errors ) {
			process.showErrors( errors || [] );
		} );
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.setDimensions = function () {
	var dialog = this;

	// Parent method
	OO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );

	this.fitLabel();

	// If there are many actions, they might be shown on multiple lines. Their layout can change
	// when resizing the dialog and when changing the actions. Adjust the height of the footer to
	// fit them.
	dialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) );
	// Wait for CSS transition to finish and do it again :(
	setTimeout( function () {
		dialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) );
	}, 300 );
};

/**
 * Fit label between actions.
 *
 * @private
 * @chainable
 * @return {OO.ui.MessageDialog} The dialog, for chaining
 */
OO.ui.ProcessDialog.prototype.fitLabel = function () {
	var safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth,
		size = this.getSizeProperties();

	if ( typeof size.width !== 'number' ) {
		if ( this.isOpened() ) {
			navigationWidth = this.$head.width() - 20;
		} else if ( this.isOpening() ) {
			if ( !this.fitOnOpen ) {
				// Size is relative and the dialog isn't open yet, so wait.
				// FIXME: This should ideally be handled by setup somehow.
				this.manager.lifecycle.opened.done( this.fitLabel.bind( this ) );
				this.fitOnOpen = true;
			}
			return;
		} else {
			return;
		}
	} else {
		navigationWidth = size.width - 20;
	}

	safeWidth = this.$safeActions.width();
	primaryWidth = this.$primaryActions.width();
	biggerWidth = Math.max( safeWidth, primaryWidth );

	labelWidth = this.title.$element.width();

	if ( !OO.ui.isMobile() && 2 * biggerWidth + labelWidth < navigationWidth ) {
		// We have enough space to center the label
		leftWidth = rightWidth = biggerWidth;
	} else {
		// Let's hope we at least have enough space not to overlap, because we can't wrap
		// the label.
		if ( this.getDir() === 'ltr' ) {
			leftWidth = safeWidth;
			rightWidth = primaryWidth;
		} else {
			leftWidth = primaryWidth;
			rightWidth = safeWidth;
		}
	}

	this.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } );

	return this;
};

/**
 * Handle errors that occurred during accept or reject processes.
 *
 * @private
 * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled
 */
OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {
	var i, len, $item, actions,
		items = [],
		abilities = {},
		recoverable = true,
		warning = false;

	if ( errors instanceof OO.ui.Error ) {
		errors = [ errors ];
	}

	for ( i = 0, len = errors.length; i < len; i++ ) {
		if ( !errors[ i ].isRecoverable() ) {
			recoverable = false;
		}
		if ( errors[ i ].isWarning() ) {
			warning = true;
		}
		$item = $( '
' )
			.addClass( 'oo-ui-processDialog-error' )
			.append( errors[ i ].getMessage() );
		items.push( $item[ 0 ] );
	}
	this.$errorItems = $( items );
	if ( recoverable ) {
		abilities[ this.currentAction ] = true;
		// Copy the flags from the first matching action.
		actions = this.actions.get( { actions: this.currentAction } );
		if ( actions.length ) {
			this.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() );
		}
	} else {
		abilities[ this.currentAction ] = false;
		this.actions.setAbilities( abilities );
	}
	if ( warning ) {
		this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );
	} else {
		this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );
	}
	this.retryButton.toggle( recoverable );
	this.$errorsTitle.after( this.$errorItems );
	this.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );
};

/**
 * Hide errors.
 *
 * @private
 */
OO.ui.ProcessDialog.prototype.hideErrors = function () {
	this.$errors.addClass( 'oo-ui-element-hidden' );
	if ( this.$errorItems ) {
		this.$errorItems.remove();
		this.$errorItems = null;
	}
};

/**
 * @inheritdoc
 */
OO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {
	// Parent method
	return OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data )
		.first( function () {
			// Make sure to hide errors.
			this.hideErrors();
			this.fitOnOpen = false;
		}, this );
};
<|endoftext|>


<commit_before>import Immutable from 'immutable';
import installDevTools from 'immutable-devtools';

import thunk from 'redux-thunk';
import { createStore, applyMiddleware, compose } from 'redux';
import createLogger from 'redux-logger';
import { createHistory, useBasename, useBeforeUnload } from 'history';
import { useRouterHistory } from 'react-router';
import { syncHistoryWithStore, routerMiddleware } from 'react-router-redux';
import { singleSignOnMiddleware, reducer as singleSignOn } from 'containers/SingleSignOn';
import { reducer as privileges } from 'containers/Privileges';
import { combineReducers } from 'redux-immutable';
import routerReducer from './routerReducer';

if (DEVELOPMENT) {
  installDevTools(Immutable);
}

export function setupStore(reducer) {
  const browserHistory = useRouterHistory(useBeforeUnload(useBasename(createHistory)))({
    basename: '/',
  });

  const loggerMiddleware = createLogger();
  const reduxRouterMiddleware = routerMiddleware(browserHistory);
  const createStoreWithMiddleware = compose(
    applyMiddleware(
      singleSignOnMiddleware,
      reduxRouterMiddleware,
      thunk,
      loggerMiddleware
    )
  )(createStore);

  const store = createStoreWithMiddleware(createReducer(reducer),
      window.devToolsExtension && window.devToolsExtension());

  const syncBrowserHistory = syncHistoryWithStore(browserHistory, store, {
    selectLocationState: (state) => state.get('routing').toJS(),
  });

  return { store, syncBrowserHistory };
}

function createReducer(reducer) {
  return combineReducers({
    rootReducer: combineReducers(Object.assign({ privileges }, reducer)),
    routing: routerReducer,
    singleSignOn,
  });
}

export default setupStore;
<commit_msg>Ignore the reducer parameter if it's not an object<commit_after>import Immutable from 'immutable';
import installDevTools from 'immutable-devtools';

import thunk from 'redux-thunk';
import { createStore, applyMiddleware, compose } from 'redux';
import createLogger from 'redux-logger';
import { createHistory, useBasename, useBeforeUnload } from 'history';
import { useRouterHistory } from 'react-router';
import { syncHistoryWithStore, routerMiddleware } from 'react-router-redux';
import { singleSignOnMiddleware, reducer as singleSignOn } from 'containers/SingleSignOn';
import { reducer as privileges } from 'containers/Privileges';
import { combineReducers } from 'redux-immutable';
import routerReducer from './routerReducer';
import is from 'is_js';

if (DEVELOPMENT) {
  installDevTools(Immutable);
}

export function setupStore(reducer) {
  const browserHistory = useRouterHistory(useBeforeUnload(useBasename(createHistory)))({
    basename: '/',
  });

  const loggerMiddleware = createLogger();
  const reduxRouterMiddleware = routerMiddleware(browserHistory);
  const createStoreWithMiddleware = compose(
    applyMiddleware(
      singleSignOnMiddleware,
      reduxRouterMiddleware,
      thunk,
      loggerMiddleware
    )
  )(createStore);

  const store = createStoreWithMiddleware(createReducer(reducer),
      window.devToolsExtension && window.devToolsExtension());

  const syncBrowserHistory = syncHistoryWithStore(browserHistory, store, {
    selectLocationState: (state) => state.get('routing').toJS(),
  });

  return { store, syncBrowserHistory };
}

function createReducer(reducer) {
  return combineReducers({
    rootReducer: buildRootReducer(reducer),
    routing: routerReducer,
    singleSignOn,
  });
}

function buildRootReducer(reducer) {
  if (is.not.object(reducer) || is.empty(reducer)) {
    return combineReducers({ privileges });
  }

  return combineReducers(Object.assign({ privileges }, reducer));
}

export default setupStore;
<|endoftext|>


<commit_before>#!/usr/bin/env node

'use strict';

process.logging = function() {
  return console.log;
};

process.logStream = process.stdout;

var dictator = require('..');
var fs = require('fs');

var filename = process.argv[2] || 'dictator.json';
var procs = JSON.parse(fs.readFileSync(filename));

dictator.rule(procs);
process.on('SIGINT', function() { dictator.terminate(procs); });
process.on('exit', function() { dictator.terminate(procs); });
<commit_msg>Disable stdout max listener limit<commit_after>#!/usr/bin/env node

'use strict';

process.logging = function() {
  return console.log;
};

process.logStream = process.stdout;
process.logStream.setMaxListeners(0);

var dictator = require('..');
var fs = require('fs');

var filename = process.argv[2] || 'dictator.json';
var procs = JSON.parse(fs.readFileSync(filename));

dictator.rule(procs);
process.on('SIGINT', function() { dictator.terminate(procs); });
process.on('exit', function() { dictator.terminate(procs); });
<|endoftext|>


<commit_before>const async = require('async');

module.exports.findQueueURL = (sqs, queueName, callback) => {
  sqs.createQueue({ QueueName: queueName }, (err, data) => {
    if (err) callback(err);
    else callback(null, data.QueueUrl);
  });
};

module.exports.pullMessage = (sqs, queueURL, callback) => {
  async.doUntil((cb) => {
    sqs.receiveMessage({
      QueueUrl: queueURL,
      MaxNumberOfMessages: 1
    }, (err, data) => {
      if (err) cb(err);
      else if (data.Messages.length === 0) setTimeout(cb, 1000);
      else {
        const message = data.Messages[0];
        cb(null, {
          queueURL,
          timestamp: (new Date()).toISOString(),
          message,
          messageBody: message.Body,
          messageDetail: JSON.parse(message.Body),
          receiptHandle: message.ReceiptHandle
        });
      }
    });
  }, message => message, callback);
};
<commit_msg>Fix buggy message check<commit_after>const async = require('async');

module.exports.findQueueURL = (sqs, queueName, callback) => {
  sqs.createQueue({ QueueName: queueName }, (err, data) => {
    if (err) callback(err);
    else callback(null, data.QueueUrl);
  });
};

module.exports.pullMessage = (sqs, queueURL, callback) => {
  async.doUntil((cb) => {
    sqs.receiveMessage({
      QueueUrl: queueURL,
      MaxNumberOfMessages: 1
    }, (err, data) => {
      if (err) cb(err);
      else if (!data.Messages || data.Messages.length === 0) setTimeout(cb, 1000);
      else {
        const message = data.Messages[0];
        cb(null, {
          queueURL,
          timestamp: (new Date()).toISOString(),
          message,
          messageBody: message.Body,
          messageDetail: JSON.parse(message.Body),
          receiptHandle: message.ReceiptHandle
        });
      }
    });
  }, message => message, callback);
};
<|endoftext|>


<commit_before>import test from 'ava';
import plugin from '..';
import {
  processCSSFactory,
  processCSSWithPresetFactory,
} from '../../../../util/testHelpers';

const { processCSS } = processCSSFactory(plugin);
const { processCSS: withDefaultPreset } = processCSSWithPresetFactory(
  'default'
);

test(
  'should trim whitespace from nested functions',
  processCSS,
  'h1{width:calc(10px - ( 100px / var(--test) ))}',
  'h1{width:calc(10px - (100px / var(--test)))}'
);

test(
  'should trim whitespace from nested functions (uppercase "calc")',
  processCSS,
  'h1{width:CALC(10px - ( 100px / var(--test) ))}',
  'h1{width:CALC(10px - (100px / var(--test)))}'
);

test(
  'should trim whitespace from nested functions (preset)',
  withDefaultPreset,
  'h1{width:calc(10px - ( 100px / var(--test) ))}',
  'h1{width:calc(10px - 100px/var(--test))}'
);
<commit_msg>test(postcss-normalize-whitespace): var and env functions (#747)<commit_after>import test from 'ava';
import plugin from '..';
import {
  processCSSFactory,
  processCSSWithPresetFactory,
} from '../../../../util/testHelpers';

const { processCSS } = processCSSFactory(plugin);
const { processCSS: withDefaultPreset } = processCSSWithPresetFactory(
  'default'
);

test(
  'should trim whitespace from nested functions',
  processCSS,
  'h1{width:calc(10px - ( 100px / var(--test) ))}',
  'h1{width:calc(10px - (100px / var(--test)))}'
);

test(
  'should trim whitespace from nested functions (uppercase "calc")',
  processCSS,
  'h1{width:CALC(10px - ( 100px / var(--test) ))}',
  'h1{width:CALC(10px - (100px / var(--test)))}'
);

test(
  'should trim whitespace from css variables',
  processCSS,
  'h1{width:var(--foo, calc(10px + 10px))}',
  'h1{width:var(--foo,calc(10px + 10px))}'
);

test(
  'should trim whitespace from env variables',
  processCSS,
  'h1{width:env(--foo, calc(10px + 10px))}',
  'h1{width:env(--foo,calc(10px + 10px))}'
);

test(
  'should trim whitespace from var with calc',
  processCSS,
  'h1{width:var(--foo, calc(10px * 10px))}',
  'h1{width:var(--foo,calc(10px * 10px))}'
);

test(
  'should trim whitespace from nested functions (preset)',
  withDefaultPreset,
  'h1{width:calc(10px - ( 100px / var(--test) ))}',
  'h1{width:calc(10px - 100px/var(--test))}'
);
<|endoftext|>


<commit_before>/** @license
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shaka.ui.TextDisplayer');

goog.require('shaka.util.Dom');

/**
 * @implements {shaka.extern.TextDisplayer}
 * @final
 * @export
 */
shaka.ui.TextDisplayer = class {
  /**
   * Constructor.
   * @param {HTMLMediaElement} video
   * @param {HTMLElement} videoContainer
   */
  constructor(video, videoContainer) {
    /** @private {boolean} */
    this.isTextVisible_ = false;

    /** @private {!Array.<!shaka.extern.Cue>} */
    this.cues_ = [];

    /** @private {HTMLMediaElement} */
    this.video_ = video;

    /** @private {HTMLElement} */
    this.videoContainer_ = videoContainer;

    /** @type {HTMLElement} */
    this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
    this.textContainer_.classList.add('shaka-text-container');
    this.videoContainer_.appendChild(this.textContainer_);

    /**
     * The captions' update period in seconds.
     * @private {number}
     */
    const updatePeriod = 0.25;

    /** @private {shaka.util.Timer} */
    this.captionsTimer_ = new shaka.util.Timer(() => {
      this.updateCaptions_();
    }).tickEvery(updatePeriod);

    /** private {Map.<!shaka.extern.Cue, !HTMLElement>} */
    this.currentCuesMap_ = new Map();
  }

  /**
   * @override
   * @export
   */
  append(cues) {
    // Add the cues.
    this.cues_ = this.cues_.concat(cues);
  }

  /**
   * @override
   * @export
   */
  destroy() {
    // Remove the text container element from the UI.
    this.videoContainer_.removeChild(this.textContainer_);
    this.textContainer_ = null;

    this.isTextVisible_ = false;
    this.cues_ = [];
    if (this.captionsTimer_) {
      this.captionsTimer_.stop();
    }

    this.currentCuesMap_.clear();
  }

  /**
   * @override
   * @export
   */
  remove(start, end) {
    // Return false if destroy() has been called.
    if (!this.textContainer_) {
      return false;
    }

    // Remove the cues out of the time range.
    this.cues_ = this.cues_.filter(
        (cue) => cue.startTime < start || cue.endTime >= end);
    this.updateCaptions_();

    return true;
  }

  /**
   * @override
   * @export
   */
  isTextVisible() {
    return this.isTextVisible_;
  }

  /**
   * @override
   * @export
   */
  setTextVisibility(on) {
    this.isTextVisible_ = on;
  }

  /**
   * Display the current captions.
   * @private
   */
  updateCaptions_() {
    const currentTime = this.video_.currentTime;

    // Return true if the cue should be displayed at the current time point.
    const shouldCueBeDisplayed = (cue) => {
      return this.cues_.includes(cue) && this.isTextVisible_ &&
             cue.startTime <= currentTime && cue.endTime >= currentTime;
    };

    // For each cue in the current cues map, if the cue's end time has passed,
    // remove the entry from the map, and remove the captions from the page.
    for (const cue of this.currentCuesMap_.keys()) {
      if (!shouldCueBeDisplayed(cue)) {
        const captions = this.currentCuesMap_.get(cue);
        this.textContainer_.removeChild(captions);
        this.currentCuesMap_.delete(cue);
      }
    }

    // Sometimes we don't remove a cue element correctly.  So check all the
    // child nodes and remove any that don't have an associated cue.
    const expectedChildren = new Set(this.currentCuesMap_.values());
    for (const child of Array.from(this.textContainer_.childNodes)) {
      if (!expectedChildren.has(child)) {
        this.textContainer_.removeChild(child);
      }
    }

    // Get the current cues that should be added to display. If the cue is not
    // being displayed already, add it to the map, and add the captions onto the
    // page.
    const currentCues = this.cues_.filter((cue) => {
      return shouldCueBeDisplayed(cue) && !this.currentCuesMap_.has(cue);
    }).sort((a, b) => {
      if (a.startTime != b.startTime) {
        return a.startTime - b.startTime;
      } else {
        return a.endTime - b.endTime;
      }
    });

    for (const cue of currentCues) {
      this.displayCue_(this.textContainer_, cue);
    }
  }

  /**
   * Displays a nested cue
   *
   * @param {Element} container
   * @param {!shaka.extern.Cue} cue
   * @return {Element} the created captions container
   * @private
   */
  displayNestedCue_(container, cue) {
    const captions = shaka.util.Dom.createHTMLElement('span');

    if (cue.spacer) {
      captions.style.display = 'block';
    } else {
      this.setCaptionStyles_(captions, cue, /* isNested= */ true);
    }

    container.appendChild(captions);

    return captions;
  }

  /**
   * Displays a cue
   *
   * @param {Element} container
   * @param {!shaka.extern.Cue} cue
   * @private
   */
  displayCue_(container, cue) {
    if (cue.nestedCues.length) {
      const nestedCuesContainer = shaka.util.Dom.createHTMLElement('p');
      nestedCuesContainer.style.width = '100%';
      this.setCaptionStyles_(nestedCuesContainer, cue, /* isNested= */ false);

      for (let i = 0; i < cue.nestedCues.length; i++) {
        this.displayNestedCue_(nestedCuesContainer, cue.nestedCues[i]);
      }

      container.appendChild(nestedCuesContainer);
      this.currentCuesMap_.set(cue, nestedCuesContainer);
    } else {
      this.currentCuesMap_.set(cue, this.displayNestedCue_(container, cue));
    }
  }

  /**
   * @param {!HTMLElement} captions
   * @param {!shaka.extern.Cue} cue
   * @param {boolean} isNested
   * @private
   */
  setCaptionStyles_(captions, cue, isNested) {
    const Cue = shaka.text.Cue;
    const captionsStyle = captions.style;
    const panelStyle = this.textContainer_.style;

    // Set white-space to 'pre-line' to enable showing line breaks in the text.
    captionsStyle.whiteSpace = 'pre-line';
    captions.textContent = cue.payload;
    captionsStyle.backgroundColor = cue.backgroundColor;
    captionsStyle.color = cue.color;
    captionsStyle.direction = cue.direction;

    if (cue.backgroundImage) {
      captionsStyle.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
      captionsStyle.backgroundRepeat = 'no-repeat';
      captionsStyle.backgroundSize = 'contain';
      captionsStyle.backgroundPosition = 'center';
      if (cue.backgroundColor == '') {
        captionsStyle.backgroundColor = 'transparent';
      }
    }
    if (cue.backgroundImage && cue.region) {
      const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
      const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
      const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
      captionsStyle.height = cue.region.height + heightUnit;
      captionsStyle.width = cue.region.width + widthUnit;
    }

    // The displayAlign attribute specifys the vertical alignment of the
    // captions inside the text container. Before means at the top of the
    // text container, and after means at the bottom.
    if (cue.displayAlign == Cue.displayAlign.BEFORE) {
      captionsStyle.justifyContent = 'flex-start';
    } else if (cue.displayAlign == Cue.displayAlign.CENTER) {
      captionsStyle.justifyContent = 'center';
    } else {
      captionsStyle.justifyContent = 'flex-end';
    }
    if (cue.nestedCues.length) {
      captionsStyle.display = 'flex';
      captionsStyle.flexDirection = 'column';
      captionsStyle.margin = '0';
    }

    captionsStyle.fontFamily = cue.fontFamily;
    captionsStyle.fontWeight = cue.fontWeight.toString();
    captionsStyle.fontSize = cue.fontSize;
    captionsStyle.fontStyle = cue.fontStyle;

    // The line attribute defines the positioning of the text container inside
    // the video container.
    // - The line offsets the text container from the top, the right or left of
    //   the video viewport as defined by the writing direction.
    // - The value of the line is either as a number of lines, or a percentage
    //   of the video viewport height or width.
    // The lineAlign is an alignment for the text container's line.
    // - The Start alignment means the text container’s top side (for horizontal
    //   cues), left side (for vertical growing right), or right side (for
    //   vertical growing left) is aligned at the line.
    // - The Center alignment means the text container is centered at the line
    //   (to be implemented).
    // - The End Alignment means The text container’s bottom side (for
    //   horizontal cues), right side (for vertical growing right), or left side
    //   (for vertical growing left) is aligned at the line.
    // TODO: Implement line alignment with line number.
    // TODO: Implement lineAlignment of 'CENTER'.
    if (cue.line) {
      if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
        if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
          if (cue.lineAlign == Cue.lineAlign.START) {
            panelStyle.top = cue.line + '%';
          } else if (cue.lineAlign == Cue.lineAlign.END) {
            panelStyle.bottom = cue.line + '%';
          }
        } else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
          if (cue.lineAlign == Cue.lineAlign.START) {
            panelStyle.left = cue.line + '%';
          } else if (cue.lineAlign == Cue.lineAlign.END) {
            panelStyle.right = cue.line + '%';
          }
        } else {
          if (cue.lineAlign == Cue.lineAlign.START) {
            panelStyle.right = cue.line + '%';
          } else if (cue.lineAlign == Cue.lineAlign.END) {
            panelStyle.left = cue.line + '%';
          }
        }
      }
    } else if (cue.region && cue.region.id && !isNested) {
      const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
      const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
      const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
      const viewportAnchorUnit =
          cue.region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
      captionsStyle.height = cue.region.height + heightUnit;
      captionsStyle.width = cue.region.width + widthUnit;
      captionsStyle.top = cue.region.viewportAnchorY + viewportAnchorUnit;
      captionsStyle.left = cue.region.viewportAnchorX + viewportAnchorUnit;
      captionsStyle.position = 'absolute';
    }

    captionsStyle.lineHeight = cue.lineHeight;

    // The position defines the indent of the text container in the
    // direction defined by the writing direction.
    if (cue.position) {
      if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
        panelStyle.paddingLeft = cue.position;
      } else {
        panelStyle.paddingTop = cue.position;
      }
    }

    // The positionAlign attribute is an alignment for the text container in
    // the dimension of the writing direction.
    if (cue.positionAlign == Cue.positionAlign.LEFT) {
      panelStyle.cssFloat = 'left';
    } else if (cue.positionAlign == Cue.positionAlign.RIGHT) {
      panelStyle.cssFloat = 'right';
    } else {
      panelStyle.margin = 'auto';
    }

    captionsStyle.textAlign = cue.textAlign;
    captionsStyle.textDecoration = cue.textDecoration.join(' ');
    captionsStyle.writingMode = cue.writingMode;

    // The size is a number giving the size of the text container, to be
    // interpreted as a percentage of the video, as defined by the writing
    // direction.
    if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
      panelStyle.width = cue.size + '%';
    } else {
      panelStyle.height = cue.size + '%';
    }
  }
};
<commit_msg>Fix captions display alignment<commit_after>/** @license
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shaka.ui.TextDisplayer');

goog.require('shaka.util.Dom');

/**
 * @implements {shaka.extern.TextDisplayer}
 * @final
 * @export
 */
shaka.ui.TextDisplayer = class {
  /**
   * Constructor.
   * @param {HTMLMediaElement} video
   * @param {HTMLElement} videoContainer
   */
  constructor(video, videoContainer) {
    /** @private {boolean} */
    this.isTextVisible_ = false;

    /** @private {!Array.<!shaka.extern.Cue>} */
    this.cues_ = [];

    /** @private {HTMLMediaElement} */
    this.video_ = video;

    /** @private {HTMLElement} */
    this.videoContainer_ = videoContainer;

    /** @type {HTMLElement} */
    this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
    this.textContainer_.classList.add('shaka-text-container');
    this.videoContainer_.appendChild(this.textContainer_);

    /**
     * The captions' update period in seconds.
     * @private {number}
     */
    const updatePeriod = 0.25;

    /** @private {shaka.util.Timer} */
    this.captionsTimer_ = new shaka.util.Timer(() => {
      this.updateCaptions_();
    }).tickEvery(updatePeriod);

    /** private {Map.<!shaka.extern.Cue, !HTMLElement>} */
    this.currentCuesMap_ = new Map();
  }

  /**
   * @override
   * @export
   */
  append(cues) {
    // Add the cues.
    this.cues_ = this.cues_.concat(cues);
  }

  /**
   * @override
   * @export
   */
  destroy() {
    // Remove the text container element from the UI.
    this.videoContainer_.removeChild(this.textContainer_);
    this.textContainer_ = null;

    this.isTextVisible_ = false;
    this.cues_ = [];
    if (this.captionsTimer_) {
      this.captionsTimer_.stop();
    }

    this.currentCuesMap_.clear();
  }

  /**
   * @override
   * @export
   */
  remove(start, end) {
    // Return false if destroy() has been called.
    if (!this.textContainer_) {
      return false;
    }

    // Remove the cues out of the time range.
    this.cues_ = this.cues_.filter(
        (cue) => cue.startTime < start || cue.endTime >= end);
    this.updateCaptions_();

    return true;
  }

  /**
   * @override
   * @export
   */
  isTextVisible() {
    return this.isTextVisible_;
  }

  /**
   * @override
   * @export
   */
  setTextVisibility(on) {
    this.isTextVisible_ = on;
  }

  /**
   * Display the current captions.
   * @private
   */
  updateCaptions_() {
    const currentTime = this.video_.currentTime;

    // Return true if the cue should be displayed at the current time point.
    const shouldCueBeDisplayed = (cue) => {
      return this.cues_.includes(cue) && this.isTextVisible_ &&
             cue.startTime <= currentTime && cue.endTime >= currentTime;
    };

    // For each cue in the current cues map, if the cue's end time has passed,
    // remove the entry from the map, and remove the captions from the page.
    for (const cue of this.currentCuesMap_.keys()) {
      if (!shouldCueBeDisplayed(cue)) {
        const captions = this.currentCuesMap_.get(cue);
        this.textContainer_.removeChild(captions);
        this.currentCuesMap_.delete(cue);
      }
    }

    // Sometimes we don't remove a cue element correctly.  So check all the
    // child nodes and remove any that don't have an associated cue.
    const expectedChildren = new Set(this.currentCuesMap_.values());
    for (const child of Array.from(this.textContainer_.childNodes)) {
      if (!expectedChildren.has(child)) {
        this.textContainer_.removeChild(child);
      }
    }

    // Get the current cues that should be added to display. If the cue is not
    // being displayed already, add it to the map, and add the captions onto the
    // page.
    const currentCues = this.cues_.filter((cue) => {
      return shouldCueBeDisplayed(cue) && !this.currentCuesMap_.has(cue);
    }).sort((a, b) => {
      if (a.startTime != b.startTime) {
        return a.startTime - b.startTime;
      } else {
        return a.endTime - b.endTime;
      }
    });

    for (const cue of currentCues) {
      this.displayCue_(this.textContainer_, cue);
    }
  }

  /**
   * Displays a nested cue
   *
   * @param {Element} container
   * @param {!shaka.extern.Cue} cue
   * @return {Element} the created captions container
   * @private
   */
  displayNestedCue_(container, cue) {
    const captions = shaka.util.Dom.createHTMLElement('span');

    if (cue.spacer) {
      captions.style.display = 'block';
    } else {
      this.setCaptionStyles_(captions, cue, /* isNested= */ true);
    }

    container.appendChild(captions);

    return captions;
  }

  /**
   * Displays a cue
   *
   * @param {Element} container
   * @param {!shaka.extern.Cue} cue
   * @private
   */
  displayCue_(container, cue) {
    if (cue.nestedCues.length) {
      const nestedCuesContainer = shaka.util.Dom.createHTMLElement('p');
      nestedCuesContainer.style.width = '100%';
      this.setCaptionStyles_(nestedCuesContainer, cue, /* isNested= */ false);

      for (let i = 0; i < cue.nestedCues.length; i++) {
        this.displayNestedCue_(nestedCuesContainer, cue.nestedCues[i]);
      }

      container.appendChild(nestedCuesContainer);
      this.currentCuesMap_.set(cue, nestedCuesContainer);
    } else {
      this.currentCuesMap_.set(cue, this.displayNestedCue_(container, cue));
    }
  }

  /**
   * @param {!HTMLElement} captions
   * @param {!shaka.extern.Cue} cue
   * @param {boolean} isNested
   * @private
   */
  setCaptionStyles_(captions, cue, isNested) {
    const Cue = shaka.text.Cue;
    const captionsStyle = captions.style;
    const panelStyle = this.textContainer_.style;

    // Set white-space to 'pre-line' to enable showing line breaks in the text.
    captionsStyle.whiteSpace = 'pre-line';
    captions.textContent = cue.payload;
    captionsStyle.backgroundColor = cue.backgroundColor;
    captionsStyle.color = cue.color;
    captionsStyle.direction = cue.direction;

    if (cue.backgroundImage) {
      captionsStyle.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
      captionsStyle.backgroundRepeat = 'no-repeat';
      captionsStyle.backgroundSize = 'contain';
      captionsStyle.backgroundPosition = 'center';
      if (cue.backgroundColor == '') {
        captionsStyle.backgroundColor = 'transparent';
      }
    }
    if (cue.backgroundImage && cue.region) {
      const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
      const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
      const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
      captionsStyle.height = cue.region.height + heightUnit;
      captionsStyle.width = cue.region.width + widthUnit;
    }

    // The displayAlign attribute specifys the vertical alignment of the
    // captions inside the text container. Before means at the top of the
    // text container, and after means at the bottom.
    if (cue.displayAlign == Cue.displayAlign.BEFORE) {
      captionsStyle.justifyContent = 'flex-start';
    } else if (cue.displayAlign == Cue.displayAlign.CENTER) {
      captionsStyle.justifyContent = 'center';
    } else {
      captionsStyle.justifyContent = 'flex-end';
    }
    if (cue.nestedCues.length) {
      captionsStyle.alignItems = 'center';
      captionsStyle.display = 'flex';
      captionsStyle.flexDirection = 'column';
      captionsStyle.margin = '0';
    }

    captionsStyle.fontFamily = cue.fontFamily;
    captionsStyle.fontWeight = cue.fontWeight.toString();
    captionsStyle.fontSize = cue.fontSize;
    captionsStyle.fontStyle = cue.fontStyle;

    // The line attribute defines the positioning of the text container inside
    // the video container.
    // - The line offsets the text container from the top, the right or left of
    //   the video viewport as defined by the writing direction.
    // - The value of the line is either as a number of lines, or a percentage
    //   of the video viewport height or width.
    // The lineAlign is an alignment for the text container's line.
    // - The Start alignment means the text container’s top side (for horizontal
    //   cues), left side (for vertical growing right), or right side (for
    //   vertical growing left) is aligned at the line.
    // - The Center alignment means the text container is centered at the line
    //   (to be implemented).
    // - The End Alignment means The text container’s bottom side (for
    //   horizontal cues), right side (for vertical growing right), or left side
    //   (for vertical growing left) is aligned at the line.
    // TODO: Implement line alignment with line number.
    // TODO: Implement lineAlignment of 'CENTER'.
    if (cue.line) {
      if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
        if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
          if (cue.lineAlign == Cue.lineAlign.START) {
            panelStyle.top = cue.line + '%';
          } else if (cue.lineAlign == Cue.lineAlign.END) {
            panelStyle.bottom = cue.line + '%';
          }
        } else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
          if (cue.lineAlign == Cue.lineAlign.START) {
            panelStyle.left = cue.line + '%';
          } else if (cue.lineAlign == Cue.lineAlign.END) {
            panelStyle.right = cue.line + '%';
          }
        } else {
          if (cue.lineAlign == Cue.lineAlign.START) {
            panelStyle.right = cue.line + '%';
          } else if (cue.lineAlign == Cue.lineAlign.END) {
            panelStyle.left = cue.line + '%';
          }
        }
      }
    } else if (cue.region && cue.region.id && !isNested) {
      const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
      const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
      const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
      const viewportAnchorUnit =
          cue.region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
      captionsStyle.height = cue.region.height + heightUnit;
      captionsStyle.width = cue.region.width + widthUnit;
      captionsStyle.top = cue.region.viewportAnchorY + viewportAnchorUnit;
      captionsStyle.left = cue.region.viewportAnchorX + viewportAnchorUnit;
      captionsStyle.position = 'absolute';
    }

    captionsStyle.lineHeight = cue.lineHeight;

    // The position defines the indent of the text container in the
    // direction defined by the writing direction.
    if (cue.position) {
      if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
        panelStyle.paddingLeft = cue.position;
      } else {
        panelStyle.paddingTop = cue.position;
      }
    }

    // The positionAlign attribute is an alignment for the text container in
    // the dimension of the writing direction.
    if (cue.positionAlign == Cue.positionAlign.LEFT) {
      panelStyle.cssFloat = 'left';
    } else if (cue.positionAlign == Cue.positionAlign.RIGHT) {
      panelStyle.cssFloat = 'right';
    } else {
      panelStyle.margin = 'auto';
    }

    captionsStyle.textAlign = cue.textAlign;
    captionsStyle.textDecoration = cue.textDecoration.join(' ');
    captionsStyle.writingMode = cue.writingMode;

    // The size is a number giving the size of the text container, to be
    // interpreted as a percentage of the video, as defined by the writing
    // direction.
    if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
      panelStyle.width = cue.size + '%';
    } else {
      panelStyle.height = cue.size + '%';
    }
  }
};
<|endoftext|>


<commit_before>//Bibliotheques
//Serveur Web
var express = require('express');
var app = express();
var server = require('http').Server(app);
//Serveur Socket
var io = require('socket.io')(server);
//Gestion de Config
var config = require('./config.js');

var u = require("./utils.js");
var path = require('path');

//Mise en place des variables globals
global.socketio = {};
global.socketio.enable = false;

//Init du Serveur
server.listen(config.ServPort);

//Def racine
app.get('/', function (req, res) {
    var tag = "Express"
    res.sendfile(config.ServRoot + '/test.html');
    u.log(tag, "/")
});

//Def fichier ressources
app.use("/assets", express.static(path.join(config.ServRoot, 'assets')));

//Def Socket
io.on('connection', function (socket) {
    global.socketio.enable = true;

    //Affiche logs sur page web
    global.socketio.sendLog = function(tag, msg){
        socket.emit("log",{ "msg": "[" + tag + "] " + msg });
    }

    //Message de bienvenue
    var tag = "SOCKET.IO";
    global.socketio.sendLog(tag, "Hello")
    u.log(tag, "Nouveau client");
});<commit_msg>Stabilisation des web logs<commit_after>//Bibliotheques
//Serveur Web
var express = require('express');
var app = express();
var server = require('http').Server(app);
//Serveur Socket
var io = require('socket.io')(server);
//Gestion de Config
var config = require('./config.js');

var u = require("./utils.js");
var path = require('path');

//Mise en place des variables globals
global.socketio = {};
global.socketio.enable = false;
//Pour éviter les crashs
global.socketio.sendLog = function() {return false;}

//Init du Serveur
server.listen(config.ServPort);

//Def racine
app.get('/', function (req, res) {
    var tag = "Express"
    res.sendfile(config.ServRoot + '/test.html');
    u.log(tag, "/")
});

//Def fichier ressources
app.use("/assets", express.static(path.join(config.ServRoot, 'assets')));

//Def Socket
io.on('connection', function (socket) {
    global.socketio.enable = true;

    //Affiche logs sur page web
    global.socketio.sendLog = function(tag, msg){
        socket.emit("log",{ "msg": "[" + tag + "] " + msg });
    }

    //Message de bienvenue
    var tag = "SOCKET.IO";
    global.socketio.sendLog(tag, "Hello")
    u.log(tag, "Nouveau client");
});<|endoftext|>


<commit_before>'use strict'

const path    = require('path')
const mm      = require('micromatch')
const mime    = require('mime')
const rename  = require('rename-extension')
const log     = require('./log')
const cache   = require('./cache')
const execute = require('./execute')

/**
 * Sends a chunk of the response body and signals the server
 * that all of the response headers and body have been sent.
 * @param {Object} res - Object which was created internally by a HTTP server.
 * @param {String} contentType - MIME-Type of the data.
 * @param {String|Buffer} data
 */
const send = function(res, contentType, data) {

	res.setHeader('Content-Type', contentType)
	res.end(data)

}

/**
 * Match and rewrite a request.
 * @public
 * @param {Array} routes - Array of route configurations.
 * @param {String} srcPath - Path to the source folder.
 */
module.exports = function(routes, srcPath) {

	return (req, res, next) => {

		// Remove first character and query to convert URL to a relative path
		const fileRoute = req.url.substr(1).split('?')[0]

		// Absolute path to the requested file
		const filePath = path.join(srcPath, fileRoute)

		// Generate an array of matching routes and use the first matching route only
		const matches = routes.filter((route) => mm.isMatch(fileRoute, route.path))
		const route   = matches[0]

		// Continue without a rewrite when no matching route has been found
		if (route==null) return next()

		// Use cached handler response when available
		if (cache.get(fileRoute)!=null) {

			log(`{cyan:Using cached handler: {magenta:${ route.name } {grey:${ fileRoute }`)

			const cachedHandler = cache.get(fileRoute)

			// Send file to browser
			return send(res, cachedHandler.contentType, cachedHandler.data)

		}

		// Get mime type of request files
		const contentType = mime.lookup(filePath)

		// Load file with a different extension as filePath points to the target extension
		const fileLoad = rename(filePath, route.handler.in(route.opts))

		// Execute handler
		execute(route, fileRoute, fileLoad, (err, data) => {

			if (err!=null) return next(err)

			// Send file to browser
			send(res, contentType, data)

			// Cache the response of the handler
			cache.set(fileRoute, {
				contentType : contentType,
				data        : data,
				cache       : route.handler.cache
			})

		})

	}

}<commit_msg>Changed location of filePath definition<commit_after>'use strict'

const path    = require('path')
const mm      = require('micromatch')
const mime    = require('mime')
const rename  = require('rename-extension')
const log     = require('./log')
const cache   = require('./cache')
const execute = require('./execute')

/**
 * Sends a chunk of the response body and signals the server
 * that all of the response headers and body have been sent.
 * @param {Object} res - Object which was created internally by a HTTP server.
 * @param {String} contentType - MIME-Type of the data.
 * @param {String|Buffer} data
 */
const send = function(res, contentType, data) {

	res.setHeader('Content-Type', contentType)
	res.end(data)

}

/**
 * Match and rewrite a request.
 * @public
 * @param {Array} routes - Array of route configurations.
 * @param {String} srcPath - Path to the source folder.
 */
module.exports = function(routes, srcPath) {

	return (req, res, next) => {

		// Remove first character and query to convert URL to a relative path
		const fileRoute = req.url.substr(1).split('?')[0]

		// Generate an array of matching routes and use the first matching route only
		const matches = routes.filter((route) => mm.isMatch(fileRoute, route.path))
		const route   = matches[0]

		// Continue without a rewrite when no matching route has been found
		if (route==null) return next()

		// Use cached handler response when available
		if (cache.get(fileRoute)!=null) {

			log(`{cyan:Using cached handler: {magenta:${ route.name } {grey:${ fileRoute }`)

			const cachedHandler = cache.get(fileRoute)

			// Send file to browser
			return send(res, cachedHandler.contentType, cachedHandler.data)

		}

		// Absolute path to the requested file
		const filePath = path.join(srcPath, fileRoute)

		// Load file with a different extension as filePath points to the target extension
		const fileLoad = rename(filePath, route.handler.in(route.opts))

		// Get mime type of request files
		const contentType = mime.lookup(filePath)

		// Execute handler
		execute(route, fileRoute, fileLoad, (err, data) => {

			if (err!=null) return next(err)

			// Send file to browser
			send(res, contentType, data)

			// Cache the response of the handler
			cache.set(fileRoute, {
				contentType : contentType,
				data        : data,
				cache       : route.handler.cache
			})

		})

	}

}<|endoftext|>


<commit_before>define([
  'dojo/_base/declare',

  'dojo/dom-construct'
], function(declare, domConstruct) {

  return declare(null, {
    scaffoldClass: 'simpleFormElement',

    factory: null,

    _renderItem: function(item) {
      if(!item.properties) {
        item.properties = {};
      }

      item = this._mountOnChangeEvent(item);
      var element = this._createElement(item);

      return this._createScaffold(item, element);
    },

    _createElement: function(item) {
      var node = domConstruct.create('span', null, this.containerNode);
      return this.factory.createElement(item, node);
    },

    /**
     * Overrideable
     */
    _createScaffold: function(item, element) {
      var scaffoldNode = domConstruct.create('span', {
        'class': this.scaffoldClass
      });

      domConstruct.create('span', {
        innerHTML: item.label,
        'class': 'label'
      }, scaffoldNode);

      var elementNode = domConstruct.create('span', {
        'class': 'element'
      }, scaffoldNode);

      domConstruct.place(element.domNode || element, elementNode, 'only');

      return scaffoldNode;
    },

    _mountOnChangeEvent: function(item) {
      var onChange = item.properties.onChange;
      item.properties.onChange = this._onChange.bind(this, item, onChange);
      console.log(item);
      return item;
    },

    _onChange: function(item, callback, value) {
       var props = item.properties;

       if(props.hasOwnProperty('checked')) {
         props.checked = value;
       } else {
         props.value = value;
       }

       this.set('skipRendering', true);
       this.store.put(item).then((function() {
         if(typeof callback === 'function') {
          callback(value);
         }
         this.set('skipRendering', false);
       }).bind(this));
    },

    _setStoreAttr: function(store) {
      this.inherited(arguments);
      if(store && this.factory && this.factory.isLoaded()) {
        this._render();
      }
    },

    _setFactoryAttr: function(factory) {
      this._set('factory', factory);
      this.factory.loaded.then(this._render.bind(this));
    }
  });

});<commit_msg>rename scaffoldClass in _ComponentMixin to dcomponent.<commit_after>define([
  'dojo/_base/declare',

  'dojo/dom-construct'
], function(declare, domConstruct) {

  return declare(null, {
    scaffoldClass: 'dcomponent',

    factory: null,

    _renderItem: function(item) {
      if(!item.properties) {
        item.properties = {};
      }

      item = this._mountOnChangeEvent(item);
      var element = this._createElement(item);

      return this._createScaffold(item, element);
    },

    _createElement: function(item) {
      var node = domConstruct.create('span', null, this.containerNode);
      return this.factory.createElement(item, node);
    },

    /**
     * Overrideable
     */
    _createScaffold: function(item, element) {
      var scaffoldNode = domConstruct.create('span', {
        'class': this.scaffoldClass
      });

      domConstruct.create('span', {
        innerHTML: item.label,
        'class': 'label'
      }, scaffoldNode);

      var elementNode = domConstruct.create('span', {
        'class': 'element'
      }, scaffoldNode);

      domConstruct.place(element.domNode || element, elementNode, 'only');

      return scaffoldNode;
    },

    _mountOnChangeEvent: function(item) {
      var onChange = item.properties.onChange;
      item.properties.onChange = this._onChange.bind(this, item, onChange);
      console.log(item);
      return item;
    },

    _onChange: function(item, callback, value) {
       var props = item.properties;

       if(props.hasOwnProperty('checked')) {
         props.checked = value;
       } else {
         props.value = value;
       }

       this.set('skipRendering', true);
       this.store.put(item).then((function() {
         if(typeof callback === 'function') {
          callback(value);
         }
         this.set('skipRendering', false);
       }).bind(this));
    },

    _setStoreAttr: function(store) {
      this.inherited(arguments);
      if(store && this.factory && this.factory.isLoaded()) {
        this._render();
      }
    },

    _setFactoryAttr: function(factory) {
      this._set('factory', factory);
      this.factory.loaded.then(this._render.bind(this));
    }
  });

});<|endoftext|>


<commit_before>import Activity from 'rmw-shell/lib/containers/Activity'
import Add from '@material-ui/icons/Add'
import FilterList from '@material-ui/icons/FilterList'
import IconButton from '@material-ui/core/IconButton'
import List from '@material-ui/core/List'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import ReactList from 'react-list'
import Scrollbar from 'rmw-shell/lib/components/Scrollbar'
import SearchField from 'rmw-shell/lib/components/SearchField'
import Tooltip from '@material-ui/core/Tooltip'
import isGranted from 'rmw-shell/lib/utils/auth'
import { Fab } from '@material-ui/core'
import { FilterDrawer, filterSelectors, filterActions } from 'material-ui-filter'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { getList } from 'firekit'
import { getLocation } from 'firekit/lib/store/lists/actions'
import { injectIntl } from 'react-intl'
import { withFirebase } from 'firekit-provider'
import { withRouter } from 'react-router-dom'

class ListActivity extends Component {
  componentDidMount() {
    const { watchList, path, name } = this.props
    watchList(path || name)
  }

  componentWillUnmount() {
    const { unwatchList, path, name } = this.props
    unwatchList(path || name)
  }

  render() {
    const {
      createGrant,
      filterFields,
      hasFilters,
      history,
      intl,
      isGranted,
      list,
      name,
      setFilterIsOpen,
      renderItem,
      handleCreateClick,
      disableCreate,
      title,
      activityProps = {}
    } = this.props

    const fields = filterFields.map(field => {
      if (!field.label) {
        return {
          label: intl.formatMessage({ id: `${field.name}_label` }),
          ...field
        }
      }
      return field
    })

    return (
      <Activity
        title={title || intl.formatMessage({ id: name })}
        appBarContent={

            <SearchField filterName={name} />
            <Tooltip title={intl.formatMessage({ id: 'open_filter' })}>
              <IconButton
                color="inherit"
                aria-label="open drawer"
                onClick={() => {
                  setFilterIsOpen(name, true)
                }}
              >
                <FilterList color={hasFilters ? 'secondary' : 'inherit'} />
              </IconButton>
            </Tooltip>

        }
        {...activityProps}
      >

          <Scrollbar>
            <List>
              <ReactList
                itemRenderer={i => renderItem(list[i].key, list[i].val)}
                length={list ? list.length : 0}
                type="simple"
              />
            </List>
          </Scrollbar>

          {disableCreate !== true && isGranted(createGrant) && (
            <Fab
              onClick={
                handleCreateClick != null
                  ? handleCreateClick
                  : () => {
                    history.push(`/${name}/create`)
                  }
              }
              style={{ position: 'fixed', bottom: 15, right: 20, zIndex: 99 }}
              color={'secondary'}
            >
              <Add />
            </Fab>
          )}

        <FilterDrawer name={name} fields={fields} formatMessage={intl.formatMessage} />
      </Activity>
    )
  }
}

ListActivity.propTypes = {
  isGranted: PropTypes.func.isRequired
}

const mapStateToProps = (state, ownProps) => {
  const { firebaseApp } = ownProps
  const { filters } = state
  const { name, path, isGranted: customIsGranted } = ownProps

  const location = firebaseApp ? getLocation(firebaseApp, path) : path
  const ref = location || name

  const { hasFilters } = filterSelectors.selectFilterProps(name, filters)
  const list = filterSelectors.getFilteredList(ref, filters, getList(state, ref), fieldValue => fieldValue.val)

  return {
    ref,
    name,
    hasFilters,
    list,
    isGranted: grant => (customIsGranted ? customIsGranted(state, grant) : isGranted(state, grant))
  }
}

export default compose(
  connect(
    mapStateToProps,
    { ...filterActions }
  ),
  injectIntl,
  withFirebase,
  withRouter
)(ListActivity)
<commit_msg>Changed ListActivity list type from simple to uniform<commit_after>import Activity from 'rmw-shell/lib/containers/Activity'
import Add from '@material-ui/icons/Add'
import FilterList from '@material-ui/icons/FilterList'
import IconButton from '@material-ui/core/IconButton'
import List from '@material-ui/core/List'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import ReactList from 'react-list'
import Scrollbar from 'rmw-shell/lib/components/Scrollbar'
import SearchField from 'rmw-shell/lib/components/SearchField'
import Tooltip from '@material-ui/core/Tooltip'
import isGranted from 'rmw-shell/lib/utils/auth'
import { Fab } from '@material-ui/core'
import { FilterDrawer, filterSelectors, filterActions } from 'material-ui-filter'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { getList } from 'firekit'
import { getLocation } from 'firekit/lib/store/lists/actions'
import { injectIntl } from 'react-intl'
import { withFirebase } from 'firekit-provider'
import { withRouter } from 'react-router-dom'

class ListActivity extends Component {
  componentDidMount() {
    const { watchList, path, name } = this.props
    watchList(path || name)
  }

  componentWillUnmount() {
    const { unwatchList, path, name } = this.props
    unwatchList(path || name)
  }

  render() {
    const {
      createGrant,
      filterFields,
      hasFilters,
      history,
      intl,
      isGranted,
      list,
      name,
      setFilterIsOpen,
      renderItem,
      handleCreateClick,
      disableCreate,
      title,
      activityProps = {},
      reactListProps={}
    } = this.props

    const fields = filterFields.map(field => {
      if (!field.label) {
        return {
          label: intl.formatMessage({ id: `${field.name}_label` }),
          ...field
        }
      }
      return field
    })

    return (
      <Activity
        title={title || intl.formatMessage({ id: name })}
        appBarContent={

            <SearchField filterName={name} />
            <Tooltip title={intl.formatMessage({ id: 'open_filter' })}>
              <IconButton
                color="inherit"
                aria-label="open drawer"
                onClick={() => {
                  setFilterIsOpen(name, true)
                }}
              >
                <FilterList color={hasFilters ? 'secondary' : 'inherit'} />
              </IconButton>
            </Tooltip>

        }
        {...activityProps}
      >

          <Scrollbar>
            <List>
              <ReactList
                itemRenderer={i => renderItem(list[i].key, list[i].val)}
                length={list ? list.length : 0}
                type="uniform"
                {...reactListProps}
              />
            </List>
          </Scrollbar>

          {disableCreate !== true && isGranted(createGrant) && (
            <Fab
              onClick={
                handleCreateClick != null
                  ? handleCreateClick
                  : () => {
                    history.push(`/${name}/create`)
                  }
              }
              style={{ position: 'fixed', bottom: 15, right: 20, zIndex: 99 }}
              color={'secondary'}
            >
              <Add />
            </Fab>
          )}

        <FilterDrawer name={name} fields={fields} formatMessage={intl.formatMessage} />
      </Activity>
    )
  }
}

ListActivity.propTypes = {
  isGranted: PropTypes.func.isRequired
}

const mapStateToProps = (state, ownProps) => {
  const { firebaseApp } = ownProps
  const { filters } = state
  const { name, path, isGranted: customIsGranted } = ownProps

  const location = firebaseApp ? getLocation(firebaseApp, path) : path
  const ref = location || name

  const { hasFilters } = filterSelectors.selectFilterProps(name, filters)
  const list = filterSelectors.getFilteredList(ref, filters, getList(state, ref), fieldValue => fieldValue.val)

  return {
    ref,
    name,
    hasFilters,
    list,
    isGranted: grant => (customIsGranted ? customIsGranted(state, grant) : isGranted(state, grant))
  }
}

export default compose(
  connect(
    mapStateToProps,
    { ...filterActions }
  ),
  injectIntl,
  withFirebase,
  withRouter
)(ListActivity)
<|endoftext|>