Skip to content

Commit

Permalink
Unpin App - closes #49
Browse files Browse the repository at this point in the history
  • Loading branch information
benfrancis committed Dec 6, 2024
1 parent df1cc62 commit b259d2a
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 18 deletions.
35 changes: 34 additions & 1 deletion chrome/js/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.
*
Expand Down
19 changes: 19 additions & 0 deletions chrome/js/models/web-apps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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();
Expand Down
70 changes: 66 additions & 4 deletions chrome/js/views/components/browser-window.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
</style>
<menu class="title-bar">
<img src="${this.DEFAULT_FAVICON_URL}" class="title-bar-icon" />
Expand Down Expand Up @@ -262,16 +273,28 @@ class BrowserWindow extends HTMLElement {
*
* @param {string} name The name of the app.
*/
setAppName(name) {
setApplicationName(name) {
this.applicationName = name;
this.titleBarText.textContent = name;
}

/**
* Set the src of the title bar icon.
*
* @param {string} url The URL of the icon to use.
*/
setApplicationIcon(url) {
this.applicationIcon = url;
this.titleBarIcon.src = url;
}

/**
* Set the icon of the app, if any, to which the currently loaded web page belongs.
*
* @param {string} url The URL of the icon to load.
*/
setAppIcon(url) {
set(url) {
this.applicationIcon = url;
this.titleBarIcon.src = url;
}

Expand All @@ -295,6 +318,8 @@ class BrowserWindow extends HTMLElement {
this.handleIPCMessage.bind(this));
this.webview.addEventListener('did-attach',
this.handleWebviewReady.bind(this));
this.titleBarIcon.addEventListener('click',
this.handleTitleBarIconClick.bind(this));
this.urlBarInput.addEventListener('focus',
this.handleUrlBarFocus.bind(this));
this.urlBarInput.addEventListener('blur',
Expand Down Expand Up @@ -338,10 +363,10 @@ class BrowserWindow extends HTMLElement {
case 'display-mode':
break;
case 'application-name':
this.setAppName(newValue);
this.setApplicationName(newValue || '');
break;
case 'application-icon':
this.setAppIcon(newValue);
this.setApplicationIcon(newValue || this.DEFAULT_FAVICON_URL);
break;
case 'src':
if (newValue != this.currentUrl) {
Expand All @@ -358,6 +383,30 @@ class BrowserWindow extends HTMLElement {
this.webview.goBack();
}

/**
* Handle a click on the title bar icon.
*
* @param {Event} event The click event.
*/
handleTitleBarIconClick(event) {
let hostname;
try {
hostname = new URL(this.currentUrl).hostname;
} catch(error) {
hostname = '';
}
const siteInfoMenu = new SiteInfoMenu(
this.applicationName,
hostname,
// TODO: Figure out how to get a higher resolution icon
this.applicationIcon,
true,
true);
this.shadowRoot.appendChild(siteInfoMenu);
siteInfoMenu.addEventListener('_unpinappbuttonclicked',
this.dispatchUnpinAppRequest.bind(this));
}

/**
* Handle a navigation to a new page.
*
Expand Down Expand Up @@ -669,6 +718,19 @@ class BrowserWindow extends HTMLElement {
console.error('Failed to fetch or parse web app manifest: ' + error);
});
}

/**
* Dispatch a request to unpin the app the current page belongs to.
*/
dispatchUnpinAppRequest() {
const documentUrl = this.currentUrl;
this.dispatchEvent(new CustomEvent('_unpinapprequested', {
detail: {
documentUrl: documentUrl
},
bubbles: true
}));
}
}

// Register custom element
Expand Down
33 changes: 23 additions & 10 deletions chrome/js/views/components/site-info-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ class SiteInfoMenu extends HTMLElement {
* @param {string} hostname - Host name of website or web app.
* @param {string} iconUrl - URL of app icon or site icon.
* @param {boolean} isApp - True if web app manifest detected.
* @param {boolean} isPinned - True if app is pinned.
*/
constructor(name, hostname, iconUrl, isApp) {
constructor(name, hostname, iconUrl, isApp, isPinned) {
super();

this.attachShadow({ mode: 'open' });
Expand All @@ -32,14 +33,12 @@ class SiteInfoMenu extends HTMLElement {
}
.site-info {
left: 9px;
top: 60px;
position: fixed;
width: 250px;
display: block;
background-color: #e5e5e5;
border-radius: 5px;
border: solid 1px #bfbfbf;
position: fixed;
padding: 10px;
margin: 7.5px 0;
z-index: 2;
Expand Down Expand Up @@ -115,15 +114,23 @@ class SiteInfoMenu extends HTMLElement {
</menu>
`;

let siteInfo;
let pinOrUnpin;
if(isPinned) {
this.isPinned = true;
pinOrUnpin = 'Unpin';
} else {
this.isPinned = false;
pinOrUnpin = 'Pin';
}

let siteInfo;
if (isApp) {
siteInfo = `
<h1>Pin App</h1>
<h1>${pinOrUnpin} App</h1>
<img class="app-icon" src="${iconUrl}" />
<span class="app-name">${name}</span>
<span class="app-hostname">from ${hostname}</span>
<button class="pin-button">Pin</button>
<button class="pin-button">${pinOrUnpin}</button>
`;
} else {
siteInfo = `
Expand Down Expand Up @@ -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();
}

Expand Down
41 changes: 38 additions & 3 deletions chrome/js/views/windows-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down Expand Up @@ -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.
*
Expand All @@ -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');
}
},

Expand Down

0 comments on commit b259d2a

Please sign in to comment.