OctoSpaccHub/public/FramesBrowser/index.html

323 lines
10 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta property="og:url" content="https://hub.octt.eu.org/FramesBrowser"/>
<link rel="canonical" href="https://hub.octt.eu.org/FramesBrowser"/>
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png"/>
<link rel="manifest" href="./manifest.json"/>
<title>🪟️ Frames Browser (WIP)</title>
<meta name="description" content="iFrame-based HTML5 Browser for fun and development"/>
<meta property="og:title" content="🪟️ Frames Browser (WIP)"/>
<meta property="og:description" content="iFrame-based HTML5 Browser for fun and development"/>
<style>
:root {
--BaseMargin: 8px;
--BtnHeight: calc(1rem + var(--BaseMargin));
--BtnActionHeight: calc(2rem + var(--BaseMargin));
--ColorBg: #f0f0f0;
--ColorFg: #0f0f0f;
}
* {
box-sizing: border-box;
}
body {
margin: 0px;
overflow: hidden;
max-width: 100vw;
max-height: 100vh;
}
button {
height: var(--BtnHeight);
}
iframe {
border: none;
width: 100vw;
height: calc(100vh - var(--BtnActionHeight));
position: relative;
}
#BoxControls {
overflow: auto;
}
#BoxControls table, #BoxControls table td, #BoxControls table td > * {
height: var(--BtnActionHeight);
min-width: var(--BtnActionHeight);
padding-top: 0;
padding-bottom: 0;
border-spacing: 0;
}
.BoxPopup {
left: 0;
right: 0;
}
.BoxPopup.Container {
position: absolute;
top: 10vh;
z-index: 1;
text-align: center;
}
.BoxPopup.Content {
position: relative;
margin-left: auto;
margin-right: auto;
width: fit-content;
max-height: 80vh;
overflow: auto;
box-shadow: gray 4px 4px 4px 0px;
color: var(--ColorFg);
background-color: var(--ColorBg);
}
.BtnAction {
height: var(--BtnActionHeight);
}
</style>
<script>
var FrameZoomLevels = [50, 200];
var AppInfoString = `
Frames Browser v2023-09-23 (WIP).
`.trim();
</script>
</head>
<body>
<div id="BoxControls"><table><tr>
<td><button onclick="ShowAppInfo()"> Info</button></td>
<td><button onclick="ToggleDevTools()">📐️ Tools</button></td>
<td>
<button id="BtnFile" onclick="this.nextElementSibling.click()">📄 File</button>
<input type="file" hidden="hidden" style="display: none;" onchange="LoadFile(this.files[0])"/>
</td>
<td><button onclick="ZoomFrame()">🔍️ Zoom</button></td>
<td><button onclick="ListFrames()">🪟 Frames</button></td>
<td style="width: 100%;"><input id="InputUri" type="text" style="min-width: 100%;" placeholder="🔗️ Enter URI..." onkeydown="InputHandleKey(event)"/></td>
<td><button id="BtnLoad" onclick="LoadFrame()">↩️ Load</button></td>
<td><button id="BtnExcise" onclick="ExciseFrame()">↗️ Excise</button></td>
<tr></table></div>
<noscript><p class="NoScript">
This is an actual app, not a badly-made website.
<br>
It needs JavaScript to work, so you need to enable it.
<br>
The code is fully open, and you can review it with "View Page Source".
</p></noscript>
<div id="BoxHandy"></div>
<div><iframe></iframe></div>
<script>
var CurrentFrames = [];
var FrameIndexes = [-1];
var FrameZoomIndex = -1;
function $new(tag, props){
var el = document.createElement(tag);
if (props) {
Object.keys(props).forEach(function(key){
el[key] = props[key];
});
};
return el;
};
function $request(url, opts){
if (!opts.method) {
opts.method = 'GET';
};
var req = new XMLHttpRequest();
req.onreadystatechange = function(){
if (this.readyState == 4) {
opts.callback(req.responseText);
};
};
req.open(opts.method, url, true);
req.send();
};
function ShowAppInfo(){
alert(AppInfoString);
};
function InputHandleKey(ev){
// Enter
if (ev.keyCode == 13) {
LoadFrame();
};
};
function ShowFrame(index){
ListFramesClose();
FrameIndexes = [index];
SaveCurrentFrames();
document.querySelector('iframe').hidden = false;
document.querySelector('iframe').style.display = '';
InputUri.disabled = false;
InputUri.value = CurrentFrames[index];
BtnFile.disabled = false;
BtnLoad.disabled = false;
BtnExcise.disabled = false;
document.querySelector('iframe').src = CurrentFrames[index];
};
function ShowRootFrame(){
ListFramesClose();
FrameIndexes = [-1];
SaveCurrentFrames();
document.querySelector('iframe').hidden = true;
document.querySelector('iframe').style.display = 'none';
InputUri.disabled = true;
InputUri.value = '';
BtnFile.disabled = true;
BtnLoad.disabled = true;
BtnExcise.disabled = true;
document.querySelector('iframe').src = '';
};
function SaveUrl(){
var url = document.querySelector('input[type="text"]').value;
localStorage.setItem('FramesBrowser.url', url);
CurrentFrames[FrameIndexes[0]] = url;
SaveCurrentFrames();
return url;
};
function AddFrame(){
CurrentFrames = CurrentFrames.concat(['']);
ListFrames(); //ListFrames();
ShowFrame(CurrentFrames.length - 1);
SaveCurrentFrames();
};
function CloseFrame(index){
CurrentFrames.pop(index);
if (FrameIndexes[0] === index) {
ShowRootFrame();
} else if (FrameIndexes[0] > index) {
FrameIndexes[0] --;
};
ListFrames(); ListFrames();
SaveCurrentFrames();
};
function SaveCurrentFrames(){
localStorage.setItem('FramesBrowser.CurrentFrames', JSON.stringify(CurrentFrames));
localStorage.setItem('FramesBrowser.FrameIndexes', JSON.stringify(FrameIndexes));
};
function LoadFrame(){
document.querySelector('iframe').src = SaveUrl();
};
function ExciseFrame(){
var uri = SaveUrl();
if (uri.toLowerCase().startsWith('data:')) {
opendatauri(uri);
} else {
open(uri, '_blank');
};
};
function LoadFile(file){
var reader = new FileReader();
reader.onload = function(){
document.querySelector('input[type="text"]').value = reader.result;
LoadFrame();
};
reader.readAsDataURL(file);
};
function ZoomFrame(){
if (FrameZoomIndex === FrameZoomLevels.length - 1) {
FrameZoomIndex = -1;
} else {
FrameZoomIndex ++;
};
var level = FrameZoomLevels[FrameZoomIndex];
var levelopp = FrameZoomLevels[FrameZoomLevels.length - 1 - FrameZoomIndex];
var stylepos = (level < 100
? `right: ${level}vw; bottom: calc(${level}vh - 16px);`
: `left: ${levelopp/2}vw; top: calc(${levelopp/2}vh - 8px);`
);
document.querySelector('iframe').style = (FrameZoomIndex === -1
? ''
: `scale: ${level/100}; width: ${levelopp}vw; height: calc(${levelopp}vh - (var(--BtnActionHeight) * ${levelopp / 100})); ${stylepos}`
);
};
function ListFrames(){
if (!ListFramesClose()){
var Box = NewBoxPopup('BoxFramesList');
var BtnAdd = $new('button', { className: 'BtnAction', innerHTML: ' Add', onclick: AddFrame });
Box.Content.appendChild(BtnAdd);
var BoxList = $new('ul');
Box.Content.appendChild(BoxList);
var LiMain = $new('li');
BoxList.appendChild(LiMain);
var BtnMain = $new('button', { innerHTML: 'Root Window', onclick: ShowRootFrame, disabled: FrameIndexes[0] === -1 });
LiMain.appendChild(BtnMain);
for (var i=0; i<CurrentFrames.length; i++) {
var li = $new('li');
li.ItemIndex = i;
BoxList.appendChild(li);
var open = $new('button', { innerHTML: `&nbsp;${CurrentFrames[i].slice(0, 16)}&nbsp;`, onclick: function(){ShowFrame(this.parentElement.ItemIndex)}, disabled: FrameIndexes[0] === i });
li.append(open);
var close = $new('button', { innerHTML: '✖️', onclick: function(){CloseFrame(this.parentElement.ItemIndex)} });
li.append(close);
};
};
};
function ListFramesClose(){
var exist = document.querySelector('#BoxFramesList');
if (exist) {
BoxFramesList.remove();
};
return exist;
};
function ToggleDevTools(){
//var src = 'https://cdn.jsdelivr.net/npm/eruda';
//document.write('<scr' + 'ipt src="' + src + '" onload="eruda.init();"></scr' + 'ipt>');
//document.write('<scr' + 'ipt>eruda.init()</scr' + 'ipt>');
$request('https://cdn.jsdelivr.net/npm/eruda', { callback: function(text){
eval(text);
eruda.init();
} });
document.querySelector('button[onclick="ToggleDevTools()"]').disabled = true;
};
function NewBoxPopup(id){
var Container = $new('div', { id: id, className: 'BoxPopup Container' });
var Content = $new('div', { className: 'BoxPopup Content' });
Container.appendChild(Content);
var BtnClose = $new('button', { className: 'BtnAction', innerHTML: '❌ Close', onclick: function(){this.parentElement.parentElement.remove()} });
Content.appendChild(BtnClose);
BoxHandy.appendChild(Container);
return { Container: Container, Content: Content };
};
window.opendatauri = function opendatauri(data){
var head = data.split(',')[0].split('data:')[1];
var [mime, encoding] = head.split(';');
data = data.split(',').slice(1).join(',');
if (encoding.toLowerCase() === 'base64') {
data = atob(data);
};
var bytes = new Array(data.length);
for (var i = 0; i < data.length; i++) {
bytes[i] = data.charCodeAt(i);
};
window.open(URL.createObjectURL(
new Blob([new Uint8Array(bytes)], { type: `${mime};${encoding ? encoding : 'utf8'}` })
), '_blank');
};
onload = function(){
Array.from(document.querySelectorAll('noscript, .NoScript')).forEach(function(el){ el.remove() });
CurrentFrames = (JSON.parse(localStorage.getItem('FramesBrowser.CurrentFrames')) || []);
FrameIndexes = (JSON.parse(localStorage.getItem('FramesBrowser.FrameIndexes')) || [-1]);
document.querySelector('input[type="text"]').value = localStorage.getItem('FramesBrowser.url');
var frame0 = FrameIndexes[0];
frame0 === -1 ? ShowRootFrame() : ShowFrame(frame0);
};
</script>
</body>
</html>