[hub] Realtime Sync Progress

- Show progress from the migration plan
- Show products being synced
- Disallow publishing in sync state
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json
index a5338e9..10bb959 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json
@@ -373,7 +373,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "depends_on": "enabled", 
+   "depends_on": "", 
    "fieldname": "publish_section", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -675,8 +675,8 @@
  "issingle": 1, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-07-28 00:48:21.210413", 
- "modified_by": "cave@aperture.com", 
+ "modified": "2018-07-30 08:01:09.024358", 
+ "modified_by": "Administrator", 
  "module": "Hub Node", 
  "name": "Hub Settings", 
  "name_case": "", 
diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js
index 4c3e9ef..28309f3 100644
--- a/erpnext/public/js/hub/marketplace.js
+++ b/erpnext/public/js/hub/marketplace.js
@@ -825,17 +825,44 @@
 erpnext.hub.Publish = class Publish extends SubPage {
 	make_wrapper() {
 		super.make_wrapper();
+		this.items_to_publish = [];
+		this.unpublished_items = [];
+		this.fetched_items = [];
+	}
+
+	refresh() {
 		if(!hub.settings.sync_in_progress) {
-			this.make_publish_header();
+			this.make_publish_ready_state();
 		} else {
-			this.show_publishing_state();
+			this.make_publish_in_progress_state();
 		}
 	}
 
-	make_publish_header() {
-		const title_html = `<b>${__('Select Products to Publish')}</b>`;
+	make_publish_ready_state() {
+		this.$wrapper.empty();
+		this.$wrapper.append(this.get_publishing_header());
 
-		const info = `<p class="text-muted">${__("Status decided by the 'Publish in Hub' field in Item.")}</p>`;
+		make_search_bar({
+			wrapper: this.$wrapper,
+			on_search: keyword => {
+				this.search_value = keyword;
+				this.get_items_and_render();
+			},
+			placeholder: __('Search Items')
+		});
+
+		this.setup_publishing_events();
+
+		if(hub.settings.last_sync) {
+			this.show_message(`Last sync was <a href="#marketplace/profile">${hub.settings.last_sync}</a>.
+				<a href="#marketplace/my-products">See your Published Products</a>.`);
+		}
+
+		this.get_items_and_render();
+	}
+
+	get_publishing_header() {
+		const title_html = `<b>${__('Select Products to Publish')}</b>`;
 
 		const subtitle_html = `<p class="text-muted">
 			${__(`Only products with an image, description and category can be published.
@@ -847,10 +874,7 @@
 			<span class="hidden-xs">${__('Publish')}</span>
 		</button>`;
 
-		const select_all_button = `<button class="btn btn-secondary btn-default btn-xs margin-right select-all">${__('Select All')}</button>`;
-		const deselect_all_button = `<button class="btn btn-secondary btn-default btn-xs deselect-all">${__('Deselect All')}</button>`;
-
-		const subpage_header = $(`
+		return $(`
 			<div class='subpage-title flex'>
 				<div>
 					${title_html}
@@ -859,27 +883,12 @@
 				${publish_button_html}
 			</div>
 		`);
-
-		this.$wrapper.append(subpage_header);
-
-		make_search_bar({
-			wrapper: this.$wrapper,
-			on_search: keyword => {
-				this.search_value = keyword;
-				this.get_items_and_render();
-			},
-			placeholder: __('Search Items')
-		});
-
-		this.setup_events();
 	}
 
-	setup_events() {
+	setup_publishing_events() {
 		this.$wrapper.find('.publish-items').on('click', () => {
 			this.publish_selected_items()
-				.then(r => {
-					console.log(`${r.message} items will be published`);
-				});
+				.then(this.refresh.bind(this))
 		});
 
 		this.$wrapper.on('click', '.hub-card', (e) => {
@@ -903,33 +912,95 @@
 		});
 	}
 
-	get_items_and_render() {
-		if(hub.settings.sync_in_progress) {
-			this.show_publishing_state();
-			return;
-		}
+	show_message(message) {
+		const $message = $(`<div class="subpage-message">
+			<p class="text-muted flex">
+				<span>
+					${message}
+				</span>
+				<i class="octicon octicon-x text-extra-muted"></i>
+			</p>
+		</div>`);
 
-		this.$wrapper.find('.hub-card-container').empty();
-		this.get_valid_items()
-			.then(r => {
-				this.render(r.message);
-			});
+		$message.find('.octicon-x').on('click', () => {
+			$message.remove();
+		});
+
+		this.$wrapper.prepend($message);
 	}
 
-	refresh() {
-		if (hub.settings.sync_in_progress) {
-			this.load_publishing_state();
+	make_publish_in_progress_state() {
+		this.$wrapper.empty();
+
+		this.$wrapper.append(this.show_publish_progress());
+
+		const subtitle_html = `<p class="text-muted">
+			${__(`Only products with an image, description and category can be published.
+			Please update them if an item in your inventory does not appear.`)}
+		</p>`;
+
+		this.$wrapper.append(subtitle_html);
+
+		// Show search list with only desctiption, and don't set any events
+		make_search_bar({
+			wrapper: this.$wrapper,
+			on_search: keyword => {
+				this.search_value = keyword;
+				this.get_items_and_render();
+			},
+			placeholder: __('Search Items')
+		});
+
+		this.get_items_and_render();
+	}
+
+	show_publish_progress() {
+		const items_to_publish = this.items_to_publish.length
+			? this.items_to_publish
+			: JSON.parse(hub.settings.custom_data);
+
+		const $publish_progress = $(`<div class="sync-progress">
+			<p><b>${__(`Syncing ${items_to_publish.length} Products`)}</b></p>
+			<div class="progress">
+				<div class="progress-bar" style="width: 12.875%"></div>
+			</div>
+
+		</div>`);
+
+		const items_to_publish_container = $(get_item_card_container_html(
+			items_to_publish, '', get_local_item_card_html));
+
+		items_to_publish_container.find('.hub-card').addClass('active');
+
+		$publish_progress.append(items_to_publish_container);
+
+		return $publish_progress;
+	}
+
+	get_items_and_render(wrapper = this.$wrapper) {
+		wrapper.find('.results').remove();
+		const items = this.get_valid_items();
+
+		if(!items.then) {
+			this.render(items, wrapper);
 		} else {
-			this.get_items_and_render();
+			items.then(r => {
+				this.fetched_items = r.message;
+				this.render(r.message, wrapper);
+			});
 		}
 	}
 
-	render(items) {
+	render(items, wrapper) {
 		const items_container = $(get_item_card_container_html(items, '', get_local_item_card_html));
-		this.$wrapper.append(items_container);
+		items_container.addClass('results');
+		wrapper.append(items_container);
 	}
 
 	get_valid_items() {
+		if(this.unpublished_items.length) {
+			return this.unpublished_items;
+		}
 		return frappe.call(
 			'erpnext.hub_node.get_valid_items',
 			{
@@ -938,26 +1009,33 @@
 		);
 	}
 
-	show_publishing_state() {
-		this.$wrapper.html(get_empty_state(
-			'Publishing items ... You will be notified once published.'
-		));
-	}
-
 	publish_selected_items() {
-		const items_to_publish = [];
+		const item_codes_to_publish = [];
 		this.$wrapper.find('.hub-card.active').map(function () {
-			items_to_publish.push($(this).attr("data-id"));
+			item_codes_to_publish.push($(this).attr("data-id"));
 		});
 
-		this.show_publishing_state();
+		this.unpublished_items = this.fetched_items.filter(item => {
+			return !item_codes_to_publish.includes(item.item_code);
+		});
 
-		return frappe.call(
-			'erpnext.hub_node.publish_selected_items',
-			{
-				items_to_publish: items_to_publish
-			}
-		);
+		const items_to_publish = this.fetched_items.filter(item => {
+			return item_codes_to_publish.includes(item.item_code);
+		});
+		this.items_to_publish = items_to_publish;
+
+		return frappe.db.set_value("Hub Settings", "Hub Settings", {
+			custom_data: JSON.stringify(items_to_publish),
+			// sync_in_progress: 1
+		}).then(() => {
+			hub.settings.sync_in_progress = 1;
+		})
+		// .then(frappe.call(
+		// 	'erpnext.hub_node.publish_selected_items',
+		// 	{
+		// 		items_to_publish: item_codes_to_publish
+		// 	}
+		// ));
 	}
 }
 
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index a432156..c610aad 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -31,14 +31,33 @@
 		border-color: #59b81c;
 	}
 
+	.progress-bar {
+		background-color: #89da28;
+	}
+
 	.subpage-title.flex {
 		align-items: flex-start;
 		justify-content: space-between;
 	}
 
+	.subpage-message {
+		p {
+			padding: 10px 15px;
+			margin-top: 0px;
+			margin-bottom: 15px;
+			background-color: #f9fbf7;
+			justify-content: space-between;
+		}
+
+		.octicon-x {
+			cursor: pointer;
+		}
+	}
+
 	.hub-card {
-		border: 1px solid @border-color;
 		margin-bottom: 25px;
+		position: relative;
+		border: 1px solid @border-color;
 		border-radius: 4px;
 		overflow: hidden;
 		cursor: pointer;