Compare commits
26 Commits
e812aa8feb
...
b48278bb17
Author | SHA1 | Date |
---|---|---|
Thomas Rupprecht | b48278bb17 | |
Thomas Rupprecht | 67382eadc0 | |
Thomas Rupprecht | 45c8deb007 | |
Thomas Rupprecht | cbf21a24ad | |
Thomas Rupprecht | de70d42b2b | |
Thomas Rupprecht | 6b69d7cd6a | |
Thomas Rupprecht | fc2ad7afad | |
Thomas Rupprecht | 4f717f7c80 | |
Thomas Rupprecht | a1cf58d8d3 | |
Thomas Rupprecht | b41bf2b183 | |
Thomas Rupprecht | c3cf1e9b96 | |
Thomas Rupprecht | 489d675ba8 | |
Thomas Rupprecht | f519a44573 | |
Thomas Rupprecht | 27fe41e9b3 | |
Thomas Rupprecht | e1fdc193f5 | |
Thomas Rupprecht | 7d36a610dc | |
Thomas Rupprecht | 241715e7ac | |
Thomas Rupprecht | 50d97a681e | |
Thomas Rupprecht | 2f5e6ecdb1 | |
Thomas Rupprecht | d898b1ea2a | |
Thomas Rupprecht | 0476519e99 | |
Thomas Rupprecht | e3d6cce769 | |
Thomas Rupprecht | ea13c09289 | |
Thomas Rupprecht | 57d290c744 | |
Thomas Rupprecht | dc89ef43b5 | |
Thomas Rupprecht | 863315d77c |
|
@ -3,7 +3,7 @@
|
|||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*.{html,css,js,json}]
|
||||
[*.{html,css,js,json,svg}]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
/web-ext-artifacts
|
||||
/web-ext-artifacts
|
||||
src/browser-polyfill.js
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
## Install
|
||||
```ssh
|
||||
$ git clone https://gitea.usrspace.at/XimeX/usrspace-browser-addon.git
|
||||
$ npm install -g web-ext
|
||||
$ npm install
|
||||
```
|
||||
|
||||
- Open `about:debugging` or `about:debugging-new` in Firefox
|
||||
|
@ -20,9 +22,8 @@ $ git clone https://gitea.usrspace.at/XimeX/usrspace-browser-addon.git
|
|||
|
||||
## Release
|
||||
```ssh
|
||||
$ git commit -m "release vX.Y"
|
||||
$ git tag vX.Y
|
||||
$ web-ext build -n _usr_space-X.Y.zip
|
||||
$ npm version {major|minor|patch|...}
|
||||
$ web-ext build
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>/usr/space</title>
|
||||
<script type="application/javascript" src="../browser-polyfill.js"></script>
|
||||
<script type="application/javascript" src="../config.js"></script>
|
||||
<script type="application/javascript" src="index.js"></script>
|
||||
</head>
|
||||
</html>
|
|
@ -1,87 +0,0 @@
|
|||
function fetchJson(url) {
|
||||
return fetch(url).then((response) => (response.json()));
|
||||
}
|
||||
|
||||
function fetchCalendar(days = 28) {
|
||||
let url = `${Config.calenderUrl}?o=json`;
|
||||
if (days) {
|
||||
url += `&r=${days}`;
|
||||
}
|
||||
return fetchJson(url);
|
||||
}
|
||||
|
||||
function fetchSpaceApi() {
|
||||
return fetchJson(Config.spaceApiUrl);
|
||||
}
|
||||
|
||||
async function updateBadge(open) {
|
||||
let badgeText, badgeColor;
|
||||
if (open) {
|
||||
badgeText = browser.browserAction.setBadgeText({text: 'open'});
|
||||
badgeColor = browser.browserAction.setBadgeBackgroundColor({color: Config.openColor});
|
||||
} else {
|
||||
badgeText = browser.browserAction.setBadgeText({text: ''});
|
||||
badgeColor = browser.browserAction.setBadgeBackgroundColor({color: null});
|
||||
}
|
||||
try {
|
||||
await Promise.all([badgeText, badgeColor]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function sendNotification(open) {
|
||||
browser.notifications.create('status-change', {
|
||||
type: 'basic',
|
||||
title: 'Space Status',
|
||||
message: `Space ist jetzt ${open ? 'offen' : 'geschlossen'}.`,
|
||||
iconUrl: browser.runtime.getURL('icons/favicon.svg')
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchNewData() {
|
||||
try {
|
||||
const json = await fetchCalendar();
|
||||
window.calendar = json;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const json = await fetchSpaceApi();
|
||||
if (window.spaceApi && window.spaceApi.state.open !== json.state.open) {
|
||||
sendNotification(json.state.open)
|
||||
}
|
||||
window.spaceApi = json;
|
||||
updateBadge(window.spaceApi.state.open);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
let intervalHandler = null;
|
||||
|
||||
function stopFetching() {
|
||||
if (intervalHandler !== null) {
|
||||
clearInterval(intervalHandler);
|
||||
intervalHandler = null;
|
||||
}
|
||||
}
|
||||
function startFetching() {
|
||||
fetchNewData();
|
||||
if (intervalHandler === null) {
|
||||
intervalHandler = setInterval(() => {
|
||||
fetchNewData();
|
||||
}, Config.refreshTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
stopFetching();
|
||||
});
|
||||
window.addEventListener('online', () => {
|
||||
startFetching();
|
||||
});
|
||||
if (window.navigator.onLine) {
|
||||
startFetching();
|
||||
}
|
1187
browser-polyfill.js
|
@ -1,23 +0,0 @@
|
|||
<svg id="root" width="16" height="16" viewBox="-32 -32 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>/usr/space</title>
|
||||
<desc>Logo /usr/space</desc>
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
#root {
|
||||
stroke: #2AA1BF;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#root {
|
||||
stroke: #FFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<marker id="circle" viewBox="-4 -4 8 8" markerWidth="8.5" markerHeight="8.5" markerUnits="userSpaceOnUse" refX="3" orient="auto-start-reverse">
|
||||
<circle cx="0" cy="0" r="2" fill="none" stroke-width="4"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<g fill="none" stroke-width="4">
|
||||
<path marker-start="url(#circle)" marker-end="url(#circle)" d="M 0,-21.69431 0,-1.299519 a 2.2499999,2.2499999 0 0 0 1.125624,1.948918 l 21.5,12.403846 A 2.25,2.25 0 0 0 26,11.104327 v -24.804808 a 2.2499999,2.2499999 0 0 0 -1.125624,-1.948918 l -23.75,-13.701923 a 2.25,2.25 0 0 0 -2.248752,0 l -23.749999,13.701923 A 2.2499999,2.2499999 0 0 0 -26,-13.700481 v 24.804808 a 2.25,2.25 0 0 0 3.374376,1.948918 L -6.154917,3.550914"/>
|
||||
<path marker-start="url(#circle)" marker-end="url(#circle)" d="m 18.850376,19.124785 -17.726,10.226537 a 2.25,2.25 0 0 1 -2.248752,0 L -18.844457,19.128198"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 30 KiB |
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="10cm" height="10cm" viewBox="0 0 100 100">
|
||||
<g>
|
||||
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||
<path d="M10 19.5A66 66 0 0 1 90 19.5L76.7 37.0A44 44 0 0 0 23.3 37.0Z" fill="none" stroke="#111111"/>
|
||||
<path d="M10 53.7A44 44 0 0 1 90 53.7L70.0 62.8A22 22 0 0 0 30.0 62.8Z" fill="none" stroke="#666666"/>
|
||||
<circle cx="50" cy="72" r="22" fill="none" stroke="#bbbbbb"/>
|
||||
<text x="50" y="24" font-family="Liberation Sans" font-size="14px" text-anchor="middle" fill="#111111" dx="6px">/usr/space</text>
|
||||
<text x="50" y="47" font-family="Liberation Sans" font-size="14px" text-anchor="middle" fill="#666666">Kernel</text>
|
||||
<text x="50" y="72" font-family="Liberation Sans" font-size="14px" text-anchor="middle" fill="#bbbbbb" font-weight="bold" dy="5.5px">HW</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 918 B |
BIN
icons/logo.png
Before Width: | Height: | Size: 33 KiB |
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="10cm" height="10cm" viewBox="0 0 100 100">
|
||||
<g>
|
||||
<rect width="100%" height="100%" fill="#ffffff"/>
|
||||
<path d="M10 19.5A66 66 0 0 1 90 19.5L76.7 37.0A44 44 0 0 0 23.3 37.0Z" fill="#111111" stroke="none"/>
|
||||
<path d="M10 53.7A44 44 0 0 1 90 53.7L70.0 62.8A22 22 0 0 0 30.0 62.8Z" fill="#666666" stroke="none"/>
|
||||
<circle cx="50" cy="72" r="22" fill="#bbbbbb" stroke="none"/>
|
||||
<text x="50" y="24" font-family="Liberation Sans" font-size="14px" text-anchor="middle" fill="#ffffff" dx="6px">/usr/space</text>
|
||||
<text x="50" y="47" font-family="Liberation Sans" font-size="14px" text-anchor="middle" fill="#ffffff">Kernel</text>
|
||||
<text x="50" y="72" font-family="Liberation Sans" font-size="14px" text-anchor="middle" fill="#ffffff" font-weight="bold" dy="5.5px">HW</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 918 B |
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "/usr/space",
|
||||
"version": "0.7",
|
||||
"description": "An Add-on for the Hacker-/Maker-Space /usr/space.",
|
||||
"icons": {
|
||||
"48": "icons/favicon.svg",
|
||||
"96": "icons/favicon.svg"
|
||||
},
|
||||
"background": {
|
||||
"page": "background/index.html"
|
||||
},
|
||||
"browser_action": {
|
||||
"browser_style": true,
|
||||
//"chrome_style": true,
|
||||
"default_title": "/usr/space",
|
||||
//"default_icon": "icons/favicon.svg",
|
||||
"default_icon": {
|
||||
"16": "icons/favicon.svg",
|
||||
"32": "icons/favicon.svg"
|
||||
},
|
||||
"default_area": "navbar",
|
||||
"default_popup": "popup/index.html"
|
||||
},
|
||||
"permissions": [
|
||||
"https://www.usrspace.at/*",
|
||||
"webRequest",
|
||||
"notifications"
|
||||
],
|
||||
"author": "Thomas Rupprecht"
|
||||
//"developer": {
|
||||
// "name": "Thomas Rupprecht",
|
||||
// "url": "https://blog.ximex.at/"
|
||||
//}
|
||||
}
|
|
@ -1,26 +1,31 @@
|
|||
{
|
||||
"name": "usrspace-browser-addon",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"web-ext-types": "^3.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-ext-types": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-ext-types/-/web-ext-types-3.2.1.tgz",
|
||||
"integrity": "sha512-oQZYDU3W8X867h8Jmt3129kRVKklz70db40Y6OzoTTuzOJpF/dB2KULJUf0txVPyUUXuyzV8GmT3nVvRHoG+Ew==",
|
||||
"dev": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"web-ext-types": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-ext-types/-/web-ext-types-3.2.1.tgz",
|
||||
"integrity": "sha512-oQZYDU3W8X867h8Jmt3129kRVKklz70db40Y6OzoTTuzOJpF/dB2KULJUf0txVPyUUXuyzV8GmT3nVvRHoG+Ew==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
"name": "usrspace-browser-addon",
|
||||
"version": "0.8.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "usrspace-browser-addon",
|
||||
"version": "0.8.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"web-ext-types": "^3.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-ext-types": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-ext-types/-/web-ext-types-3.2.1.tgz",
|
||||
"integrity": "sha512-oQZYDU3W8X867h8Jmt3129kRVKklz70db40Y6OzoTTuzOJpF/dB2KULJUf0txVPyUUXuyzV8GmT3nVvRHoG+Ew==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webextension-polyfill": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz",
|
||||
"integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
33
package.json
|
@ -1,5 +1,32 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"web-ext-types": "^3.2.1"
|
||||
}
|
||||
"name": "usrspace-browser-addon",
|
||||
"description": "WebExtension for the Hacker-/Maker-Space /usr/space",
|
||||
"version": "0.8.1",
|
||||
"dependencies": {
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"web-ext-types": "^3.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"copy-browser-polyfill": "cp node_modules/webextension-polyfill/dist/browser-polyfill.js src/browser-polyfill.js",
|
||||
"copy-version": "sed -i 's/^\t\"version\": \".*\",$/\t\"version\": \"'$(rg '^\t\"version\": \"(.+)\",$' -r '$1' < package.json)'\",/' src/manifest.json",
|
||||
"prebuild": "cp LICENSE.txt src/",
|
||||
"build": "web-ext build -o -s src/ -n \"_usr_space-{version}.zip\"",
|
||||
"postbuild": "rm src/LICENSE.txt",
|
||||
"test": "echo \"Error: no test specified\" && exit 0",
|
||||
"postinstall": "npm run copy-browser-polyfill",
|
||||
"preversion": "npm test",
|
||||
"version": "npm run copy-version && git add -u",
|
||||
"postversion": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitea.usrspace.at/XimeX/usrspace-browser-addon"
|
||||
},
|
||||
"keywords": [
|
||||
"/usr/space"
|
||||
],
|
||||
"author": "Thomas Rupprecht",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
100
popup/index.js
|
@ -1,100 +0,0 @@
|
|||
const calendarSVG = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="currentColor" viewBox="0 0 16 16" role="img" aria-label="Kalender">
|
||||
<path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/>
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
</svg>
|
||||
`;
|
||||
const doorClosedSVG = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="currentColor" viewBox="0 0 16 16" role="img" aria-label="Geschlossen">
|
||||
<path d="M3 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v13h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3V2zm1 13h8V2H4v13z"/>
|
||||
<path d="M9 9a1 1 0 1 0 2 0 1 1 0 0 0-2 0z"/>
|
||||
</svg>
|
||||
`;
|
||||
const doorOpenSVG = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="currentColor" viewBox="0 0 16 16" role="img" aria-label="Offen">
|
||||
<path d="M8.5 10c-.276 0-.5-.448-.5-1s.224-1 .5-1 .5.448.5 1-.224 1-.5 1z"/>
|
||||
<path d="M10.828.122A.5.5 0 0 1 11 .5V1h.5A1.5 1.5 0 0 1 13 2.5V15h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3V1.5a.5.5 0 0 1 .43-.495l7-1a.5.5 0 0 1 .398.117zM11.5 2H11v13h1V2.5a.5.5 0 0 0-.5-.5zM4 1.934V15h6V1.077l-6 .857z"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
async function linkElementClickListener(event) {
|
||||
try {
|
||||
const tab = await browser.tabs.create({url: event.currentTarget.dataset.url});
|
||||
// console.log(tab);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const linkElements = Array.from(document.getElementsByClassName('link'));
|
||||
linkElements.forEach((linkElement) => {
|
||||
linkElement.addEventListener('click', linkElementClickListener);
|
||||
});
|
||||
|
||||
try {
|
||||
const page = await browser.runtime.getBackgroundPage();
|
||||
|
||||
updateNextEvent(page.calendar);
|
||||
updateSpaceApiJson(page.spaceApi);
|
||||
updateState(page.spaceApi);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
init();
|
||||
|
||||
function updateNextEvent(nextEvents) {
|
||||
const calendarElement = document.getElementById('calendar');
|
||||
calendarElement.innerText = '';
|
||||
|
||||
if (nextEvents.length === 0) {
|
||||
const hintNode = document.createTextNode('Keine Termine in den nächsten 4 Wochen!');
|
||||
const strongElement = document.createElement('strong');
|
||||
strongElement.append(hintNode);
|
||||
calendarElement.append(strongElement);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextEventDate = nextEvents[0].begin.substr(0, 10);
|
||||
const nextEventDateEvents = nextEvents.filter((nextEvent) => (nextEvent.begin.startsWith(nextEventDate)));
|
||||
|
||||
nextEventDateEvents.forEach((nextEventDateEvent) => {
|
||||
const divElement = document.createElement('div');
|
||||
divElement.innerHTML = calendarSVG;
|
||||
const beginDate = new Date(nextEventDateEvent.begin);
|
||||
const strongElement = document.createElement('strong');
|
||||
const timeElement = document.createElement('time');
|
||||
strongElement.textContent = nextEventDateEvent.name;
|
||||
timeElement.datetime = beginDate.toISOString();
|
||||
const dateNode = document.createTextNode(beginDate.toLocaleString([], {dateStyle: "medium", timeStyle: "short"}));
|
||||
// divElement.innerText = '';
|
||||
divElement.append(strongElement, ' ', timeElement);
|
||||
timeElement.append(dateNode);
|
||||
if (nextEventDateEvent.location) {
|
||||
const locationNode = document.createTextNode(` (${nextEventDateEvent.location})`);
|
||||
divElement.append(locationNode);
|
||||
}
|
||||
calendarElement.append(divElement);
|
||||
});
|
||||
}
|
||||
|
||||
function updateSpaceApiJson(spaceApi) {
|
||||
const spaceApiElement = document.querySelector('#space-api code');
|
||||
const json = JSON.stringify(spaceApi, null, 2).replace(/ /g, ' ').replace(/\n/g, '<br/>');
|
||||
spaceApiElement.innerHTML = json;
|
||||
// const jsonNode = document.createTextNode(json);
|
||||
// spaceApiElement.innerText = '';
|
||||
// spaceApiElement.append(jsonNode);
|
||||
}
|
||||
|
||||
function updateState(spaceApi) {
|
||||
const stateElement = document.getElementById('state');
|
||||
const since = new Date(spaceApi.state.lastchange * 1000);
|
||||
const sinceStr = ` seit <time datetime="${since.toISOString()}">${since.toLocaleString([], {dateStyle: "medium", timeStyle: "short"})}</time>`;
|
||||
if (spaceApi.state.open) {
|
||||
stateElement.innerHTML = `${doorOpenSVG}Offen ${sinceStr}`;
|
||||
} else {
|
||||
stateElement.innerHTML = `${doorClosedSVG}Geschlossen ${sinceStr}`;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"extensionName": {
|
||||
"message": "/usr/space"
|
||||
},
|
||||
"extensionDescription": {
|
||||
"message": "WebExtension für den Hacker-/Maker-Space /usr/space"
|
||||
},
|
||||
"buttonTitle": {
|
||||
"message": "/usr/space"
|
||||
},
|
||||
"loading": {
|
||||
"message": "Lädt…"
|
||||
},
|
||||
"currentState": {
|
||||
"message": "Akuteller Status"
|
||||
},
|
||||
"links": {
|
||||
"message": "Links"
|
||||
},
|
||||
"nextEvent": {
|
||||
"message": "Nächstes Event"
|
||||
},
|
||||
"spaceApiJson": {
|
||||
"message": "Space-API JSON"
|
||||
},
|
||||
"noEventsInNext4Weeks": {
|
||||
"message": "Keine Termine in den nächsten 4 Wochen!"
|
||||
},
|
||||
"openSince": {
|
||||
"message": "Offen seit "
|
||||
},
|
||||
"closedSince": {
|
||||
"message": "Geschlossen seit "
|
||||
},
|
||||
"stateNotificationTitle": {
|
||||
"message": "Space Status"
|
||||
},
|
||||
"stateNotificationMessage": {
|
||||
"message": "Space ist jetzt $STATE$.",
|
||||
"placeholders": {
|
||||
"STATE" : {
|
||||
"content" : "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"open": {
|
||||
"message": "offen"
|
||||
},
|
||||
"closed": {
|
||||
"message": "geschlossen"
|
||||
},
|
||||
"badgeOpen": {
|
||||
"message": "open"
|
||||
},
|
||||
"badgeClosed": {
|
||||
"message": " "
|
||||
},
|
||||
"badgeEvent": {
|
||||
"message": " "
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"extensionName": {
|
||||
"message": "/usr/space"
|
||||
},
|
||||
"extensionDescription": {
|
||||
"message": "WebExtension for the Hacker-/Maker-Space /usr/space"
|
||||
},
|
||||
"buttonTitle": {
|
||||
"message": "/usr/space"
|
||||
},
|
||||
"loading": {
|
||||
"message": "Loading…"
|
||||
},
|
||||
"currentState": {
|
||||
"message": "Current State"
|
||||
},
|
||||
"links": {
|
||||
"message": "Links"
|
||||
},
|
||||
"nextEvent": {
|
||||
"message": "Next Event"
|
||||
},
|
||||
"spaceApiJson": {
|
||||
"message": "Space-API JSON"
|
||||
},
|
||||
"noEventsInNext4Weeks": {
|
||||
"message": "No Events in the next 4 weeks!"
|
||||
},
|
||||
"openSince": {
|
||||
"message": "Open since "
|
||||
},
|
||||
"closedSince": {
|
||||
"message": "Closed since "
|
||||
},
|
||||
"stateNotificationTitle": {
|
||||
"message": "Space State"
|
||||
},
|
||||
"stateNotificationMessage": {
|
||||
"message": "Space is now $STATE$.",
|
||||
"placeholders": {
|
||||
"STATE" : {
|
||||
"content" : "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"open": {
|
||||
"message": "open"
|
||||
},
|
||||
"closed": {
|
||||
"message": "closed"
|
||||
},
|
||||
"badgeOpen": {
|
||||
"message": "open"
|
||||
},
|
||||
"badgeClosed": {
|
||||
"message": " "
|
||||
},
|
||||
"badgeEvent": {
|
||||
"message": " "
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>/usr/space</title>
|
||||
<script type="module" src="browser-polyfill.js"></script>
|
||||
<script type="module" src="background.js"></script>
|
||||
</head>
|
||||
</html>
|
|
@ -0,0 +1,104 @@
|
|||
import Config from './config.js';
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
async function fetchJson(url) {
|
||||
const response = await fetch(url);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} days
|
||||
* @returns {Promise<Array<object>>}
|
||||
*/
|
||||
function fetchCalendar(days = 28) {
|
||||
let url = `${Config.calenderUrl}?o=json`;
|
||||
if (days) {
|
||||
url += `&r=${days}`;
|
||||
}
|
||||
return fetchJson(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
function fetchSpaceApi() {
|
||||
return fetchJson(Config.spaceApiUrl);
|
||||
}
|
||||
|
||||
async function fetchNewData() {
|
||||
try {
|
||||
const result = await Promise.allSettled([fetchCalendar(), fetchSpaceApi()]);
|
||||
const calendarJson = result[0].value;
|
||||
const spaceApiJson = result[1].value;
|
||||
|
||||
if (spaceApiJson) {
|
||||
const now = new Date();
|
||||
const eventActive = calendarJson?.some((event) => (
|
||||
new Date(event.begin) <= now && now <= new Date(event.end)
|
||||
)) ?? false;
|
||||
|
||||
const badgeText = browser.i18n.getMessage(spaceApiJson.state.open ? 'badgeOpen' : eventActive ? 'badgeEvent' : 'badgeClosed');
|
||||
const badgeBgColor = spaceApiJson.state.open ? Config.openColor : eventActive ? Config.eventColor : Config.closedColor;
|
||||
await browser.browserAction.setBadgeText({text: badgeText});
|
||||
await browser.browserAction.setBadgeBackgroundColor({color: badgeBgColor});
|
||||
|
||||
const {spaceApi} = await browser.storage.local.get('spaceApi');
|
||||
if (spaceApi && spaceApi.state.open !== spaceApiJson.state.open) {
|
||||
const state = browser.i18n.getMessage(spaceApiJson.state.open ? 'open' : 'closed');
|
||||
await browser.notifications.create('status-change', {
|
||||
type: 'basic',
|
||||
title: browser.i18n.getMessage('stateNotificationTitle'),
|
||||
message: browser.i18n.getMessage('stateNotificationMessage', state),
|
||||
iconUrl: browser.runtime.getURL('icons/favicon.svg'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await browser.storage.local.set({
|
||||
calendar: calendarJson,
|
||||
spaceApi: spaceApiJson,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
browser.runtime.onStartup.addListener(async () => {
|
||||
await browser.storage.local.remove(['calendar', 'spaceApi']);
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {Alarm} alarm
|
||||
*/
|
||||
function handleAlarm(alarm) {
|
||||
if (alarm.name === 'fetchData') {
|
||||
fetchNewData();
|
||||
}
|
||||
}
|
||||
|
||||
function startFetching() {
|
||||
fetchNewData();
|
||||
|
||||
browser.alarms.onAlarm.addListener(handleAlarm);
|
||||
|
||||
const now = new Date();
|
||||
now.setMinutes(Math.ceil((now.getMinutes() + 1) / Config.refreshTimeout) * Config.refreshTimeout, 0, 0);
|
||||
browser.alarms.create('fetchData', {
|
||||
when: now.getTime(),
|
||||
periodInMinutes: Config.refreshTimeout,
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('offline', async () => {
|
||||
browser.alarms.onAlarm.removeListener(handleAlarm);
|
||||
await browser.alarms.clear('fetchData');
|
||||
});
|
||||
window.addEventListener('online', () => {
|
||||
startFetching();
|
||||
});
|
||||
if (window.navigator.onLine) {
|
||||
startFetching();
|
||||
}
|
|
@ -1,24 +1,26 @@
|
|||
window.Config = {
|
||||
refreshTimeout: 1000 * 60 * 5, // 5min,
|
||||
export default {
|
||||
refreshTimeout: 5,
|
||||
spaceApiUrl: 'https://www.usrspace.at/spaceapi.json',
|
||||
calenderUrl: 'https://www.usrspace.at/calendar.php',
|
||||
openColor: '#33cc33',
|
||||
closedColor: '#cc3333',
|
||||
eventColor: '#9933cc',
|
||||
quickLinks: [
|
||||
{
|
||||
url: 'https://www.usrspace.at/',
|
||||
text: 'Homepage'
|
||||
text: 'Homepage',
|
||||
},
|
||||
{
|
||||
url: 'https://wiki.usrspace.at/',
|
||||
text: 'Wiki'
|
||||
text: 'Wiki',
|
||||
},
|
||||
{
|
||||
url: 'https://gitea.usrspace.at/',
|
||||
text: 'Gitea'
|
||||
text: 'Gitea',
|
||||
},
|
||||
{
|
||||
url: 'https://cloud.usrspace.at/',
|
||||
text: 'Nextcloud'
|
||||
}
|
||||
]
|
||||
text: 'Nextcloud',
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
<svg id="root" width="64" height="64" viewBox="-32 -32 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>/usr/space</title>
|
||||
<desc>Logo /usr/space</desc>
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
#root {
|
||||
stroke: #2AA1BF;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#root {
|
||||
stroke: #FFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<marker id="circle" viewBox="-4 -4 8 8" markerWidth="8.5" markerHeight="8.5" markerUnits="userSpaceOnUse" refX="3" orient="auto-start-reverse">
|
||||
<circle cx="0" cy="0" r="2" fill="none" stroke-width="4"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<g fill="none" stroke-width="4">
|
||||
<path marker-start="url(#circle)" marker-end="url(#circle)" d="M 0,-21.69431 0,-1.299519 a 2.2499999,2.2499999 0 0 0 1.125624,1.948918 l 21.5,12.403846 A 2.25,2.25 0 0 0 26,11.104327 v -24.804808 a 2.2499999,2.2499999 0 0 0 -1.125624,-1.948918 l -23.75,-13.701923 a 2.25,2.25 0 0 0 -2.248752,0 l -23.749999,13.701923 A 2.2499999,2.2499999 0 0 0 -26,-13.700481 v 24.804808 a 2.25,2.25 0 0 0 3.374376,1.948918 L -6.154917,3.550914"/>
|
||||
<path marker-start="url(#circle)" marker-end="url(#circle)" d="m 18.850376,19.124785 -17.726,10.226537 a 2.25,2.25 0 0 1 -2.248752,0 L -18.844457,19.128198"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,25 @@
|
|||
<svg id="root" width="640" height="640" viewBox="-32 -32 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>/usr/space</title>
|
||||
<desc>Logo /usr/space</desc>
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
#root {
|
||||
background-color: #FFF;
|
||||
stroke: #2AA1BF;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#root {
|
||||
background-color: #000;
|
||||
stroke: #FFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<marker id="circle" viewBox="-3 -3 6 6" refX="-2.25" orient="auto-start-reverse" markerWidth="6" markerHeight="6" markerUnits="userSpaceOnUse">
|
||||
<circle cx="0" cy="0" r="2.25" fill="none" stroke-width="1.1"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<g fill="none" stroke-width="1.1">
|
||||
<path marker-start="url(#circle)" marker-end="url(#circle)" d="M 0,-21.69431 0,-1.299519 a 2.2499999,2.2499999 0 0 0 1.125624,1.948918 l 21.5,12.403846 A 2.25,2.25 0 0 0 26,11.104327 v -24.804808 a 2.2499999,2.2499999 0 0 0 -1.125624,-1.948918 l -23.75,-13.701923 a 2.25,2.25 0 0 0 -2.248752,0 l -23.749999,13.701923 A 2.2499999,2.2499999 0 0 0 -26,-13.700481 v 24.804808 a 2.25,2.25 0 0 0 3.374376,1.948918 L -6.154917,3.550914"/>
|
||||
<path marker-start="url(#circle)" marker-end="url(#circle)" d="m 18.850376,19.124785 -17.726,10.226537 a 2.25,2.25 0 0 1 -2.248752,0 L -18.844457,19.128198"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_extensionName__",
|
||||
"description": "__MSG_extensionDescription__",
|
||||
"version": "0.8.1",
|
||||
"icons": {
|
||||
"48": "icons/favicon.svg",
|
||||
"96": "icons/favicon.svg"
|
||||
},
|
||||
"background": {
|
||||
"page": "background.html"
|
||||
},
|
||||
"browser_action": {
|
||||
"browser_style": true,
|
||||
"default_title": "__MSG_buttonTitle__",
|
||||
"default_icon": "icons/favicon.svg",
|
||||
"default_area": "navbar",
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"default_locale": "de",
|
||||
"permissions": [
|
||||
"https://www.usrspace.at/*",
|
||||
"alarms",
|
||||
"notifications",
|
||||
"storage",
|
||||
"webRequest"
|
||||
],
|
||||
"author": "Thomas Rupprecht",
|
||||
"homepage_url": "https://gitea.usrspace.at/XimeX/usrspace-browser-addon",
|
||||
"developer": {
|
||||
"name": "Thomas Rupprecht",
|
||||
"url": "https://blog.ximex.at/"
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ h2 {
|
|||
}
|
||||
summary {
|
||||
margin: 6px 12px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
summary > h2 {
|
||||
margin: 0;
|
||||
|
@ -70,6 +71,9 @@ svg {
|
|||
width: 23px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#calendar > div > svg, #state > svg {
|
||||
margin-right: 4px;
|
|
@ -1,17 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="utf-8" />
|
||||
<title>/usr/space</title>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
<link rel="stylesheet" href="popup.css" />
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<h2>Aktueller Status</h2>
|
||||
<div id="state">loading...</div>
|
||||
<h2 id="currentState"></h2>
|
||||
<div id="state"></div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Links</h2>
|
||||
<h2 id="links"></h2>
|
||||
<ul class="panel-section panel-section-list">
|
||||
<li class="panel-list-item link" data-url="https://www.usrspace.at/">
|
||||
<div class="icon">
|
||||
|
@ -53,18 +53,17 @@
|
|||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Nächster Termin</h2>
|
||||
<div id="calendar">loading...</div>
|
||||
<h2 id="nextEvent"></h2>
|
||||
<div id="calendar"></div>
|
||||
</section>
|
||||
<section class="no-padding">
|
||||
<details>
|
||||
<summary><h2>SpaceApi JSON</h2></summary>
|
||||
<pre id="space-api"><code>loading...</code></pre>
|
||||
<summary><h2 id="spaceApiJson"></h2></summary>
|
||||
<pre id="space-api"><code></code></pre>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<script type="application/javascript" src="../browser-polyfill.js"></script>
|
||||
<script type="application/javascript" src="../config.js"></script>
|
||||
<script type="application/javascript" src="index.js"></script>
|
||||
<script type="module" src="browser-polyfill.js"></script>
|
||||
<script type="module" src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,129 @@
|
|||
const calendarSVG = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="currentColor" viewBox="0 0 16 16" role="img" aria-label="Kalender">
|
||||
<path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/>
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
</svg>
|
||||
`;
|
||||
const doorClosedSVG = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="currentColor" viewBox="0 0 16 16" role="img" aria-label="Geschlossen">
|
||||
<path d="M3 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v13h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3V2zm1 13h8V2H4v13z"/>
|
||||
<path d="M9 9a1 1 0 1 0 2 0 1 1 0 0 0-2 0z"/>
|
||||
</svg>
|
||||
`;
|
||||
const doorOpenSVG = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="currentColor" viewBox="0 0 16 16" role="img" aria-label="Offen">
|
||||
<path d="M8.5 10c-.276 0-.5-.448-.5-1s.224-1 .5-1 .5.448.5 1-.224 1-.5 1z"/>
|
||||
<path d="M10.828.122A.5.5 0 0 1 11 .5V1h.5A1.5 1.5 0 0 1 13 2.5V15h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3V1.5a.5.5 0 0 1 .43-.495l7-1a.5.5 0 0 1 .398.117zM11.5 2H11v13h1V2.5a.5.5 0 0 0-.5-.5zM4 1.934V15h6V1.077l-6 .857z"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const dateTimeFormat = Intl.DateTimeFormat([], {dateStyle: 'medium', timeStyle: 'short'});
|
||||
|
||||
function setL10n() {
|
||||
const loadingText = browser.i18n.getMessage('loading');
|
||||
document.getElementById('state').textContent = loadingText;
|
||||
document.getElementById('calendar').textContent = loadingText;
|
||||
document.querySelector('#space-api > code').textContent = loadingText;
|
||||
document.getElementById('currentState').textContent = browser.i18n.getMessage('currentState');
|
||||
document.getElementById('links').textContent = browser.i18n.getMessage('links');
|
||||
document.getElementById('nextEvent').textContent = browser.i18n.getMessage('nextEvent');
|
||||
document.getElementById('spaceApiJson').textContent = browser.i18n.getMessage('spaceApiJson');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
*/
|
||||
async function linkElementClickListener(event) {
|
||||
try {
|
||||
await browser.tabs.create({url: event.currentTarget.dataset.url});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
setL10n();
|
||||
|
||||
const linkElements = Array.from(document.getElementsByClassName('link'));
|
||||
linkElements.forEach((linkElement) => {
|
||||
linkElement.addEventListener('click', linkElementClickListener);
|
||||
});
|
||||
|
||||
const {calendar, spaceApi} = await browser.storage.local.get(['calendar', 'spaceApi']);
|
||||
|
||||
if (calendar) {
|
||||
updateNextEvent(calendar);
|
||||
}
|
||||
if (spaceApi) {
|
||||
updateSpaceApiJson(spaceApi);
|
||||
updateState(spaceApi);
|
||||
}
|
||||
}
|
||||
init();
|
||||
|
||||
/**
|
||||
* @param {Array<object>} nextEvents
|
||||
*/
|
||||
function updateNextEvent(nextEvents) {
|
||||
const calendarElement = document.getElementById('calendar');
|
||||
calendarElement.innerText = '';
|
||||
|
||||
if (nextEvents.length === 0) {
|
||||
const strongElement = document.createElement('strong');
|
||||
strongElement.textContent = browser.i18n.getMessage('noEventsInNext4Weeks');
|
||||
calendarElement.append(strongElement);
|
||||
} else {
|
||||
const nextEventDate = nextEvents[0].begin.substr(0, 10);
|
||||
const nextEventDateEvents = nextEvents.filter((nextEvent) => (nextEvent.begin.startsWith(nextEventDate)));
|
||||
|
||||
nextEventDateEvents.forEach((nextEventDateEvent) => {
|
||||
const strongElement = document.createElement('strong');
|
||||
strongElement.textContent = nextEventDateEvent.name;
|
||||
|
||||
const beginDate = new Date(nextEventDateEvent.begin);
|
||||
const timeElement = document.createElement('time');
|
||||
timeElement.dateTime = beginDate.toISOString();
|
||||
timeElement.textContent = dateTimeFormat.format(beginDate);
|
||||
|
||||
const divElement = document.createElement('div');
|
||||
divElement.innerHTML = calendarSVG;
|
||||
divElement.append(strongElement, ' ', timeElement);
|
||||
if (nextEventDateEvent.location) {
|
||||
const locationNode = document.createTextNode(` (${nextEventDateEvent.location})`);
|
||||
divElement.append(locationNode);
|
||||
}
|
||||
calendarElement.append(divElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} spaceApi
|
||||
*/
|
||||
function updateSpaceApiJson(spaceApi) {
|
||||
const json = JSON.stringify(spaceApi, null, 2)
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n/g, '<br/>');
|
||||
const spaceApiElement = document.querySelector('#space-api > code');
|
||||
spaceApiElement.innerHTML = json;
|
||||
// const jsonNode = document.createTextNode(json);
|
||||
// spaceApiElement.innerText = '';
|
||||
// spaceApiElement.append(jsonNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} spaceApi
|
||||
*/
|
||||
function updateState(spaceApi) {
|
||||
const stateIcon = spaceApi.state.open ? doorOpenSVG : doorClosedSVG;
|
||||
const stateTextNode = document.createTextNode(browser.i18n.getMessage(spaceApi.state.open ? 'openSince' : 'closedSince'));
|
||||
|
||||
const since = new Date(spaceApi.state.lastchange * 1000);
|
||||
const timeElement = document.createElement('time');
|
||||
timeElement.dateTime = since.toISOString();
|
||||
timeElement.textContent = dateTimeFormat.format(since);
|
||||
|
||||
const stateElement = document.getElementById('state');
|
||||
stateElement.innerHTML = stateIcon;
|
||||
stateElement.append(stateTextNode, timeElement);
|
||||
}
|