feat: 新增包文件
This commit is contained in:
9
packages/chrome-ui/ui/manifest.json
Normal file
9
packages/chrome-ui/ui/manifest.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "WebUI",
|
||||
"version": "1.0.0",
|
||||
"manifest_version": 3,
|
||||
"permissions": [],
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "new-tab.html"
|
||||
}
|
||||
}
|
||||
31
packages/chrome-ui/ui/new-tab.html
Normal file
31
packages/chrome-ui/ui/new-tab.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>New Tab</title>
|
||||
<style>
|
||||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>New Tab</h1>
|
||||
<ul>
|
||||
<li><a href="https://www.google.com">https://www.google.com</a></li>
|
||||
<li><a href="https://www.youtube.com">https://www.youtube.com</a></li>
|
||||
<li>
|
||||
<a href="https://github.com/electron/electron">https://github.com/electron/electron</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/samuelmaddock/electron-browser-shell"
|
||||
>https://github.com/samuelmaddock/electron-browser-shell</a
|
||||
>
|
||||
</li>
|
||||
<li><a href="https://chromewebstore.google.com">https://chromewebstore.google.com</a></li>
|
||||
<li><a href="https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home">https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home</a></li>
|
||||
<li><a href="https://permission.site">https://permission.site</a></li>
|
||||
<li><a href="https://samuelmaddock.com">https://samuelmaddock.com</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
253
packages/chrome-ui/ui/webui.html
Normal file
253
packages/chrome-ui/ui/webui.html
Normal file
@@ -0,0 +1,253 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Shell Browser</title>
|
||||
<style>
|
||||
:root {
|
||||
/* https://colorhunt.co/palette/161920 */
|
||||
--bg-color: #39375b;
|
||||
--text-color: #ececec;
|
||||
--tab-color: #745c97;
|
||||
--toolbar-control-color: #d597ce;
|
||||
|
||||
/* electron-chrome-extensions colors */
|
||||
--browser-action-badge-outline: var(--tab-color);
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--tab-color);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
/*----- TABS -----*/
|
||||
|
||||
#tabstrip {
|
||||
width: 100%;
|
||||
height: calc(env(titlebar-area-height, 31px) + 1px);
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
position: fixed;
|
||||
left: env(titlebar-area-x);
|
||||
top: env(titlebar-area-y);
|
||||
width: env(titlebar-area-width);
|
||||
height: calc(env(titlebar-area-height, 31px) + 1px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#createtab {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
#createtab:hover {
|
||||
background: rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 0.2rem 0.4rem;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
width: 12rem;
|
||||
box-shadow: inset -1px 0 0 0 rgba(0,0,0,0.33);
|
||||
}
|
||||
|
||||
.tab[data-active] {
|
||||
background: var(--tab-color);
|
||||
}
|
||||
|
||||
.tab .favicon[src] {
|
||||
display: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
|
||||
.tab .favicon[src].loaded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab .title {
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 0.725rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tab .controls {
|
||||
flex: 0 0 auto;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.tab .controls .control {
|
||||
background: rgba(0,0,0,0.2);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
margin-left: 0.2rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: #aaa;
|
||||
font-size: 0.7rem;
|
||||
vertical-align: middle;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.tab .controls .control:disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-drag {
|
||||
flex: 1 0 auto;
|
||||
-webkit-app-region: drag;
|
||||
height: calc(100% - 5px);
|
||||
min-width: 2rem;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.platform-linux .window-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.window-controls .control {
|
||||
width: 2.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.window-controls .control:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/*----- TOOLBAR -----*/
|
||||
|
||||
.toolbar {
|
||||
height: 1.875rem;
|
||||
background-color: var(--tab-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.2rem 0.5rem;
|
||||
}
|
||||
|
||||
.toolbar .page-controls {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.address-bar {
|
||||
flex: 1 0 auto;
|
||||
line-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.address-bar input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
border: none;
|
||||
border-radius: 2rem;
|
||||
padding: 0 0.5rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.toolbar .control {
|
||||
background: none;
|
||||
border: none;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<div id="tabstrip">
|
||||
<div class="tab-container">
|
||||
<ul class="tab-list">
|
||||
<template id="tabtemplate">
|
||||
<li class="tab">
|
||||
<img class="favicon" />
|
||||
<span class="title"></span>
|
||||
<div class="controls">
|
||||
<button class="control audio" disabled>🔊</button>
|
||||
<button class="control close">✕</button>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<button id="createtab">+</button>
|
||||
<div class="app-drag"></div>
|
||||
<div class="window-controls">
|
||||
<button id="minimize" class="control">🗕</button>
|
||||
<button id="maximize" class="control">🗖</button>
|
||||
<button id="close" class="control">🗙</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<div class="page-controls">
|
||||
<button id="goback" class="control">⬅️</button>
|
||||
<button id="goforward" class="control">➡️</button>
|
||||
<button id="reload" class="control">🔄</button>
|
||||
</div>
|
||||
<div class="address-bar">
|
||||
<input id="addressurl" spellcheck="false" />
|
||||
</div>
|
||||
<browser-action-list id="actions" alignment="bottom left"></browser-action-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./webui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
193
packages/chrome-ui/ui/webui.js
Normal file
193
packages/chrome-ui/ui/webui.js
Normal file
@@ -0,0 +1,193 @@
|
||||
class WebUI {
|
||||
windowId = -1
|
||||
activeTabId = -1
|
||||
/** @type {chrome.tabs.Tab[]} */
|
||||
tabList = []
|
||||
|
||||
constructor() {
|
||||
const $ = document.querySelector.bind(document)
|
||||
|
||||
this.$ = {
|
||||
tabList: $('#tabstrip .tab-list'),
|
||||
tabTemplate: $('#tabtemplate'),
|
||||
createTabButton: $('#createtab'),
|
||||
goBackButton: $('#goback'),
|
||||
goForwardButton: $('#goforward'),
|
||||
reloadButton: $('#reload'),
|
||||
addressUrl: $('#addressurl'),
|
||||
|
||||
browserActions: $('#actions'),
|
||||
|
||||
minimizeButton: $('#minimize'),
|
||||
maximizeButton: $('#maximize'),
|
||||
closeButton: $('#close'),
|
||||
}
|
||||
|
||||
this.$.createTabButton.addEventListener('click', () => chrome.tabs.create())
|
||||
this.$.goBackButton.addEventListener('click', () => chrome.tabs.goBack())
|
||||
this.$.goForwardButton.addEventListener('click', () => chrome.tabs.goForward())
|
||||
this.$.reloadButton.addEventListener('click', () => chrome.tabs.reload())
|
||||
this.$.addressUrl.addEventListener('keypress', this.onAddressUrlKeyPress.bind(this))
|
||||
|
||||
this.$.minimizeButton.addEventListener('click', () =>
|
||||
chrome.windows.get(chrome.windows.WINDOW_ID_CURRENT, (win) => {
|
||||
chrome.windows.update(win.id, { state: win.state === 'minimized' ? 'normal' : 'minimized' })
|
||||
}),
|
||||
)
|
||||
this.$.maximizeButton.addEventListener('click', () =>
|
||||
chrome.windows.get(chrome.windows.WINDOW_ID_CURRENT, (win) => {
|
||||
chrome.windows.update(win.id, { state: win.state === 'maximized' ? 'normal' : 'maximized' })
|
||||
}),
|
||||
)
|
||||
this.$.closeButton.addEventListener('click', () => chrome.windows.remove())
|
||||
|
||||
const platformClass = `platform-${navigator.userAgentData.platform.toLowerCase()}`
|
||||
document.body.classList.add(platformClass)
|
||||
|
||||
this.initTabs()
|
||||
}
|
||||
|
||||
async initTabs() {
|
||||
const tabs = await new Promise((resolve) => chrome.tabs.query({ windowId: -2 }, resolve))
|
||||
this.tabList = [...tabs]
|
||||
this.renderTabs()
|
||||
|
||||
const activeTab = this.tabList.find((tab) => tab.active)
|
||||
if (activeTab) {
|
||||
this.setActiveTab(activeTab)
|
||||
}
|
||||
|
||||
// Wait to setup tabs and windowId prior to listening for updates.
|
||||
this.setupBrowserListeners()
|
||||
}
|
||||
|
||||
setupBrowserListeners() {
|
||||
if (!chrome.tabs.onCreated) {
|
||||
throw new Error(`chrome global not setup. Did the extension preload not get run?`)
|
||||
}
|
||||
|
||||
const findTab = (tabId) => {
|
||||
const existingTab = this.tabList.find((tab) => tab.id === tabId)
|
||||
return existingTab
|
||||
}
|
||||
|
||||
const findOrCreateTab = (tabId) => {
|
||||
const existingTab = findTab(tabId)
|
||||
if (existingTab) return existingTab
|
||||
|
||||
const newTab = { id: tabId }
|
||||
this.tabList.push(newTab)
|
||||
return newTab
|
||||
}
|
||||
|
||||
chrome.tabs.onCreated.addListener((tab) => {
|
||||
if (tab.windowId !== this.windowId) return
|
||||
const newTab = findOrCreateTab(tab.id)
|
||||
Object.assign(newTab, tab)
|
||||
this.renderTabs()
|
||||
})
|
||||
|
||||
chrome.tabs.onActivated.addListener((activeInfo) => {
|
||||
if (activeInfo.windowId !== this.windowId) return
|
||||
|
||||
this.setActiveTab(activeInfo)
|
||||
})
|
||||
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, details) => {
|
||||
const tab = findTab(tabId)
|
||||
if (!tab) return
|
||||
Object.assign(tab, details)
|
||||
this.renderTabs()
|
||||
if (tabId === this.activeTabId) this.renderToolbar(tab)
|
||||
})
|
||||
|
||||
chrome.tabs.onRemoved.addListener((tabId) => {
|
||||
const tabIndex = this.tabList.findIndex((tab) => tab.id === tabId)
|
||||
if (tabIndex > -1) {
|
||||
this.tabList.splice(tabIndex, 1)
|
||||
this.$.tabList.querySelector(`[data-tab-id="${tabId}"]`).remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setActiveTab(activeTab) {
|
||||
this.activeTabId = activeTab.id || activeTab.tabId
|
||||
this.windowId = activeTab.windowId
|
||||
|
||||
for (const tab of this.tabList) {
|
||||
if (tab.id === this.activeTabId) {
|
||||
tab.active = true
|
||||
this.renderTab(tab)
|
||||
this.renderToolbar(tab)
|
||||
} else {
|
||||
if (tab.active) {
|
||||
tab.active = false
|
||||
this.renderTab(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAddressUrlKeyPress(event) {
|
||||
if (event.code === 'Enter') {
|
||||
const url = this.$.addressUrl.value
|
||||
chrome.tabs.update({ url })
|
||||
}
|
||||
}
|
||||
|
||||
createTabNode(tab) {
|
||||
const tabElem = this.$.tabTemplate.content.cloneNode(true).firstElementChild
|
||||
tabElem.dataset.tabId = tab.id
|
||||
|
||||
tabElem.addEventListener('click', () => {
|
||||
chrome.tabs.update(tab.id, { active: true })
|
||||
})
|
||||
tabElem.querySelector('.close').addEventListener('click', () => {
|
||||
chrome.tabs.remove(tab.id)
|
||||
})
|
||||
const faviconElem = tabElem.querySelector('.favicon')
|
||||
faviconElem?.addEventListener('load', () => {
|
||||
faviconElem.classList.toggle('loaded', true)
|
||||
})
|
||||
faviconElem?.addEventListener('error', () => {
|
||||
faviconElem.classList.toggle('loaded', false)
|
||||
})
|
||||
|
||||
this.$.tabList.appendChild(tabElem)
|
||||
return tabElem
|
||||
}
|
||||
|
||||
renderTab(tab) {
|
||||
let tabElem = this.$.tabList.querySelector(`[data-tab-id="${tab.id}"]`)
|
||||
if (!tabElem) tabElem = this.createTabNode(tab)
|
||||
|
||||
if (tab.active) {
|
||||
tabElem.dataset.active = ''
|
||||
} else {
|
||||
delete tabElem.dataset.active
|
||||
}
|
||||
|
||||
const favicon = tabElem.querySelector('.favicon')
|
||||
if (tab.favIconUrl) {
|
||||
favicon.src = tab.favIconUrl
|
||||
} else {
|
||||
delete favicon.src
|
||||
}
|
||||
|
||||
tabElem.querySelector('.title').textContent = tab.title
|
||||
tabElem.querySelector('.audio').disabled = !tab.audible
|
||||
}
|
||||
|
||||
renderTabs() {
|
||||
this.tabList.forEach((tab) => {
|
||||
this.renderTab(tab)
|
||||
})
|
||||
}
|
||||
|
||||
renderToolbar(tab) {
|
||||
this.$.addressUrl.value = tab.url
|
||||
// this.$.browserActions.tab = tab.id
|
||||
}
|
||||
}
|
||||
|
||||
window.webui = new WebUI()
|
||||
Reference in New Issue
Block a user