conflicts resolved

pull/12/head
cssbubble 2022-06-02 14:57:58 +01:00
commit 380ff0f337
24 zmienionych plików z 1736 dodań i 244 usunięć

1
.github/FUNDING.yml vendored 100644
Wyświetl plik

@ -0,0 +1 @@
open_collective: learnawesome

Wyświetl plik

@ -1,16 +1,55 @@
# LearnAwesome
An offline-browsable collection of learning resources organized by topics, formats, difficulty level etc.
This is a collection of learning resources organized by topics, formats, difficulty levels, and quality tags like visual / interactive / challenging etc. It also includes reviews from experts and metadata like paywall/loginwall to help you find the best resource for your learning goals.
## Users
For certain resources like research paper or books, there will be direct links thanks to projects like InternetArchive, LibGen, Arxiv, SciHub etc.
Run `datasette . -o` in the top-level directory which opens the Datasette default view. Click on "home" in the top-left to open the custom UI which is much nicer.
This requires us to build a giant taxonomy of all human knowledge. Arranging topics in a hierarchy is not sufficient. Instead we are creating a graph of topics and levels with multiple types of edges: "is-a-subtopic-of", "is-a-prerequisite-of" etc. If you are an expert or educator in some domain, you can contribute to this project via our GitHub repository.
## Developers
In conjunction with this, we're also building an online game where this is presented as a skill-tree for life and allows you to chase ambitious life goals and keep track of your progress while inspiring and being inspired by your friends. More on this will be revealed soon.
When you modify the *.csv files in `db/`, generate the sqlite database with `./generatedb.sh`.
Run `npm run dev` to keep building the JS bundle as you edit the source code.
## To use:
## Details
[Visit https://learnawesome.vercel.app/](https://learnawesome.vercel.app/)
The dataset here is identical to https://learnawesome.org/. But this runs on your computer so there are no user accounts, no social features like learning feeds or ActivityPub. Your bookmarks will be saved in browser's localStorage.
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.
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).
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.
## To contribute:
This is a Wikipedia-scale project and we could use all kind of help:
- Spread word about this project among your friends, family, colleagues and online followers
- 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 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)
## 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.
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`.
## Architecture
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`.
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.
For the front-end, we write Svelte components in `src` and generate `bundle.js` and `bundle.css` via `npm run dev` / `npm run build`.
These bundles are then used by `templates/index.html` which datasette loads on the first visit. We keep a second database file `dummy.db` in the same directory so that datasette opens `/` and not `/learn`.
For UI, we make use of TailwindCSS (currently loaded via CDN with some plugins) and Shoelace.Style. Whenever possible, we use Shoelace's existing components.

