add svelte experiment as well
This commit is contained in:
parent
a4d37e4014
commit
2037020f2f
1
svelte/.eslintignore
Normal file
1
svelte/.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
webpack.config.js
|
29
svelte/.eslintrc.js
Normal file
29
svelte/.eslintrc.js
Normal file
@ -0,0 +1,29 @@
|
||||
module.exports = {
|
||||
extends: ["eslint:recommended"],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2019,
|
||||
sourceType: "module"
|
||||
},
|
||||
env: {
|
||||
es6: true,
|
||||
browser: true
|
||||
},
|
||||
plugins: ["svelte3"],
|
||||
overrides: [
|
||||
{
|
||||
files: ["**/*.svelte"],
|
||||
processor: "svelte3/svelte3"
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" }
|
||||
],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" }
|
||||
]
|
||||
},
|
||||
settings: {}
|
||||
};
|
6
svelte/.gitignore
vendored
Normal file
6
svelte/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
public/bundle.*
|
||||
dist
|
||||
es
|
||||
.vscode
|
6
svelte/.prettierrc
Normal file
6
svelte/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"svelteSortOrder" : "styles-scripts-markup",
|
||||
"svelteStrictMode": true,
|
||||
"svelteBracketNewLine": true,
|
||||
"pluginSearchDirs": ["."]
|
||||
}
|
38
svelte/Makefile
Normal file
38
svelte/Makefile
Normal file
@ -0,0 +1,38 @@
|
||||
SHELL := bash
|
||||
.SHELLFLAGS := -eu -o pipefail -c
|
||||
.DELETE_ON_ERROR:
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
.SUFFIXES:
|
||||
|
||||
$(shell mkdir -p .build)
|
||||
|
||||
.PHONY: all
|
||||
all: run
|
||||
|
||||
.PHONY: run
|
||||
run: build-tslib
|
||||
tmux new -d -s anki-react "make run-pybackend"
|
||||
tmux split-window "make run-svelte"
|
||||
tmux attach
|
||||
|
||||
.PHONY: run-pybackend
|
||||
run-pybackend:
|
||||
../pyenv/bin/pip install bottle
|
||||
../pyenv/bin/python ../react/pybackend.py
|
||||
|
||||
.PHONY: build-tslib
|
||||
build-tslib:
|
||||
(cd ../tslib && make build)
|
||||
|
||||
.PHONY: run-svelte
|
||||
run-svelte: .build/deps
|
||||
npm run dev
|
||||
|
||||
.build/deps: package.json
|
||||
npm i
|
||||
@touch $@
|
||||
|
||||
.PHONY: svelete-lint
|
||||
svelte-lint:
|
||||
npx --no-install svelte-type-checker
|
2
svelte/README
Normal file
2
svelte/README
Normal file
@ -0,0 +1,2 @@
|
||||
This is another experiment, using Svelte instead of React for the UI.
|
||||
Instructions are the same as ../react/README
|
11317
svelte/package-lock.json
generated
Normal file
11317
svelte/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
svelte/package.json
Normal file
41
svelte/package.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "anki-svelte",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@sveltejs/svelte-virtual-list": "^3.0.1",
|
||||
"@types/jest": "^24.0.23",
|
||||
"@typescript-eslint/eslint-plugin": "^2.11.0",
|
||||
"@typescript-eslint/parser": "^2.11.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^2.1.1",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-svelte3": "^2.7.3",
|
||||
"jest": "^24.9.0",
|
||||
"mini-css-extract-plugin": "^0.6.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier-plugin-svelte": "^0.7.0",
|
||||
"serve": "^11.0.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"svelte": "^3.16.0",
|
||||
"svelte-loader": "^2.13.3",
|
||||
"svelte-spa-router": "^2.0.0",
|
||||
"svelte-type-checker": "^0.1.4",
|
||||
"ts-jest": "^24.2.0",
|
||||
"ts-loader": "^6.2.1",
|
||||
"typescript": "^3.7.3",
|
||||
"webpack": "^4.30.0",
|
||||
"webpack-cli": "^3.3.0",
|
||||
"webpack-dev-server": "^3.3.1",
|
||||
"anki": "../tslib"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"dev": "webpack-dev-server --content-base public"
|
||||
},
|
||||
"dependencies": {
|
||||
"protobufjs": "^6.8.8"
|
||||
},
|
||||
"proxy": "http://localhost:5006/"
|
||||
}
|
63
svelte/public/global.css
Normal file
63
svelte/public/global.css
Normal file
@ -0,0 +1,63 @@
|
||||
html, body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(0,100,200);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(0,80,160);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input, button, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: 0.4em;
|
||||
margin: 0 0 0.5em 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
color: #333;
|
||||
background-color: #f4f4f4;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
border-color: #666;
|
||||
}
|
17
svelte/public/index.html
Normal file
17
svelte/public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width'>
|
||||
|
||||
<title>Anki</title>
|
||||
|
||||
<link rel='stylesheet' href='global.css'>
|
||||
<link rel='stylesheet' href='bundle.css'>
|
||||
|
||||
<script defer src='bundle.js'></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
31
svelte/src/ui/App.svelte
Normal file
31
svelte/src/ui/App.svelte
Normal file
@ -0,0 +1,31 @@
|
||||
<style>
|
||||
/* Style for "active" links; need to mark this :global because the router adds the class directly */
|
||||
:global(a.active) {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import DeckDueTree from "./DeckDueTree.svelte";
|
||||
import BrowseScreen from "./BrowseScreen.svelte";
|
||||
|
||||
import Router from "svelte-spa-router";
|
||||
|
||||
import { link } from "svelte-spa-router";
|
||||
import active from "svelte-spa-router/active";
|
||||
|
||||
const routes = {
|
||||
// Exact path
|
||||
"/": DeckDueTree,
|
||||
"/browse": BrowseScreen
|
||||
};
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<nav>
|
||||
<a href="/" use:link use:active>Decks</a>
|
||||
<a href="/browse" use:link use:active>Browse</a>
|
||||
|
||||
</nav>
|
||||
<Router {routes} />
|
||||
</body>
|
43
svelte/src/ui/BrowseRow.svelte
Normal file
43
svelte/src/ui/BrowseRow.svelte
Normal file
@ -0,0 +1,43 @@
|
||||
<style>
|
||||
.row {
|
||||
/* ensures text is center-aligned */
|
||||
line-height: 35px;
|
||||
height: 35px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background-color: white;
|
||||
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
.rowAlt {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.rowSelected {
|
||||
background-color: #77f;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { Browser } from "anki/dist/browser";
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
|
||||
export let idx = 0;
|
||||
export let browser = new Browser();
|
||||
|
||||
$: cardData = null;
|
||||
|
||||
onMount(() => {
|
||||
browser.getRowData(idx, data => (cardData = data));
|
||||
});
|
||||
onDestroy(() => {
|
||||
browser.cancelRequest(idx);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="row" class:rowAlt="{idx % 2 === 0}" class:rowSelected="{false}">
|
||||
{#if cardData !== null}{idx}: {cardData}{/if}
|
||||
</div>
|
21
svelte/src/ui/BrowseScreen.svelte
Normal file
21
svelte/src/ui/BrowseScreen.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script>
|
||||
import BrowseTable from "./BrowseTable";
|
||||
import BrowseSearchInput from "./BrowseSearchInput";
|
||||
import { Browser } from "anki/dist/browser";
|
||||
|
||||
let browser = new Browser();
|
||||
|
||||
async function onSearchEnter(evt) {
|
||||
console.log(`got ${evt.detail.searchText}`);
|
||||
browser = new Browser();
|
||||
|
||||
const b = new Browser();
|
||||
await b.search(evt.detail.searchText);
|
||||
browser = b;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<BrowseSearchInput on:enter="{onSearchEnter}" />
|
||||
</div>
|
||||
<BrowseTable {browser} />
|
21
svelte/src/ui/BrowseSearchInput.svelte
Normal file
21
svelte/src/ui/BrowseSearchInput.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let searchText;
|
||||
|
||||
function onKeyUp(e) {
|
||||
if (e.keyCode === 13) {
|
||||
console.log(`on key up: e.target.keycode`);
|
||||
dispatch("enter", { searchText });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:value="{searchText}"
|
||||
placeholder="hit enter to search"
|
||||
on:keyup="{onKeyUp}"
|
||||
autofocus
|
||||
/>
|
17
svelte/src/ui/BrowseTable.svelte
Normal file
17
svelte/src/ui/BrowseTable.svelte
Normal file
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import VirtualList from "@sveltejs/svelte-virtual-list";
|
||||
import BrowseRow from "./BrowseRow.svelte";
|
||||
|
||||
export let browser;
|
||||
|
||||
let start, end;
|
||||
|
||||
let rows;
|
||||
$: rows = Array.from(Array(browser.rows()).keys());
|
||||
</script>
|
||||
|
||||
<p>showing {start}-{end} of {rows.length} rows</p>
|
||||
|
||||
<VirtualList items="{rows}" let:item bind:start bind:end itemHeight="{35}">
|
||||
<BrowseRow idx="{item}" {browser} />
|
||||
</VirtualList>
|
70
svelte/src/ui/DeckDueNode.svelte
Normal file
70
svelte/src/ui/DeckDueNode.svelte
Normal file
@ -0,0 +1,70 @@
|
||||
<style>
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.nodeOuter {
|
||||
border-bottom: 1px solid #eee;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.nodeInner {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.counts {
|
||||
float: right;
|
||||
font-size: smaller;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.new {
|
||||
color: #000099;
|
||||
}
|
||||
.due {
|
||||
color: #007700;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { slide } from "svelte/transition";
|
||||
import { pb } from "anki/dist/backend";
|
||||
|
||||
export let record = new pb.DeckTreeNode();
|
||||
|
||||
$: deck = record.names[record.names.length - 1];
|
||||
$: dueCount = record.reviewCount + record.learnCount;
|
||||
$: collapsed = record.collapsed;
|
||||
$: indent = record.names.length > 1 ? 1 : 0;
|
||||
|
||||
function onToggleRow() {
|
||||
collapsed = !collapsed;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="nodeOuter" style="margin-left: {indent * 15}px;">
|
||||
<div class="nodeInner">
|
||||
{#if record.children.length > 0}
|
||||
<button on:click="{onToggleRow}">
|
||||
{#if collapsed}+{:else}-{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<button></button>
|
||||
{/if}
|
||||
{deck}
|
||||
<div class="counts">
|
||||
<span class="due">{dueCount}</span>
|
||||
<br />
|
||||
<span class="new">{record.newCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if !collapsed}
|
||||
<div transition:slide|local>
|
||||
{#each record.children as child, idx (child.deckId)}
|
||||
<svelte:self record="{child}" />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
27
svelte/src/ui/DeckDueTree.svelte
Normal file
27
svelte/src/ui/DeckDueTree.svelte
Normal file
@ -0,0 +1,27 @@
|
||||
<style>
|
||||
.dueTable {
|
||||
max-width: 50em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import DeckDueNode from "./DeckDueNode.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { deckTree } from "anki/dist/backend";
|
||||
|
||||
$: records = [];
|
||||
|
||||
onMount(async () => {
|
||||
records = await deckTree();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="outer">
|
||||
<div class="dueTable">
|
||||
{#each records as record}
|
||||
<DeckDueNode {record} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
14
svelte/src/ui/main.js
Normal file
14
svelte/src/ui/main.js
Normal file
@ -0,0 +1,14 @@
|
||||
import App from "./App.svelte";
|
||||
|
||||
const app = new App({
|
||||
target: document.body
|
||||
});
|
||||
|
||||
window.onerror = function(e) {
|
||||
window.alert(`An error occurred: ${e}`);
|
||||
};
|
||||
window.onunhandledrejection = function(e) {
|
||||
window.alert(`An error occurred: ${e.reason}`);
|
||||
};
|
||||
|
||||
export default app;
|
22
svelte/tsconfig.json
Normal file
22
svelte/tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"exclude": ["node_modules", "dist", "es"],
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
|
||||
"module": "es6" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
"rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
"strictNullChecks": true /* Enable strict null checks. */,
|
||||
"strictFunctionTypes": true /* Enable strict checking of function types. */,
|
||||
"strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */,
|
||||
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
|
||||
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
|
||||
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
}
|
||||
}
|
70
svelte/webpack.config.js
Normal file
70
svelte/webpack.config.js
Normal file
@ -0,0 +1,70 @@
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const path = require("path");
|
||||
|
||||
const mode = process.env.NODE_ENV || "development";
|
||||
const prod = mode === "production";
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
bundle: ["./src/ui/main.js"]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
svelte: path.resolve("node_modules", "svelte")
|
||||
},
|
||||
extensions: [".mjs", ".js", ".svelte", ".ts"],
|
||||
mainFields: ["svelte", "browser", "module", "main"]
|
||||
},
|
||||
output: {
|
||||
path: __dirname + "/public",
|
||||
filename: "[name].js",
|
||||
chunkFilename: "[name].[id].js"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.svelte$/,
|
||||
use: [
|
||||
{
|
||||
loader: "svelte-loader",
|
||||
options: {
|
||||
emitCss: true,
|
||||
hotReload: true
|
||||
}
|
||||
},
|
||||
{ loader: "eslint-loader" }
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
/**
|
||||
* MiniCssExtractPlugin doesn't support HMR.
|
||||
* For developing, use 'style-loader' instead.
|
||||
* */
|
||||
prod ? MiniCssExtractPlugin.loader : "style-loader",
|
||||
"css-loader"
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: ["ts-loader", "eslint-loader"],
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
mode,
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].css"
|
||||
})
|
||||
],
|
||||
devtool: prod ? false : "source-map",
|
||||
devServer: { port: 3000,
|
||||
proxy: {
|
||||
'/request': {
|
||||
target: 'http://localhost:5006'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user