removed app logic and assets (#90)

Co-authored-by: rafapaezbas <rafa@holepunch.com>
This commit is contained in:
rafapaezbas
2024-03-14 14:33:37 +01:00
committed by rafapaezbas
parent 543b19ad1e
commit ee31b5b5e9
12 changed files with 0 additions and 1379 deletions

View File

@@ -1,23 +0,0 @@
name: Build Status
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
strategy:
matrix:
node-version: [lts/*]
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test

View File

@@ -1,27 +0,0 @@
# Pear Desktop Changelog
## v1.1.0
### Features
- Label Pear API's as experimental.
- Added guide on terminal debuging.
- Added 'Apps' section and keet.md.
- Added note on Bare Runtime + cross platform app example.
- Added update button.
- Removed deprecated bultinMaps configuration from documentation.
- Implemented anchor auto scroll on navigation.
### UX/UI Improvements
- Improved system path setup UI.
- Various typo fixes.
- Language improvements.
### Fixes
- Windows - System Path Setup is manual only for now.
- Fixed navigation after Pear installation.
- Path text is selectable in installation screen.
- Fixed indexes for Gitbook and Pear-Desktop app.
## v1.0.0
First public release 🍐

View File

@@ -1 +0,0 @@
<svg width="64px" height="64px" viewBox="0 0 32.00 32.00" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" fill="#B0D944" stroke="#B0D944"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="0.384"></g><g id="SVGRepo_iconCarrier"> <title>arrow-left-square</title> <desc>Created with Sketch Beta.</desc> <defs> </defs> <g id="Page-1" stroke-width="0.48" fill="none" fill-rule="evenodd" sketch:type="MSPage"> <g id="Icon-Set" sketch:type="MSLayerGroup" transform="translate(-412.000000, -983.000000)" fill="#b0D944"> <path d="M434,998 L423.414,998 L427.535,993.879 C427.926,993.488 427.926,992.855 427.535,992.465 C427.145,992.074 426.512,992.074 426.121,992.465 L420.465,998.121 C420.225,998.361 420.15,998.689 420.205,999 C420.15,999.311 420.225,999.639 420.465,999.879 L426.121,1005.54 C426.512,1005.93 427.145,1005.93 427.535,1005.54 C427.926,1005.15 427.926,1004.51 427.535,1004.12 L423.414,1000 L434,1000 C434.553,1000 435,999.553 435,999 C435,998.448 434.553,998 434,998 L434,998 Z M442,1011 C442,1012.1 441.104,1013 440,1013 L416,1013 C414.896,1013 414,1012.1 414,1011 L414,987 C414,985.896 414.896,985 416,985 L440,985 C441.104,985 442,985.896 442,987 L442,1011 L442,1011 Z M440,983 L416,983 C413.791,983 412,984.791 412,987 L412,1011 C412,1013.21 413.791,1015 416,1015 L440,1015 C442.209,1015 444,1013.21 444,1011 L444,987 C444,984.791 442.209,983 440,983 L440,983 Z" id="arrow-left-square" sketch:type="MSShapeGroup"> </path> </g> </g> </g></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1 +0,0 @@
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M11.75 5.5C11.1977 5.5 10.75 5.05228 10.75 4.5V2C10.75 1.44772 11.1977 1 11.75 1H12.25C12.8023 1 13.25 1.44772 13.25 2V4.5C13.25 5.05228 12.8023 5.5 12.25 5.5H11.75Z" fill="#B0D944"></path> <path d="M16.4194 7.22698C16.0289 6.83646 16.0289 6.20329 16.4194 5.81277L18.1872 4.045C18.5777 3.65447 19.2109 3.65447 19.6014 4.045L19.9549 4.39855C20.3455 4.78908 20.3455 5.42224 19.9549 5.81277L18.1872 7.58053C17.7967 7.97106 17.1635 7.97106 16.773 7.58053L16.4194 7.22698Z" fill="#B0D944"></path> <path d="M18.5 11.75C18.5 11.1977 18.9477 10.75 19.5 10.75H22C22.5523 10.75 23 11.1977 23 11.75V12.25C23 12.8023 22.5523 13.25 22 13.25H19.5C18.9477 13.25 18.5 12.8023 18.5 12.25V11.75Z" fill="#B0D944"></path> <path d="M16.7728 16.4194C17.1633 16.0289 17.7965 16.0289 18.187 16.4194L19.9548 18.1872C20.3453 18.5777 20.3453 19.2109 19.9548 19.6014L19.6012 19.9549C19.2107 20.3455 18.5775 20.3455 18.187 19.9549L16.4192 18.1872C16.0287 17.7967 16.0287 17.1635 16.4192 16.773L16.7728 16.4194Z" fill="#B0D944"></path> <path d="M12.25 18.5C12.8023 18.5 13.25 18.9477 13.25 19.5V22C13.25 22.5523 12.8023 23 12.25 23H11.75C11.1977 23 10.75 22.5523 10.75 22V19.5C10.75 18.9477 11.1977 18.5 11.75 18.5H12.25Z" fill="#B0D944"></path> <path d="M7.58059 16.773C7.97111 17.1635 7.97111 17.7967 7.58059 18.1872L5.81282 19.955C5.4223 20.3455 4.78913 20.3455 4.39861 19.955L4.04505 19.6014C3.65453 19.2109 3.65453 18.5778 4.04505 18.1872L5.81282 16.4195C6.20334 16.0289 6.83651 16.0289 7.22703 16.4195L7.58059 16.773Z" fill="#B0D944"></path> <path d="M5.5 12.25C5.5 12.8023 5.05228 13.25 4.5 13.25H2C1.44772 13.25 1 12.8023 1 12.25V11.75C1 11.1977 1.44772 10.75 2 10.75H4.5C5.05228 10.75 5.5 11.1977 5.5 11.75V12.25Z" fill="#B0D944"></path> <path d="M7.22722 7.58059C6.8367 7.97111 6.20353 7.97111 5.81301 7.58059L4.04524 5.81282C3.65472 5.4223 3.65472 4.78913 4.04524 4.39861L4.3988 4.04505C4.78932 3.65453 5.42248 3.65453 5.81301 4.04505L7.58078 5.81282C7.9713 6.20334 7.9713 6.83651 7.58078 7.22703L7.22722 7.58059Z" fill="#B0D944"></path> <path d="M7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12Z" fill="#B0D944"></path> </g></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

View File

@@ -1,328 +0,0 @@
<!DOCTYPE html>
<body>
<style>
@font-face {
font-family: 'overpass-mono';
src: url('~redhat-overpass-font/webfonts/overpass-mono-webfont/overpass-mono-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'overpass-mono';
src: url('~redhat-overpass-font/webfonts/overpass-mono-webfont/overpass-mono-bold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
}
body {
background: #151517;
box-sizing: border-box;
border-radius: 8px;
font-family: 'overpass-mono';
-webkit-font-smoothing: antialiased;
color: #efeaea;
transition: background-color .4s, color .4s;
}
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-track {
background: #151517;
}
::-webkit-scrollbar-thumb {
background: #727272;
}
html.light body {
background: #efeaea;
color: #151517;
}
#logo {
float: left;
margin-right: 2rem;
margin-left: 0.65rem;
margin-top: -0.1rem;
}
nav {
height: 146px;
float: left;
padding-left: .75rem;
padding-top: 2em;
}
nav>a {
display: block;
}
section {
display: block;
}
pre {
background: none;
color: #B0D944;
font-family: overpass-mono;
border: none;
height: 146px;
user-select: none;
position: relative;
right: 5em;
top: 0.33em;
float: right;
}
header {
top: 0;
left: 0;
height: 170px;
z-index: 10;
background: rgba(21, 21, 23, 0.9);
padding-top: 20px;
width: 100%;
transition: background-color .4s, color .4s;
position: fixed;
}
#headin {
transform: scale(0.92);
width: 72rem;
margin: 0 auto;
margin-top: .75rem;
user-select: none;
}
h1 {
padding: .5rem;
border-right: 1px solid #B0D944;
border-bottom: 1px solid #B0D944;
display: inline-block;
padding-right: 0.75em;
padding-left: 0.5em;
font-weight: bold;
font-size: 1.6rem;
margin-block-start: 0.67em;
margin-block-end: 0.67em;
}
blockquote {
outline: 1px solid #323532;
margin-inline-start: 0;
margin-inline-end: 0;
display: block;
margin-block-start: 1rem;
margin-block-end: 0;
padding: 1px;
font-size: .825rem
}
blockquote::before {
content: "";
float: left;
font-size: 1.625rem;
margin-left: 1rem;
margin-right: 0.625rem;
}
.col {
max-width: 63rem;
margin: 0 auto;
margin-top: 1rem;
}
#back,
#mode {
width: 32px;
height: 32px;
position: absolute;
right: 0.4rem;
cursor: pointer;
user-select: none;
opacity: .65;
}
#back {
top: 10px;
}
#mode {
top: 60px;
}
#bar {
backdrop-filter: blur(64px);
-webkit-app-region: drag;
padding: 0;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
color: #B0D944;
white-space: nowrap;
box-sizing: border-box;
position: fixed;
z-index: 100;
width: 100%;
left: 0;
top: 0;
}
#bar:hover {
outline: #B0D944;
}
pear-ctrl {
margin-top: 9px;
}
pear-ctrl[data-platform="darwin"] {
margin-top: 16px;
margin-left: 8px;
}
system-status,
docs-viewer {
position: relative;
top: 156px
}
developer-tooling {
position: relative;
top: 92px
}
a:visited,
a:active,
a {
color: #B0D944;
outline: none;
text-decoration: none;
}
app-router a:visited,
app-router a:active,
app-router a {
color: #B0D944;
}
app-router[data-load="system-status"] a[href='/'],
app-router[data-load="system-status"] a:active[href='/'],
app-router[data-load="system-status"] a:visited[href='/'],
app-router[data-load="docs-viewer"] a[href='/documentation'],
app-router[data-load="docs-viewer"] a:active[href='/documentation'],
app-router[data-load="docs-viewer"] a:visited[href='/documentation'],
app-router[data-load="developer-tooling"] a[href='/devtools'],
app-router[data-load="developer-tooling"] a:active[href='/devtools'],
app-router[data-load="developer-tooling"] a:visited[href='/devtools'] {
color: rgb(78, 250, 92);
}
</style>
<div id="bar"><pear-ctrl></pear-ctrl></div>
<app-router docs-viewer="/documentation" developer-tooling="/devtools" system-status="/" data-load="system-status">
<header>
<div id="headin">
<svg id="logo" width="102" height="146" viewBox="0 0 102 146" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8912_10861)">
<path d="M47.4056 0.838379H54.5943V15.0361H47.4056V0.838379Z" fill="#B0D944" />
<path d="M43.8113 19.5305V22.406H36.6226V26.0004H65.3774V22.406H58.1887V16.8347H51V19.5305H43.8113Z" fill="#B0D944" />
<path d="M72.5662 27.7974H51V30.4931H29.4339V36.963H72.5662V27.7974Z" fill="#B0D944" />
<path d="M79.7548 38.7593H51V41.455H22.2451V47.9249H79.7548V38.7593Z" fill="#B0D944" />
<path d="M79.7548 49.7219H51V52.4177H22.2451V58.8875H79.7548V49.7219Z" fill="#B0D944" />
<path d="M86.9436 60.6846H51V63.3803H15.0565V69.8502H86.9436V60.6846Z" fill="#B0D944" />
<path d="M86.9436 71.6481H51V74.3438H15.0565V80.8137H86.9436V71.6481Z" fill="#B0D944" />
<path d="M94.1323 82.61H51V85.3058H7.86774V91.7756H94.1323V82.61Z" fill="#B0D944" />
<path d="M101.321 93.5726H51V96.2684H0.679016V102.738H101.321V93.5726Z" fill="#B0D944" />
<path d="M101.321 104.536H51V107.232H0.679016V113.702H101.321V104.536Z" fill="#B0D944" />
<path d="M101.321 115.499H51V118.195H0.679016V124.664H101.321V115.499Z" fill="#B0D944" />
<path d="M86.9436 126.461H51V129.156H15.0565V135.626H86.9436V126.461Z" fill="#B0D944" />
<path d="M72.5662 137.424H51V140.12H29.4339V144.613H72.5662V137.424Z" fill="#B0D944" />
</g>
<defs>
<clipPath id="clip0_8912_10861">
<rect width="100.642" height="145.571" fill="white" transform="translate(0.679016 0.214233)" />
</clipPath>
</defs>
</svg>
<img id="mode" src="/assets/light.svg" onclick="document.documentElement.classList.toggle('light');">
<img id="back" src="/assets/arrow.svg" onclick="history.back()">
<nav>
<a href="/">System Status</a>
<a href="/documentation">Documentation</a>
<a href="/devtools">Developer Tooling</a>
</nav>
<pre>
__ \ _ \ _` | __|
| | __/ ( | |
.__/ \___| \__,_| _|
_|
</pre>
</div>
</header>
<section>
<div class="col"><system-status></system-status></div>
</section>
<script type='module' src='./lib/system-status.js'></script>
<section>
<div class="col"><docs-viewer></docs-viewer></div>
</section>
<script type='module' src='./lib/docs-viewer.js'></script>
<section>
<div class="col"><developer-tooling></developer-tooling></div>
</section>
<script type="module">
customElements.define('pear-version', class extends HTMLElement {
constructor () {
super()
this.template = document.createElement('template')
this.template.innerHTML = `
<style>
:host > div {
position: fixed;
top: 96vh;
font-size: .6rem;
background: #1a1a1a;
color: white;
opacity: 1;
padding: 8px;
padding-top: 0px;
padding-bottom: 0px;
border-radius: .35rem;
border: 1px solid #3a4816;
transition: top 0.3s;
display: block;
}
:host > div:hover {
top: 92vh;
transition: top 0.3s;
}
</style>
<div>
<p id="platform-version"></p>
<p id="app-version"></p>
</div>
`
this.root = this.attachShadow({ mode: 'open' })
Pear.versions().then(({ app, platform }) => {
this.root.appendChild(this.template.content.cloneNode(true))
this.root.querySelector('#platform-version').innerText = `Pear Runtime ${platform.fork}.${platform.length}.${platform.key}`
this.root.querySelector('#app-version').innerText = `Pear Desktop ${app.fork}.${app.length}.${app.key}`
})
}
})
</script>
<pear-version></pear-version>
<script>
process.env.WS_NO_BUFFER_UTIL = false // ws buffer-util opt-out
</script>
<script type='module' src='./lib/devtools.js'></script>
</app-router>
<script type='module' src='./lib/app-router.js'></script>
</body>

View File

@@ -1,85 +0,0 @@
/* eslint-env browser */
customElements.define('app-router', class AppRouter extends HTMLElement {
constructor () {
super()
this.routes = {}
this.page = null
this.anchor = null
}
unload () {
for (const element of Object.values(this.routes)) element?.unload && element.unload()
}
async load (pathname = '/', opts = {}) {
if (this.page === pathname && this.anchor === opts.anchor) return
this.page = pathname
this.anchor = opts.anchor
for (const [route, element] of Object.entries(this.routes)) {
if (pathname.startsWith(route)) {
const page = pathname.slice(route.length) || '/'
this.unload()
document.documentElement.scrollTop = 0
this.dataset.load = element.tagName.toLowerCase()
await element.load(page, opts)
const isDocumentationPage = pathname.startsWith('/documentation')
const anchor = opts.anchor
const shouldShowSpecificSection = anchor && isDocumentationPage
if (shouldShowSpecificSection) {
const element = this.routes['/documentation'].shadowRoot.getElementById(anchor)
element.scrollIntoView()
const elementY = Math.floor(element.getBoundingClientRect().y)
const pearHeaderHeight = 170
const extraScroll = 80
const isUnderPearHeader = elementY < pearHeaderHeight + extraScroll
if (isUnderPearHeader) {
window.scrollBy(0, -1 * (pearHeaderHeight + extraScroll - elementY))
}
}
if (!opts.back) history.pushState({ pathname, anchor: opts.anchor }, null, pathname)
break
}
}
}
link (evt) {
if (evt.target?.tagName !== 'A') return
evt.preventDefault()
if (evt.target.origin !== location.origin) return window.open(evt.target.href)
const { tagName } = evt.target.getRootNode().host || {}
const route = tagName ? this.getAttribute(tagName) : ''
if (evt.target.pathname.startsWith(route)) {
const anchor = evt.target.href.split('#')[1]
this.load(evt.target.pathname, { anchor }).catch(console.error)
} else {
this.load(route + evt.target.pathname).catch(console.error)
}
}
connectedCallback () {
for (const { name, value } of Array.from(this.attributes)) {
if (name.startsWith('data-')) continue
this.routes[value] = this.querySelector(name)
this.routes[value].router = this
}
this.addEventListener('click', (evt) => this.link(evt))
window.addEventListener('popstate', (evt) => {
this.load(evt.state?.pathname, { back: true, anchor: evt.state?.anchor }).catch(console.error)
})
window.addEventListener('load', () => {
const page = '/' + (Pear.config.linkData || '')
console.log('load', page)
this.load(page).catch(console.error)
Pear.wakeups(({ data }) => {
Pear.Window.self.focus({ steal: true }).catch(console.error)
const page = '/' + (data || '')
this.load(page).catch(console.error)
})
})
}
})

View File

@@ -1,429 +0,0 @@
/* eslint-env browser */
import http from 'http'
import { WebSocketServer } from 'ws'
import { Session } from 'pear-inspect'
import b4a from 'b4a'
import { spawn } from 'child_process'
customElements.define('developer-tooling', class extends HTMLElement {
router = null
port = 9342
constructor () {
super()
this.template = document.createElement('template')
this.template.innerHTML = `
<div>
<style>
:host > div {
font-size: .9em;
margin-left: -4px; /* scrollbar offset compensate */
}
#add-key-input {
width: 100%;
}
#add-key-error,
#change-port-error {
color: red;
}
#server-message:hover #change-port-show {
display: inline-block;
}
#change-port-show {
display: none;
}
#change-port-input {
width: 80px;
}
/* hide up/down arrow for input type=number */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.panels-wrapper {
display: flex;
gap: 30px;
}
.panel-left {
flex: 1;
}
.panel-right {
width: 400px;
}
.hidden {
display: none;
}
.app {
display: flex;
align-items: center;
padding: 0.25rem;
padding-top: 0.1rem;
padding-bottom: 0.15rem;
}
.app .title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.app:hover .copy,
.app:hover .open-in-chrome,
.app:hover .remove {
display: block;
}
.app .copy,
.app .open-in-chrome {
margin-right: 10px;
}
.app .copy,
.app .open-in-chrome,
.app .remove {
display: none;
}
.button {
cursor: pointer;
background: #3a4816;
color: #efeaea;
padding: 0 0.25rem;
font-family: 'overpass-mono';
border-radius: 1px;
white-space: nowrap;
}
#server-message {
font-size: 0.8rem;
margin-top: 30px;
}
h2 { margin: 0 }
p {
margin-block-start: 0.75em;
margin-block-end: 0.75em;
}
input {
all: unset;
border: 1px ridge #B0D944;
background: #000;
color: #B0D944;
padding: .45rem;
font-family: monospace;
font-size: 1rem;
line-height: 1rem;
}
code {
background: #3a4816;
color: #efeaea;
padding: 0.25rem;
padding-top: 0.1rem;
padding-bottom: 0.15rem;
font-family: 'overpass-mono';
border-radius: 1px;
font-size: .9em;
}
pre > code { display: block; line-height: 1.025rem; padding-left: 1em; background: #181e19 }
h1, h2, h3, h4, h5, h6 { font-weight: bold; }
h1 { font-size: 1.6rem; }
h2 { font-size: 1.4rem; }
h3 { font-size: 1.2rem; }
h4 { font-size: 1rem; }
h5 { font-size: .8rem; }
h6 { font-size: .7rem; }
h1 { padding: .5rem; border-right: 1px solid #B0D944; border-bottom: 1px solid #B0D944; display: inline-block; padding-right: 0.75em; padding-left: 0.5em; }
</style>
<div>
<h1>Developer Tooling</h1>
<div class="panels-wrapper">
<div class="panel-left">
<h2>Remotely inspect Pear applications.</h2>
<p> Some application setup is required to enable remote debugging </p>
<p> Install the <code>pear-inspect</code> module into the application </p>
<pre><code>npm install pear-inspect</code></pre>
<p> Add the following code to the application, before any other code: </p>
<pre><code>if (Pear.config.dev) {
const { Inspector } = await import('pear-inspect')
const inspector = await new Inspector()
const key = await inspector.enable()
console.log('Debug with pear://runtime/devtools/'
+ key.toString('hex'))
}</code></pre>
<p>When the application is opened in development mode the inspection key will be logged.</p>
<p>Paste the logged key into the input to add it. Then open in Chrome or copy the URL to a compatible inspect tool to view the remote target.</p>
</div>
<div class="panel-right">
<div>
<form id="add-key-form">
<input id="add-key-input" type="text" placeholder="Paste Pear Inspector Key Here"/>
<p id="add-key-error"></p>
</form>
</div>
<h2>Apps</h2>
<h3 id="no-apps">No apps added. Add an inspect key to start debugging.</h3>
<div id="apps"></div>
<div id="server-message" class="hidden">
Pear DevTools connection running on
<div>
<span id="server-location">localhost</span>
<span id="change-port-show" class="button">
Change port
</span>
<div>
<form id="change-port-form" class="hidden">
<input
id="change-port-input"
type="number"
placeholder="New port"
min="1024"
max="65535"
/>
</form>
<p id="change-port-error"></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`
this.root = this.attachShadow({ mode: 'open' })
this.root.appendChild(this.template.content.cloneNode(true))
this.addKeyFormElem = this.root.querySelector('#add-key-form')
this.addKeyInputElem = this.root.querySelector('#add-key-input')
this.addKeyErrorElem = this.root.querySelector('#add-key-error')
this.changePortFormElem = this.root.querySelector('#change-port-form')
this.changePortInputElem = this.root.querySelector('#change-port-input')
this.changePortErrorElem = this.root.querySelector('#change-port-error')
this.changePortShowElem = this.root.querySelector('#change-port-show')
this.appsElem = this.root.querySelector('#apps')
this.noAppsElem = this.root.querySelector('#no-apps')
this.changePortElem = this.root.querySelector('#change-port')
this.apps = new Map()
this.addKeyInputElem.addEventListener('keydown', e => {
this.addKeyErrorElem.textContent = ''
})
this.addKeyFormElem.addEventListener('submit', e => {
e.preventDefault()
const inspectorKey = this.addKeyInputElem.value
this.addApp(inspectorKey)
})
this.changePortInputElem.addEventListener('keydown', e => {
const isEscape = e.key === 'Escape'
this.changePortErrorElem.textContent = ''
if (isEscape) {
this.changePortFormElem.classList.add('hidden')
this.changePortInputElem.value = ''
}
})
this.changePortShowElem.addEventListener('click', () => {
this.changePortFormElem.classList.remove('hidden')
})
this.changePortFormElem.addEventListener('submit', e => {
e.preventDefault()
this.port = Number(this.changePortInputElem.value)
this.initServer()
})
const shouldLoadApp = Pear.config.linkData?.startsWith('devtools/')
if (shouldLoadApp) {
this.addApp(Pear.config.linkData)
}
this.initServer()
}
render () {
this.appsElem.replaceChildren(...[...this.apps].map(([sessionId, app]) => {
const div = document.createElement('div')
div.innerHTML = `
<div class="app">
<div class="title">${app.title} (${app.url})</div>
<div class="button copy">Copy URL</div>
<div class="button open-in-chrome">Open in Chrome</div>
<div class="button remove">✕</div>
</div>
`
div.querySelector('.copy').addEventListener('click', () => {
navigator.clipboard.writeText(`devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:${this.port}/${sessionId}`)
})
div.querySelector('.open-in-chrome').addEventListener('click', () => {
openChrome(`devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:${this.port}/${sessionId}`)
})
div.querySelector('.remove').addEventListener('click', () => {
this.apps.delete(sessionId)
this.render()
})
return div
}))
if (this.apps.size > 0) {
this.noAppsElem.classList.add('hidden')
} else {
this.noAppsElem.classList.remove('hidden')
}
if (this.port) {
this.root.querySelector('#server-location').textContent = `http://localhost:${this.port}`
this.root.querySelector('#server-message').classList.remove('hidden')
}
}
addApp (inspectorKey) {
const isUrl = inspectorKey.includes('devtools/')
if (isUrl) inspectorKey = inspectorKey.split('devtools/').pop()
const isIncorrectLength = inspectorKey.length !== 64
if (isIncorrectLength) {
this.addKeyErrorElem.textContent = 'Key needs to be 64 characters long'
return
}
const sessionId = generateUuid()
const inspectorSession = new Session({ inspectorKey: b4a.from(inspectorKey, 'hex') })
const app = {
inspectorKey,
title: '',
url: '',
inspectorSession
}
inspectorSession.on('close', () => {
this.apps.delete(sessionId)
this.render()
})
inspectorSession.on('info', ({ filename }) => {
app.url = filename
app.title = filename.split('/').pop()
this.apps.set(sessionId, app)
this.render()
})
this.addKeyInputElem.value = ''
this.addKeyErrorElem.textContent = ''
this.render()
}
initServer () {
this.devtoolsHttpServer?.close()
this.devtoolsHttpServer = http.createServer()
const devToolsWsServer = new WebSocketServer({ noServer: true })
this.devtoolsHttpServer.listen(this.port, () => {
console.log(`[devtoolsHttpServer] running on port ${this.port}`)
this.render()
this.changePortFormElem.classList.add('hidden')
})
this.devtoolsHttpServer.on('error', err => {
this.changePortErrorElem.textContent = err?.message
})
this.devtoolsHttpServer.on('request', (req, res) => {
if (req.url !== '/json/list') {
res.writeHead(404)
res.end()
return
}
const targets = [...this.apps].map(([sessionId, app]) => ({
description: 'node.js instance', // `Pear app: ${app.name}`,
devtoolsFrontendUrl: `devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:${this.port}/${sessionId}`,
devtoolsFrontendUrlCompat: `devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:${this.port}/${sessionId}`,
faviconUrl: 'https://nodejs.org/static/images/favicons/favicon.ico',
id: sessionId,
title: app.title,
type: 'node',
url: `file://${app.url}`,
webSocketDebuggerUrl: `ws://127.0.0.1:${this.port}/${sessionId}`
}))
res.writeHead(200, {
'Content-Type': 'application/json; charset=UTF-8',
'Cache-Control': 'no-cache',
'Content-Length': JSON.stringify(targets).length
})
res.end(JSON.stringify(targets))
})
this.devtoolsHttpServer.on('upgrade', (request, socket, head) => {
console.log(`[devtoolsHttpServer] UPGRADE. url=${request.url}`)
const sessionId = request.url.substr(1)
const sessionIdExists = this.apps.has(sessionId)
if (!sessionIdExists) return socket.destroy()
devToolsWsServer.handleUpgrade(request, socket, head, ws => devToolsWsServer.emit('connection', ws, request))
})
devToolsWsServer.on('connection', (devtoolsSocket, request) => {
const sessionId = request.url.substr(1)
const app = this.apps.get(sessionId)
if (!app) return devtoolsSocket.destroy()
const { inspectorSession } = app
const onMessage = msg => {
const { pearInspectMethod } = msg
const isACDPMessage = !pearInspectMethod
if (isACDPMessage) return devtoolsSocket.send(JSON.stringify(msg))
}
inspectorSession.connect()
inspectorSession.on('message', onMessage)
devtoolsSocket.on('message', msg => inspectorSession.post(JSON.parse(msg)))
devtoolsSocket.on('close', () => {
inspectorSession.disconnect()
inspectorSession.off('message', onMessage)
app.connected = false
this.render()
})
app.connected = true
this.render()
})
}
load () {
this.style.display = ''
}
unload () {
this.style.display = 'none'
}
})
// Can't use `uuid` module for some reason as it results in a throw with `crypto` when importing
function generateUuid () {
let result, i, j
result = ''
for (j = 0; j < 32; j++) {
if (j === 8 || j === 12 || j === 16 || j === 20) {
result = result + '-'
}
i = Math.floor(Math.random() * 16).toString(16)
result = result + i
}
return result
}
function openChrome (url) {
const params = {
darwin: ['open', '-a', 'Google Chrome', url],
linux: ['google-chrome', url],
win32: ['start', 'chrome', url]
}[process.platform]
if (!params) throw new Error('Cannot open Chrome')
const [command, ...args] = params
spawn(command, args)
}

File diff suppressed because one or more lines are too long

View File

@@ -1,234 +0,0 @@
/* eslint-env browser */
import os from 'os'
import fs from 'fs'
import path from 'path'
import { spawn } from 'child_process'
const { config } = Pear
const BIN = path.join(config.pearDir, 'bin')
const isWin = process.platform === 'win32'
customElements.define('pear-welcome', class extends HTMLElement {
constructor () {
super()
this.template = document.createElement('template')
this.template.innerHTML = `
<style>
blockquote { outline: 1px solid #323532; margin-inline-start: 0; margin-inline-end: 0; display: block; margin-block-start: 1rem; margin-block-end: 0; padding: 1px; font-size: .825rem; margin-top: -1rem; margin-bottom: 1rem; border-radius: 2px; }
blockquote::before { content: "✔"; float: left; font-size: 1.625rem; margin-left: 1rem; margin-right: 0.625rem; }
video { float: right; outline: 1px solid #323532; border-radius: 2px; }
#welcome { float: left; width: calc(100% - 420px); }
code {
background: #3a4816;
color: #efeaea;
padding: 0.25rem;
padding-top: 0.1rem;
padding-bottom: 0.15rem;
font-family: 'overpass-mono';
border-radius: 1px;
}
</style>
<blockquote>
<p> Pear is in the system PATH and ready to go .</p>
</blockquote>
<div id="welcome">
<h2> Welcome... </h2>
<p> ...to the Internet of Peers. <p>
<p> Pear provides the <code>pear</code> Command-line Interface as the primary interface for developing, sharing & maintaining unstoppable peer-to-peer applications and systems. </p>
<p> To get started, open a terminal, type <code>pear</code> and hit return. </p>
</div>
<video width="380" height="390" autoplay muted style="background:#000">
<source src="./assets/usage.mp4" type="video/mp4">
</video>
`
this.root = this.attachShadow({ mode: 'open' })
this.root.appendChild(this.template.content.cloneNode(true))
}
})
customElements.define('system-status', class extends HTMLElement {
router = null
connectedCallback () {
this.root.addEventListener('click', (evt) => {
this.router.link(evt)
})
}
load () {
this.style.display = ''
}
unload () {
this.style.display = 'none'
}
constructor () {
super()
this.zsh = false
this.bash = false
this.statement = `export PATH="${BIN}":$PATH`
this.stmtrx = new RegExp(`^export PATH="${BIN}":\\$PATH$`, 'm')
this.shellProfiles = null
this.root = this.attachShadow({ mode: 'open' })
this.update = null
this.#render()
}
#render () {
this.shadowRoot.innerHTML = `
<div id="panel">
<style>
#panel { user-select: none; }
blockquote { outline: 1px solid #323532; margin-inline-start: 0; margin-inline-end: 0; display: block; margin-block-start: 1rem; margin-block-end: 0; padding: 1px; font-size: .825rem; border-radius: 2px; }
blockquote::before { content: ""; float: left; font-size: 1.625rem; margin-left: 1rem; margin-right: 0.625rem; }
button { background: #151517; color: #B0D944; border: 1px solid; padding: .575em .65em; cursor: pointer; margin-top: 2rem; font-size: 1.20rem; }
#tip { text-indent: 4px; margin-top: -.25rem }
code {
background: #3a4816;
color: #efeaea;
padding: 0.25rem;
padding-top: 0.1rem;
padding-bottom: 0.15rem;
font-family: 'overpass-mono';
border-radius: 1px;
user-select: text;
}
#tip > p { margin-top: 6px; margin-bottom: 6px; padding: 0}
#tip {
margin-top: 3rem;
}
#update-button {
position: fixed;
left: 893px;
top: 170px;
width: 171px;
}
h1 {
padding: 0.5rem;
display: inline-block;
padding-right: 0.75em;
font-weight: bold;
font-size: 2.46rem;
margin-left: -0.7rem;
margin-top: 1rem;
margin-bottom: 1.25rem;
}
</style>
<h1>System Status</h1>
<button id="update-button"> Update Available </button>
${
this.#installed()
? '<pear-welcome></pear-welcome>'
: `
<blockquote>
<p>Pear setup is nearly complete.</p>
</blockquote>
<p>To finish installing Pear Runtime set your system path to</p>
<p><code>${BIN}</code></p>
<p>${!isWin ? ' or click the button.' : ''}</p>
${!isWin ? '<p><button id="setup-button"> Automatic Setup Completion </button><p>' : ''}
`
}
</div>
`
this.updateButton = this.shadowRoot.querySelector('#update-button')
this.updateButton.style.display = 'none'
this.updateButton.addEventListener('mouseenter', (e) => {
e.target.innerText = 'Click to Restart'
})
this.updateButton.addEventListener('mouseout', (e) => {
e.target.innerText = 'Update Available'
})
this.shadowRoot.querySelector('#setup-button')?.addEventListener('click', () => {
this.#install().then(() => this.#render()).catch((err) => this.#error(err))
})
Pear.updates((update) => {
this.update = update
this.updateButton.style.display = ''
if (this.updateButtonListener) {
this.updateButton.removeEventListener('click', this.updateButtonListener)
}
this.updateButtonListener = () => Pear.restart({ platform: this.update.app === false }).catch(console.error)
this.updateButton.addEventListener('click', this.updateButtonListener)
})
}
#error (err) {
console.error(err)
}
#installed () {
if (isWin === false) {
let hasPear = false
this.shellProfiles = {}
for (const file of ['.zshrc', '.zshenv', '.zshprofile', '.zlogin', '.profile', '.bashrc']) {
const filepath = path.join(os.homedir(), file)
let contents = null
try { contents = fs.readFileSync(filepath, { encoding: 'utf-8' }) } catch {}
if (contents !== null) {
this.shellProfiles[file] = { filepath, hasPear: this.stmtrx.test(contents) }
hasPear = hasPear || this.shellProfiles[file].hasPear
}
}
if (hasPear) process.env.PATH = `${BIN}:${process.env.PATH}`
}
this.paths = process.env.PATH.split(path.delimiter)
return this.paths.some((bin) => {
return bin === BIN && fs.existsSync(path.join(BIN, isWin ? 'pear.cmd' : 'pear'))
})
}
#install () {
const runtime = path.join('..', 'current', 'by-arch', process.platform + '-' + process.arch, 'bin', 'pear-runtime')
fs.mkdirSync(BIN, { recursive: true })
if (isWin) {
const ps1tmp = path.join(BIN, Math.floor(Math.random() * 1000) + '.pear')
fs.writeFileSync(ps1tmp, `function pear { & "${runtime}" }; Export-ModuleMember -Function pear`)
fs.renameSync(ps1tmp, path.join(BIN, 'pear.ps1'))
const cmdtmp = path.join(BIN, Math.floor(Math.random() * 1000) + '.pear')
fs.writeFileSync(cmdtmp, `@echo off\n"${runtime}" %*`)
fs.renameSync(cmdtmp, path.join(BIN, 'pear.cmd'))
return new Promise((resolve, reject) => {
spawn('powershell', ['-Command', `[System.Environment]::SetEnvironmentVariables("PATH", "${BIN};${process.env.PATH}", "User")`]).on('exit', (code) => {
if (code !== 0) {
reject(new Error('Failed to set PATH'))
return
}
process.env.PATH = `${BIN};${process.env.PATH}`
resolve()
})
})
}
const comment = '# Added by Pear Runtime, configures system with Pear CLI\n'
const profiles = Object.values(this.shellProfiles)
if (profiles.length > 0) {
for (const { filepath, hasPear } of profiles) {
if (hasPear === false) fs.writeFileSync(filepath, '\n' + comment + this.statement + '\n', { flag: 'a' })
}
} else {
const bash = this.paths.some((bin) => fs.existsSync(path.join(bin, 'bash')))
const zsh = this.paths.some((bin) => fs.existsSync(path.join(bin, 'zsh')))
fs.writeFileSync(path.join(os.homedir(), bash ? '.bashrc' : '.profile'), this.statement + '\n', { flag: 'a' })
if (zsh) fs.writeFileSync(path.join(os.homedir(), '.zshrc'), '\n' + comment + this.statement + '\n', { flag: 'a' })
}
const pear = path.join(BIN, 'pear')
const tmp = path.join(BIN, Math.floor(Math.random() * 1000) + '.pear')
fs.symlinkSync(runtime, tmp)
fs.renameSync(tmp, pear)
fs.chmodSync(pear, 0o755)
process.env.PATH = `${BIN}:${process.env.PATH}`
return Promise.resolve()
}
})

