Skip to content

Commit

Permalink
View pinned apps - closes #50
Browse files Browse the repository at this point in the history
  • Loading branch information
benfrancis committed Dec 3, 2024
1 parent 443937c commit df1cc62
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 27 deletions.
11 changes: 10 additions & 1 deletion chrome/js/models/web-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class WebApp {
* https://w3c.github.io/manifest/#processing-text-members
*
* @param {string} value The raw value provided.
* @return {string} The processed value.
* @return {string|undefined} The processed value.
*/
processTextMember(value) {
// "If json[member] doesn't exists or json[member] is not a string, return."
Expand Down Expand Up @@ -540,6 +540,15 @@ class WebApp {
}
}

/**
* Get the shortest available app name.
*
* @returns {string|undefined} The short_name or name of the app.
*/
getShortestName() {
return this.dictionary.shortName || this.dictionary.name;
}

/**
* Get navigation scope.
*
Expand Down
26 changes: 15 additions & 11 deletions chrome/js/models/web-apps.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,22 @@ class WebApps {
* Refresh the list of apps in memory.
*/
async refreshAppList() {
this.db.listApps().then((appRecords) => {
appRecords.forEach((appRecord, appId) => {
try {
let webApp = new WebApp(appRecord.manifest, appRecord.manifestUrl,
appRecord.documentUrl);
this.apps.set(appId, webApp);
} catch(error) {
console.error('Failed to instantiate web app with id ' + appId + ' ' + error);
}
});
return;
let appRecords = new Map();
try {
appRecords = await this.db.listApps();
} catch(error) {
console.error('Error retrieving list of apps from database: ' + error);
}
appRecords.forEach((appRecord, appId) => {
try {
let webApp = new WebApp(appRecord.manifest, appRecord.manifestUrl,
appRecord.documentUrl);
this.apps.set(appId, webApp);
} catch(error) {
console.error('Failed to instantiate web app with id ' + appId + ' ' + error);
}
});
return;
}

/**
Expand Down
66 changes: 66 additions & 0 deletions chrome/js/views/components/app-icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* App Icon.
*
* An app launcher icon.
*/
class AppIcon extends HTMLElement {

/**
* Constructor.
*
* @param {string} id - App ID to associate with icon.
* @param {string} src - Path of icon image.
* @param {string} name - Name of app from web app manifest.
* @param {string} startUrl - Starting URL of app to load.
*/
constructor(id, src, name, startUrl) {
super();
this.id = id;
this.startUrl = startUrl;

this.attachShadow({ mode: 'open' });
const template = document.createElement('template');

template.innerHTML = `
<style>
.app-icon {
display: block;
width: 128px;
height: 128px;
color: #fff;
text-align: center;
margin-left: 0;
margin-right: 16px;
margin-bottom: 16px;
text-decoration: none;
}
.app-icon-img {
width: 64px;
height: 64px;
}
.app-icon-name {
display: block;
margin-top: 10px;
overflow: hidden;
text-overflow: ellipsis;
font-size: 13px;
height: 1.3em;
}
</style>
<a id=${id} href="${startUrl}" class="app-icon">
<img src="${src}" class="app-icon-img" />
<span class="app-icon-name">${name}</span>
</span>
`;

const templateClone = template.content.cloneNode(true);
this.shadowRoot.appendChild(templateClone);
}

}

// Register custom element
customElements.define('app-icon', AppIcon);
55 changes: 43 additions & 12 deletions chrome/js/views/components/browser-window.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
const path = require('path');

/**
* Browser Window.
*
* A web component representing a window in browser or standalone display mode.
*
*/
class BrowserWindow extends HTMLElement {

NEW_TAB_URL = 'file://' + path.join(__dirname, '/newtab/index.html');
DEFAULT_FAVICON_URL = 'images/default-favicon.svg';
SITE_INFO_APP_ICON_SIZE = 64;
currentFaviconUrl = this.DEFAULT_FAVICON_URL;
static observedAttributes = ['display', 'application-name', 'application-icon'];
static observedAttributes = ['display-mode', 'application-name', 'application-icon', 'src'];

/**
* Constructor.
Expand All @@ -35,7 +37,7 @@ class BrowserWindow extends HTMLElement {
display: none;
}
:host([display='standalone']) .title-bar {
:host([display-mode='standalone']) .title-bar {
display: flex;
position: absolute;
top: 0;
Expand Down Expand Up @@ -75,7 +77,7 @@ class BrowserWindow extends HTMLElement {
box-sizing: border-box;
}
:host([display='standalone']) .browser-toolbar {
:host([display-mode='standalone']) .browser-toolbar {
display: none;
}
Expand Down Expand Up @@ -211,7 +213,7 @@ class BrowserWindow extends HTMLElement {
<input type="button" value="" class="reload-button">
</form>
</menu>
<webview class="browser-window-webview" src="https://duckduckgo.com/" preload="js/webview-preload.js"></webview>
<webview class="browser-window-webview" src="${this.NEW_TAB_URL}" preload="js/webview-preload.js"></webview>
`;

this.shadowRoot.appendChild(template.content.cloneNode(true));
Expand Down Expand Up @@ -274,7 +276,7 @@ class BrowserWindow extends HTMLElement {
}

/**
* Add event listeners when element appended into document.
* Add event listeners when element added into document.
*/
connectedCallback() {
this.webview.addEventListener('will-navigate',
Expand All @@ -291,6 +293,8 @@ class BrowserWindow extends HTMLElement {
this.handleFaviconUpdated.bind(this));
this.webview.addEventListener('ipc-message',
this.handleIPCMessage.bind(this));
this.webview.addEventListener('did-attach',
this.handleWebviewReady.bind(this));
this.urlBarInput.addEventListener('focus',
this.handleUrlBarFocus.bind(this));
this.urlBarInput.addEventListener('blur',
Expand All @@ -305,6 +309,16 @@ class BrowserWindow extends HTMLElement {
this.handleFaviconClick.bind(this));
}

/**
* Do things that need to be done once the webview is attached to the DOM.
*
* @param {Event} event The did-attach event.
*/
handleWebviewReady(event) {
// Uncomment this line to show developer tools for embedded webview.
//this.webview.openDevTools();
}

/**
* Remove event listeners when element disconnected from DOM.
*/
Expand All @@ -321,14 +335,19 @@ class BrowserWindow extends HTMLElement {
*/
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case 'display':
case 'display-mode':
break;
case 'application-name':
this.setAppName(newValue);
break;
case 'application-icon':
this.setAppIcon(newValue);
break;
case 'src':
if (newValue != this.currentUrl) {
this.webview.loadURL(newValue);
}
break;
}
}

Expand All @@ -355,8 +374,16 @@ class BrowserWindow extends HTMLElement {
// Reset manifest URL and favicon
this.currentManifestUrl = null;
this.favicon.src = this.currentFaviconUrl = this.DEFAULT_FAVICON_URL;
this.urlBarInput.value = hostname;
this.urlBarInput.blur();
// If new tab then clear and focus the URL bar
if (event.url === this.NEW_TAB_URL) {
this.urlBarInput.focus();
this.urlBarInput.value = '';
// Otherwise blur the URL bar and set its value to the hostname
} else {
this.urlBarInput.blur();
this.urlBarInput.value = hostname;
}
this.setAttribute('src', event.url);
// Dispatch _locationchanged event
this.dispatchEvent(new CustomEvent('_locationchanged', {
detail: {
Expand Down Expand Up @@ -388,8 +415,12 @@ class BrowserWindow extends HTMLElement {
*/
handleUrlBarFocus() {
this.urlBar.classList.add('focused');
this.urlBarInput.value = this.currentUrl;
this.urlBarInput.select();
if (this.currentUrl === this.NEW_TAB_URL) {
this.urlBarInput.value = '';
} else {
this.urlBarInput.value = this.currentUrl;
this.urlBarInput.select();
}
}

/**
Expand Down Expand Up @@ -439,7 +470,7 @@ class BrowserWindow extends HTMLElement {
/**
* Handle the webview starting loading.
*/
handleStartLoading() {
handleStartLoading() {
this.urlBar.classList.add('loading');
}

Expand Down
6 changes: 3 additions & 3 deletions chrome/js/views/windows-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const WindowsView = {
const documentUrl = browserWindow.element.getUrl();
if(app.isWithinScope(documentUrl)) {
// Apply manifest to turn browsing context into application context
browserWindow.element.setAttribute('display', 'standalone');
browserWindow.element.setAttribute('display-mode', 'standalone');
browserWindow.element.setAttribute('application-name', app.name || app.short_name || '');
browserWindow.element.setAttribute('application-icon', app.getBestIconUrl(this.TITLE_BAR_APP_ICON_SIZE));
}
Expand Down Expand Up @@ -224,12 +224,12 @@ const WindowsView = {
const browserWindow = this.windows.get(windowId);
if (app) {
// Apply manifest to turn the browsing context into an application context
browserWindow.element.setAttribute('display', 'standalone');
browserWindow.element.setAttribute('display-mode', 'standalone');
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
browserWindow.element.setAttribute('display', 'browser');
browserWindow.element.setAttribute('display-mode', 'browser');
browserWindow.element.setAttribute('application-name', '');
browserWindow.element.setAttribute('application-icon', this.DEFAULT_APP_ICON_URL);
}
Expand Down
19 changes: 19 additions & 0 deletions chrome/newtab/css/new-tab.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}

body {
font-family: 'Fira Sans', sans-serif;
background-color: #5d5d5d;
}

#apps {
padding: 32px;
}

app-icon {
float: left;
}
18 changes: 18 additions & 0 deletions chrome/newtab/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Tab</title>
<link rel="stylesheet" href="../css/fonts.css" type="text/css" />
<link rel="stylesheet" href="css/new-tab.css" type="text/css" />
<script type="text/javascript" src="../js/database.js"></script>
<script type="text/javascript" src="../js/models/web-app.js"></script>
<script type="text/javascript" src="../js/models/web-apps.js"></script>
<script type="text/javascript" src="../js/views/components/app-icon.js"></script>
<script type="text/javascript" src="js/new-tab.js"></script>
</head>
<body>
<div id="apps"></div>
</body>
</html>
43 changes: 43 additions & 0 deletions chrome/newtab/js/new-tab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* New Tab.
*
* New tab page. Displays app icons of pinned apps.
*/
const NewTab = {
APP_ICON_SIZE: 64,

start: function() {
console.log('Starting new tab page...');
this.appsElement = document.getElementById('apps');
// Start database, app manager and views.
Database.start().then(() => {
this.webApps = new WebApps(Database);
return this.webApps.start();
}).then(() => {
this.showApps();
});
},

/**
* Show app icons for all pinned apps.
*/
showApps: function() {
const webApps = this.webApps.list();
webApps.forEach((webApp, webAppId, map) => {
const appIcon = new AppIcon(
webApp.id,
webApp.getBestIconUrl(this.APP_ICON_SIZE),
webApp.getShortestName(),
webApp.startUrl);
this.appsElement.insertAdjacentElement('beforeend', appIcon);
});
}
}

/**
* Start on load.
*/
window.addEventListener('load', function newtab_onLoad() {
window.removeEventListener('load', newtab_onLoad);
NewTab.start();
});

0 comments on commit df1cc62

Please sign in to comment.