1297
package-lock.json wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -6,19 +6,26 @@
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public --no-clear"
"start": "sirv public --no-clear",
"publish": "datasette publish vercel learn.db --project learnawesome -m metadata.json --template-dir templates --static static:static --setting max_returned_rows 20000"
},
"devDependencies": {
"@rgossiaux/svelte-headlessui": "^1.0.1",
"@rgossiaux/svelte-heroicons": "^0.1.2",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@tsconfig/svelte": "^3.0.0",
"rollup": "^2.3.4",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0"
"rollup-plugin-typescript2": "^0.31.2",
"svelte": "^3.0.0",
"svelte-check": "^2.7.1",
"svelte-preprocess": "^4.10.6",
"tslib": "^2.4.0",
"typescript": "^4.7.2"
},
"dependencies": {
"@svelte-parts/zoom": "^0.0.23",

Wyświetl plik

@ -5,6 +5,9 @@ import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';
import autoPreprocess from 'svelte-preprocess';
import typescript from 'rollup-plugin-typescript2';
const production = !process.env.ROLLUP_WATCH;
function serve() {
@ -38,11 +41,13 @@ export default {
},
plugins: [
svelte({
preprocess: autoPreprocess(),
compilerOptions: {
// enable run-time checks when not in production
dev: !production
}
}),
typescript({ sourceMap: !production }),
// we'll extract any component CSS out into
// a separate file - better for performance
css({ output: 'bundle.css' }),

Wyświetl plik

@ -37,7 +37,7 @@
<svg class="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
<input bind:value={query} type="text" class="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-800 placeholder-gray-400 focus:ring-0 sm:text-sm" placeholder="Search..." role="combobox" aria-expanded="false" aria-controls="options" >
<input bind:this={elm} bind:value={query} type="text" class="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-800 placeholder-gray-400 focus:ring-0 sm:text-sm" placeholder="Search..." role="combobox" aria-expanded="false" aria-controls="options" >
</div>
{#if !query }

Wyświetl plik

@ -3,7 +3,7 @@
import * as TailwindUI from "./tailwindui/index"
import Home from "./Home.svelte"
import SkillTree from "./SkillTree.svelte"
import TreeMap from "./TreeMap.svelte"
import TopicList from "./TopicList.svelte"
import TopicDetail from "./TopicDetail.svelte"
import FormatList from "./FormatList.svelte"
@ -65,8 +65,8 @@
<svelte:fragment slot="content">
{#if currentView === "/home" || currentView === "/"}
<Home/>
{:else if currentView === "/game"}
<SkillTree/>
{:else if currentView === "/map"}
<TreeMap/>
{:else if currentView === "/topics"}
<TopicList {alltopics}/>
{:else if currentView.startsWith("/topic/")}
@ -97,6 +97,10 @@
<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>

Wyświetl plik

@ -12,7 +12,7 @@
topic: "",
format: "",
level: "",
quality: "",
tags: "",
sortby: "rating"
};
@ -31,7 +31,7 @@
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 quality filter
// TODO Apply tags filter
return true;
}).sort((a,b) => {
if(query.sortby == 'rating') { return (a.rating - b.rating) };

Wyświetl plik

@ -1,6 +1,5 @@
<script>
import Icon from "./tailwindui/Icon.svelte"
export let item;
<script lang="ts">
export let item: {name: string, creators: string};
</script>
<a class="flex flex-wrap p-8 justify-between rounded-lg break-inside-avoid w-64 max-w-sm bg-lightPrimCont text-lightPrimary dark:bg-darkPrimCont dark:text-darkPrimary hover:bg-lightPrimary hover:bg-darkPrimary" href="#/item/{item.rowid}">

Wyświetl plik

@ -2,13 +2,19 @@
<div class="lg:grid lg:grid-cols-12 lg:gap-8">
<div class="sm:text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left">
<h1>
<span class="block text-sm font-semibold uppercase tracking-wide text-gray-500 sm:text-base lg:text-sm xl:text-base">Coming soon</span>
<span class="block text-sm font-semibold uppercase tracking-wide text-gray-500 sm:text-base lg:text-sm xl:text-base"></span>
<span class="mt-1 block text-4xl tracking-tight font-extrabold sm:text-5xl xl:text-6xl">
<span class="block text-gray-900">LearnAwesome</span>
<span class="block text-indigo-600">Humanity's learning map</span>
<span class="block text-indigo-600 text-3xl mt-2">Humanity's learning map</span>
</span>
</h1>
<p class="mt-3 text-base text-gray-500 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">Today's learning platforms are not actually learner-centric. They want the students to be locked within their apps & websites, learn ONLY from their own content and their own preferred formats. But the way we learn in 21st century is far too rich. We learn from blogs, podcasts, videos, tweetstorms, livestreams, games, newsletters, infographics, Q&A sites, forums and much more. Discovering the right kind of learning material for YOU at the right time is still too hard.</p>
<p class="mt-3 text-base text-gray-500 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">This is a collection of learning resources organized by topics, formats, difficulty levels, and quality tags like visual / interactive / challenging etc. It also includes reviews from experts and metadata like paywall/loginwall to help you find the best resource for your learning goals.</p>
<p class="mt-3 text-base text-gray-500 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">For certain resources like research paper or books, there will be direct links thanks to projects like InternetArchive, LibGen, Arxiv, SciHub etc.</p>
<p class="mt-3 text-base text-gray-500 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">This requires us to build a giant taxonomy of all human knowledge. Arranging topics in a hierarchy is not sufficient. Instead we are creating a graph of topics and levels with multiple types of edges: "is-a-subtopic-of", "is-a-prerequisite-of" etc. If you are an expert or educator in some domain, you can contribute to this project via our GitHub repository.</p>
<p class="mt-3 text-base text-gray-500 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">In conjunction with this, we're also building an online game where this is presented as a skill-tree for life and allows you to chase ambitious life goals and keep track of your progress while inspiring and being inspired by your friends. More on this will be revealed soon.</p>
</div>
<div class="mt-12 relative sm:max-w-lg sm:mx-auto lg:mt-0 lg:max-w-none lg:mx-0 lg:col-span-6 lg:flex lg:items-center">
<svg class="absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-8 scale-75 origin-top sm:scale-100 lg:hidden" width="640" height="784" fill="none" viewBox="0 0 640 784" aria-hidden="true">

Wyświetl plik

@ -2,6 +2,7 @@
import ButtonGroup from "./ButtonGroup.svelte";
import { bookmarks } from "./stores.js"
import Icon from "./tailwindui/Icon.svelte"
import Review from "./Review.svelte"
export let itemid;
let item;
@ -206,8 +207,7 @@
</div>
<!-- review -->
{#if item.review}
<hr />
{#if reviews}
<section class="my-8">
<div class="flex justify-between items-center">
<h2 class="text-base font-bold text-gray-100">Reviews</h2>
@ -215,167 +215,12 @@
<div class="flex flex-col md:flex-row md:overflow-x-auto md:pb-5 mt-3 gap-2 scroll">
{#each reviews as review}
<article class="px-3 py-4 bg-lightSecondary2 text-lightSecondary1 dark:bg-darkSecondary2 dark:text-darkSecondary1 rounded-lg text-xs w-48 max-w-sm shrink-0">
<h3 class="font-semibold">{review.blurb.toString().slice(0,10)}...</h3>
<p class="mt-2 line-clamp">{review.blurb}</p>
<div class="mt-3">
<sl-rating value={review.rating}></sl-rating>
<span class="ml-2">...{review.by}</span>
</div>
</article>
<Review {review}/>
{/each}
</div>
</section>
{/if}
<!-- more books by same author -->
<!-- <section class="my-8 overflow-hidden">
<div class="flex justify-between items-center">
<h2 class="text-base font-bold">More Books by Priyanka Trivedi</h2>
<button class="float-right text-xs hover:underline">See All</button>
</div>
<div class="flex pb-5 mt-3 gap-2 w-full overflow-x-auto">
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
<div class="flex flex-col items-start text-xs gap-1">
<h4 class="font-semibold">A Modest Proposal</h4>
<span class="text-gray-500">Priyanka Trivedi</span>
<button class="border rounded-2xl uppercase px-2 py-0.5 border-black">Get</button>
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
<div class="flex flex-col items-start text-xs gap-1">
<h4 class="font-semibold">A Modest Proposal</h4>
<span class="text-gray-500">Priyanka Trivedi</span>
<button class="border rounded-2xl uppercase px-2 py-0.5 border-black">Get</button>
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
<div class="flex flex-col items-start text-xs gap-1">
<h4 class="font-semibold">A Modest Proposal</h4>
<span class="text-gray-500">Priyanka Trivedi</span>
<button class="border rounded-2xl uppercase px-2 py-0.5 border-black">Get</button>
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
<div class="flex flex-col items-start text-xs gap-1">
<h4 class="font-semibold">A Modest Proposal</h4>
<span class="text-gray-500">Priyanka Trivedi</span>
<button class="border rounded-2xl uppercase px-2 py-0.5 border-black">Get</button>
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
<div class="flex flex-col items-start text-xs gap-1">
<h4 class="font-semibold">A Modest Proposal</h4>
<span class="text-gray-500">Priyanka Trivedi</span>
<button class="border rounded-2xl uppercase px-2 py-0.5 border-black">Get</button>
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
<div class="flex flex-col items-start text-xs gap-1">
<h4 class="font-semibold">A Modest Proposal</h4>
<span class="text-gray-500">Priyanka Trivedi</span>
<button class="border rounded-2xl uppercase px-2 py-0.5 border-black">Get</button>
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
<div class="flex flex-col items-start text-xs gap-1">
<h4 class="font-semibold">A Modest Proposal</h4>
<span class="text-gray-500">Priyanka Trivedi</span>
<button class="border rounded-2xl uppercase px-2 py-0.5 border-black">Get</button>
</div>
</div>
</div>
</section>
<hr /> -->
<!-- Also bought -->
<!-- <section class="my-8 overflow-hidden">
<div class="flex justify-between items-center">
<h2 class="text-base font-bold">Other items on the same topics</h2>
<button class="text-xs hover:underline">See All</button>
</div>
<div class="flex pb-5 mt-3 gap-2 w-full overflow-x-scroll">
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
</div>
<div class="shrink-0">
<div class="" >
<img class="mb-4 h-44 w-auto transform rounded-md border border-purple-200 shadow-md md:shadow-xl" src="{item.image}" alt="" />
</div>
</div>
</div>
</section> -->
</div>
</div>
{:else}

Wyświetl plik

@ -1,5 +1,12 @@
<script>
<script lang="ts">
export let review: any;
</script>
review
<article class="px-3 py-4 bg-lightSecondary2 text-lightSecondary1 dark:bg-darkSecondary2 dark:text-darkSecondary1 rounded-lg text-xs w-48 max-w-sm shrink-0">
<h3 class="font-semibold">{review.blurb.toString().slice(0,10)}...</h3>
<p class="mt-2 line-clamp">{review.blurb}</p>
<div class="mt-3">
<sl-rating value={review.rating}></sl-rating>
<span class="ml-2">...{review.by}</span>
</div>
</article>

Wyświetl plik

@ -43,12 +43,12 @@
<sl-select class="ml-2 w-44" 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="childlike">Inspirational</sl-menu-item>
<sl-menu-item value="beginner">Educational</sl-menu-item>
<sl-menu-item value="intermediate">Challenging</sl-menu-item>
<sl-menu-item value="advanced">Entertaining</sl-menu-item>
<sl-menu-item value="research">Visual</sl-menu-item>
<sl-menu-item value="research">Interactive</sl-menu-item>
<sl-menu-item value="inspirational">Inspirational</sl-menu-item>
<sl-menu-item value="educational">Educational</sl-menu-item>
<sl-menu-item value="challenging">Challenging</sl-menu-item>
<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-select>
<sl-select class="ml-2 w-44" on:sl-change="{e => query.level = e.target.value}" value={query.level}>

Wyświetl plik

@ -1,3 +0,0 @@
<script>
</script>

Wyświetl plik

@ -16,7 +16,7 @@
topic: "",
format: "",
level: "",
quality: "",
tags: "",
sortby: "rating"
};
@ -36,7 +36,7 @@
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 quality filter
// TODO: apply tags filter
return true;
}).sort((a,b) => {
if(query.sortby == 'rating') { return (a.rating - b.rating) };
@ -46,52 +46,8 @@
</script>
<TopicMasonryGrid {topicname} {alltopics}/>
<SearchForm {alltopics} on:queryChanged={handleQueryChanged} hideTopic={true} hideFormat={true}/>
<ItemList items={filteredItems}/>
<!-- <div class="mt-10">
<div class="">
<sl-tab-group placement="start">
{#each formats.filter(f => items.filter(x => x.links.includes(f.id + '|')).length > 0) as format, i}
<sl-tab slot="nav" panel={format.id} active={i == 0}>{format.name}</sl-tab>
{#if format.id == 'book'}
<sl-tab-panel name={format.id} active={i == 0}>
<div class="grid gap-5 grid-cols-2 sm:grid-cols-3 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 justify-items-center">
{#each filteredItems.filter(x => x.links.includes(format.id + '|')) as item}
<BookCard {item}/>
{/each}
</div>
</sl-tab-panel>
{:else if format.id == 'video'}
<sl-tab-panel name={format.id} active={i == 0}>
<div class="max-w-lg mx-auto grid gap-5 lg:grid-cols-2 lg:max-w-none xl:grid-cols-3">
{#each filteredItems.filter(x => x.links.includes(format.id + '|')) as item}
<VideoCard {item}/>
{/each}
</div>
</sl-tab-panel>
{:else}
<sl-tab-panel name={format.id} active={i == 0}>
<div class="max-w-lg mx-auto grid gap-5 lg:grid-cols-2 lg:max-w-none xl:grid-cols-4 2xl:grid-cols-6 ">
{#each filteredItems.filter(x => x.links.includes(format.id + '|')) as item}
<GenericCard {item}/>
{/each}
</div>
</sl-tab-panel>
{/if}
{/each}
</sl-tab-group>
</div>
</div> -->

Wyświetl plik

@ -1,5 +1,5 @@
<script>
export let topicname; // undefined for top level
export let topicname = null; // undefined for top level
let topic;
export let alltopics;
let map = new Map();

Wyświetl plik

@ -0,0 +1 @@
<iframe title="treemap" src="/static/map.html" class="w-full h-screen"></iframe>

Wyświetl plik

@ -29,5 +29,6 @@ export const formats = [
{id: "webmeet", name: "Online meetups", image: "https://images.unsplash.com/photo-1586543354240-2187898bb2e8?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"},
{id: "webconf", name: "Online conferences", image: "https://images.unsplash.com/photo-1586985564150-11ee04838034?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"},
{id: "thing", name: "Things & Toys", image: "https://images.unsplash.com/photo-1416339134316-0e91dc9ded92?ixlib=rb-1.2.1&raw_url=true&q=60&fm=jpg&crop=entropy&cs=tinysrgb&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Nnx8c3R1ZmZ8ZW58MHx8MHx8&auto=format&fit=crop&w=400"},
{id: "summary", name: "Summaries & Notes", image: "https://images.unsplash.com/photo-1623039405147-547794f92e9e?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"},
]

Wyświetl plik

@ -0,0 +1 @@
export {};

Wyświetl plik

@ -1,6 +1,6 @@
<script>
export let kind;
export let size;
export const size = 0;
</script>
{#if kind === 'home'}

Wyświetl plik

@ -19,10 +19,10 @@
-->
<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
<!-- Active: "bg-gray-100", Not Active: "" -->
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
<a href="/" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
<a href="/" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
<a href="/" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
</div>
</div>

323
static/map.html 100644
Wyświetl plik

@ -0,0 +1,323 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>TreeMap</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0px;
padding: 0px;
font-family: 'Open Sans', sans-serif;
font-size: 16px;
}
#chart {
margin: 0px;
padding: 0px;
background: #E0E7FF;
}
text {
pointer-events: none;
}
.grandparent text {
font-weight: bold;
}
rect {
fill: none;
stroke: #fff;
}
rect.parent,
.grandparent rect {
stroke-width: 2px;
}
.grandparent rect {
fill: rgb(232, 255, 0);
}
.grandparent:hover rect {
fill: rgb(202, 225, 0);
}
.children rect.parent,
.grandparent rect {
cursor: pointer;
}
.children rect.parent {
fill: #99F6E4;
fill-opacity: .5;
}
.children:hover rect.child {
fill: #D7CBFA;
}
</style>
<link href='http://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){
// this is different from TopicMasonryGrid because this needs a specific object structure down to all leafs, not a map with just 2 levels
// every node needs id, name. Leaf nodes need a value like 100. Non-leafs need a children[].
// see: https://github.com/mekarpeles/math.mx/blob/master/static/data/simple.json
const nest = (items, id) =>
items
.filter(item => item.parent_id === 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}
});
let topnodes = nest(topic_array, ''); // includes childless nodes
// put childless nodes under misc
let misc = {id: 'misc', name: 'Misc', children: topnodes.filter(t => t.value).slice(0,15)};
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});
initialize(root);
accumulate(root);
layout(root);
display(root);
});
var margin = {top: 20, right: 0, bottom: 0, left: 0},
wh = browserDimensions(),
width = wh[0] - (2 * margin.left) - (2*margin.right),
height = wh[1] - (2 * margin.top - margin.bottom),
formatNumber = d3.format(",d"),
transitioning;
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([0, height]);
var treemap = d3.layout.treemap()
.children(function(d, depth) { return depth ? null : d._children; })
.sort(function(a, b) { return a.value - b.value; })
.ratio(height / width * 0.5 * (1 + Math.sqrt(5)))
.round(false);
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.style("margin-left", -margin.left + "px")
.style("margin.right", -margin.right + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("shape-rendering", "crispEdges");
var grandparent = svg.append("g")
.attr("class", "grandparent");
grandparent.append("rect")
.attr("y", -margin.top)
.attr("width", width)
.attr("height", margin.top);
grandparent.append("text")
.attr("x", 6)
.attr("y", 6 - margin.top)
.attr("dy", ".75em");
function initialize(root) {
root.x = root.y = 0;
root.dx = width;
root.dy = height;
root.depth = 0;
}
// Aggregate the values for internal nodes. This is normally done by the
// treemap layout, but not here because of our custom implementation.
// We also take a snapshot of the original children (_children) to avoid
// the children being overwritten when when layout is computed.
function accumulate(d) {
return (d._children = d.children)
? d.value = d.children.reduce(function(p, v) { return p + accumulate(v); }, 0)
: d.value;
}
// Compute the treemap layout recursively such that each group of siblings
// uses the same size (1×1) rather than the dimensions of the parent cell.
// This optimizes the layout for the current zoom state. Note that a wrapper
// object is created for the parent node for each group of siblings so that
// the parents dimensions are not discarded as we recurse. Since each group
// of sibling was laid out in 1×1, we must rescale to fit using absolute
// coordinates. This lets us use a viewport to zoom.
function layout(d) {
if (d._children) {
treemap.nodes({_children: d._children});
d._children.forEach(function(c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);
});
}
}
function display(d) {
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function(d) { return d._children; })
.classed("children", true)
.on("click", transition);
g.selectAll(".child")
.data(function(d) { return d._children || [d]; })
.enter().append("rect")
.attr("class", "child")
.call(rect);
g.append("rect")
.attr("class", "parent")
.call(rect)
.append("title")
.text(function(d) { return formatNumber(d.value); });
g.append("text")
.attr("dy", ".75em")
.text(function(d) { return d.name.split('{')[0].split('(')[0]
.split('[')[0]; })
.call(text);
function transition(d) {
if (transitioning || !d) return;
transitioning = true;
var g2 = display(d),
t1 = g1.transition().duration(750),
t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; });
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
// Transition to the new view.
t1.selectAll("text").call(text).style("fill-opacity", 0);
t2.selectAll("text").call(text).style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
// Remove the old node when the transition is finished.
t1.remove().each("end", function() {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
}
return g;
}
function text(text) {
text.attr("x", function(d) { return x(d.x) + 10; })
.attr("y", function(d) { return y(d.y) + 10; });
}
function rect(rect) {
rect.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.attr("width", function(d) { return x(d.x + d.dx) - x(d.x); })
.attr("height", function(d) { return y(d.y + d.dy) - y(d.y); });
}
function name(d) {
return d.parent
? name(d.parent) + " → " + d.name
: d.name;
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
function browserDimensions() {
var w = 0, h = 0;
if( typeof( window.innerWidth ) == 'number' ) {
//Non-IE
w = window.innerWidth;
h = window.innerHeight;
} else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
//IE 6+ in 'standards compliant mode'
w = document.documentElement.clientWidth;
h = document.documentElement.clientHeight;
} else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
//IE 4 compatible
w = document.body.clientWidth;
h = document.body.clientHeight;
}
return [w, h];
}
</script>
</body>
</html>

5
tsconfig.json 100644
Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"include": ["src/**/*"],
"exclude": ["node_modules/*", "public/*"],
}