View File

@@ -1,33 +0,0 @@
{
"name": "pear-desktop",
"version": "1.0.0",
"main": "index.html",
"type": "module",
"scripts": {
"test": "standard --fix",
"check-urls": "node test/urls.tests.cjs"
},
"pear": {
"gui": {
"backgroundColor": "#151517",
"height": 780,
"width": 1120
}
},
"standard": {
"globals": [
"Pear"
]
},
"dependencies": {
"b4a": "^1.6.4",
"bare-path": "^2.1.0",
"pear-inspect": "^1.0.3",
"redhat-overpass-font": "^1.0.0",
"ws": "^8.16.0"
},
"devDependencies": {
"brittle": "^3.4.0",
"standard": "^17.1.0"
}
}

View File

@@ -1,65 +0,0 @@
const { readdir, stat, readFile } = require('fs/promises')
const test = require('brittle')
const path = require('path')
const https = require('https')
test('check that all urls can be reached', async (t) => {
const docs = await readMarkdownFiles()
let urls = await Promise.all(docs.map(async (doc) => {
const content = (await readFile(doc)).toString()
const urlRegex = /(?<url>https?:\/\/[^\s)"'`]+)/gi
return content.match(urlRegex)
}))
urls = urls.flat().filter(u => u !== null).filter(u => !u.startsWith('http://localhost'))
const cache = new Map()
const responses = await Promise.all(urls.map(async url => {
if (cache.get(url)) return cache.get(url)
const result = await checkUrl(url.trim())
cache.set(url, result)
return { result, url }
}))
for (const response of responses) {
t.ok(response.result, `${response.url} should return 200 code`)
}
})
async function readMarkdownFiles (folderPath = path.join(__dirname, '..')) {
let result = []
const files = await readdir(folderPath)
for (const file of files) {
const filePath = path.join(folderPath, file)
const stats = await stat(filePath)
if (!filePath.includes('node_modules') && stats.isDirectory()) {
result = result.concat(await readMarkdownFiles(filePath))
} else if (path.extname(filePath) === '.md') {
result.push(filePath)
}
}
return result
}
function checkUrl (url) {
return new Promise((resolve, reject) => {
try {
https.get(url, (res) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(true)
} else {
resolve(false)
}
}).on('error', () => {
resolve(false)
})
} catch (err) {
console.log(err)
resolve(false)
}
})
}