mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
deploy: leporello-js/app@a6e3f1b36f
This commit is contained in:
@@ -2,34 +2,33 @@
|
|||||||
// Author: Sarah Bricault
|
// Author: Sarah Bricault
|
||||||
|
|
||||||
// Canvas setup
|
// Canvas setup
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement("canvas")
|
||||||
canvas.width = 700
|
canvas.width = 700
|
||||||
canvas.height = 700
|
canvas.height = 700
|
||||||
document.body.appendChild(canvas)
|
document.body.appendChild(canvas)
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext("2d")
|
||||||
ctx.translate(canvas.width / 2, canvas.height)
|
ctx.translate(canvas.width / 2, canvas.height)
|
||||||
|
|
||||||
// Draw a tree
|
// Draw a tree
|
||||||
await fractalTreeBasic({totalIterations: 10, basicLength: 10, rotate: 25})
|
await fractalTreeBasic({ totalIterations: 10, basicLength: 10, rotate: 25 })
|
||||||
|
|
||||||
function sleep() {
|
function sleep() {
|
||||||
return new Promise(resolve => setTimeout(resolve, 3))
|
return new Promise(resolve => setTimeout(resolve, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
async function fractalTreeBasic({ totalIterations, basicLength, rotate }) {
|
||||||
|
|
||||||
// Draw the tree trunk
|
// Draw the tree trunk
|
||||||
const trunkLength = basicLength * 2 * Math.pow(1.2, totalIterations + 1)
|
const trunkLength = basicLength * 2 * Math.pow(1.2, totalIterations + 1)
|
||||||
const width = Math.pow(totalIterations, 0.6)
|
const width = Math.pow(totalIterations, 0.6)
|
||||||
|
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(0, 0)
|
ctx.moveTo(0, 0)
|
||||||
ctx.lineTo(0, - trunkLength)
|
ctx.lineTo(0, -trunkLength)
|
||||||
ctx.lineWidth = width
|
ctx.lineWidth = width
|
||||||
ctx.strokeStyle = 'black'
|
ctx.strokeStyle = "black"
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|
||||||
await drawBranch(90, [0, - trunkLength], totalIterations + 1)
|
await drawBranch(90, [0, -trunkLength], totalIterations + 1)
|
||||||
|
|
||||||
async function drawBranch(angle, startPoint, iterations) {
|
async function drawBranch(angle, startPoint, iterations) {
|
||||||
const len = basicLength * Math.pow(1.2, iterations)
|
const len = basicLength * Math.pow(1.2, iterations)
|
||||||
@@ -38,7 +37,7 @@ async function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
|||||||
|
|
||||||
const red = Math.floor(255 - (iterations / totalIterations) * 255)
|
const red = Math.floor(255 - (iterations / totalIterations) * 255)
|
||||||
const green = 0
|
const green = 0
|
||||||
const blue = Math.floor( 255 - (iterations / totalIterations) * 255)
|
const blue = Math.floor(255 - (iterations / totalIterations) * 255)
|
||||||
const color = `rgb(${red}, ${green}, ${blue})`
|
const color = `rgb(${red}, ${green}, ${blue})`
|
||||||
|
|
||||||
const x1 = startPoint[0]
|
const x1 = startPoint[0]
|
||||||
@@ -47,7 +46,7 @@ async function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
|||||||
const y2 = y1 - len * Math.sin((angle * Math.PI) / 180)
|
const y2 = y1 - len * Math.sin((angle * Math.PI) / 180)
|
||||||
const x2 = x1 + len * Math.cos((angle * Math.PI) / 180)
|
const x2 = x1 + len * Math.cos((angle * Math.PI) / 180)
|
||||||
|
|
||||||
console.log('draw branch', x1, y1, x2, y2)
|
console.log("draw branch", x1, y1, x2, y2)
|
||||||
|
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(x1, y1)
|
ctx.moveTo(x1, y1)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Original source:
|
// Original source:
|
||||||
// https://www.freecodecamp.org/news/how-to-create-animated-bubbles-with-html5-canvas-and-javascript/
|
// https://www.freecodecamp.org/news/how-to-create-animated-bubbles-with-html5-canvas-and-javascript/
|
||||||
|
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement("canvas")
|
||||||
canvas.style.backgroundColor = '#00b4ff'
|
canvas.style.backgroundColor = "#00b4ff"
|
||||||
document.body.appendChild(canvas)
|
document.body.appendChild(canvas)
|
||||||
canvas.width = window.innerWidth
|
canvas.width = window.innerWidth
|
||||||
canvas.height = window.innerHeight
|
canvas.height = window.innerHeight
|
||||||
@@ -10,9 +10,9 @@ canvas.height = window.innerHeight
|
|||||||
const context = canvas.getContext("2d")
|
const context = canvas.getContext("2d")
|
||||||
|
|
||||||
context.font = "30px Arial"
|
context.font = "30px Arial"
|
||||||
context.textAlign = 'center'
|
context.textAlign = "center"
|
||||||
context.fillStyle = 'white'
|
context.fillStyle = "white"
|
||||||
context.fillText('Click to spawn bubbles', canvas.width/2, canvas.height/2)
|
context.fillText("Click to spawn bubbles", canvas.width / 2, canvas.height / 2)
|
||||||
|
|
||||||
let circles = []
|
let circles = []
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ function draw(circle) {
|
|||||||
1,
|
1,
|
||||||
circle.x + 0.5,
|
circle.x + 0.5,
|
||||||
circle.y + 0.5,
|
circle.y + 0.5,
|
||||||
circle.radius
|
circle.radius,
|
||||||
)
|
)
|
||||||
|
|
||||||
gradient.addColorStop(0.3, "rgba(255, 255, 255, 0.3)")
|
gradient.addColorStop(0.3, "rgba(255, 255, 255, 0.3)")
|
||||||
@@ -39,20 +39,20 @@ function draw(circle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function move(circle, timeDelta) {
|
function move(circle, timeDelta) {
|
||||||
circle.x = circle.x + timeDelta*circle.dx
|
circle.x = circle.x + timeDelta * circle.dx
|
||||||
circle.y = circle.y - timeDelta*circle.dy
|
circle.y = circle.y - timeDelta * circle.dy
|
||||||
}
|
}
|
||||||
|
|
||||||
let intervalId
|
let intervalId
|
||||||
|
|
||||||
function startAnimation() {
|
function startAnimation() {
|
||||||
if(intervalId == null) {
|
if (intervalId == null) {
|
||||||
intervalId = setInterval(animate, 20)
|
intervalId = setInterval(animate, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopAnimation() {
|
function stopAnimation() {
|
||||||
if(intervalId != null) {
|
if (intervalId != null) {
|
||||||
clearInterval(intervalId)
|
clearInterval(intervalId)
|
||||||
intervalId = null
|
intervalId = null
|
||||||
}
|
}
|
||||||
@@ -65,31 +65,31 @@ const animate = () => {
|
|||||||
const timeDelta = prevFrameTime == null ? 0 : now - prevFrameTime
|
const timeDelta = prevFrameTime == null ? 0 : now - prevFrameTime
|
||||||
prevFrameTime = now
|
prevFrameTime = now
|
||||||
|
|
||||||
if(circles.length == 0) {
|
if (circles.length == 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height)
|
context.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
circles.forEach(circle => {
|
circles.forEach(circle => {
|
||||||
move(circle, timeDelta)
|
move(circle, timeDelta)
|
||||||
draw(circle)
|
draw(circle)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const createCircles = (event) => {
|
const createCircles = event => {
|
||||||
startAnimation()
|
startAnimation()
|
||||||
|
|
||||||
circles = circles.concat(Array.from({length: 50}, () => (
|
circles = circles.concat(
|
||||||
{
|
Array.from({ length: 50 }, () => ({
|
||||||
x: event.pageX,
|
x: event.pageX,
|
||||||
y: event.pageY,
|
y: event.pageY,
|
||||||
radius: Math.random() * 50,
|
radius: Math.random() * 50,
|
||||||
dx: Math.random() * 0.3,
|
dx: Math.random() * 0.3,
|
||||||
dy: Math.random() * 0.7,
|
dy: Math.random() * 0.7,
|
||||||
hue: 200,
|
hue: 200,
|
||||||
}
|
})),
|
||||||
)))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.addEventListener("click", createCircles)
|
canvas.addEventListener("click", createCircles)
|
||||||
|
|||||||
@@ -1,34 +1,40 @@
|
|||||||
window.addEventListener('load', () => {
|
window.addEventListener("load", () => {
|
||||||
const text = document.createElement('input')
|
const text = document.createElement("input")
|
||||||
|
|
||||||
const checkbox = document.createElement('input')
|
const checkbox = document.createElement("input")
|
||||||
checkbox.setAttribute('type', 'checkbox')
|
checkbox.setAttribute("type", "checkbox")
|
||||||
|
|
||||||
const radio = document.createElement('input')
|
const radio = document.createElement("input")
|
||||||
radio.setAttribute('type', 'radio')
|
radio.setAttribute("type", "radio")
|
||||||
|
|
||||||
const range = document.createElement('input')
|
const range = document.createElement("input")
|
||||||
range.setAttribute('type', 'range')
|
range.setAttribute("type", "range")
|
||||||
|
|
||||||
const select = document.createElement('select')
|
const select = document.createElement("select")
|
||||||
Array.from({length: 5}, (_, i) => i).forEach(i => {
|
Array.from({ length: 5 }, (_, i) => i).forEach(i => {
|
||||||
const option = document.createElement('option')
|
const option = document.createElement("option")
|
||||||
option.setAttribute('value', i)
|
option.setAttribute("value", i)
|
||||||
option.innerText = i
|
option.innerText = i
|
||||||
select.appendChild(option)
|
select.appendChild(option)
|
||||||
})
|
})
|
||||||
|
|
||||||
const div = document.createElement('div')
|
const div = document.createElement("div")
|
||||||
|
|
||||||
|
const elements = { text, checkbox, range, select, radio, div }
|
||||||
|
|
||||||
const elements = { text, checkbox, range, select, radio, div}
|
|
||||||
|
|
||||||
Object.entries(elements).forEach(([name, el]) => {
|
Object.entries(elements).forEach(([name, el]) => {
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el)
|
||||||
['click', 'input', 'change'].forEach(type => {
|
;["click", "input", "change"].forEach(type => {
|
||||||
el.addEventListener(type, e => {
|
el.addEventListener(type, e => {
|
||||||
const row = document.createElement('div')
|
const row = document.createElement("div")
|
||||||
div.appendChild(row)
|
div.appendChild(row)
|
||||||
row.innerText = [name, type, e.target.value, e.target.checked, e.target.selectedIndex].join(', ')
|
row.innerText = [
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
e.target.value,
|
||||||
|
e.target.checked,
|
||||||
|
e.target.selectedIndex,
|
||||||
|
].join(", ")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import {ethers} from 'https://unpkg.com/ethers/dist/ethers.js'
|
import { ethers } from "ethers"
|
||||||
|
|
||||||
const URL = 'https://rpc.ankr.com/eth'
|
const URL = "https://eth-mainnet.public.blastapi.io"
|
||||||
|
|
||||||
const provider = await ethers.getDefaultProvider(URL)
|
const provider = await ethers.getDefaultProvider(URL)
|
||||||
|
|
||||||
const latest = await provider.getBlock('latest')
|
const latest = await provider.getBlock("latest")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Find ethereum block by timestamp using binary search
|
Find ethereum block by timestamp using binary search
|
||||||
*/
|
*/
|
||||||
async function getBlockNumberByTimestamp(timestamp, low = 0, high = latest.number) {
|
async function getBlockNumberByTimestamp(
|
||||||
if(low + 1 == high) {
|
timestamp,
|
||||||
|
low = 0,
|
||||||
|
high = latest.number,
|
||||||
|
) {
|
||||||
|
if (low + 1 == high) {
|
||||||
return low
|
return low
|
||||||
} else {
|
} else {
|
||||||
const mid = Math.floor((low + high) / 2)
|
const mid = Math.floor((low + high) / 2)
|
||||||
const midBlock = await provider.getBlock(mid)
|
const midBlock = await provider.getBlock(mid)
|
||||||
if(midBlock.timestamp > timestamp) {
|
if (midBlock.timestamp > timestamp) {
|
||||||
return getBlockNumberByTimestamp(timestamp, low, mid)
|
return getBlockNumberByTimestamp(timestamp, low, mid)
|
||||||
} else {
|
} else {
|
||||||
return getBlockNumberByTimestamp(timestamp, mid, high)
|
return getBlockNumberByTimestamp(timestamp, mid, high)
|
||||||
@@ -23,6 +27,6 @@ async function getBlockNumberByTimestamp(timestamp, low = 0, high = latest.numbe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = new Date('2019-06-01').getTime()/1000
|
const timestamp = new Date("2019-06-01").getTime() / 1000
|
||||||
const blockNumber = await getBlockNumberByTimestamp(timestamp)
|
const blockNumber = await getBlockNumberByTimestamp(timestamp)
|
||||||
const block = await provider.getBlock(blockNumber)
|
const block = await provider.getBlock(blockNumber)
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import {ethers} from 'https://unpkg.com/ethers@5.7.2/dist/ethers.esm.js'
|
import { ethers } from "ethers"
|
||||||
|
|
||||||
const URL = 'https://rpc.ankr.com/eth_goerli'
|
const URL = "https://eth-mainnet.public.blastapi.io"
|
||||||
|
|
||||||
const p = ethers.getDefaultProvider(URL)
|
const p = ethers.getDefaultProvider(URL)
|
||||||
|
|
||||||
const latest = await p.getBlock()
|
const latest = await p.getBlock()
|
||||||
|
|
||||||
const txs = await Promise.all(latest.transactions.map(t =>
|
const txs = await Promise.all(
|
||||||
p.getTransactionReceipt(t)
|
latest.transactions.map(t => p.getTransactionReceipt(t)),
|
||||||
))
|
)
|
||||||
|
|
||||||
const totalGas = txs
|
const totalGas = txs
|
||||||
.filter(tx => tx != null)
|
.filter(tx => tx != null)
|
||||||
.reduce((gas,tx) => gas.add(tx.gasUsed), ethers.BigNumber.from(0))
|
.reduce((gas, tx) => gas.add(tx.gasUsed), ethers.BigNumber.from(0))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Fibonacci numbers
|
// Fibonacci numbers
|
||||||
|
|
||||||
function fib(n) {
|
function fib(n) {
|
||||||
if(n == 0 || n == 1) {
|
if (n == 0 || n == 1) {
|
||||||
return n
|
return n
|
||||||
} else {
|
} else {
|
||||||
return fib(n - 1) + fib(n - 2)
|
return fib(n - 1) + fib(n - 2)
|
||||||
|
|||||||
@@ -2,30 +2,29 @@
|
|||||||
// Author: Sarah Bricault
|
// Author: Sarah Bricault
|
||||||
|
|
||||||
// Canvas setup
|
// Canvas setup
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement("canvas")
|
||||||
canvas.width = 700
|
canvas.width = 700
|
||||||
canvas.height = 700
|
canvas.height = 700
|
||||||
document.body.appendChild(canvas)
|
document.body.appendChild(canvas)
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext("2d")
|
||||||
ctx.translate(canvas.width / 2, canvas.height)
|
ctx.translate(canvas.width / 2, canvas.height)
|
||||||
|
|
||||||
// Draw a tree
|
// Draw a tree
|
||||||
fractalTreeBasic({totalIterations: 10, basicLength: 10, rotate: 25})
|
fractalTreeBasic({ totalIterations: 10, basicLength: 10, rotate: 25 })
|
||||||
|
|
||||||
function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
|
||||||
|
|
||||||
|
function fractalTreeBasic({ totalIterations, basicLength, rotate }) {
|
||||||
// Draw the tree trunk
|
// Draw the tree trunk
|
||||||
const trunkLength = basicLength * 2 * Math.pow(1.2, totalIterations + 1)
|
const trunkLength = basicLength * 2 * Math.pow(1.2, totalIterations + 1)
|
||||||
const width = Math.pow(totalIterations, 0.6)
|
const width = Math.pow(totalIterations, 0.6)
|
||||||
|
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(0, 0)
|
ctx.moveTo(0, 0)
|
||||||
ctx.lineTo(0, - trunkLength)
|
ctx.lineTo(0, -trunkLength)
|
||||||
ctx.lineWidth = width
|
ctx.lineWidth = width
|
||||||
ctx.strokeStyle = 'black'
|
ctx.strokeStyle = "black"
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|
||||||
drawBranch(90, [0, - trunkLength], totalIterations + 1)
|
drawBranch(90, [0, -trunkLength], totalIterations + 1)
|
||||||
|
|
||||||
function drawBranch(angle, startPoint, iterations) {
|
function drawBranch(angle, startPoint, iterations) {
|
||||||
const len = basicLength * Math.pow(1.2, iterations)
|
const len = basicLength * Math.pow(1.2, iterations)
|
||||||
@@ -34,7 +33,7 @@ function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
|||||||
|
|
||||||
const red = Math.floor(255 - (iterations / totalIterations) * 255)
|
const red = Math.floor(255 - (iterations / totalIterations) * 255)
|
||||||
const green = 0
|
const green = 0
|
||||||
const blue = Math.floor( 255 - (iterations / totalIterations) * 255)
|
const blue = Math.floor(255 - (iterations / totalIterations) * 255)
|
||||||
const color = `rgb(${red}, ${green}, ${blue})`
|
const color = `rgb(${red}, ${green}, ${blue})`
|
||||||
|
|
||||||
const x1 = startPoint[0]
|
const x1 = startPoint[0]
|
||||||
@@ -43,7 +42,7 @@ function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
|||||||
const y2 = y1 - len * Math.sin((angle * Math.PI) / 180)
|
const y2 = y1 - len * Math.sin((angle * Math.PI) / 180)
|
||||||
const x2 = x1 + len * Math.cos((angle * Math.PI) / 180)
|
const x2 = x1 + len * Math.cos((angle * Math.PI) / 180)
|
||||||
|
|
||||||
console.log('draw branch', x1, y1, x2, y2)
|
console.log("draw branch", x1, y1, x2, y2)
|
||||||
|
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(x1, y1)
|
ctx.moveTo(x1, y1)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import _ from 'https://unpkg.com/lodash-es'
|
import _ from "lodash-es"
|
||||||
|
|
||||||
async function getPopularLanguages() {
|
async function getPopularLanguages() {
|
||||||
const url = 'https://api.github.com/search/repositories?q=stars:%3E1&sort=stars'
|
const url =
|
||||||
|
"https://api.github.com/search/repositories?q=stars:%3E1&sort=stars"
|
||||||
const resp = await fetch(url)
|
const resp = await fetch(url)
|
||||||
const repos = await resp.json()
|
const repos = await resp.json()
|
||||||
return _(repos.items)
|
return _(repos.items)
|
||||||
@@ -9,7 +10,7 @@ async function getPopularLanguages() {
|
|||||||
.filter(l => l != null)
|
.filter(l => l != null)
|
||||||
.countBy()
|
.countBy()
|
||||||
.toPairs()
|
.toPairs()
|
||||||
.orderBy(([lang, useCount]) => useCount, 'desc')
|
.orderBy(([lang, useCount]) => useCount, "desc")
|
||||||
.map(([lang]) => lang)
|
.map(([lang]) => lang)
|
||||||
.value()
|
.value()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import _ from 'https://unpkg.com/lodash-es'
|
import _ from "lodash-es"
|
||||||
|
|
||||||
const url = 'https://api.github.com/search/repositories?q=stars:%3E1&sort=stars'
|
const url = "https://api.github.com/search/repositories?q=stars:%3E1&sort=stars"
|
||||||
const resp = await fetch(url)
|
const resp = await fetch(url)
|
||||||
const repos = await resp.json()
|
const repos = await resp.json()
|
||||||
const langs = _(repos.items)
|
const langs = _(repos.items)
|
||||||
@@ -8,43 +8,18 @@ const langs = _(repos.items)
|
|||||||
.filter(l => l != null)
|
.filter(l => l != null)
|
||||||
.countBy()
|
.countBy()
|
||||||
.toPairs()
|
.toPairs()
|
||||||
.map(([language, count]) => ({language, count}))
|
.map(([language, count]) => ({ language, count }))
|
||||||
.value()
|
.value()
|
||||||
|
|
||||||
import {barY} from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm";
|
import { barY } from "@observablehq/plot"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Move the cursor to the following line and see the plot displayed alongside the code
|
Move the cursor to the following line and see the plot displayed alongside the code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
barY(langs, {x: "language", y: "count", sort: {x: "y", reverse: true}, fill: 'purple'})
|
barY(langs, {
|
||||||
.plot()
|
x: "language",
|
||||||
|
y: "count",
|
||||||
|
sort: { x: "y", reverse: true },
|
||||||
|
fill: "purple",
|
||||||
|
}).plot()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,44 @@
|
|||||||
const fib = n => {
|
const fib = n => {
|
||||||
if(n == 0) {
|
if (n == 0) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if(n == 1) {
|
if (n == 1) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return fib(n - 1) + fib(n - 2)
|
return fib(n - 1) + fib(n - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* external */
|
|
||||||
import {h, render} from 'https://unpkg.com/preact?module';
|
|
||||||
|
|
||||||
/* external */
|
/* external */
|
||||||
import {Stateful} from './stateful.js'
|
import { h, render } from "preact"
|
||||||
|
|
||||||
|
/* external */
|
||||||
|
import { Stateful } from "./stateful.js"
|
||||||
|
|
||||||
const Fibonacci = Stateful({
|
const Fibonacci = Stateful({
|
||||||
getInitialState: () => ({index: 0}),
|
getInitialState: () => ({ index: 0 }),
|
||||||
|
|
||||||
handlers: {
|
handlers: {
|
||||||
prev: ({index}, event) => ({index: index - 1}),
|
prev: ({ index }, event) => ({ index: index - 1 }),
|
||||||
next: ({index}, event) => ({index: index + 1}),
|
next: ({ index }, event) => ({ index: index + 1 }),
|
||||||
},
|
},
|
||||||
|
|
||||||
render: (props, state, handlers) =>
|
render: (props, state, handlers) =>
|
||||||
h('div', null,
|
h(
|
||||||
h('h1', null,
|
"div",
|
||||||
'nth Fibonacci number is ',
|
null,
|
||||||
|
h(
|
||||||
|
"h1",
|
||||||
|
null,
|
||||||
|
"nth Fibonacci number is ",
|
||||||
fib(state.index),
|
fib(state.index),
|
||||||
' for n = ',
|
" for n = ",
|
||||||
state.index
|
state.index,
|
||||||
),
|
),
|
||||||
h('button', {onClick: handlers.prev}, 'Previous'), ' ',
|
h("button", { onClick: handlers.prev }, "Previous"),
|
||||||
h('button', {onClick: handlers.next}, 'Next'), ' ',
|
" ",
|
||||||
)
|
h("button", { onClick: handlers.next }, "Next"),
|
||||||
|
" ",
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
render(h(Fibonacci), globalThis.document.body)
|
render(h(Fibonacci), globalThis.document.body)
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import {Component} from 'https://unpkg.com/preact?module';
|
import { Component } from "preact"
|
||||||
|
|
||||||
export const Stateful = ({getInitialState, handlers, render}) => {
|
|
||||||
|
|
||||||
|
export const Stateful = ({ getInitialState, handlers, render }) => {
|
||||||
return class extends Component {
|
return class extends Component {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.compState = getInitialState()
|
this.compState = getInitialState()
|
||||||
this.handlers = Object.fromEntries(
|
this.handlers = Object.fromEntries(
|
||||||
Object
|
Object.entries(handlers).map(([name, h]) => [
|
||||||
.entries(handlers)
|
name,
|
||||||
.map(([name, h]) =>
|
this.makeHandler(h),
|
||||||
[name, this.makeHandler(h)]
|
]),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,5 +24,4 @@ export const Stateful = ({getInitialState, handlers, render}) => {
|
|||||||
return render(this.props, this.compState, this.handlers)
|
return render(this.props, this.compState, this.handlers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Example of a TODO app built using the Preact library
|
Example of a TODO app built using the Preact library
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'https://esm.sh/preact/compat'
|
import React from "preact/compat"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ let state
|
|||||||
if (globalThis.leporello) {
|
if (globalThis.leporello) {
|
||||||
// Retrieve initial state from Leporello storage
|
// Retrieve initial state from Leporello storage
|
||||||
// See: https://github.com/leporello-js/leporello-js?tab=readme-ov-file#saving-state-between-page-reloads
|
// See: https://github.com/leporello-js/leporello-js?tab=readme-ov-file#saving-state-between-page-reloads
|
||||||
state = globalThis.leporello.storage.get('state')
|
state = globalThis.leporello.storage.get("state")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -21,14 +21,16 @@ if (globalThis.leporello) {
|
|||||||
This helper function wraps such a function so that its result updates the global state
|
This helper function wraps such a function so that its result updates the global state
|
||||||
and can be used as an event handler.
|
and can be used as an event handler.
|
||||||
*/
|
*/
|
||||||
const handler = fn => (...args) => {
|
const handler =
|
||||||
state = fn(state, ...args)
|
fn =>
|
||||||
if (globalThis.leporello) {
|
(...args) => {
|
||||||
// Persist state to Leporello storage to restore it after page reloads
|
state = fn(state, ...args)
|
||||||
globalThis.leporello.storage.set('state', state)
|
if (globalThis.leporello) {
|
||||||
|
// Persist state to Leporello storage to restore it after page reloads
|
||||||
|
globalThis.leporello.storage.set("state", state)
|
||||||
|
}
|
||||||
|
render()
|
||||||
}
|
}
|
||||||
render()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Higher-order function that injects the current state into a component
|
// Higher-order function that injects the current state into a component
|
||||||
const connect = comp => props => comp(props, state)
|
const connect = comp => props => comp(props, state)
|
||||||
@@ -39,12 +41,12 @@ const render = () => React.render(<App />, document.body)
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = {
|
state = {
|
||||||
todos: [],
|
todos: [],
|
||||||
text: '',
|
text: "",
|
||||||
filter: 'ALL',
|
filter: "ALL",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', render)
|
window.addEventListener("load", render)
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
|
||||||
@@ -68,10 +70,10 @@ const Footer = () => (
|
|||||||
const FilterLink = connect(({ filter, children }, state) => {
|
const FilterLink = connect(({ filter, children }, state) => {
|
||||||
const disabled = state.filter == filter
|
const disabled = state.filter == filter
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={handler(changeFilter.bind(null, filter))}
|
onClick={handler(changeFilter.bind(null, filter))}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={{ marginLeft: '4px' }}
|
style={{ marginLeft: "4px" }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
@@ -87,9 +89,9 @@ const TodoList = connect((_, state) => (
|
|||||||
))
|
))
|
||||||
|
|
||||||
const Todo = ({ todo }) => (
|
const Todo = ({ todo }) => (
|
||||||
<li
|
<li
|
||||||
onClick={handler(toggleTodo.bind(null, todo))}
|
onClick={handler(toggleTodo.bind(null, todo))}
|
||||||
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
|
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
|
||||||
>
|
>
|
||||||
{todo.text}
|
{todo.text}
|
||||||
</li>
|
</li>
|
||||||
@@ -99,11 +101,7 @@ const AddTodo = connect((_, state) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={handler(createTodo)}>
|
<form onSubmit={handler(createTodo)}>
|
||||||
<input
|
<input value={state.text} onChange={handler(changeText)} autoFocus />
|
||||||
value={state.text}
|
|
||||||
onChange={handler(changeText)}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
<button type="submit">Add Todo</button>
|
<button type="submit">Add Todo</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,14 +112,14 @@ const AddTodo = connect((_, state) => {
|
|||||||
|
|
||||||
// Returns a filtered list of TODOs based on the current filter state
|
// Returns a filtered list of TODOs based on the current filter state
|
||||||
function visibleTodos(state) {
|
function visibleTodos(state) {
|
||||||
if (state.filter == 'ALL') {
|
if (state.filter == "ALL") {
|
||||||
return state.todos
|
return state.todos
|
||||||
} else if (state.filter == 'ACTIVE') {
|
} else if (state.filter == "ACTIVE") {
|
||||||
return state.todos.filter(t => !t.completed)
|
return state.todos.filter(t => !t.completed)
|
||||||
} else if (state.filter == 'COMPLETED') {
|
} else if (state.filter == "COMPLETED") {
|
||||||
return state.todos.filter(t => t.completed)
|
return state.todos.filter(t => t.completed)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown filter')
|
throw new Error("Unknown filter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,18 +144,18 @@ function createTodo(state, e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
todos: [...state.todos, { text: state.text }],
|
todos: [...state.todos, { text: state.text }],
|
||||||
text: '',
|
text: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggles the completion state of a TODO item
|
// Toggles the completion state of a TODO item
|
||||||
function toggleTodo(todo, state) {
|
function toggleTodo(todo, state) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
todos: state.todos.map(t =>
|
todos: state.todos.map(t =>
|
||||||
t == todo ? { ...todo, completed: !todo.completed } : t
|
t == todo ? { ...todo, completed: !todo.completed } : t,
|
||||||
)
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Redux Todos Example</title>
|
<title>Redux Todos Example</title>
|
||||||
|
|
||||||
<script src='https://unpkg.com/react/umd/react.development.js'></script>
|
<script src="https://unpkg.com/react/umd/react.development.js"></script>
|
||||||
<script src='https://unpkg.com/react-dom/umd/react-dom.development.js'></script>
|
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
|
||||||
<script src='https://unpkg.com/redux'></script>
|
<script src="https://unpkg.com/redux"></script>
|
||||||
<script src='https://unpkg.com/react-redux'></script>
|
<script src="https://unpkg.com/react-redux"></script>
|
||||||
|
|
||||||
<script type='module'>
|
<script type="module">
|
||||||
if(new URLSearchParams(window.location.search).get('leporello') == null) {
|
if (
|
||||||
await import('../src/index.js');
|
new URLSearchParams(window.location.search).get("leporello") == null
|
||||||
|
) {
|
||||||
|
await import("../src/index.js")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ const nextTodoId = new Function(`
|
|||||||
`)()
|
`)()
|
||||||
|
|
||||||
export const addTodo = text => ({
|
export const addTodo = text => ({
|
||||||
type: 'ADD_TODO',
|
type: "ADD_TODO",
|
||||||
id: nextTodoId(),
|
id: nextTodoId(),
|
||||||
text
|
text,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const setVisibilityFilter = filter => ({
|
export const setVisibilityFilter = filter => ({
|
||||||
type: 'SET_VISIBILITY_FILTER',
|
type: "SET_VISIBILITY_FILTER",
|
||||||
filter
|
filter,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const toggleTodo = id => ({
|
export const toggleTodo = id => ({
|
||||||
type: 'TOGGLE_TODO',
|
type: "TOGGLE_TODO",
|
||||||
id
|
id,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const VisibilityFilters = {
|
export const VisibilityFilters = {
|
||||||
SHOW_ALL: 'SHOW_ALL',
|
SHOW_ALL: "SHOW_ALL",
|
||||||
SHOW_COMPLETED: 'SHOW_COMPLETED',
|
SHOW_COMPLETED: "SHOW_COMPLETED",
|
||||||
SHOW_ACTIVE: 'SHOW_ACTIVE'
|
SHOW_ACTIVE: "SHOW_ACTIVE",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import Footer from './Footer.js'
|
import Footer from "./Footer.js"
|
||||||
import AddTodo from '../containers/AddTodo.js'
|
import AddTodo from "../containers/AddTodo.js"
|
||||||
import VisibleTodoList from '../containers/VisibleTodoList.js'
|
import VisibleTodoList from "../containers/VisibleTodoList.js"
|
||||||
|
|
||||||
const h = React.createElement
|
const h = React.createElement
|
||||||
|
|
||||||
const App = () => (
|
const App = () => h("div", null, h(AddTodo), h(VisibleTodoList), h(Footer))
|
||||||
h('div', null,
|
|
||||||
h(AddTodo),
|
|
||||||
h(VisibleTodoList),
|
|
||||||
h(Footer),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import FilterLink from '../containers/FilterLink.js'
|
import FilterLink from "../containers/FilterLink.js"
|
||||||
import { VisibilityFilters } from '../actions/index.js'
|
import { VisibilityFilters } from "../actions/index.js"
|
||||||
|
|
||||||
const h = React.createElement
|
const h = React.createElement
|
||||||
|
|
||||||
const Footer = () => (
|
const Footer = () =>
|
||||||
h('div', null,
|
h(
|
||||||
h('span', null, 'Show: '),
|
"div",
|
||||||
h(FilterLink, {filter: VisibilityFilters.SHOW_ALL}, 'All'),
|
null,
|
||||||
h(FilterLink, {filter: VisibilityFilters.SHOW_ACTIVE}, 'Active'),
|
h("span", null, "Show: "),
|
||||||
h(FilterLink, {filter: VisibilityFilters.SHOW_COMPLETED}, 'Completed'),
|
h(FilterLink, { filter: VisibilityFilters.SHOW_ALL }, "All"),
|
||||||
|
h(FilterLink, { filter: VisibilityFilters.SHOW_ACTIVE }, "Active"),
|
||||||
|
h(FilterLink, { filter: VisibilityFilters.SHOW_COMPLETED }, "Completed"),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
export default Footer
|
export default Footer
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
const h = React.createElement
|
const h = React.createElement
|
||||||
|
|
||||||
const Link = ({ active, children, onClick }) => (
|
const Link = ({ active, children, onClick }) =>
|
||||||
h('button', {
|
h(
|
||||||
onClick,
|
"button",
|
||||||
disabled: active,
|
{
|
||||||
style:{
|
onClick,
|
||||||
marginLeft: '4px',
|
disabled: active,
|
||||||
}
|
style: {
|
||||||
}, children)
|
marginLeft: "4px",
|
||||||
)
|
},
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
)
|
||||||
|
|
||||||
export default Link
|
export default Link
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
const h = React.createElement
|
const h = React.createElement
|
||||||
|
|
||||||
const Todo = ({ onClick, completed, text }) => (
|
const Todo = ({ onClick, completed, text }) =>
|
||||||
h('li', {
|
h(
|
||||||
onClick,
|
"li",
|
||||||
style: {
|
{
|
||||||
textDecoration: completed ? 'line-through' : 'none'
|
onClick,
|
||||||
|
style: {
|
||||||
|
textDecoration: completed ? "line-through" : "none",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, text)
|
text,
|
||||||
)
|
)
|
||||||
|
|
||||||
export default Todo
|
export default Todo
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import Todo from './Todo.js'
|
import Todo from "./Todo.js"
|
||||||
|
|
||||||
const h = React.createElement
|
const h = React.createElement
|
||||||
|
|
||||||
const TodoList = ({ todos, toggleTodo }) => (
|
const TodoList = ({ todos, toggleTodo }) =>
|
||||||
h('ul', null,
|
h(
|
||||||
|
"ul",
|
||||||
|
null,
|
||||||
todos.map(todo =>
|
todos.map(todo =>
|
||||||
h(Todo, {
|
h(Todo, {
|
||||||
key: todo.id,
|
key: todo.id,
|
||||||
...todo,
|
...todo,
|
||||||
onClick: () => toggleTodo(todo.id),
|
onClick: () => toggleTodo(todo.id),
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
export default TodoList
|
export default TodoList
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
import { addTodo } from '../actions/index.js'
|
import { addTodo } from "../actions/index.js"
|
||||||
|
|
||||||
const h = React.createElement
|
const h = React.createElement
|
||||||
|
|
||||||
const AddTodo = ({ dispatch }) => {
|
const AddTodo = ({ dispatch }) => {
|
||||||
const inputref = {}
|
const inputref = {}
|
||||||
|
|
||||||
return (
|
return h(
|
||||||
h('div', null,
|
"div",
|
||||||
h('form', {
|
null,
|
||||||
|
h(
|
||||||
|
"form",
|
||||||
|
{
|
||||||
onSubmit: e => {
|
onSubmit: e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (inputref.input.value.trim()) {
|
if (inputref.input.value.trim()) {
|
||||||
dispatch(addTodo(inputref.input.value))
|
dispatch(addTodo(inputref.input.value))
|
||||||
Object.assign(inputref.input, {value: ''})
|
Object.assign(inputref.input, { value: "" })
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
h('input', {ref: input => Object.assign(inputref, {input})}),
|
h("input", { ref: input => Object.assign(inputref, { input }) }),
|
||||||
h('button', {type: 'submit'}, 'Add Todo')
|
h("button", { type: "submit" }, "Add Todo"),
|
||||||
)
|
),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { setVisibilityFilter } from '../actions/index.js'
|
import { setVisibilityFilter } from "../actions/index.js"
|
||||||
import Link from '../components/Link.js'
|
import Link from "../components/Link.js"
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
active: ownProps.filter === state.visibilityFilter
|
active: ownProps.filter === state.visibilityFilter,
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
|
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default ReactRedux.connect(
|
export default ReactRedux.connect(mapStateToProps, mapDispatchToProps)(Link)
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Link)
|
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
import { toggleTodo } from '../actions/index.js'
|
import { toggleTodo } from "../actions/index.js"
|
||||||
import TodoList from '../components/TodoList.js'
|
import TodoList from "../components/TodoList.js"
|
||||||
import { VisibilityFilters } from '../actions/index.js'
|
import { VisibilityFilters } from "../actions/index.js"
|
||||||
|
|
||||||
const getVisibleTodos = (todos, filter) => {
|
const getVisibleTodos = (todos, filter) => {
|
||||||
if(filter == VisibilityFilters.SHOW_ALL) {
|
if (filter == VisibilityFilters.SHOW_ALL) {
|
||||||
return todos
|
return todos
|
||||||
} else if(filter == VisibilityFilters.SHOW_COMPLETED) {
|
} else if (filter == VisibilityFilters.SHOW_COMPLETED) {
|
||||||
return todos.filter(t => t.completed)
|
return todos.filter(t => t.completed)
|
||||||
} else if(filter == VisibilityFilters.SHOW_ACTIVE) {
|
} else if (filter == VisibilityFilters.SHOW_ACTIVE) {
|
||||||
return todos.filter(t => !t.completed)
|
return todos.filter(t => !t.completed)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown filter: ' + filter)
|
throw new Error("Unknown filter: " + filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
todos: getVisibleTodos(state.todos, state.visibilityFilter)
|
todos: getVisibleTodos(state.todos, state.visibilityFilter),
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
toggleTodo: id => dispatch(toggleTodo(id))
|
toggleTodo: id => dispatch(toggleTodo(id)),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default ReactRedux.connect(
|
export default ReactRedux.connect(mapStateToProps, mapDispatchToProps)(TodoList)
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(TodoList)
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import App from './components/App.js'
|
import App from "./components/App.js"
|
||||||
import rootReducer from './reducers/index.js'
|
import rootReducer from "./reducers/index.js"
|
||||||
|
|
||||||
const h = React.createElement
|
const h = React.createElement
|
||||||
|
|
||||||
const store = Redux.createStore(rootReducer)
|
const store = Redux.createStore(rootReducer)
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
h(ReactRedux.Provider, {store}, h(App)),
|
h(ReactRedux.Provider, { store }, h(App)),
|
||||||
document.getElementById('root')
|
document.getElementById("root"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import todos from './todos.js'
|
import todos from "./todos.js"
|
||||||
import visibilityFilter from './visibilityFilter.js'
|
import visibilityFilter from "./visibilityFilter.js"
|
||||||
|
|
||||||
export default Redux.combineReducers({
|
export default Redux.combineReducers({
|
||||||
todos,
|
todos,
|
||||||
visibilityFilter
|
visibilityFilter,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
const todos = (state = [], action) => {
|
const todos = (state = [], action) => {
|
||||||
if(action.type == 'ADD_TODO') {
|
if (action.type == "ADD_TODO") {
|
||||||
return [
|
return [
|
||||||
...state,
|
...state,
|
||||||
{
|
{
|
||||||
id: action.id,
|
id: action.id,
|
||||||
text: action.text,
|
text: action.text,
|
||||||
completed: false
|
completed: false,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
} else if(action.type == 'TOGGLE_TODO') {
|
} else if (action.type == "TOGGLE_TODO") {
|
||||||
return state.map(todo =>
|
return state.map(todo =>
|
||||||
(todo.id === action.id)
|
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo,
|
||||||
? {...todo, completed: !todo.completed}
|
|
||||||
: todo
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return state
|
return state
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { VisibilityFilters } from '../actions/index.js'
|
import { VisibilityFilters } from "../actions/index.js"
|
||||||
|
|
||||||
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
|
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
|
||||||
if(action.type == 'SET_VISIBILITY_FILTER') {
|
if (action.type == "SET_VISIBILITY_FILTER") {
|
||||||
return action.filter
|
return action.filter
|
||||||
} else {
|
} else {
|
||||||
return state
|
return state
|
||||||
|
|||||||
523
index.html
523
index.html
@@ -5,6 +5,8 @@
|
|||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
<title>Leporello.js</title>
|
<title>Leporello.js</title>
|
||||||
|
|
||||||
|
<script type='module' src='./src/ts_libs.js' async></script>
|
||||||
|
|
||||||
<script src='ace/ace.js'></script>
|
<script src='ace/ace.js'></script>
|
||||||
<script src='ace/keybinding-vim.js'></script>
|
<script src='ace/keybinding-vim.js'></script>
|
||||||
<script src='ace/ext-language_tools.js'></script>
|
<script src='ace/ext-language_tools.js'></script>
|
||||||
@@ -20,526 +22,7 @@
|
|||||||
|
|
||||||
<script type='module' src="./src/launch.js"></script>
|
<script type='module' src="./src/launch.js"></script>
|
||||||
|
|
||||||
<style>
|
<link rel=stylesheet href='./styles.css'></link>
|
||||||
|
|
||||||
:root {
|
|
||||||
--shadow_color: rgb(171 200 214);
|
|
||||||
--active_color: rgb(173, 228, 253);
|
|
||||||
--error-color: #ff000024;
|
|
||||||
--warn-color: #fff6d5;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body, .app {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
display: inline-block;
|
|
||||||
height: 0.8em;
|
|
||||||
width: 0.8em;
|
|
||||||
min-width: 0.8em;
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top: none !important;
|
|
||||||
border: 2px solid;
|
|
||||||
animation: rotate 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes rotate {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.app {
|
|
||||||
/* same as ace editor */
|
|
||||||
font-family: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app::backdrop {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root {
|
|
||||||
height: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas:
|
|
||||||
"code code"
|
|
||||||
"bottom files"
|
|
||||||
"statusbar statusbar";
|
|
||||||
grid-template-columns: 70% 30%;
|
|
||||||
grid-template-rows: auto 1fr 2.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor_container, .bottom, .files_container, .statusbar {
|
|
||||||
box-shadow: 1px 1px 3px 0px var(--shadow_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor_container, .bottom, .statusbar, .files_container {
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor_container:focus-within,
|
|
||||||
.bottom:focus-within,
|
|
||||||
.files_container:focus-within,
|
|
||||||
dialog {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 1px 1px 6px 3px var(--shadow_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab_content:focus-within, .problems_container:focus-within {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor_container {
|
|
||||||
height: 55vh;
|
|
||||||
resize: vertical;
|
|
||||||
position: relative;
|
|
||||||
grid-area: code;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ace markers */
|
|
||||||
|
|
||||||
.selection {
|
|
||||||
position: absolute;
|
|
||||||
background-color: #ff00ff;
|
|
||||||
z-index: 1; /* make it on top of evaluated_ok and evaluated_error */
|
|
||||||
}
|
|
||||||
|
|
||||||
.evaluated_ok {
|
|
||||||
position: absolute;
|
|
||||||
background-color: rgb(225, 244, 253);
|
|
||||||
}
|
|
||||||
.evaluated_error {
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--error-color);
|
|
||||||
}
|
|
||||||
.error-code {
|
|
||||||
/*
|
|
||||||
TODO: make underline like in all editors
|
|
||||||
*/
|
|
||||||
position: absolute;
|
|
||||||
border-bottom: 4px solid red;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* end of ace markers */
|
|
||||||
|
|
||||||
.eval_error {
|
|
||||||
padding: 0em 1em;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tabs */
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
padding-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs > .tab {
|
|
||||||
margin-right: 1em;
|
|
||||||
padding: 0.3em 1em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs > .tab.active {
|
|
||||||
background-color: rgb(225, 244, 253);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* debugger */
|
|
||||||
|
|
||||||
.bottom {
|
|
||||||
grid-area: bottom;
|
|
||||||
overflow: auto;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debugger {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debugger_wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debugger, .problems_container {
|
|
||||||
padding: 5px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs, .io_trace {
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs .log {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs .log.active {
|
|
||||||
background-color: var(--active_color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs .log.error {
|
|
||||||
background-color: var(--error-color);
|
|
||||||
color: black !important; /* override red color that is set for calltree */
|
|
||||||
&.native {
|
|
||||||
color: grey !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs .log.warn {
|
|
||||||
background-color: var(--warn-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab_content {
|
|
||||||
flex: 1;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.callnode {
|
|
||||||
/* This makes every callnode be the size of the the longest one, so
|
|
||||||
* every callnode is clickable anywhere in the calltree view, and
|
|
||||||
* background for active callnodes is as wide as the entire container.
|
|
||||||
* Useful when scrolling very wide call trees */
|
|
||||||
min-width: fit-content;
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
.callnode .active {
|
|
||||||
background-color: var(--active_color);
|
|
||||||
}
|
|
||||||
.call_el {
|
|
||||||
/*
|
|
||||||
Make active callnode background start from the left of the calltree
|
|
||||||
view
|
|
||||||
*/
|
|
||||||
margin-left: -1000vw;
|
|
||||||
padding-left: 1000vw;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.call_el .expand_icon, .call_el .expand_icon_placeholder {
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call_header {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.call_header.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.call_header.error.native {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.call_header.native {
|
|
||||||
font-style: italic;
|
|
||||||
color: grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call_header .loop_step {
|
|
||||||
color: grey;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 1px 5px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin-right: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* problems view */
|
|
||||||
.problem a {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* files */
|
|
||||||
|
|
||||||
.files_container {
|
|
||||||
overflow: auto;
|
|
||||||
grid-area: files;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.allow_file_access {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.allow_file_access .subtitle {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files {
|
|
||||||
overflow: auto;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files .file {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files > .file {
|
|
||||||
margin-left: 0em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files .file_title {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files .file_title.active {
|
|
||||||
background-color: var(--active_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.files .file_title .select_entrypoint {
|
|
||||||
margin-left: auto;
|
|
||||||
width: 40px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files .file_title .icon {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 5px;
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file_actions {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
padding: 5px;
|
|
||||||
background-color: rgb(225 244 253 / 80%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file_actions .file_action {
|
|
||||||
margin-right: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file_actions .select_entrypoint_title {
|
|
||||||
width: 40px;
|
|
||||||
position: absolute;
|
|
||||||
right: 7px;
|
|
||||||
font-size: 0.8em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* value_explorer */
|
|
||||||
|
|
||||||
.embed_value_explorer_container.is_not_dom_el {
|
|
||||||
height: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed_value_explorer_container.is_dom_el {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed_value_explorer_wrapper {
|
|
||||||
/* preserve wrapper from getting clicks for code line left to it */
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed_value_explorer_container.is_not_dom_el .embed_value_explorer_wrapper {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed_value_explorer_content {
|
|
||||||
pointer-events: initial;
|
|
||||||
white-space: pre;
|
|
||||||
max-width: fit-content;
|
|
||||||
background-color: white;
|
|
||||||
box-shadow: 1px 2px 9px -1px var(--shadow_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed_value_explorer_content:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 1px 2px 11px 1px var(--shadow_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed_value_explorer_content > .value_explorer_node {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed_value_explorer_control {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value_explorer_node {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value_explorer_header {
|
|
||||||
display: inline-block;
|
|
||||||
padding-right: 1em;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value_explorer_header .expand_icon {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value_explorer_header.active {
|
|
||||||
background-color: rgb(148, 227, 191);
|
|
||||||
}
|
|
||||||
|
|
||||||
.value_explorer_key {
|
|
||||||
color: rgb(150, 0, 128);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* status */
|
|
||||||
|
|
||||||
.statusbar {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
grid-area: statusbar;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.statusbar .spinner {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status, .current_file {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
.status {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.statusbar_action {
|
|
||||||
margin-right: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.statusbar_action.first {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.open_app_window_button {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.open_app_window_tooltip {
|
|
||||||
padding: 1em;
|
|
||||||
position: absolute;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
bottom: 100%;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.7em;
|
|
||||||
background-color: rgb(120 206 247);
|
|
||||||
border-radius: 21px;
|
|
||||||
transform: scale(0);
|
|
||||||
transition: transform 0.3s;
|
|
||||||
}
|
|
||||||
.open_app_window_tooltip.on {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
.open_app_window_tooltip:after {
|
|
||||||
content: '';
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 20px solid transparent;
|
|
||||||
border-right: 20px solid transparent;
|
|
||||||
border-top: 20px solid rgb(120 206 247);
|
|
||||||
position: absolute;
|
|
||||||
bottom: -20px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
.options > * {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show_help, .github {
|
|
||||||
margin: 0em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.share_button, .upload_button {
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
background: rgb(23 166 236);
|
|
||||||
}
|
|
||||||
|
|
||||||
.share_button {
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin-left: 1em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.share_button[disabled] {
|
|
||||||
background: grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.share_dialog input, .share_dialog button {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.share_dialog button {
|
|
||||||
padding: 5px;
|
|
||||||
height: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog::backdrop {
|
|
||||||
background-color: rgb(225 244 253 / 80%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.help_dialog[open] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0;
|
|
||||||
min-width: 70%;
|
|
||||||
min-height: 70%;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help {
|
|
||||||
padding: 2em;
|
|
||||||
border-spacing: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help th {
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help th.key {
|
|
||||||
width: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help td.key {
|
|
||||||
background-color: rgb(225, 244, 253, 0.5);
|
|
||||||
border-radius: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help_dialog form {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
114
src/ts_libs.js
Normal file
114
src/ts_libs.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
const lib_names = [
|
||||||
|
'lib.d.ts',
|
||||||
|
'lib.decorators.d.ts',
|
||||||
|
'lib.decorators.legacy.d.ts',
|
||||||
|
'lib.dom.asynciterable.d.ts',
|
||||||
|
'lib.dom.d.ts',
|
||||||
|
'lib.dom.iterable.d.ts',
|
||||||
|
'lib.es2015.collection.d.ts',
|
||||||
|
'lib.es2015.core.d.ts',
|
||||||
|
'lib.es2015.d.ts',
|
||||||
|
'lib.es2015.generator.d.ts',
|
||||||
|
'lib.es2015.iterable.d.ts',
|
||||||
|
'lib.es2015.promise.d.ts',
|
||||||
|
'lib.es2015.proxy.d.ts',
|
||||||
|
'lib.es2015.reflect.d.ts',
|
||||||
|
'lib.es2015.symbol.d.ts',
|
||||||
|
'lib.es2015.symbol.wellknown.d.ts',
|
||||||
|
'lib.es2016.array.include.d.ts',
|
||||||
|
'lib.es2016.d.ts',
|
||||||
|
'lib.es2016.full.d.ts',
|
||||||
|
'lib.es2016.intl.d.ts',
|
||||||
|
'lib.es2017.arraybuffer.d.ts',
|
||||||
|
'lib.es2017.d.ts',
|
||||||
|
'lib.es2017.date.d.ts',
|
||||||
|
'lib.es2017.full.d.ts',
|
||||||
|
'lib.es2017.intl.d.ts',
|
||||||
|
'lib.es2017.object.d.ts',
|
||||||
|
'lib.es2017.sharedmemory.d.ts',
|
||||||
|
'lib.es2017.string.d.ts',
|
||||||
|
'lib.es2017.typedarrays.d.ts',
|
||||||
|
'lib.es2018.asyncgenerator.d.ts',
|
||||||
|
'lib.es2018.asynciterable.d.ts',
|
||||||
|
'lib.es2018.d.ts',
|
||||||
|
'lib.es2018.full.d.ts',
|
||||||
|
'lib.es2018.intl.d.ts',
|
||||||
|
'lib.es2018.promise.d.ts',
|
||||||
|
'lib.es2018.regexp.d.ts',
|
||||||
|
'lib.es2019.array.d.ts',
|
||||||
|
'lib.es2019.d.ts',
|
||||||
|
'lib.es2019.full.d.ts',
|
||||||
|
'lib.es2019.intl.d.ts',
|
||||||
|
'lib.es2019.object.d.ts',
|
||||||
|
'lib.es2019.string.d.ts',
|
||||||
|
'lib.es2019.symbol.d.ts',
|
||||||
|
'lib.es2020.bigint.d.ts',
|
||||||
|
'lib.es2020.d.ts',
|
||||||
|
'lib.es2020.date.d.ts',
|
||||||
|
'lib.es2020.full.d.ts',
|
||||||
|
'lib.es2020.intl.d.ts',
|
||||||
|
'lib.es2020.number.d.ts',
|
||||||
|
'lib.es2020.promise.d.ts',
|
||||||
|
'lib.es2020.sharedmemory.d.ts',
|
||||||
|
'lib.es2020.string.d.ts',
|
||||||
|
'lib.es2020.symbol.wellknown.d.ts',
|
||||||
|
'lib.es2021.d.ts',
|
||||||
|
'lib.es2021.full.d.ts',
|
||||||
|
'lib.es2021.intl.d.ts',
|
||||||
|
'lib.es2021.promise.d.ts',
|
||||||
|
'lib.es2021.string.d.ts',
|
||||||
|
'lib.es2021.weakref.d.ts',
|
||||||
|
'lib.es2022.array.d.ts',
|
||||||
|
'lib.es2022.d.ts',
|
||||||
|
'lib.es2022.error.d.ts',
|
||||||
|
'lib.es2022.full.d.ts',
|
||||||
|
'lib.es2022.intl.d.ts',
|
||||||
|
'lib.es2022.object.d.ts',
|
||||||
|
'lib.es2022.regexp.d.ts',
|
||||||
|
'lib.es2022.string.d.ts',
|
||||||
|
'lib.es2023.array.d.ts',
|
||||||
|
'lib.es2023.collection.d.ts',
|
||||||
|
'lib.es2023.d.ts',
|
||||||
|
'lib.es2023.full.d.ts',
|
||||||
|
'lib.es2023.intl.d.ts',
|
||||||
|
'lib.es2024.arraybuffer.d.ts',
|
||||||
|
'lib.es2024.collection.d.ts',
|
||||||
|
'lib.es2024.d.ts',
|
||||||
|
'lib.es2024.full.d.ts',
|
||||||
|
'lib.es2024.object.d.ts',
|
||||||
|
'lib.es2024.promise.d.ts',
|
||||||
|
'lib.es2024.regexp.d.ts',
|
||||||
|
'lib.es2024.sharedmemory.d.ts',
|
||||||
|
'lib.es2024.string.d.ts',
|
||||||
|
'lib.es5.d.ts',
|
||||||
|
'lib.es6.d.ts',
|
||||||
|
'lib.esnext.array.d.ts',
|
||||||
|
'lib.esnext.collection.d.ts',
|
||||||
|
'lib.esnext.d.ts',
|
||||||
|
'lib.esnext.decorators.d.ts',
|
||||||
|
'lib.esnext.disposable.d.ts',
|
||||||
|
'lib.esnext.full.d.ts',
|
||||||
|
'lib.esnext.intl.d.ts',
|
||||||
|
'lib.esnext.iterator.d.ts',
|
||||||
|
]
|
||||||
|
|
||||||
|
export let ts_libs_promise = load_ts_libs()
|
||||||
|
|
||||||
|
async function load_ts_libs() {
|
||||||
|
if(globalThis.process == null) {
|
||||||
|
return Object.fromEntries(await Promise.all(
|
||||||
|
lib_names.map(name =>
|
||||||
|
fetch('typescript/' + name).then(r => r.text()).then(text => ([name, text]))
|
||||||
|
)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
const fs = await import('fs')
|
||||||
|
return Object.fromEntries(
|
||||||
|
lib_names.map(name =>
|
||||||
|
[name, fs.readFileSync('typescript/' + name, 'ascii')]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
548
styles.css
Normal file
548
styles.css
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
:root {
|
||||||
|
--shadow_color: rgb(171 200 214);
|
||||||
|
--active_color: rgb(173, 228, 253);
|
||||||
|
--error-color: #ff000024;
|
||||||
|
--warn-color: #fff6d5;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, .app {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
height: 0.8em;
|
||||||
|
width: 0.8em;
|
||||||
|
min-width: 0.8em;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: none !important;
|
||||||
|
border: 2px solid;
|
||||||
|
animation: rotate 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
/* same as ace editor */
|
||||||
|
font-family: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app::backdrop {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"code"
|
||||||
|
"bottom"
|
||||||
|
"statusbar";
|
||||||
|
grid-template-rows: auto 1fr 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor_container, .bottom, .statusbar {
|
||||||
|
box-shadow: 1px 1px 3px 0px var(--shadow_color);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor_container, .bottom, .statusbar {
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor_container:focus-within,
|
||||||
|
.bottom:focus-within,
|
||||||
|
dialog {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 1px 1px 6px 3px var(--shadow_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab_content:focus-within, .problems_container:focus-within {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor_container {
|
||||||
|
height: 55vh;
|
||||||
|
resize: vertical;
|
||||||
|
position: relative;
|
||||||
|
grid-area: code;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ace markers */
|
||||||
|
|
||||||
|
.selection {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #ff00ff;
|
||||||
|
z-index: 1; /* make it on top of evaluated_ok and evaluated_error */
|
||||||
|
}
|
||||||
|
|
||||||
|
.evaluated_ok {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgb(225, 244, 253);
|
||||||
|
}
|
||||||
|
.evaluated_error {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--error-color);
|
||||||
|
}
|
||||||
|
.error-code {
|
||||||
|
/*
|
||||||
|
TODO: make underline like in all editors
|
||||||
|
*/
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 4px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of ace markers */
|
||||||
|
|
||||||
|
.eval_error {
|
||||||
|
padding: 0em 1em;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
font-family: system-ui;
|
||||||
|
display: flex;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs > .tab {
|
||||||
|
margin-right: 1em;
|
||||||
|
padding: 0.3em 1em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs > .tab.active {
|
||||||
|
background-color: rgb(225, 244, 253);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* debugger */
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
grid-area: bottom;
|
||||||
|
overflow: auto;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugger {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugger_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugger, .problems_container {
|
||||||
|
padding: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs, .io_trace {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs .log {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs .log.active {
|
||||||
|
background-color: var(--active_color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs .log.error {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
color: black !important; /* override red color that is set for calltree */
|
||||||
|
&.native {
|
||||||
|
color: grey !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs .log.warn {
|
||||||
|
background-color: var(--warn-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab_content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callnode {
|
||||||
|
/* This makes every callnode be the size of the the longest one, so
|
||||||
|
* every callnode is clickable anywhere in the calltree view, and
|
||||||
|
* background for active callnodes is as wide as the entire container.
|
||||||
|
* Useful when scrolling very wide call trees */
|
||||||
|
min-width: fit-content;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
.callnode .active {
|
||||||
|
background-color: var(--active_color);
|
||||||
|
}
|
||||||
|
.call_el {
|
||||||
|
/*
|
||||||
|
Make active callnode background start from the left of the calltree
|
||||||
|
view
|
||||||
|
*/
|
||||||
|
margin-left: -1000vw;
|
||||||
|
padding-left: 1000vw;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.call_el .expand_icon, .call_el .expand_icon_placeholder {
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call_header {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.call_header.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.call_header.error.native {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.call_header.native {
|
||||||
|
font-style: italic;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call_header .loop_step {
|
||||||
|
color: grey;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* io trace */
|
||||||
|
.io_trace .event {
|
||||||
|
border-radius: 1em;
|
||||||
|
line-height: 2em;
|
||||||
|
padding: 0.1em 0.5em;
|
||||||
|
background-color: var(--active_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* problems view */
|
||||||
|
.problem a {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* files */
|
||||||
|
|
||||||
|
.files_container {
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.allow_file_access {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.allow_file_access .subtitle {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files .file {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files > .file {
|
||||||
|
margin-left: 0em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files .file_title {
|
||||||
|
display: flex;
|
||||||
|
margin-left: -100vw;
|
||||||
|
padding-left: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files .file_title.active {
|
||||||
|
background-color: var(--active_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.files .file_title .select_entrypoint {
|
||||||
|
margin-left: auto;
|
||||||
|
width: 3em;
|
||||||
|
margin-right: 0.7em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files .file_title .icon {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file_actions {
|
||||||
|
font-family: system-ui;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: rgb(225 244 253 / 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file_actions .file_action {
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file_actions .select_entrypoint_title {
|
||||||
|
width: 3em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* value_explorer */
|
||||||
|
|
||||||
|
.embed_value_explorer_container.is_not_dom_el {
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed_value_explorer_container.is_dom_el {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed_value_explorer_wrapper {
|
||||||
|
/* preserve wrapper from getting clicks for code line left to it */
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed_value_explorer_container.is_not_dom_el .embed_value_explorer_wrapper {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed_value_explorer_content {
|
||||||
|
pointer-events: initial;
|
||||||
|
white-space: pre;
|
||||||
|
max-width: fit-content;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 1px 2px 9px -1px var(--shadow_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed_value_explorer_content:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 1px 2px 11px 1px var(--shadow_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed_value_explorer_content > .value_explorer_node {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed_value_explorer_control {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value_explorer_node {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value_explorer_header {
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value_explorer_header .expand_icon {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value_explorer_header.active {
|
||||||
|
background-color: rgb(148, 227, 191);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value_explorer_key {
|
||||||
|
color: rgb(150, 0, 128);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* status */
|
||||||
|
|
||||||
|
.statusbar {
|
||||||
|
font-family: system-ui;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
grid-area: statusbar;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar .spinner {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status, .current_file {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar_action {
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar_action.first {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open_app_window_button {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.open_app_window_tooltip {
|
||||||
|
padding: 1em;
|
||||||
|
position: absolute;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
bottom: 100%;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.7em;
|
||||||
|
background-color: rgb(120 206 247);
|
||||||
|
border-radius: 21px;
|
||||||
|
transform: scale(0);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
.open_app_window_tooltip.on {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
.open_app_window_tooltip:after {
|
||||||
|
content: '';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 20px solid transparent;
|
||||||
|
border-right: 20px solid transparent;
|
||||||
|
border-top: 20px solid rgb(120 206 247);
|
||||||
|
position: absolute;
|
||||||
|
bottom: -20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.options > * {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show_help, .github {
|
||||||
|
margin: 0em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar_button, .upload_button {
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
background: rgb(23 166 236);
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar_button {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin-left: 1em;
|
||||||
|
&:last-of-type {
|
||||||
|
margin: 0em 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar_button[disabled] {
|
||||||
|
background: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share_dialog input, .share_dialog button {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share_dialog button {
|
||||||
|
padding: 5px;
|
||||||
|
height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog::backdrop {
|
||||||
|
background-color: rgb(225 244 253 / 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help_dialog[open] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 70%;
|
||||||
|
min-height: 70%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help {
|
||||||
|
padding: 2em;
|
||||||
|
border-spacing: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help th {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help th.key {
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help td.key {
|
||||||
|
background-color: rgb(225, 244, 253, 0.5);
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help_dialog form {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel:not([open]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel[open] {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px 0px 0px auto;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
animation: slide-in 0.2s ease-in forwards;
|
||||||
|
&::backdrop {
|
||||||
|
background-color: rgb(225 244 253 / 60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in{
|
||||||
|
0% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user