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,11 +2,11 @@
|
||||
// Author: Sarah Bricault
|
||||
|
||||
// Canvas setup
|
||||
const canvas = document.createElement('canvas')
|
||||
const canvas = document.createElement("canvas")
|
||||
canvas.width = 700
|
||||
canvas.height = 700
|
||||
document.body.appendChild(canvas)
|
||||
const ctx = canvas.getContext('2d')
|
||||
const ctx = canvas.getContext("2d")
|
||||
ctx.translate(canvas.width / 2, canvas.height)
|
||||
|
||||
// Draw a tree
|
||||
@@ -17,7 +17,6 @@ function sleep() {
|
||||
}
|
||||
|
||||
async function fractalTreeBasic({ totalIterations, basicLength, rotate }) {
|
||||
|
||||
// Draw the tree trunk
|
||||
const trunkLength = basicLength * 2 * Math.pow(1.2, totalIterations + 1)
|
||||
const width = Math.pow(totalIterations, 0.6)
|
||||
@@ -26,7 +25,7 @@ async function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
||||
ctx.moveTo(0, 0)
|
||||
ctx.lineTo(0, -trunkLength)
|
||||
ctx.lineWidth = width
|
||||
ctx.strokeStyle = 'black'
|
||||
ctx.strokeStyle = "black"
|
||||
ctx.stroke()
|
||||
|
||||
await drawBranch(90, [0, -trunkLength], totalIterations + 1)
|
||||
@@ -47,7 +46,7 @@ async function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
||||
const y2 = y1 - len * Math.sin((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.moveTo(x1, y1)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Original source:
|
||||
// https://www.freecodecamp.org/news/how-to-create-animated-bubbles-with-html5-canvas-and-javascript/
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.style.backgroundColor = '#00b4ff'
|
||||
const canvas = document.createElement("canvas")
|
||||
canvas.style.backgroundColor = "#00b4ff"
|
||||
document.body.appendChild(canvas)
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
@@ -10,9 +10,9 @@ canvas.height = window.innerHeight
|
||||
const context = canvas.getContext("2d")
|
||||
|
||||
context.font = "30px Arial"
|
||||
context.textAlign = 'center'
|
||||
context.fillStyle = 'white'
|
||||
context.fillText('Click to spawn bubbles', canvas.width/2, canvas.height/2)
|
||||
context.textAlign = "center"
|
||||
context.fillStyle = "white"
|
||||
context.fillText("Click to spawn bubbles", canvas.width / 2, canvas.height / 2)
|
||||
|
||||
let circles = []
|
||||
|
||||
@@ -28,7 +28,7 @@ function draw(circle) {
|
||||
1,
|
||||
circle.x + 0.5,
|
||||
circle.y + 0.5,
|
||||
circle.radius
|
||||
circle.radius,
|
||||
)
|
||||
|
||||
gradient.addColorStop(0.3, "rgba(255, 255, 255, 0.3)")
|
||||
@@ -77,19 +77,19 @@ const animate = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const createCircles = (event) => {
|
||||
const createCircles = event => {
|
||||
startAnimation()
|
||||
|
||||
circles = circles.concat(Array.from({length: 50}, () => (
|
||||
{
|
||||
circles = circles.concat(
|
||||
Array.from({ length: 50 }, () => ({
|
||||
x: event.pageX,
|
||||
y: event.pageY,
|
||||
radius: Math.random() * 50,
|
||||
dx: Math.random() * 0.3,
|
||||
dy: Math.random() * 0.7,
|
||||
hue: 200,
|
||||
}
|
||||
)))
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
canvas.addEventListener("click", createCircles)
|
||||
|
||||
@@ -1,34 +1,40 @@
|
||||
window.addEventListener('load', () => {
|
||||
const text = document.createElement('input')
|
||||
window.addEventListener("load", () => {
|
||||
const text = document.createElement("input")
|
||||
|
||||
const checkbox = document.createElement('input')
|
||||
checkbox.setAttribute('type', 'checkbox')
|
||||
const checkbox = document.createElement("input")
|
||||
checkbox.setAttribute("type", "checkbox")
|
||||
|
||||
const radio = document.createElement('input')
|
||||
radio.setAttribute('type', 'radio')
|
||||
const radio = document.createElement("input")
|
||||
radio.setAttribute("type", "radio")
|
||||
|
||||
const range = document.createElement('input')
|
||||
range.setAttribute('type', 'range')
|
||||
const range = document.createElement("input")
|
||||
range.setAttribute("type", "range")
|
||||
|
||||
const select = document.createElement('select')
|
||||
const select = document.createElement("select")
|
||||
Array.from({ length: 5 }, (_, i) => i).forEach(i => {
|
||||
const option = document.createElement('option')
|
||||
option.setAttribute('value', i)
|
||||
const option = document.createElement("option")
|
||||
option.setAttribute("value", i)
|
||||
option.innerText = i
|
||||
select.appendChild(option)
|
||||
})
|
||||
|
||||
const div = document.createElement('div')
|
||||
const div = document.createElement("div")
|
||||
|
||||
const elements = { text, checkbox, range, select, radio, div }
|
||||
|
||||
Object.entries(elements).forEach(([name, el]) => {
|
||||
document.body.appendChild(el);
|
||||
['click', 'input', 'change'].forEach(type => {
|
||||
document.body.appendChild(el)
|
||||
;["click", "input", "change"].forEach(type => {
|
||||
el.addEventListener(type, e => {
|
||||
const row = document.createElement('div')
|
||||
const row = document.createElement("div")
|
||||
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,15 +1,19 @@
|
||||
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 latest = await provider.getBlock('latest')
|
||||
const latest = await provider.getBlock("latest")
|
||||
|
||||
/*
|
||||
Find ethereum block by timestamp using binary search
|
||||
*/
|
||||
async function getBlockNumberByTimestamp(timestamp, low = 0, high = latest.number) {
|
||||
async function getBlockNumberByTimestamp(
|
||||
timestamp,
|
||||
low = 0,
|
||||
high = latest.number,
|
||||
) {
|
||||
if (low + 1 == high) {
|
||||
return low
|
||||
} else {
|
||||
@@ -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 block = await provider.getBlock(blockNumber)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
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 latest = await p.getBlock()
|
||||
|
||||
const txs = await Promise.all(latest.transactions.map(t =>
|
||||
p.getTransactionReceipt(t)
|
||||
))
|
||||
const txs = await Promise.all(
|
||||
latest.transactions.map(t => p.getTransactionReceipt(t)),
|
||||
)
|
||||
|
||||
const totalGas = txs
|
||||
.filter(tx => tx != null)
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
// Author: Sarah Bricault
|
||||
|
||||
// Canvas setup
|
||||
const canvas = document.createElement('canvas')
|
||||
const canvas = document.createElement("canvas")
|
||||
canvas.width = 700
|
||||
canvas.height = 700
|
||||
document.body.appendChild(canvas)
|
||||
const ctx = canvas.getContext('2d')
|
||||
const ctx = canvas.getContext("2d")
|
||||
ctx.translate(canvas.width / 2, canvas.height)
|
||||
|
||||
// Draw a tree
|
||||
fractalTreeBasic({ totalIterations: 10, basicLength: 10, rotate: 25 })
|
||||
|
||||
function fractalTreeBasic({ totalIterations, basicLength, rotate }) {
|
||||
|
||||
// Draw the tree trunk
|
||||
const trunkLength = basicLength * 2 * Math.pow(1.2, totalIterations + 1)
|
||||
const width = Math.pow(totalIterations, 0.6)
|
||||
@@ -22,7 +21,7 @@ function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
||||
ctx.moveTo(0, 0)
|
||||
ctx.lineTo(0, -trunkLength)
|
||||
ctx.lineWidth = width
|
||||
ctx.strokeStyle = 'black'
|
||||
ctx.strokeStyle = "black"
|
||||
ctx.stroke()
|
||||
|
||||
drawBranch(90, [0, -trunkLength], totalIterations + 1)
|
||||
@@ -43,7 +42,7 @@ function fractalTreeBasic({totalIterations, basicLength, rotate}) {
|
||||
const y2 = y1 - len * Math.sin((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.moveTo(x1, y1)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import _ from 'https://unpkg.com/lodash-es'
|
||||
import _ from "lodash-es"
|
||||
|
||||
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 repos = await resp.json()
|
||||
return _(repos.items)
|
||||
@@ -9,7 +10,7 @@ async function getPopularLanguages() {
|
||||
.filter(l => l != null)
|
||||
.countBy()
|
||||
.toPairs()
|
||||
.orderBy(([lang, useCount]) => useCount, 'desc')
|
||||
.orderBy(([lang, useCount]) => useCount, "desc")
|
||||
.map(([lang]) => lang)
|
||||
.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 repos = await resp.json()
|
||||
const langs = _(repos.items)
|
||||
@@ -11,40 +11,15 @@ const langs = _(repos.items)
|
||||
.map(([language, count]) => ({ language, count }))
|
||||
.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
|
||||
*/
|
||||
|
||||
barY(langs, {x: "language", y: "count", sort: {x: "y", reverse: true}, fill: 'purple'})
|
||||
.plot()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
barY(langs, {
|
||||
x: "language",
|
||||
y: "count",
|
||||
sort: { x: "y", reverse: true },
|
||||
fill: "purple",
|
||||
}).plot()
|
||||
|
||||
@@ -9,10 +9,10 @@ const fib = n => {
|
||||
}
|
||||
|
||||
/* external */
|
||||
import {h, render} from 'https://unpkg.com/preact?module';
|
||||
import { h, render } from "preact"
|
||||
|
||||
/* external */
|
||||
import {Stateful} from './stateful.js'
|
||||
import { Stateful } from "./stateful.js"
|
||||
|
||||
const Fibonacci = Stateful({
|
||||
getInitialState: () => ({ index: 0 }),
|
||||
@@ -23,16 +23,22 @@ const Fibonacci = Stateful({
|
||||
},
|
||||
|
||||
render: (props, state, handlers) =>
|
||||
h('div', null,
|
||||
h('h1', null,
|
||||
'nth Fibonacci number is ',
|
||||
h(
|
||||
"div",
|
||||
null,
|
||||
h(
|
||||
"h1",
|
||||
null,
|
||||
"nth Fibonacci number is ",
|
||||
fib(state.index),
|
||||
' for n = ',
|
||||
state.index
|
||||
" for n = ",
|
||||
state.index,
|
||||
),
|
||||
h("button", { onClick: handlers.prev }, "Previous"),
|
||||
" ",
|
||||
h("button", { onClick: handlers.next }, "Next"),
|
||||
" ",
|
||||
),
|
||||
h('button', {onClick: handlers.prev}, 'Previous'), ' ',
|
||||
h('button', {onClick: handlers.next}, 'Next'), ' ',
|
||||
)
|
||||
})
|
||||
|
||||
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 }) => {
|
||||
|
||||
return class extends Component {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.compState = getInitialState()
|
||||
this.handlers = Object.fromEntries(
|
||||
Object
|
||||
.entries(handlers)
|
||||
.map(([name, h]) =>
|
||||
[name, this.makeHandler(h)]
|
||||
)
|
||||
Object.entries(handlers).map(([name, h]) => [
|
||||
name,
|
||||
this.makeHandler(h),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,5 +24,4 @@ export const Stateful = ({getInitialState, handlers, render}) => {
|
||||
return render(this.props, this.compState, this.handlers)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Example of a TODO app built using the Preact library
|
||||
*/
|
||||
|
||||
import React from 'https://esm.sh/preact/compat'
|
||||
import React from "preact/compat"
|
||||
|
||||
// Core
|
||||
|
||||
@@ -13,7 +13,7 @@ let state
|
||||
if (globalThis.leporello) {
|
||||
// Retrieve initial state from Leporello storage
|
||||
// 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,11 +21,13 @@ if (globalThis.leporello) {
|
||||
This helper function wraps such a function so that its result updates the global state
|
||||
and can be used as an event handler.
|
||||
*/
|
||||
const handler = fn => (...args) => {
|
||||
const handler =
|
||||
fn =>
|
||||
(...args) => {
|
||||
state = fn(state, ...args)
|
||||
if (globalThis.leporello) {
|
||||
// Persist state to Leporello storage to restore it after page reloads
|
||||
globalThis.leporello.storage.set('state', state)
|
||||
globalThis.leporello.storage.set("state", state)
|
||||
}
|
||||
render()
|
||||
}
|
||||
@@ -39,12 +41,12 @@ const render = () => React.render(<App />, document.body)
|
||||
if (state == null) {
|
||||
state = {
|
||||
todos: [],
|
||||
text: '',
|
||||
filter: 'ALL',
|
||||
text: "",
|
||||
filter: "ALL",
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', render)
|
||||
window.addEventListener("load", render)
|
||||
|
||||
// Components
|
||||
|
||||
@@ -71,7 +73,7 @@ const FilterLink = connect(({ filter, children }, state) => {
|
||||
<button
|
||||
onClick={handler(changeFilter.bind(null, filter))}
|
||||
disabled={disabled}
|
||||
style={{ marginLeft: '4px' }}
|
||||
style={{ marginLeft: "4px" }}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
@@ -89,7 +91,7 @@ const TodoList = connect((_, state) => (
|
||||
const Todo = ({ todo }) => (
|
||||
<li
|
||||
onClick={handler(toggleTodo.bind(null, todo))}
|
||||
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
|
||||
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
|
||||
>
|
||||
{todo.text}
|
||||
</li>
|
||||
@@ -99,11 +101,7 @@ const AddTodo = connect((_, state) => {
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={handler(createTodo)}>
|
||||
<input
|
||||
value={state.text}
|
||||
onChange={handler(changeText)}
|
||||
autoFocus
|
||||
/>
|
||||
<input value={state.text} onChange={handler(changeText)} autoFocus />
|
||||
<button type="submit">Add Todo</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -114,14 +112,14 @@ const AddTodo = connect((_, state) => {
|
||||
|
||||
// Returns a filtered list of TODOs based on the current filter state
|
||||
function visibleTodos(state) {
|
||||
if (state.filter == 'ALL') {
|
||||
if (state.filter == "ALL") {
|
||||
return state.todos
|
||||
} else if (state.filter == 'ACTIVE') {
|
||||
} else if (state.filter == "ACTIVE") {
|
||||
return state.todos.filter(t => !t.completed)
|
||||
} else if (state.filter == 'COMPLETED') {
|
||||
} else if (state.filter == "COMPLETED") {
|
||||
return state.todos.filter(t => t.completed)
|
||||
} else {
|
||||
throw new Error('Unknown filter')
|
||||
throw new Error("Unknown filter")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +146,7 @@ function createTodo(state, e) {
|
||||
return {
|
||||
...state,
|
||||
todos: [...state.todos, { text: state.text }],
|
||||
text: '',
|
||||
text: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +155,7 @@ function toggleTodo(todo, state) {
|
||||
return {
|
||||
...state,
|
||||
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>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Redux Todos Example</title>
|
||||
|
||||
<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/redux'></script>
|
||||
<script src='https://unpkg.com/react-redux'></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/redux"></script>
|
||||
<script src="https://unpkg.com/react-redux"></script>
|
||||
|
||||
<script type='module'>
|
||||
if(new URLSearchParams(window.location.search).get('leporello') == null) {
|
||||
await import('../src/index.js');
|
||||
<script type="module">
|
||||
if (
|
||||
new URLSearchParams(window.location.search).get("leporello") == null
|
||||
) {
|
||||
await import("../src/index.js")
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -6,23 +6,23 @@ const nextTodoId = new Function(`
|
||||
`)()
|
||||
|
||||
export const addTodo = text => ({
|
||||
type: 'ADD_TODO',
|
||||
type: "ADD_TODO",
|
||||
id: nextTodoId(),
|
||||
text
|
||||
text,
|
||||
})
|
||||
|
||||
export const setVisibilityFilter = filter => ({
|
||||
type: 'SET_VISIBILITY_FILTER',
|
||||
filter
|
||||
type: "SET_VISIBILITY_FILTER",
|
||||
filter,
|
||||
})
|
||||
|
||||
export const toggleTodo = id => ({
|
||||
type: 'TOGGLE_TODO',
|
||||
id
|
||||
type: "TOGGLE_TODO",
|
||||
id,
|
||||
})
|
||||
|
||||
export const VisibilityFilters = {
|
||||
SHOW_ALL: 'SHOW_ALL',
|
||||
SHOW_COMPLETED: 'SHOW_COMPLETED',
|
||||
SHOW_ACTIVE: 'SHOW_ACTIVE'
|
||||
SHOW_ALL: "SHOW_ALL",
|
||||
SHOW_COMPLETED: "SHOW_COMPLETED",
|
||||
SHOW_ACTIVE: "SHOW_ACTIVE",
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import Footer from './Footer.js'
|
||||
import AddTodo from '../containers/AddTodo.js'
|
||||
import VisibleTodoList from '../containers/VisibleTodoList.js'
|
||||
import Footer from "./Footer.js"
|
||||
import AddTodo from "../containers/AddTodo.js"
|
||||
import VisibleTodoList from "../containers/VisibleTodoList.js"
|
||||
|
||||
const h = React.createElement
|
||||
|
||||
const App = () => (
|
||||
h('div', null,
|
||||
h(AddTodo),
|
||||
h(VisibleTodoList),
|
||||
h(Footer),
|
||||
)
|
||||
)
|
||||
const App = () => h("div", null, h(AddTodo), h(VisibleTodoList), h(Footer))
|
||||
|
||||
export default App
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import FilterLink from '../containers/FilterLink.js'
|
||||
import { VisibilityFilters } from '../actions/index.js'
|
||||
import FilterLink from "../containers/FilterLink.js"
|
||||
import { VisibilityFilters } from "../actions/index.js"
|
||||
|
||||
const h = React.createElement
|
||||
|
||||
const Footer = () => (
|
||||
h('div', null,
|
||||
h('span', null, 'Show: '),
|
||||
h(FilterLink, {filter: VisibilityFilters.SHOW_ALL}, 'All'),
|
||||
h(FilterLink, {filter: VisibilityFilters.SHOW_ACTIVE}, 'Active'),
|
||||
h(FilterLink, {filter: VisibilityFilters.SHOW_COMPLETED}, 'Completed'),
|
||||
)
|
||||
const Footer = () =>
|
||||
h(
|
||||
"div",
|
||||
null,
|
||||
h("span", null, "Show: "),
|
||||
h(FilterLink, { filter: VisibilityFilters.SHOW_ALL }, "All"),
|
||||
h(FilterLink, { filter: VisibilityFilters.SHOW_ACTIVE }, "Active"),
|
||||
h(FilterLink, { filter: VisibilityFilters.SHOW_COMPLETED }, "Completed"),
|
||||
)
|
||||
|
||||
export default Footer
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
const h = React.createElement
|
||||
|
||||
const Link = ({ active, children, onClick }) => (
|
||||
h('button', {
|
||||
const Link = ({ active, children, onClick }) =>
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
onClick,
|
||||
disabled: active,
|
||||
style: {
|
||||
marginLeft: '4px',
|
||||
}
|
||||
}, children)
|
||||
marginLeft: "4px",
|
||||
},
|
||||
},
|
||||
children,
|
||||
)
|
||||
|
||||
export default Link
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
const h = React.createElement
|
||||
|
||||
const Todo = ({ onClick, completed, text }) => (
|
||||
h('li', {
|
||||
const Todo = ({ onClick, completed, text }) =>
|
||||
h(
|
||||
"li",
|
||||
{
|
||||
onClick,
|
||||
style: {
|
||||
textDecoration: completed ? 'line-through' : 'none'
|
||||
textDecoration: completed ? "line-through" : "none",
|
||||
},
|
||||
}, text)
|
||||
},
|
||||
text,
|
||||
)
|
||||
|
||||
export default Todo
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import Todo from './Todo.js'
|
||||
import Todo from "./Todo.js"
|
||||
|
||||
const h = React.createElement
|
||||
|
||||
const TodoList = ({ todos, toggleTodo }) => (
|
||||
h('ul', null,
|
||||
const TodoList = ({ todos, toggleTodo }) =>
|
||||
h(
|
||||
"ul",
|
||||
null,
|
||||
todos.map(todo =>
|
||||
h(Todo, {
|
||||
key: todo.id,
|
||||
...todo,
|
||||
onClick: () => toggleTodo(todo.id),
|
||||
})
|
||||
)
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
export default TodoList
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import { addTodo } from '../actions/index.js'
|
||||
import { addTodo } from "../actions/index.js"
|
||||
|
||||
const h = React.createElement
|
||||
|
||||
const AddTodo = ({ dispatch }) => {
|
||||
const inputref = {}
|
||||
|
||||
return (
|
||||
h('div', null,
|
||||
h('form', {
|
||||
return h(
|
||||
"div",
|
||||
null,
|
||||
h(
|
||||
"form",
|
||||
{
|
||||
onSubmit: e => {
|
||||
e.preventDefault()
|
||||
if (inputref.input.value.trim()) {
|
||||
dispatch(addTodo(inputref.input.value))
|
||||
Object.assign(inputref.input, {value: ''})
|
||||
}
|
||||
Object.assign(inputref.input, { value: "" })
|
||||
}
|
||||
},
|
||||
h('input', {ref: input => Object.assign(inputref, {input})}),
|
||||
h('button', {type: 'submit'}, 'Add Todo')
|
||||
)
|
||||
)
|
||||
},
|
||||
h("input", { ref: input => Object.assign(inputref, { input }) }),
|
||||
h("button", { type: "submit" }, "Add Todo"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { setVisibilityFilter } from '../actions/index.js'
|
||||
import Link from '../components/Link.js'
|
||||
import { setVisibilityFilter } from "../actions/index.js"
|
||||
import Link from "../components/Link.js"
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
active: ownProps.filter === state.visibilityFilter
|
||||
active: ownProps.filter === state.visibilityFilter,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
|
||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
|
||||
})
|
||||
|
||||
export default ReactRedux.connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Link)
|
||||
export default ReactRedux.connect(mapStateToProps, mapDispatchToProps)(Link)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { toggleTodo } from '../actions/index.js'
|
||||
import TodoList from '../components/TodoList.js'
|
||||
import { VisibilityFilters } from '../actions/index.js'
|
||||
import { toggleTodo } from "../actions/index.js"
|
||||
import TodoList from "../components/TodoList.js"
|
||||
import { VisibilityFilters } from "../actions/index.js"
|
||||
|
||||
const getVisibleTodos = (todos, filter) => {
|
||||
if (filter == VisibilityFilters.SHOW_ALL) {
|
||||
@@ -10,19 +10,16 @@ const getVisibleTodos = (todos, filter) => {
|
||||
} else if (filter == VisibilityFilters.SHOW_ACTIVE) {
|
||||
return todos.filter(t => !t.completed)
|
||||
} else {
|
||||
throw new Error('Unknown filter: ' + filter)
|
||||
throw new Error("Unknown filter: " + filter)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
todos: getVisibleTodos(state.todos, state.visibilityFilter)
|
||||
todos: getVisibleTodos(state.todos, state.visibilityFilter),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleTodo: id => dispatch(toggleTodo(id))
|
||||
toggleTodo: id => dispatch(toggleTodo(id)),
|
||||
})
|
||||
|
||||
export default ReactRedux.connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TodoList)
|
||||
export default ReactRedux.connect(mapStateToProps, mapDispatchToProps)(TodoList)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import App from './components/App.js'
|
||||
import rootReducer from './reducers/index.js'
|
||||
import App from "./components/App.js"
|
||||
import rootReducer from "./reducers/index.js"
|
||||
|
||||
const h = React.createElement
|
||||
|
||||
@@ -7,5 +7,5 @@ const store = Redux.createStore(rootReducer)
|
||||
|
||||
ReactDOM.render(
|
||||
h(ReactRedux.Provider, { store }, h(App)),
|
||||
document.getElementById('root')
|
||||
document.getElementById("root"),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import todos from './todos.js'
|
||||
import visibilityFilter from './visibilityFilter.js'
|
||||
import todos from "./todos.js"
|
||||
import visibilityFilter from "./visibilityFilter.js"
|
||||
|
||||
export default Redux.combineReducers({
|
||||
todos,
|
||||
visibilityFilter
|
||||
visibilityFilter,
|
||||
})
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
const todos = (state = [], action) => {
|
||||
if(action.type == 'ADD_TODO') {
|
||||
if (action.type == "ADD_TODO") {
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
id: action.id,
|
||||
text: action.text,
|
||||
completed: false
|
||||
}
|
||||
completed: false,
|
||||
},
|
||||
]
|
||||
} else if(action.type == 'TOGGLE_TODO') {
|
||||
} else if (action.type == "TOGGLE_TODO") {
|
||||
return state.map(todo =>
|
||||
(todo.id === action.id)
|
||||
? {...todo, completed: !todo.completed}
|
||||
: todo
|
||||
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo,
|
||||
)
|
||||
} else {
|
||||
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) => {
|
||||
if(action.type == 'SET_VISIBILITY_FILTER') {
|
||||
if (action.type == "SET_VISIBILITY_FILTER") {
|
||||
return action.filter
|
||||
} else {
|
||||
return state
|
||||
|
||||
523
index.html
523
index.html
@@ -5,6 +5,8 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<title>Leporello.js</title>
|
||||
|
||||
<script type='module' src='./src/ts_libs.js' async></script>
|
||||
|
||||
<script src='ace/ace.js'></script>
|
||||
<script src='ace/keybinding-vim.js'></script>
|
||||
<script src='ace/ext-language_tools.js'></script>
|
||||
@@ -20,526 +22,7 @@
|
||||
|
||||
<script type='module' src="./src/launch.js"></script>
|
||||
|
||||
<style>
|
||||
|
||||
: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>
|
||||
<link rel=stylesheet href='./styles.css'></link>
|
||||
|
||||
</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