Merge pull request #65 from Flow-Works/42-add-full-support-for-flowserver
[✨] Finished Settings application
This commit is contained in:
commit
126ba38138
10 changed files with 165 additions and 188 deletions
|
|
@ -1,9 +1,12 @@
|
||||||
importScripts('/uv/uv.sw.js');
|
importScripts('/uv/uv.sw.js');
|
||||||
|
|
||||||
const sw = new UVServiceWorker();
|
const params = new URLSearchParams(self.serviceWorker.scriptURL)
|
||||||
|
|
||||||
|
var cfg = JSON.parse(params.get('config'));
|
||||||
|
const sw = new UVServiceWorker(cfg);
|
||||||
|
|
||||||
self.addEventListener('fetch', event =>
|
self.addEventListener('fetch', event =>
|
||||||
event.respondWith(
|
event.respondWith(sw.fetch(event))
|
||||||
sw.fetch(event)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,9 @@ self.xor = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.__uv$config = {
|
self.__uv$config = {
|
||||||
prefix: '/service/',
|
prefix: '/service/',
|
||||||
bare: 'https://server.flow-works.me/bare/',
|
bare: 'https://server.flow-works.me' + '/bare/',
|
||||||
encodeUrl: self.xor.encode,
|
encodeUrl: self.xor.encode,
|
||||||
decodeUrl: self.xor.decode,
|
decodeUrl: self.xor.decode,
|
||||||
handler: '/uv/uv.handler.js',
|
handler: '/uv/uv.handler.js',
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ importScripts('/uv/uv.bundle.js');
|
||||||
importScripts('/uv/uv.config.js');
|
importScripts('/uv/uv.config.js');
|
||||||
|
|
||||||
class UVServiceWorker extends EventEmitter {
|
class UVServiceWorker extends EventEmitter {
|
||||||
constructor(config = __uv$config) {
|
constructor(flowURL, config = __uv$config) {
|
||||||
super();
|
super();
|
||||||
if (!config.bare) config.bare = '/bare/';
|
config.bare = flowURL + '/bare/';
|
||||||
this.addresses = typeof config.bare === 'string' ? [ new URL(config.bare, location) ] : config.bare.map(str => new URL(str, location));
|
this.addresses = typeof config.bare === 'string' ? [ new URL(config.bare, location) ] : config.bare.map(str => new URL(str, location));
|
||||||
this.headers = {
|
this.headers = {
|
||||||
csp: [
|
csp: [
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ launcher {
|
||||||
|
|
||||||
preloader {
|
preloader {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 999999999999999999;
|
z-index: 9999999;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: var(--crust);
|
background: var(--crust);
|
||||||
|
|
|
||||||
|
|
@ -15,24 +15,7 @@ export default class SettingsApp implements App {
|
||||||
configFolderLoc = '/.flow/'
|
configFolderLoc = '/.flow/'
|
||||||
|
|
||||||
async open (): Promise<FlowWindow> {
|
async open (): Promise<FlowWindow> {
|
||||||
const fs = new (window as any).Filer.FileSystem()
|
const win = window.wm.createWindow({
|
||||||
|
|
||||||
try {
|
|
||||||
fs.exists(this.configFolderLoc, function (exists: any) {
|
|
||||||
if (exists === false) {
|
|
||||||
fs.mkdir(this.configFolderLoc, () => {})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (e) { alert(e.message) }
|
|
||||||
try {
|
|
||||||
fs.exists(this.configFileLoc, function (exists: any) {
|
|
||||||
if (exists === false) {
|
|
||||||
fs.writeFile(this.configFileLoc, '{"clock-Type":"0", "tab-title": "Flow OS", "favicon-icon": "to-be-replaced", "proxy-type": "uv", "search-engine": "google" }', () => {})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const win = (window as any).wm.createWindow({
|
|
||||||
title: this.meta.name,
|
title: this.meta.name,
|
||||||
icon: this.meta.icon,
|
icon: this.meta.icon,
|
||||||
width: 700,
|
width: 700,
|
||||||
|
|
@ -41,167 +24,81 @@ export default class SettingsApp implements App {
|
||||||
})
|
})
|
||||||
|
|
||||||
win.content.style.padding = '10px'
|
win.content.style.padding = '10px'
|
||||||
|
|
||||||
win.content.innerHTML = `
|
win.content.innerHTML = `
|
||||||
|
<h1>Settings</h1>
|
||||||
<div id=>
|
<div class="settings"></div>
|
||||||
|
<button class="save">Save!</button>
|
||||||
<h1>Appereance</h1>
|
|
||||||
|
|
||||||
<p>Clock</p>
|
|
||||||
<button class="btn"> 12 Hour Clock </button>
|
|
||||||
<button class="btn"> 24 Hour Clock </button>
|
|
||||||
|
|
||||||
<p>Tab Title</p>
|
|
||||||
<input class="btn" placeholder="Enter Tab Title">
|
|
||||||
<p>Tab Icon</p>
|
|
||||||
<input class="btn" placeholder="Enter Icon URL">
|
|
||||||
<p class="error-text"> </p>
|
|
||||||
|
|
||||||
<h1>Browser Settings</h1>
|
|
||||||
|
|
||||||
<p>Proxy Type</p>
|
|
||||||
<select class="btn">
|
|
||||||
<option value="uv">Ultraviolet</option>
|
|
||||||
<option value="dy">Dynamic</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<p>Search Engine</p>
|
|
||||||
<select class="btn">
|
|
||||||
<option value="google">Google</option>
|
|
||||||
<option value="bing">Bing</option>
|
|
||||||
<option value="yahoo">Yahoo!</option>
|
|
||||||
<option value="duck">DuckDuckGo</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<p>Server</p>
|
|
||||||
<input class="btn" placeholder="Enter Server URL">
|
|
||||||
<p class="error-text"> </p>
|
|
||||||
|
|
||||||
|
|
||||||
<button disabled class="btn save"> Save </button>
|
|
||||||
<style>
|
<style>
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.error-text {
|
input, button {
|
||||||
font-size:12px;
|
background: var(--mantle);
|
||||||
color:red;
|
padding: 2.5px;
|
||||||
}
|
border: 1px solid var(--surface-0);
|
||||||
|
border-radius: 5px;
|
||||||
.btn {
|
margin: 2.5px;
|
||||||
background-color:#45475a;
|
}
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 2px solid #11111b;
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
.btn:disabled {
|
|
||||||
color:rgba(205, 214, 244, 0.4);
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save {
|
|
||||||
float: right;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin: 2.5px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
`
|
`
|
||||||
// Get current data
|
|
||||||
const data = JSON.parse(await fs.promises.readFile(this.configFileLoc))
|
|
||||||
let oldData = JSON.stringify(data)
|
|
||||||
|
|
||||||
// Handle value changes
|
const titles: {
|
||||||
|
[key: string]: string
|
||||||
win.content.getElementsByClassName('btn')[0].onclick = async () => {
|
} = {
|
||||||
data['clock-Type'] = '0'
|
SERVER_URL: 'FlowServer URL',
|
||||||
win.content.getElementsByClassName('btn')[0].disabled = true
|
HOSTNAME: 'Device Hostname',
|
||||||
win.content.getElementsByClassName('btn')[1].disabled = false
|
USERNAME: 'Username',
|
||||||
settingsChange()
|
'24HR_CLOCK': '24hr Clock'
|
||||||
}
|
|
||||||
win.content.getElementsByClassName('btn')[1].onclick = async () => {
|
|
||||||
data['clock-Type'] = '1'
|
|
||||||
win.content.getElementsByClassName('btn')[0].disabled = false
|
|
||||||
win.content.getElementsByClassName('btn')[1].disabled = true
|
|
||||||
settingsChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
win.content.getElementsByClassName('btn')[2].addEventListener('change', () => {
|
const config = await window.config()
|
||||||
data['tab-title'] = win.content.getElementsByClassName('btn')[2].value
|
|
||||||
settingsChange()
|
|
||||||
})
|
|
||||||
|
|
||||||
win.content.getElementsByClassName('btn')[6].addEventListener('change', () => {
|
for (const key of Object.keys(config)) {
|
||||||
if (isURL(win.content.getElementsByClassName('btn')[6].value) === true && win.content.getElementsByClassName('btn')[6].value !== '') {
|
const container = document.createElement('div')
|
||||||
data['flow-server'] = win.content.getElementsByClassName('btn')[6].value
|
const label = document.createElement('label')
|
||||||
win.content.getElementsByClassName('error-text')[1].textContent = ''
|
const input = document.createElement('input')
|
||||||
} else {
|
|
||||||
if (win.content.getElementsByClassName('btn')[6].value !== '') {
|
if (typeof (config as any)[key] === 'boolean') {
|
||||||
win.content.getElementsByClassName('error-text')[1].textContent = 'Please input a vaild URL'
|
label.innerText = `${titles[key] ?? key}`
|
||||||
} else {
|
input.type = 'checkbox'
|
||||||
win.content.getElementsByClassName('error-text')[1].textContent = ''
|
} else if (typeof (config as any)[key] === 'string') {
|
||||||
}
|
label.innerText = `${titles[key] ?? key}:\n`
|
||||||
|
input.type = 'text'
|
||||||
}
|
}
|
||||||
settingsChange()
|
|
||||||
})
|
|
||||||
|
|
||||||
win.content.getElementsByClassName('btn')[3].addEventListener('change', () => {
|
input.value = (config as any)[key]
|
||||||
if (isURL(win.content.getElementsByClassName('btn')[3].value) === true && win.content.getElementsByClassName('btn')[4].value !== '') {
|
|
||||||
data['favicon-url'] = win.content.getElementsByClassName('btn')[3].value
|
|
||||||
win.content.getElementsByClassName('error-text')[0].textContent = ''
|
|
||||||
} else {
|
|
||||||
if (win.content.getElementsByClassName('btn')[4].value !== '') {
|
|
||||||
win.content.getElementsByClassName('error-text')[0].textContent = 'Please input a vaild URL'
|
|
||||||
} else {
|
|
||||||
win.content.getElementsByClassName('error-text')[0].textContent = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
settingsChange()
|
|
||||||
})
|
|
||||||
|
|
||||||
win.content.getElementsByClassName('btn')[4].value = data['proxy-type']
|
container.appendChild(label)
|
||||||
win.content.getElementsByClassName('btn')[4].addEventListener('change', (event: any) => {
|
container.appendChild(input)
|
||||||
data['proxy-type'] = event.target.value
|
|
||||||
settingsChange()
|
|
||||||
})
|
|
||||||
|
|
||||||
win.content.getElementsByClassName('btn')[5].value = data['search-engine']
|
win.content.querySelector('.settings')?.appendChild(container)
|
||||||
win.content.getElementsByClassName('btn')[5].addEventListener('change', (event: any) => {
|
|
||||||
data['search-engine'] = event.target.value
|
|
||||||
settingsChange()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle saving.
|
win.content.querySelector('.save')?.addEventListener('click', () => {
|
||||||
|
(config as any)[key] = input.value
|
||||||
|
|
||||||
win.content.getElementsByClassName('save')[0].onclick = async () => {
|
navigator.serviceWorker.getRegistrations().then(function (registrations) {
|
||||||
await fs.promises.writeFile(this.configFileLoc, JSON.stringify(data))
|
for (const registration of registrations) {
|
||||||
oldData = JSON.stringify(data)
|
registration.unregister().then(() => {
|
||||||
settingsChange()
|
navigator.serviceWorker.register('/uv-sw.js?config=' + encodeURIComponent(config.SERVER_URL), {
|
||||||
|
scope: '/service/'
|
||||||
|
}).catch(e => console.error(e))
|
||||||
|
}).catch(e => console.error(e))
|
||||||
|
}
|
||||||
|
}).catch(e => console.error(e))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display unsaved changes / prevent Save button from being clicked twice.
|
win.content.querySelector('.save')?.addEventListener('click', () => {
|
||||||
|
window.fs.promises.writeFile('/.config/flow.json', JSON.stringify(config))
|
||||||
function settingsChange (): void {
|
.then(null)
|
||||||
if (oldData !== JSON.stringify(data)) {
|
.catch(e => console.error(e))
|
||||||
win.setTitle('Settings - Unsaved Changes')
|
})
|
||||||
win.content.getElementsByClassName('save')[0].disabled = false
|
|
||||||
} else {
|
|
||||||
win.setTitle('Settings')
|
|
||||||
win.content.getElementsByClassName('save')[0].disabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// URL checker
|
|
||||||
|
|
||||||
function isURL (input: string): any {
|
|
||||||
const regex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/
|
|
||||||
return input.match(regex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set values
|
|
||||||
win.content.getElementsByClassName('btn')[0].disabled = true
|
|
||||||
win.content.getElementsByClassName('btn')[1].disabled = false
|
|
||||||
if (data['clock-Type'] === '0') {
|
|
||||||
win.content.getElementsByClassName('btn')[0].disabled = false
|
|
||||||
win.content.getElementsByClassName('btn')[1].disabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return win
|
return win
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { getTime } from '../../utils'
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
name: 'Clock',
|
name: 'Clock',
|
||||||
description: 'Displays the date & time.',
|
description: 'Displays the date & time.',
|
||||||
|
|
@ -5,7 +7,7 @@ export const meta = {
|
||||||
version: '1.0.0'
|
version: '1.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const run = (element: HTMLDivElement): void => {
|
export const run = async (element: HTMLDivElement): Promise<void> => {
|
||||||
let date: Date = new Date()
|
let date: Date = new Date()
|
||||||
|
|
||||||
element.style.display = 'flex'
|
element.style.display = 'flex'
|
||||||
|
|
@ -19,17 +21,16 @@ export const run = (element: HTMLDivElement): void => {
|
||||||
return `<i>${split[0]}</i>,${split[1]} `
|
return `<i>${split[0]}</i>,${split[1]} `
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshClock = (): string => {
|
let clock = await getTime()
|
||||||
return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' })
|
let date_ = refreshDate()
|
||||||
}
|
element.innerHTML = `${clock}<div>${date_}</div>`
|
||||||
|
|
||||||
refreshDate()
|
|
||||||
refreshClock()
|
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
date = new Date()
|
(async () => {
|
||||||
const clock = refreshClock()
|
date = new Date()
|
||||||
const date_ = refreshDate()
|
clock = await getTime()
|
||||||
element.innerHTML = `${clock}<div>${date_}</div>`
|
date_ = refreshDate()
|
||||||
|
element.innerHTML = `${clock}<div>${date_}</div>`
|
||||||
|
})().catch(e => console.error(e))
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
src/index.ts
46
src/index.ts
|
|
@ -6,6 +6,7 @@ import WindowManager from './instances/WindowManager'
|
||||||
import Flow from './instances/Flow'
|
import Flow from './instances/Flow'
|
||||||
|
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
import { FlowConfig } from './types'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|
@ -14,6 +15,7 @@ declare global {
|
||||||
fs: typeof fs
|
fs: typeof fs
|
||||||
statusBar: StatusBar
|
statusBar: StatusBar
|
||||||
wm: WindowManager
|
wm: WindowManager
|
||||||
|
config: () => Promise<FlowConfig>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,6 +39,46 @@ window.wm = new WindowManager();
|
||||||
(async function () {
|
(async function () {
|
||||||
window.preloader.setPending('filesystem')
|
window.preloader.setPending('filesystem')
|
||||||
window.fs = new (window as any).Filer.FileSystem()
|
window.fs = new (window as any).Filer.FileSystem()
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
SERVER_URL: 'https://server.flow-works.me',
|
||||||
|
HOSTNAME: 'flow',
|
||||||
|
USERNAME: 'user',
|
||||||
|
'24HR_CLOCK': false
|
||||||
|
}
|
||||||
|
|
||||||
|
window.fs.exists('/.config', (exists) => {
|
||||||
|
if (!exists) window.fs.promises.mkdir('/.config').then(null).catch(e => console.error)
|
||||||
|
|
||||||
|
window.fs.exists('/.config/flow.json', (exists) => {
|
||||||
|
// if (!exists) {
|
||||||
|
window.fs.promises.writeFile('/.config/flow.json', JSON.stringify(defaultConfig)).then(null).catch(e => console.error)
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Gets the current FlowOS config.
|
||||||
|
*
|
||||||
|
* @returns The current FlowOS config.
|
||||||
|
*/
|
||||||
|
window.config = async (): Promise<FlowConfig> => {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
window.fs.exists('/.config/flow.json', (exists) => {
|
||||||
|
if (exists) {
|
||||||
|
window.fs.promises.readFile('/.config/flow.json')
|
||||||
|
.then(content => { resolve(JSON.parse(content.toString())) })
|
||||||
|
.catch(() => reject(new Error('Unable to read config file.')))
|
||||||
|
} else reject(new Error('Config file does not exist.'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(await window.config())
|
||||||
|
|
||||||
|
navigator.serviceWorker.register('/uv-sw.js?config=' + encodeURIComponent((await window.config()).SERVER_URL), {
|
||||||
|
scope: '/service/'
|
||||||
|
}).catch(e => console.error(e))
|
||||||
|
|
||||||
await window.preloader.setDone('filesystem')
|
await window.preloader.setDone('filesystem')
|
||||||
|
|
||||||
await window.wm.init()
|
await window.wm.init()
|
||||||
|
|
@ -45,8 +87,4 @@ window.wm = new WindowManager();
|
||||||
|
|
||||||
window.preloader.setStatus('')
|
window.preloader.setStatus('')
|
||||||
window.preloader.finish()
|
window.preloader.finish()
|
||||||
|
|
||||||
await navigator.serviceWorker.register('/uv-sw.js', {
|
|
||||||
scope: '/service/'
|
|
||||||
})
|
|
||||||
})().catch(e => console.error)
|
})().catch(e => console.error)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class StatusBar {
|
||||||
|
|
||||||
this.element.appendChild(element)
|
this.element.appendChild(element)
|
||||||
|
|
||||||
await item.run(element)
|
await item.run(element, await window.config())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import FlowWindow from './structures/FlowWindow'
|
import FlowWindow from './structures/FlowWindow'
|
||||||
|
|
||||||
export type AppOpenFunction = (data: any) => Promise<FlowWindow>
|
export type AppOpenFunction = (data: any) => Promise<FlowWindow>
|
||||||
export type PluginRunFunction = (element: HTMLDivElement) => void | Promise<void>
|
export type PluginRunFunction = (element: HTMLDivElement, config: any) => void | Promise<void>
|
||||||
|
|
||||||
export interface AppClosedEvent extends CustomEvent {
|
export interface AppClosedEvent extends CustomEvent {
|
||||||
detail: {
|
detail: {
|
||||||
|
|
@ -72,3 +72,10 @@ export interface LoadedPlugin extends Plugin {
|
||||||
export interface PackageJSON {
|
export interface PackageJSON {
|
||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FlowConfig {
|
||||||
|
SERVER_URL: string
|
||||||
|
HOSTNAME: string
|
||||||
|
USERNAME: string
|
||||||
|
'24HOUR_CLOCK': boolean
|
||||||
|
}
|
||||||
|
|
|
||||||
32
src/utils.ts
Normal file
32
src/utils.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* Gets the current time in 12hrs/24hrs.
|
||||||
|
*
|
||||||
|
* @returns The time.
|
||||||
|
*/
|
||||||
|
export const getTime = async (): Promise<string> => {
|
||||||
|
const config = await window.config()
|
||||||
|
const use24hrs = config['24HOUR_CLOCK']
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
let hours: string | number = now.getHours()
|
||||||
|
let minutes: string | number = now.getMinutes()
|
||||||
|
let period = 'AM'
|
||||||
|
|
||||||
|
if (!use24hrs) {
|
||||||
|
period = (hours >= 12) ? 'PM' : 'AM'
|
||||||
|
if (hours === 0) {
|
||||||
|
hours = 12
|
||||||
|
} else if (hours > 12) {
|
||||||
|
hours = hours % 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hours = (hours < 10) ? `0${hours}` : hours
|
||||||
|
minutes = (minutes < 10) ? `0${minutes}` : minutes
|
||||||
|
|
||||||
|
const timeString = use24hrs
|
||||||
|
? `${hours}:${minutes}`
|
||||||
|
: `${hours}:${minutes} ${period}`
|
||||||
|
|
||||||
|
return timeString
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue