kopia lustrzana https://github.com/learn-awesome/learndb
Merge remote-tracking branch 'upstream/main'
commit
94f61d253f
20
README.md
20
README.md
|
@ -12,11 +12,17 @@ In conjunction with this, we're also building an online game where this is prese
|
|||
|
||||
[Visit https://learnawesome.vercel.app/](https://learnawesome.vercel.app/)
|
||||
|
||||
This is the exact same version. Your bookmarks will still be saved in localStorage so be assured that no personal data is being tracked or saved on this site.
|
||||
<img width="1689" alt="image" src="https://user-images.githubusercontent.com/19304/171865328-b8c22482-15d7-476e-9b91-a1c482809f29.png">
|
||||
|
||||
But if you'd like faster performance or to self-host this, say in your company's intranet, you need a general-purpose computer (that means Linux/Windows/Mac but not crippled OSes like Android or iOS) with Datasette (which is an exploratory tool for SQLite databases) installed. You can find [installation instructions specific to your operating system here](https://docs.datasette.io/en/stable/installation.html).
|
||||
<img width="1697" alt="image" src="https://user-images.githubusercontent.com/19304/171865412-e3bec0b3-3205-4f89-8d95-4c0af8476c7a.png">
|
||||
|
||||
After cloning this git repository on your local machine, run `datasette . -o` in the top-level directory to start the datasette serve and open the app in your browser.
|
||||
<img width="1687" alt="image" src="https://user-images.githubusercontent.com/19304/171865546-9cd68586-a096-4754-a78f-9aaebe6164ae.png">
|
||||
|
||||
Your bookmarks are saved in localStorage so be assured that no personal data is being tracked or saved on this site.
|
||||
|
||||
But if you'd like faster performance or to self-host this, you need a general-purpose computer (that means Linux/Windows/Mac) with Datasette (which is an exploratory tool for SQLite databases) installed. You can find [installation instructions specific to your operating system here](https://docs.datasette.io/en/stable/installation.html).
|
||||
|
||||
After cloning this git repository on your local machine, run `npm run start` in the top-level directory to start the datasette server and open the app in your browser.
|
||||
|
||||
## To contribute:
|
||||
|
||||
|
@ -26,7 +32,7 @@ This is a Wikipedia-scale project and we could use all kind of help:
|
|||
- To donate funds, [visit our OpenCollective](https://opencollective.com/learnawesome)
|
||||
- To report bugs, [create an issue](https://github.com/learn-awesome/learndb/issues)
|
||||
- To improve our topic taxonomy (improve sub-topics / prerequisites etc), [raise a PR on our Github with changes in `db/topics.csv` file](https://github.com/learn-awesome/learndb/tree/main/db)
|
||||
- To improve the data about learning resources, [raise a PR on our Github with changes in `db/items.csv` file](https://github.com/learn-awesome/learndb/tree/main/db)
|
||||
- To improve the data about learning resources, first read [db/README.md](db/README.md) and [raise a PR on our Github with changes in `db/items.csv` file](https://github.com/learn-awesome/learndb/tree/main/db)
|
||||
- To improve design and suggest features, [start a discussion](https://github.com/learn-awesome/learndb/discussions)
|
||||
- To fix technical bugs, [propose solutions on the issues](https://github.com/learn-awesome/learndb/issues)
|
||||
- For anything else, [start a discussion](https://github.com/learn-awesome/learndb/discussions)
|
||||
|
@ -34,8 +40,7 @@ This is a Wikipedia-scale project and we could use all kind of help:
|
|||
## To develop:
|
||||
|
||||
When you modify the *.csv files in `db/`, you should re-generate the sqlite database with `./generatedb.sh`.
|
||||
Run `npm run dev` to keep live-building the JS bundle as you edit the source code.
|
||||
And then run `datasette . -o` to open the app in your browser.
|
||||
Run `npm run dev` to keep live-building the JS bundle as you edit the source code. This automatically runs `datasette . -o` to open the app in your browser.
|
||||
|
||||
You can install Datasette's Vercel plugin with: `datasette install datasette-publish-vercel`.
|
||||
To publish this, we first run `npm run build` followed by `npm run publish`.
|
||||
|
@ -44,7 +49,8 @@ To publish this, we first run `npm run build` followed by `npm run publish`.
|
|||
|
||||
The dataset here is identical to https://learnawesome.org/. But there are no user accounts, no social features like learning feeds or ActivityPub. Users' bookmarks are saved in browser's localStorage.
|
||||
|
||||
The source data is in `db/*.csv` files. This is imported into a sqlite database with `./generatedb.sh`.
|
||||
The source data is in `db/*.csv` files. The schema is described in [db/README.md](db/README.md).
|
||||
These CSV files get imported into a sqlite database with `./generatedb.sh`.
|
||||
We then rely on datasette to load this file and offer JSON APIs over HTTP.
|
||||
Settings and metadata are specified in `settings.json` and `metadata.json` which datasette uses.
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# CSV format
|
||||
|
||||
## topics.csv
|
||||
|
||||
`name` is used as primary key and therefore, must be unique and avoid uppercase and special characters other than hyphen and slash. Here are some examples: `physics`, `linear-algebra`, `nations/india`, `programming-languages/objective-c`.
|
||||
|
||||
`display_name` is used as human-readable name and can preserve uppercase. For eg: `ADHD`.
|
||||
|
||||
`parent_id` should be the name of the parent topic. This makes it possible to show a hierarchical view. If a topic does not have `parent_id`, it would be at the top-level but if it doesn't have children topics of its own, it will be clubbed under a dummy top-level topic called `Misc`.
|
||||
|
||||
`sort_index` is an integer that's used for controlling the ordering in which topics are displayed.
|
||||
|
||||
|
||||
## items.csv
|
||||
|
||||
`iid` should be a unique UUID. It is needed because `reviews.csv` needs to refer to items and there is no other natural primary key. Later, if we'd want to build collections of items, the same `iid` key would be helpful.
|
||||
|
||||
`description` can contain markdown with multiple lines.
|
||||
|
||||
`links` is an array value separated by `;`. Each item in this array a pair of `format` and `url` separated by `|`. For eg, `links` can have a value like this: `summary|https://sivers.org/book/Decisive;book|https://www.goodreads.com/book/show/15798078-decisive;summary|https://fourminutebooks.com/decisive-summary/`.
|
||||
|
||||
We are considering including other fields like `ipfsHash` and `image` in each value of `links`. This decision is yet to be made.
|
||||
|
||||
`topics` is a array value of topic names separated by `;`. These should exactly match `topics` table's `name` column.
|
||||
|
||||
`creators` is arbitrary string for now. For eg: `Charles Darwin`. In future, this might become a full record on its own including fields like `name`,`website`,`twitter`,`email`. In that case, we will have to somehow figure out unique key for each creator that could serve the role of primary key and foreign key.
|
||||
|
||||
`difficulty` must be empty or one of these: `childlike`, `beginner`, `intermediate`, `advanced`, `research`.
|
||||
|
||||
`rating` is on a 5.0 point scale with up to two decimal places allowed. This is a curated value and should not be simply copied from external sources.
|
||||
|
||||
`tags` can describe quality: `visual`, `entertaining`, `challenging`, `inspirational`, `interactive`.
|
||||
|
||||
## reviews.csv
|
||||
|
||||
`item_id` is a foreign key to `items.csv`.
|
||||
`by` is the name of the person or item.
|
||||
`blurb` is small description in markdown format.
|
|
@ -6,7 +6,7 @@
|
|||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv public --no-clear",
|
||||
"start": "datasette . -o",
|
||||
"publish": "datasette publish vercel learn.db --project learnawesome -m metadata.json --template-dir templates --static static:static --setting max_returned_rows 20000"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -20,7 +20,7 @@ function serve() {
|
|||
return {
|
||||
writeBundle() {
|
||||
if (server) return;
|
||||
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
|
||||
server = require('child_process').spawn('npm', ['run', 'start'], {
|
||||
stdio: ['ignore', 'inherit', 'inherit'],
|
||||
shell: true
|
||||
});
|
||||
|
|
|
@ -97,10 +97,6 @@
|
|||
<LibraryIcon class=" flex-shrink-0 h-6 w-6"/>
|
||||
</NavButtonWithLabel>
|
||||
|
||||
<NavButtonWithLabel isActive={currentView === "/map"} target="#/map" label="TreeMap">
|
||||
<LibraryIcon class=" flex-shrink-0 h-6 w-6"/>
|
||||
</NavButtonWithLabel>
|
||||
|
||||
<NavButtonWithLabel isActive={currentView === "/formats"} target="#/formats" label="Formats">
|
||||
<ViewGridIcon class=" flex-shrink-0 h-6 w-6"/>
|
||||
</NavButtonWithLabel>
|
||||
|
@ -127,11 +123,6 @@
|
|||
<NavButtonWithLabel isActive={currentView === "/finishedlearning"} target="#/finishedlearning" label="Finished learning">
|
||||
<BookmarkAltIcon class=" flex-shrink-0 h-6 w-6"/>
|
||||
</NavButtonWithLabel>
|
||||
|
||||
<a href="/learn" target="_blank" class="text-indigo-100 hover:bg-lightSecondary1 hover:text-lightSecondary2 hover:dark:bg-darkPrimaryBg w-full group flex justify-start gap-3 items-center py-5 text-sm font-medium hover:dark:text-darkSecondary2 mb-5 pl-4">
|
||||
<CogIcon class=" flex-shrink-0 h-6 w-6 "/>
|
||||
<h3 class="text-center"> Datasette</h3>
|
||||
</a>
|
||||
|
||||
</svelte:fragment>
|
||||
</TailwindUI.AppShell>
|
|
@ -10,28 +10,27 @@
|
|||
let query = {
|
||||
text: "",
|
||||
topic: "",
|
||||
format: "",
|
||||
level: "",
|
||||
tags: "",
|
||||
tag: "",
|
||||
sortby: "rating"
|
||||
};
|
||||
|
||||
$: fetch(`/learn/items.json?_shape=array&links__contains=${format}|`)
|
||||
$: query && fetch(`/learn/items.json?_shape=array&_size=100&links__contains=${format}|&topics__contains=${query.topic}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
items = data;
|
||||
});
|
||||
|
||||
function handleQueryChanged(event){
|
||||
console.log("queryChanged: ", event.detail);
|
||||
// console.log("queryChanged: ", event.detail);
|
||||
query = event.detail;
|
||||
}
|
||||
|
||||
$: filteredItems = items.filter(item => {
|
||||
if(query.text && !item.name.toLowerCase().includes(query.text.toLowerCase())){ return false; }
|
||||
if(query.format && !item.links.includes(query.format)) { return false; }
|
||||
if(query.topic && !item.topics.includes(query.topic)){ return false; }
|
||||
if(query.level && item.difficulty != query.level){ return false; }
|
||||
// TODO Apply tags filter
|
||||
if(query.tag && !item.tags.includes(query.tag)){ return false; }
|
||||
return true;
|
||||
}).sort((a,b) => {
|
||||
if(query.sortby == 'rating') { return (a.rating - b.rating) };
|
||||
|
|
|
@ -3,16 +3,12 @@
|
|||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let alltopics;
|
||||
export let hideFormat = false;
|
||||
export let hideTopic = false;
|
||||
export let hideQuality = true;
|
||||
|
||||
let query = {
|
||||
text: "",
|
||||
topic: "",
|
||||
format: "",
|
||||
level: "",
|
||||
quality: "",
|
||||
sortby: "rating",
|
||||
tag: ""
|
||||
};
|
||||
|
@ -24,24 +20,19 @@
|
|||
</script>
|
||||
|
||||
<form class="w-full p-2 inline-flex flex-wrap" on:submit|preventDefault>
|
||||
<sl-input type="search" placeholder="Type something to search items by keywords" size="medium" clearable class="flex-1 border-0 p-0 focus:ring-0" value={query.text} on:sl-input="{e => query.text = e.target.value}">
|
||||
<sl-input type="search" placeholder="Search by keywords" size="medium" clearable class="flex-1 border-0 p-0 focus:ring-0" value={query.text} on:sl-input="{e => query.text = e.target.value}">
|
||||
<sl-icon name="search" slot="prefix"></sl-icon>
|
||||
</sl-input>
|
||||
|
||||
{#if !hideTopic}
|
||||
<ComboBox options={alltopics.map(t => { return {label: t.display_name, value: t.name}; }).sort((a,b) => a.label.localeCompare(b.label)).slice(0,10)} selected={null}/>
|
||||
<fluent-combobox autocomplete="both" placeholder="Any topic" class="ml-2 mt-1 outline-none border-2 border-grey-600" on:change="{e => query.topic = e.target.value}">
|
||||
{#each alltopics.sort((a,b) => a.display_name.localeCompare(b.display_name)) as topic}
|
||||
<fluent-option value={topic.name}>{topic.display_name}</fluent-option>
|
||||
{/each}
|
||||
</fluent-combobox>
|
||||
{/if}
|
||||
|
||||
{#if !hideFormat}
|
||||
<sl-select class="ml-2 w-44" on:sl-change="{e => query.format = e.target.value}" value={query.format}>
|
||||
<sl-menu-item value="">Any format</sl-menu-item>
|
||||
<sl-menu-item value="book">Books</sl-menu-item>
|
||||
<sl-menu-item value="video">Videos</sl-menu-item>
|
||||
<sl-menu-item value="audio">Podcasts</sl-menu-item>
|
||||
</sl-select>
|
||||
{/if}
|
||||
|
||||
<sl-select class="ml-2 w-44" on:sl-change="{e => query.tag = e.target.value}" value={query.tag}>
|
||||
<sl-select class="ml-2 w-52" on:sl-change="{e => query.tag = e.target.value}" value={query.tag}>
|
||||
<sl-menu-item value="">Any tag</sl-menu-item>
|
||||
<sl-menu-item value="inspirational">Inspirational</sl-menu-item>
|
||||
<sl-menu-item value="educational">Educational</sl-menu-item>
|
||||
|
@ -49,6 +40,7 @@
|
|||
<sl-menu-item value="entertaining">Entertaining</sl-menu-item>
|
||||
<sl-menu-item value="visual">Visual</sl-menu-item>
|
||||
<sl-menu-item value="interactive">Interactive</sl-menu-item>
|
||||
<sl-menu-item value="oer">Open (no login or pay)</sl-menu-item>
|
||||
</sl-select>
|
||||
|
||||
<sl-select class="ml-2 w-44" on:sl-change="{e => query.level = e.target.value}" value={query.level}>
|
||||
|
@ -60,15 +52,6 @@
|
|||
<sl-menu-item value="research">Research</sl-menu-item>
|
||||
</sl-select>
|
||||
|
||||
{#if !hideQuality}
|
||||
<sl-select class="ml-2 w-44" on:sl-change="{e => query.quality = e.target.value}" value={query.quality}>
|
||||
<sl-menu-item value="">Any quality</sl-menu-item>
|
||||
<sl-menu-item value="visual">Visual</sl-menu-item>
|
||||
<sl-menu-item value="interactive">Interactive</sl-menu-item>
|
||||
<sl-menu-item value="entertaining">Entertaining</sl-menu-item>
|
||||
</sl-select>
|
||||
{/if}
|
||||
|
||||
<sl-select class="ml-2 w-52" on:sl-change="{e => query.sortby = e.target.value}" value={query.sortby}>
|
||||
<sl-icon name="sort-down-alt" slot="prefix"></sl-icon>
|
||||
<sl-menu-item value="rating">Sort by Rating</sl-menu-item>
|
||||
|
|
|
@ -14,9 +14,8 @@
|
|||
let query = {
|
||||
text: "",
|
||||
topic: "",
|
||||
format: "",
|
||||
level: "",
|
||||
tags: "",
|
||||
tag: "",
|
||||
sortby: "rating"
|
||||
};
|
||||
|
||||
|
@ -28,15 +27,14 @@
|
|||
});
|
||||
|
||||
function handleQueryChanged(event){
|
||||
console.log("queryChanged: ", event.detail);
|
||||
// console.log("queryChanged: ", event.detail);
|
||||
query = event.detail;
|
||||
}
|
||||
|
||||
$: filteredItems = items.filter(item => {
|
||||
if(query.text && !item.name.toLowerCase().includes(query.text.toLowerCase())){ return false; }
|
||||
if(query.format && !item.links.includes(query.format)) { return false; }
|
||||
if(query.level && item.difficulty != query.level){ return false; }
|
||||
// TODO: apply tags filter
|
||||
if(query.tag && !item.tags.includes(query.tag)){ return false; }
|
||||
return true;
|
||||
}).sort((a,b) => {
|
||||
if(query.sortby == 'rating') { return (a.rating - b.rating) };
|
||||
|
|
|
@ -4,4 +4,7 @@
|
|||
import SearchForm from "./SearchForm.svelte"
|
||||
</script>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<a href="#/map" class="font-semibold bg-lightTertiary text-lightBg rounded-lg hover:scale-110 px-3 py-2 hover:ease-in-out transition duration-200">Explore Map</a>
|
||||
</div>
|
||||
<TopicMasonryGrid {alltopics}/>
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
</sl-breadcrumb>
|
||||
</div>
|
||||
|
||||
<div class="gap-8 columns-1 sm:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5 mb-8">
|
||||
<div class="gap-8 columns-1 sm:columns-2 lg:columns-3 xl:columns-4 3xl:columns-5 mb-8">
|
||||
{#each [...map.entries()].sort((t1,t2) => (t1[0].sort_index || 100) - (t2[0].sort_index || 100)) as parent}
|
||||
<a href={"#/topic/" + parent[0].name}>
|
||||
<div class="rounded-lg shadow-md p-4 break-inside-avoid mb-4 border-8 border-lightPrimCont dark:border-darkPrimCont hover:bg-lightPrimCont dark:bg-darkPrimCont ">
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
<script>
|
||||
export let options = [];
|
||||
let userInput = '3';
|
||||
export let userInput = '';
|
||||
$: filteredOptions = options.filter(x => x.label.startsWith(userInput))
|
||||
$: console.log(userInput)
|
||||
$: console.log({options})
|
||||
$: console.log(filteredOptions)
|
||||
$: console.log({userInput})
|
||||
</script>
|
||||
|
||||
<sl-dropdown>
|
||||
<sl-input slot="trigger" on:change="{e => userInput = e.target.value}" value={userInput}></sl-input>
|
||||
<sl-dropdown class="ml-2">
|
||||
<sl-input
|
||||
slot="trigger"
|
||||
class="p-0 m-0 border-0"
|
||||
on:sl-input="{e => userInput = e.target.value}"
|
||||
value={userInput}
|
||||
placeholder="select topic">
|
||||
</sl-input>
|
||||
|
||||
<sl-menu>
|
||||
{#each filteredOptions as opt (opt.value)}
|
||||
<sl-menu-item>{opt.label}</sl-menu-item>
|
||||
<sl-menu-item on:click="{e => userInput = opt.value}">{opt.label}</sl-menu-item>
|
||||
{/each}
|
||||
</sl-menu>
|
||||
</sl-dropdown>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<title>TreeMap</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0px;
|
||||
|
@ -21,7 +22,9 @@ body {
|
|||
}
|
||||
|
||||
text {
|
||||
pointer-events: none;
|
||||
cursor: pointer;
|
||||
fill: #7a28cb;
|
||||
|
||||
}
|
||||
|
||||
.grandparent text {
|
||||
|
@ -39,11 +42,11 @@ rect.parent,
|
|||
}
|
||||
|
||||
.grandparent rect {
|
||||
fill: rgb(232, 255, 0);
|
||||
fill: #f4b393;
|
||||
}
|
||||
|
||||
.grandparent:hover rect {
|
||||
fill: rgb(202, 225, 0);
|
||||
fill: #f4d393;
|
||||
}
|
||||
|
||||
.children rect.parent,
|
||||
|
@ -51,21 +54,30 @@ rect.parent,
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.leaf rect.parent {
|
||||
cursor: pointer;
|
||||
fill: #ffdbd1;
|
||||
}
|
||||
|
||||
.leaf rect.parent:hover {
|
||||
cursor: pointer;
|
||||
fill: #ffcfd4;
|
||||
}
|
||||
|
||||
.children rect.parent {
|
||||
fill: #99F6E4;
|
||||
fill: #ceec97;
|
||||
fill-opacity: .5;
|
||||
}
|
||||
|
||||
.children:hover rect.child {
|
||||
fill: #D7CBFA;
|
||||
fill: #deec97;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300' rel='stylesheet' type='text/css'>
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300' rel='stylesheet' type='text/css'>
|
||||
</head>
|
||||
<body>
|
||||
<p id="chart"></p>
|
||||
<script src="http://d3js.org/d3.v3.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
function hierarchy(topic_array, parent_id){
|
||||
|
@ -78,8 +90,8 @@ function hierarchy(topic_array, parent_id){
|
|||
.map(item => ({ ...item, children: nest(items, item.name), value: 100 }))
|
||||
.map(item => {
|
||||
return item.children.length == 0 ?
|
||||
{id: item.name, name: item.name.split("/").reverse()[0], value: 100} :
|
||||
{id: item.name, name: item.name.split("/").reverse()[0], children: item.children}
|
||||
{id: item.name, name: item.name, value: 100} :
|
||||
{id: item.name, name: item.name, children: item.children}
|
||||
});
|
||||
|
||||
let topnodes = nest(topic_array, ''); // includes childless nodes
|
||||
|
@ -88,13 +100,13 @@ function hierarchy(topic_array, parent_id){
|
|||
let misc = {id: 'misc', name: 'Misc', children: topnodes.filter(t => t.value).slice(0,15)};
|
||||
|
||||
|
||||
return {name: "·", children: [...topnodes.filter(t => t.children), misc]};
|
||||
return {name: "", children: [...topnodes.filter(t => t.children), misc]};
|
||||
}
|
||||
|
||||
d3.json("/learn/topics.json?_shape=array&_size=5000", function(alltopics){
|
||||
// console.log({alltopics});
|
||||
var root = hierarchy(alltopics, "");
|
||||
console.log({root});
|
||||
// console.log({root});
|
||||
initialize(root);
|
||||
accumulate(root);
|
||||
layout(root);
|
||||
|
@ -203,7 +215,11 @@ grandparent.append("text")
|
|||
.classed("children", true)
|
||||
.on("click", transition);
|
||||
|
||||
g.selectAll(".child")
|
||||
g.filter(function(d) { return !d._children; })
|
||||
.classed("leaf", true)
|
||||
.on("click",(n) => { window.parent.location.href = "/#/topic/" + n.name;});
|
||||
|
||||
g.filter(function(d) { return d._children; }).selectAll(".child")
|
||||
.data(function(d) { return d._children || [d]; })
|
||||
.enter().append("rect")
|
||||
.attr("class", "child")
|
||||
|
@ -217,7 +233,8 @@ grandparent.append("text")
|
|||
|
||||
g.append("text")
|
||||
.attr("dy", ".75em")
|
||||
.text(function(d) { return d.name.split('{')[0].split('(')[0]
|
||||
.on("click", (n) => { window.parent.location.href = "/#/topic/" + n.name;})
|
||||
.text(function(d) { return d.name.split('/').reverse()[0].split('{')[0].split('(')[0]
|
||||
.split('[')[0]; })
|
||||
.call(text);
|
||||
|
||||
|
@ -260,7 +277,8 @@ grandparent.append("text")
|
|||
|
||||
function text(text) {
|
||||
text.attr("x", function(d) { return x(d.x) + 10; })
|
||||
.attr("y", function(d) { return y(d.y) + 10; });
|
||||
.attr("y", function(d) { return y(d.y) + 10; })
|
||||
.attr("text-decoration","underline");
|
||||
}
|
||||
|
||||
function rect(rect) {
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<link href="/static/bundle.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.73/dist/themes/light.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.73/dist/shoelace.js"></script>
|
||||
<script type="module" src="https://unpkg.com/@fluentui/web-components"></script>
|
||||
<title>LearnAwesome</title>
|
||||
|
||||
<!-- fonts -->
|
||||
|
|
Ładowanie…
Reference in New Issue