diff --git a/chrome/js/database.js b/chrome/js/database.js
index 9cf3620..c563bd6 100644
--- a/chrome/js/database.js
+++ b/chrome/js/database.js
@@ -66,7 +66,7 @@ const Database = {
*/
createApp: function(id, manifestUrl, documentUrl, manifest) {
return new Promise((resolve, reject) => {
- const transaction = this.db.transaction(["apps"], "readwrite");
+ const transaction = this.db.transaction(['apps'], 'readwrite');
transaction.oncomplete = (event) => {
console.log('successfully created app with id ' + id);
@@ -97,6 +97,39 @@ const Database = {
});
},
+ /**
+ * Delete an app from the database.
+ *
+ * @param {string} id The ID of the app to delete.
+ */
+ deleteApp: function(id) {
+ return new Promise((resolve, reject) => {
+ const transaction = this.db.transaction(['apps'], 'readwrite');
+
+ transaction.oncomplete = (event) => {
+ resolve(event);
+ };
+
+ transaction.onerror = (event) => {
+ console.error('Transaction error deleting app with id ' + id);
+ reject(event);
+ };
+
+ const objectStore = transaction.objectStore('apps');
+
+ const request = objectStore.delete(id);
+
+ request.onsuccess = (event) => {
+ console.log('Successfully deleted app with id ' + id);
+ };
+
+ request.onerror = (event) => {
+ console.error('Error requesting deletion of app object with id ' + id);
+ reject(event);
+ };
+ });
+ },
+
/**
* List apps in database.
*
diff --git a/chrome/js/models/web-apps.js b/chrome/js/models/web-apps.js
index 019f857..bb30e08 100644
--- a/chrome/js/models/web-apps.js
+++ b/chrome/js/models/web-apps.js
@@ -53,10 +53,28 @@ class WebApps {
return webApp;
} catch (error) {
console.error('Error pinning app with id: ' + id);
+ console.error(error);
throw new Error('PinAppFailed');
}
}
+ /**
+ * Unpin the app with the given ID.
+ *
+ * @param {string} id The ID of the app to unpin.
+ */
+ async unpin(id) {
+ try {
+ await this.db.deleteApp(id);
+ await this.refreshAppList();
+ return;
+ } catch(error) {
+ console.error('Error unpinning app with id: ' + id);
+ console.error(error);
+ throw new Error('UnpinAppFailed');
+ }
+ }
+
/**
* Get the current list of pinned apps.
*
@@ -70,6 +88,7 @@ class WebApps {
* Refresh the list of apps in memory.
*/
async refreshAppList() {
+ this.apps.clear();
let appRecords = new Map();
try {
appRecords = await this.db.listApps();
diff --git a/chrome/js/views/components/browser-window.js b/chrome/js/views/components/browser-window.js
index e9d0ffa..25fc6e7 100644
--- a/chrome/js/views/components/browser-window.js
+++ b/chrome/js/views/components/browser-window.js
@@ -198,6 +198,17 @@ class BrowserWindow extends HTMLElement {
width: 100%;
flex: 1;
}
+
+ site-info-menu {
+ position: fixed;
+ left: 9px;
+ top: 60px;
+ }
+
+ :host([display-mode='standalone']) site-info-menu {
+ left: 4px;
+ top: 28px;
+ }
`;
- let siteInfo;
+ let pinOrUnpin;
+ if(isPinned) {
+ this.isPinned = true;
+ pinOrUnpin = 'Unpin';
+ } else {
+ this.isPinned = false;
+ pinOrUnpin = 'Pin';
+ }
+ let siteInfo;
if (isApp) {
siteInfo = `
- Pin App
+ ${pinOrUnpin} App
${name}
from ${hostname}
-
+
`;
} else {
siteInfo = `
@@ -172,13 +179,19 @@ class SiteInfoMenu extends HTMLElement {
}
/**
- * Handle a click on the pin app button.
+ * Handle a click on the pin/unpin app button.
*
* @param {Event} event - The click event.
*/
handlePinAppButtonClick(event) {
- // Dispatch an event to tell the browser window the user wants to pin the current app, then self-destruct
- this.dispatchEvent(new CustomEvent('_pinappbuttonclicked'));
+ if(this.isPinned) {
+ // Dispatch an event to tell the browser window the user wants to unpin the current app
+ this.dispatchEvent(new CustomEvent('_unpinappbuttonclicked'));
+ } else {
+ // Dispatch an event to tell the browser window the user wants to pin the current app
+ this.dispatchEvent(new CustomEvent('_pinappbuttonclicked'));
+ }
+ // Self-destruct
this.remove();
}
diff --git a/chrome/js/views/windows-view.js b/chrome/js/views/windows-view.js
index 73dd3b8..14cfa93 100644
--- a/chrome/js/views/windows-view.js
+++ b/chrome/js/views/windows-view.js
@@ -33,6 +33,8 @@ const WindowsView = {
this.handleCloseWindowButtonClicked.bind(this));
this.windowsElement.addEventListener('_pinapprequested',
this.handlePinAppRequest.bind(this));
+ this.windowsElement.addEventListener('_unpinapprequested',
+ this.handleUnpinAppRequest.bind(this));
this.windowsElement.addEventListener('_locationchanged',
this.handleWindowLocationChange.bind(this));
@@ -209,6 +211,39 @@ const WindowsView = {
});
},
+/**
+ * Handle a request to unpin an app.
+ *
+ * @param {CustomEvent} event An _unpinapprequested event containing document URL of the requesting page
+ */
+handleUnpinAppRequest: function(event) {
+ // Find the app which the provided document URL belongs to
+ const app = window.webApps.match(event.detail.documentUrl);
+
+ if(!app) {
+ console.error('Found no app matching the provided document URL');
+ window.dispatchEvent(new CustomEvent('_error', { detail: { error: 'Failed to unpin app'}}));
+ }
+
+ // Delete the app
+ window.webApps.unpin(app.id).then(() => {
+ // Unpin all browser windows with a current URL within scope of the pinned app
+ this.windows.forEach((browserWindow, windowId, windowsMap) => {
+ const documentUrl = browserWindow.element.getUrl();
+ if(app.isWithinScope(documentUrl)) {
+ // Unapply manifest and revert back to browser display mode
+ browserWindow.element.setAttribute('display-mode', 'browser');
+ browserWindow.element.removeAttribute('application-name');
+ browserWindow.element.removeAttribute('application-icon');
+ // There may be another overlapping app but the manifest will get applied on the
+ // next navigation
+ }
+ });
+ }).catch((error) => {
+ window.dispatchEvent(new CustomEvent('_error', { detail: { error: 'Failed to unpin app'}}));
+ });
+},
+
/**
* Handle a location change of a window.
*
@@ -228,10 +263,10 @@ const WindowsView = {
browserWindow.element.setAttribute('application-name', app.name || app.short_name || '');
browserWindow.element.setAttribute('application-icon', app.getBestIconUrl(this.TITLE_BAR_APP_ICON_SIZE));
} else {
- // Reset display mode, application name and application icon
+ // Unapply manifest and reset display mode to browser
browserWindow.element.setAttribute('display-mode', 'browser');
- browserWindow.element.setAttribute('application-name', '');
- browserWindow.element.setAttribute('application-icon', this.DEFAULT_APP_ICON_URL);
+ browserWindow.element.removeAttribute('application-name');
+ browserWindow.element.removeAttribute('application-icon');
}
},