kopia lustrzana https://github.com/learn-awesome/learndb
conflict merged
commit
efe62f61a9
|
@ -1,4 +1,5 @@
|
||||||
name,description,image,links,topics,creators,year,difficulty,cost
|
name,description,image,links,topics,creators,year,difficulty,cost,rating,tags
|
||||||
|
sapiens book image,,https://learn-awesome.github.io/assets/book_covers/23692271.jpg,book|https://learnawesome.org/items/6c2ef4c8-a018-430a-9173-3868310a03ea-sapiens-a-brief-history-of-humankind;video|https://www.youtube.com/watch?v=p5PvHlk3yig,history
|
||||||
sapiens no image,,,book|https://learnawesome.org/items/6c2ef4c8-a018-430a-9173-3868310a03ea-sapiens-a-brief-history-of-humankind,history
|
sapiens no image,,,book|https://learnawesome.org/items/6c2ef4c8-a018-430a-9173-3868310a03ea-sapiens-a-brief-history-of-humankind,history
|
||||||
sapiens book image,,https://learn-awesome.github.io/assets/book_covers/23692271.jpg,book|https://learnawesome.org/items/6c2ef4c8-a018-430a-9173-3868310a03ea-sapiens-a-brief-history-of-humankind;video|https://www.youtube.com/watch?v=p5PvHlk3yig,history
|
sapiens book image,,https://learn-awesome.github.io/assets/book_covers/23692271.jpg,book|https://learnawesome.org/items/6c2ef4c8-a018-430a-9173-3868310a03ea-sapiens-a-brief-history-of-humankind;video|https://www.youtube.com/watch?v=p5PvHlk3yig,history
|
||||||
sapiens video image,,https://img.youtube.com/vi/p5PvHlk3yig/0.jpg,video|https://www.youtube.com/watch?v=p5PvHlk3yig,history;physics
|
sapiens video image,,https://img.youtube.com/vi/p5PvHlk3yig/0.jpg,video|https://www.youtube.com/watch?v=p5PvHlk3yig,history;physics
|
||||||
|
|
|
|
@ -1,6 +1,10 @@
|
||||||
id,display_name,image,first_parent_topic_id,second_parent_topic_id
|
id,display_name,image,first_parent_topic_id,second_parent_topic_id
|
||||||
physics,Physics
|
physics,Physics,,,,
|
||||||
mathematics,Maths,,physics,
|
mathematics,Maths,,physics,
|
||||||
language.english,English
|
language.english,English,,,,
|
||||||
programming.java,Java,,,,
|
programming.java,Java,,,,
|
||||||
history,History,,language.english,
|
history,History,,language.english,
|
||||||
|
programming.ruby,Ruby,,,,
|
||||||
|
programming.rust,Rust,,,,
|
||||||
|
programming.javascript,JS,,history,,
|
||||||
|
programming.golang,Golang,,,,
|
|
|
@ -1,11 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
let query = '';
|
let query = '';
|
||||||
let results = [];
|
let result_items = [];
|
||||||
|
let result_topics = [];
|
||||||
|
|
||||||
$: query && fetch(`/learn/items.json?_shape=array&name__contains=${query}`)
|
$: query && fetch(`/learn/items.json?_shape=array&name__contains=${query}`)
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
results = data;
|
result_items = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
$: query && fetch(`/learn/topics.json?_shape=array&display_name__contains=${query}`)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
result_topics = data;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -42,14 +49,14 @@
|
||||||
<p class="mt-2 text-gray-500">Quickly look for resources by running a global search.</p>
|
<p class="mt-2 text-gray-500">Quickly look for resources by running a global search.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{:else if results.length > 0}
|
{:else if result_items.length + result_topics.length > 0}
|
||||||
|
|
||||||
<!-- Results, show/hide based on command palette state -->
|
<!-- Results, show/hide based on command palette state -->
|
||||||
<ul class="max-h-80 scroll-pt-11 scroll-pb-2 space-y-2 overflow-y-auto pb-2" id="options" role="listbox">
|
<ul class="max-h-80 scroll-pt-11 scroll-pb-2 space-y-2 overflow-y-auto pb-2" id="options" role="listbox">
|
||||||
<li>
|
<li>
|
||||||
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Items</h2>
|
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Items</h2>
|
||||||
<ul class="mt-2 text-sm text-gray-800">
|
<ul class="mt-2 text-sm text-gray-800">
|
||||||
{#each results as item}
|
{#each result_items as item}
|
||||||
<li><a href="#/item/{item.rowid}" class="block cursor-default select-none px-4 py-2 hover:bg-indigo-600 hover:text-white cursor-pointer" id="option-1" role="option" tabindex="-1">{item.name}</a></li>
|
<li><a href="#/item/{item.rowid}" class="block cursor-default select-none px-4 py-2 hover:bg-indigo-600 hover:text-white cursor-pointer" id="option-1" role="option" tabindex="-1">{item.name}</a></li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -57,8 +64,8 @@
|
||||||
<li>
|
<li>
|
||||||
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Topics</h2>
|
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Topics</h2>
|
||||||
<ul class="mt-2 text-sm text-gray-800">
|
<ul class="mt-2 text-sm text-gray-800">
|
||||||
{#each results as topic}
|
{#each result_topics as topic}
|
||||||
<li><a href="#/topic/{topic.name}" class="block cursor-default select-none px-4 py-2 hover:bg-indigo-600 hover:text-white cursor-pointer" id="option-1" role="option" tabindex="-1">{topic.name}</a></li>
|
<li><a href="#/topic/{topic.id}" class="block cursor-default select-none px-4 py-2 hover:bg-indigo-600 hover:text-white cursor-pointer" id="option-1" role="option" tabindex="-1">{topic.display_name}</a></li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
{:else if currentView === "/topics"}
|
{:else if currentView === "/topics"}
|
||||||
<TopicList/>
|
<TopicList/>
|
||||||
{:else if currentView.startsWith("/topic/")}
|
{:else if currentView.startsWith("/topic/")}
|
||||||
<TopicDetail topic={currentView.split("/")[2]}/>
|
<TopicDetail topicid={currentView.split("/")[2]}/>
|
||||||
{:else if currentView === "/formats"}
|
{:else if currentView === "/formats"}
|
||||||
<FormatList/>
|
<FormatList/>
|
||||||
{:else if currentView.startsWith("/format/")}
|
{:else if currentView.startsWith("/format/")}
|
||||||
|
|
|
@ -1,34 +1,29 @@
|
||||||
<script>
|
<script>
|
||||||
let dataPromise = getData();
|
|
||||||
async function getData() {
|
|
||||||
const res = await fetch(`/learn.json?_shape=array&sql=select+distinct(substr(links%2C1%2Cinstr(links%2C'|')-1))+as+name+from+items`)
|
|
||||||
if(res.ok){
|
|
||||||
return await res.json();
|
|
||||||
} else {
|
|
||||||
throw new Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await dataPromise}
|
<div class="max-w-lg mx-auto grid gap-5 lg:grid-cols-3 lg:max-w-none">
|
||||||
<p>Fetching data...</p>
|
|
||||||
{:then formats}
|
|
||||||
|
|
||||||
<div class="mt-6" style="columns: 6 240px; column-gap: 1rem;">
|
<a href="#/format/podcast" class="flex flex-col rounded-lg shadow-lg overflow-hidden transform transition ease-out duration-300 hover:scale-105 hover:shadow-xl">
|
||||||
{#each formats as format}
|
<div class="flex-shrink-0">
|
||||||
<div tabindex="0" class="inline-block w-full mt-4 bg-white rounded-lg mt-4 px-4 py-4 shadow-lg focus:outline-none">
|
<img class="h-48 w-full object-cover" src="https://images.unsplash.com/photo-1496128858413-b36217c2ce36?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1679&q=80" alt="">
|
||||||
<a href="#/format/{format.name}"><h4 class="mt-1 p-1 text-gray-900 font-semibold text-lg">{ format.name }</h4></a>
|
|
||||||
|
|
||||||
<div class="mt-2 flex flex-wrap text-sm text-gray-900">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="mt-2 text-sm text-right"><span>and 37 more.</span></p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
<h1 class="text-2xl font-bold p-2 bg-indigo-500 text-white">Podcasts</h1>
|
||||||
</div>
|
</a>
|
||||||
|
|
||||||
{:catch error}
|
<a href="#/format/book" class="flex flex-col rounded-lg shadow-lg overflow-hidden transform transition ease-out duration-300 hover:scale-105 hover:shadow-xl">
|
||||||
<p>{error.message}</p>
|
<div class="flex-shrink-0">
|
||||||
{/await}
|
<img class="h-48 w-full object-cover" src="https://images.unsplash.com/photo-1547586696-ea22b4d4235d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1679&q=80" alt="">
|
||||||
|
</div>
|
||||||
|
<h1 class="text-2xl font-bold p-2 bg-indigo-500 text-white">Books</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="#/format/video" class="flex flex-col rounded-lg shadow-lg overflow-hidden transform transition ease-out duration-300 hover:scale-105 hover:shadow-xl">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<img class="h-48 w-full object-cover" src="https://images.unsplash.com/photo-1492724441997-5dc865305da7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1679&q=80" alt="">
|
||||||
|
</div>
|
||||||
|
<h1 class="text-2xl font-bold p-2 bg-indigo-500 text-white">Videos</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
|
@ -2,17 +2,10 @@
|
||||||
export let item;
|
export let item;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-80 h-min p-2 bg-pink-300 rounded-md shadow-md grid-item">
|
<a class="flex flex-col mb-8" href="#/item/{item.rowid}">
|
||||||
<a href="#/item/{item.rowid}" class="aspect-video">
|
<img class="h-64 w-44 mr-6 mb-6 float-left border border-purple-200 rounded-md shadow-md md:shadow-xl transform transition ease-out duration-300 hover:scale-105"
|
||||||
<div class=" flex flex-col justify-between items-center">
|
src={item.image} alt="">
|
||||||
<img class="mb-6 float-left border border-purple-200 rounded-md shadow-md md:shadow-xl transform transition ease-out duration-300 hover:scale-105"
|
<div class="flex-1 text-base font-semibold text-yellow-400">★ ★ ★ ★ ★</div>
|
||||||
src={item.image} alt="{item.name}">
|
</a>
|
||||||
<div class="flex flex-col items-start ">
|
|
||||||
<h1>kljdl kjlkjl lkjlkj lkj l kjhjk iuyuer ouueohh hljhk</h1>
|
|
||||||
<h2>Topics: <span>{item.topics}</span></h2>
|
|
||||||
<span>h-64 w-44</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
<!--
|
||||||
|
From https://svelte.dev/repl/d5ff70834832498882d1570b9355561e?version=3.24.1
|
||||||
|
An almost direct copy and paste of: https://css-tricks.com/a-lightweight-masonry-solution
|
||||||
|
Usage:
|
||||||
|
- stretchFirst stretches the first item across the top
|
||||||
|
<Masonry stretchFirst={true} >
|
||||||
|
{#each data as o}
|
||||||
|
<div class="_card _padding">
|
||||||
|
Here's some stuff {o.name}
|
||||||
|
<header>
|
||||||
|
<h3>{o.name}</h3>
|
||||||
|
</header>
|
||||||
|
<section>
|
||||||
|
<p>{o.text}</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Masonry>
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div bind:this={masonryElement}
|
||||||
|
class={`__grid--masonry ${stretchFirst ? '__stretch-first' : ''}`}
|
||||||
|
style={`--grid-gap: ${gridGap}; --col-width: ${colWidth};`}
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { onMount, onDestroy, getContext, setContext, tick } from 'svelte'
|
||||||
|
export let stretchFirst = false,
|
||||||
|
gridGap = '0.5em',
|
||||||
|
colWidth = 'minmax(Min(20em, 100%), 1fr)',
|
||||||
|
items = [] // pass in data if it's dynamically updated
|
||||||
|
let grids = [], masonryElement
|
||||||
|
|
||||||
|
const refreshLayout = async () => {
|
||||||
|
grids.forEach(async grid => {
|
||||||
|
/* get the post relayout number of columns */
|
||||||
|
let ncol = getComputedStyle(grid._el).gridTemplateColumns.split(' ').length
|
||||||
|
|
||||||
|
grid.items.forEach(c => {
|
||||||
|
let new_h = c.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
if(new_h !== +c.dataset.h) {
|
||||||
|
c.dataset.h = new_h
|
||||||
|
grid.mod++
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* if the number of columns has changed */
|
||||||
|
if(grid.ncol !== ncol || grid.mod) {
|
||||||
|
/* update number of columns */
|
||||||
|
grid.ncol = ncol;
|
||||||
|
/* revert to initial positioning, no margin */
|
||||||
|
grid.items.forEach(c => c.style.removeProperty('margin-top'))
|
||||||
|
/* if we have more than one column */
|
||||||
|
if(grid.ncol > 1) {
|
||||||
|
grid.items.slice(ncol).forEach((c, i) => {
|
||||||
|
let prev_fin = grid.items[i].getBoundingClientRect().bottom /* bottom edge of item above */,
|
||||||
|
curr_ini = c.getBoundingClientRect().top /* top edge of current item */;
|
||||||
|
|
||||||
|
c.style.marginTop = `${prev_fin + grid.gap - curr_ini}px`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.mod = 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcGrid = async (_masonryArr) => {
|
||||||
|
await tick()
|
||||||
|
if(_masonryArr.length && getComputedStyle(_masonryArr[0]).gridTemplateRows !== 'masonry') {
|
||||||
|
grids = _masonryArr.map(grid => {
|
||||||
|
return {
|
||||||
|
_el: grid,
|
||||||
|
gap: parseFloat(getComputedStyle(grid).gridRowGap),
|
||||||
|
items: [...grid.childNodes].filter(c => c.nodeType === 1 && +getComputedStyle(c).gridColumnEnd !== -1),
|
||||||
|
ncol: 0,
|
||||||
|
mod: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
refreshLayout() /* initial load */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let _window
|
||||||
|
onMount(() => {
|
||||||
|
_window = window
|
||||||
|
_window.addEventListener('resize', refreshLayout, false) /* on resize */
|
||||||
|
})
|
||||||
|
onDestroy(() => {
|
||||||
|
if(_window) {
|
||||||
|
_window.removeEventListener('resize', refreshLayout, false) /* on resize */
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
$: if(masonryElement) {
|
||||||
|
calcGrid([masonryElement])
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if(items) { // update if items are changed
|
||||||
|
masonryElement = masonryElement // refresh masonryElement
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
$w: var(--col-width); // minmax(Min(20em, 100%), 1fr);
|
||||||
|
$s: var(--grid-gap); // .5em;
|
||||||
|
-->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.__grid--masonry) {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, var(--col-width));
|
||||||
|
grid-template-rows: masonry;
|
||||||
|
justify-content: center;
|
||||||
|
grid-gap: var(--grid-gap);
|
||||||
|
padding: var(--grid-gap);
|
||||||
|
|
||||||
|
}
|
||||||
|
:global(.__grid--masonry > *) {
|
||||||
|
align-self: start
|
||||||
|
}
|
||||||
|
:global(.__grid--masonry.__stretch-first > *:first-child) {
|
||||||
|
grid-column: 1/ -1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
|
@ -1,22 +0,0 @@
|
||||||
<script>
|
|
||||||
export let topic;
|
|
||||||
import Icon from "./tailwindui/Icon.svelte"
|
|
||||||
|
|
||||||
$: abbr = topic.display_name.slice(0,2).toUpperCase()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<li class="col-span-1 flex shadow-sm rounded-md">
|
|
||||||
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-pink-600 text-white text-sm font-medium rounded-l-md">{abbr}</div>
|
|
||||||
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
|
|
||||||
<div class="flex-1 px-4 py-2 text-sm truncate">
|
|
||||||
<a href={"/topic/" + topic.id} class="text-gray-900 font-medium hover:text-gray-600">{topic.display_name}</a>
|
|
||||||
<p class="text-gray-500">{topic.rowid} items</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-shrink-0 pr-2">
|
|
||||||
<button type="button" class="w-8 h-8 bg-white inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
|
||||||
<span class="sr-only">Open options</span>
|
|
||||||
<Icon kind="dots"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
|
@ -1,21 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
import ItemCard from "./ItemCard.svelte";
|
import ItemCard from "./ItemCard.svelte"
|
||||||
|
import TopicMasonryGrid from "./TopicMasonryGrid.svelte"
|
||||||
|
|
||||||
export let topic;
|
export let topicid;
|
||||||
let items = [];
|
let items = [];
|
||||||
|
|
||||||
$: fetch(`/learn/items.json?_shape=array&topics__contains=${topic}`)
|
$: fetch(`/learn/items.json?_shape=array&topics__contains=${topicid}`)
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
items = data;
|
items = data;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="md:flex md:items-center md:justify-between mb-8">
|
|
||||||
<div class="flex-1 min-w-0">
|
<TopicMasonryGrid {topicid}/>
|
||||||
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">{topic}</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
{#each items as item}
|
{#each items as item}
|
||||||
|
|
|
@ -1,50 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import TopicCard from "./TopicCard.svelte"
|
import TopicMasonryGrid from "./TopicMasonryGrid.svelte"
|
||||||
|
|
||||||
let dataPromise = getData();
|
|
||||||
async function getData() {
|
|
||||||
const res = await fetch(`/learn/topics.json?_shape=array`)
|
|
||||||
if(res.ok){
|
|
||||||
return await res.json();
|
|
||||||
} else {
|
|
||||||
throw new Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hierarchy(topics){
|
|
||||||
return topics.reduce((map, topic) => {
|
|
||||||
if(!topic.first_parent_topic_id) {
|
|
||||||
map.set(topic, []);
|
|
||||||
} else {
|
|
||||||
let parent = topics.find(t => t.id == topic.first_parent_topic_id)
|
|
||||||
map.set(parent, [...map.get(parent), topic])
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}, new Map())
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await dataPromise}
|
|
||||||
<p>Fetching data...</p>
|
|
||||||
{:then topics}
|
|
||||||
|
|
||||||
<div class="mt-6" style="columns: 6 240px; column-gap: 1rem;">
|
<TopicMasonryGrid />
|
||||||
{#each [...hierarchy(topics).keys()] as parent}
|
|
||||||
<div tabindex="0" class="inline-block w-full mt-4 bg-white rounded-lg mt-4 px-4 py-4 shadow-lg focus:outline-none">
|
|
||||||
<h4 class="mt-1 p-1 text-gray-900 font-semibold text-lg">{ parent.display_name }</h4>
|
|
||||||
|
|
||||||
<div class="mt-2 flex flex-wrap text-sm text-gray-900">
|
|
||||||
{#each hierarchy(topics).get(parent) as child}
|
|
||||||
<a href={"#/topic/" + child.id} class="text-purple-600 no-underline hover:underline hover:text-purple-900 px-2">{child.display_name}</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="mt-2 text-sm text-right"><span>and 37 more.</span></p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{:catch error}
|
|
||||||
<p>{error.message}</p>
|
|
||||||
{/await}
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script>
|
||||||
|
import Masonry from './Masonry.svelte'
|
||||||
|
export let topicid; // undefined for top level
|
||||||
|
let topic;
|
||||||
|
let alltopics = [];
|
||||||
|
let tree = new Map();
|
||||||
|
|
||||||
|
function hierarchy(topics, rootid){
|
||||||
|
// rootid can be null
|
||||||
|
let hier = topics.reduce((map, topic) => {
|
||||||
|
if(topic.first_parent_topic_id == rootid) {
|
||||||
|
map.set(topic, []);
|
||||||
|
} else {
|
||||||
|
let parent = [...map.keys()].find(t => t.id == topic.first_parent_topic_id)
|
||||||
|
if(parent) map.set(parent, [...map.get(parent), topic])
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, new Map())
|
||||||
|
|
||||||
|
return hier
|
||||||
|
}
|
||||||
|
|
||||||
|
$: fetch(`/learn/topics.json?_shape=array`)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
topic = data.find(t => t.id == topicid)
|
||||||
|
alltopics = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
$: tree = hierarchy(alltopics, topic ? topic.id : "")
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if topic}
|
||||||
|
<h1 class="text-2xl font-bold">{topic.display_name}</h1>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if [...tree.keys()].length > 0}
|
||||||
|
<Masonry gridGap={'0.75rem'}>
|
||||||
|
{#each [...tree.keys()] as parent}
|
||||||
|
<div class="bg-white rounded-lg px-4 py-4 shadow-lg focus:outline-none">
|
||||||
|
<a href={"#/topic/" + parent.id}><span class="mt-1 p-1 text-gray-900 font-semibold text-lg">{ parent.display_name }</span></a>
|
||||||
|
|
||||||
|
<div class="mt-2 flex flex-wrap text-sm text-gray-900">
|
||||||
|
{#each tree.get(parent) as child}
|
||||||
|
<a href={"#/topic/" + child.id} class="text-purple-600 no-underline hover:underline hover:text-purple-900 px-2">{child.display_name}</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Masonry>
|
||||||
|
{/if}
|
|
@ -1 +1,3 @@
|
||||||
h1.svelte-b0hmqh{background-color:yellow}
|
.__grid--masonry{display:grid;grid-template-columns:repeat(auto-fit, var(--col-width));grid-template-rows:masonry;justify-content:center;grid-gap:var(--grid-gap);padding:var(--grid-gap)}.__grid--masonry > *{align-self:start
|
||||||
|
}.__grid--masonry.__stretch-first > *:first-child{grid-column:1/ -1}._padding.svelte-1wlh87t{padding:12px}._card.svelte-1wlh87t{border:1px solid #ccc
|
||||||
|
}._sticky.svelte-1wlh87t{position:sticky;top:12px}
|
2097
static/bundle.js
2097
static/bundle.js
Plik diff jest za duży
Load Diff
File diff suppressed because one or more lines are too long
Ładowanie…
Reference in New Issue