kopia lustrzana https://github.com/learn-awesome/learndb
conflict resolved
commit
1004a2a4d7
|
@ -12,10 +12,11 @@
|
|||
import ItemDetail from "./ItemDetail.svelte"
|
||||
import ItemList from "./ItemList.svelte"
|
||||
import AdvancedSearch from "./AdvancedSearch.svelte"
|
||||
import { SearchIcon } from "@rgossiaux/svelte-heroicons/outline";
|
||||
import { SearchIcon, CogIcon, BookmarkAltIcon, BookmarkIcon } from "@rgossiaux/svelte-heroicons/outline";
|
||||
|
||||
let currentView = "/topics";
|
||||
let randomItemId;
|
||||
let alltopics = [];
|
||||
|
||||
function getRandomItemId(){
|
||||
fetch('/learn.json?_shape=array&sql=select+rowid+from+items+order+by+random()+limit+1').then(r => r.json())
|
||||
|
@ -24,6 +25,12 @@
|
|||
});
|
||||
}
|
||||
|
||||
$: fetch(`/learn/topics.json?_shape=array&_size=5000`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
alltopics = data;
|
||||
});
|
||||
|
||||
async function hashchange() {
|
||||
// the poor man's router!
|
||||
const path = window.location.hash.slice(1);
|
||||
|
@ -46,14 +53,14 @@
|
|||
|
||||
<svelte:window on:hashchange={hashchange}/>
|
||||
|
||||
<TailwindUI.AppShell>
|
||||
<TailwindUI.AppShell {alltopics}>
|
||||
<svelte:fragment slot="content">
|
||||
{#if currentView === "/home" || currentView === "/"}
|
||||
<Home/>
|
||||
{:else if currentView === "/game"}
|
||||
<SkillTree/>
|
||||
{:else if currentView === "/topics"}
|
||||
<TopicList/>
|
||||
<TopicList {alltopics}/>
|
||||
{:else if currentView.startsWith("/topic/")}
|
||||
<TopicDetail topicname={currentView.split("/").slice(2).join("/")}/>
|
||||
{:else if currentView === "/formats"}
|
||||
|
@ -93,18 +100,19 @@
|
|||
Search
|
||||
</a>
|
||||
<hr/>
|
||||
<a href="#/wanttolearn" class={(currentView === "/wanttolearn" ? 'bg-gray-900' : '') + " text-white w-full hover:bg-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md"}>
|
||||
<SearchIcon class="mr-4 flex-shrink-0 h-6 w-6 text-indigo-300"/>
|
||||
<a href="#/wanttolearn" class={(currentView === "/wanttolearn" ? 'bg-indigo-800' : '') + " text-white w-full hover:bg-indigo-600 group flex items-center px-2 py-2 text-sm font-medium rounded-md"}>
|
||||
<BookmarkIcon class="mr-4 flex-shrink-0 h-6 w-6 text-indigo-300"/>
|
||||
Want to learn
|
||||
</a>
|
||||
<a href="#/finishedlearning" class={(currentView === "/finishedlearning" ? 'bg-gray-900' : '') + " text-white w-full hover:bg-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md"}>
|
||||
<SearchIcon class="mr-4 flex-shrink-0 h-6 w-6 text-indigo-300"/>
|
||||
<a href="#/finishedlearning" class={(currentView === "/finishedlearning" ? 'bg-indigo-800' : '') + " text-white w-full hover:bg-indigo-600 group flex items-center px-2 py-2 text-sm font-medium rounded-md"}>
|
||||
<BookmarkAltIcon class="mr-4 flex-shrink-0 h-6 w-6 text-indigo-300"/>
|
||||
Finished learning
|
||||
</a>
|
||||
<hr/>
|
||||
<a href="/learn" class="text-indigo-100 hover:bg-gray-900 w-full group flex items-center px-2 py-2 text-sm font-medium rounded-md">
|
||||
<SearchIcon class="mr-4 flex-shrink-0 h-6 w-6 text-indigo-300"/>
|
||||
<a href="/learn" class="text-indigo-100 hover:bg-indigo-600 w-full group flex items-center px-2 py-2 text-sm font-medium rounded-md">
|
||||
<CogIcon class="mr-4 flex-shrink-0 h-6 w-6 text-indigo-300"/>
|
||||
Datasette
|
||||
</a>
|
||||
|
||||
</svelte:fragment>
|
||||
</TailwindUI.AppShell>
|
|
@ -1,90 +1,78 @@
|
|||
<script>
|
||||
import ZoomSvg from '@svelte-parts/zoom/svg'
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
|
||||
let sidePanelShown = true;
|
||||
let currentNode = null;
|
||||
let nodes = [
|
||||
{id: 1, name: "test", x: 100, y: 200, size: 50, color: "#ff3e00"},
|
||||
{id: 2, name: "second", x: 400, y: 300, size: 30, color: "#ff3eff"}
|
||||
]
|
||||
|
||||
let edges = [
|
||||
{id: 1, from: nodes[0], to: nodes[1]}
|
||||
]
|
||||
|
||||
function handleNodeMouseEnter(ev){
|
||||
ev.target.style.fill = "#aa3e00"
|
||||
}
|
||||
|
||||
function handleNodeMouseLeave(ev){
|
||||
ev.target.style.fill = "#ff3e00"
|
||||
}
|
||||
|
||||
function handleNodeClick(ev){
|
||||
sidePanelShown = true;
|
||||
currentNode = ev.target;
|
||||
}
|
||||
import { scale } from 'svelte/transition';
|
||||
import { cubicIn } from 'svelte/easing';
|
||||
|
||||
let graph = {
|
||||
name: "Top",
|
||||
children: [
|
||||
{name: "first", children: [
|
||||
{name: "first -> A", children: [], color: "red-500"},
|
||||
{name: "first -> B", children: [], color: "green-500"},
|
||||
{name: "first -> c", children: [], color: "yellow-500"},
|
||||
{name: "first -> d", children: [], color: "blue-500"},
|
||||
], pos: [], size: 35, color: "green-300"},
|
||||
{name: "second", children: [], pos: [], size: 35, color: "red-500"},
|
||||
{name: "third", children: [], pos: [], size: 35, color: "yellow-500"},
|
||||
{name: "fourth", children: [], pos: [], size: 35, color: "teal-400"},
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
let currentParent = null;
|
||||
let currentChild = graph;
|
||||
|
||||
function findParent(child, tree){
|
||||
if(graph == child) return null;
|
||||
if(tree.children.indexOf(child) > -1) return tree;
|
||||
// Will have to look at grandchildren
|
||||
tree.children.forEach(c => {
|
||||
let x = findParent(child, c);
|
||||
if (x != -1) return c;
|
||||
});
|
||||
return -1;
|
||||
}
|
||||
|
||||
function switchTo(parent, child){
|
||||
[currentParent, currentChild] = [parent, child];
|
||||
}
|
||||
|
||||
function siblings(parent, child){
|
||||
if(!parent) return [];
|
||||
return parent.children.filter(n => n != child)
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
|
||||
<ZoomSvg viewBox="0 0 1200 900">
|
||||
{#if currentParent}<div class="flex flex-row justify-center mb-4">
|
||||
<span>
|
||||
<button type="button" on:click="{ e => switchTo(findParent(currentParent, graph), currentParent) }" class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{currentParent.name}
|
||||
</button>
|
||||
</span>
|
||||
</div>{/if}
|
||||
|
||||
{#each edges as edge}
|
||||
<line x1={edge.from.x} y1={edge.from.y} x2={edge.to.x} y2={edge.to.y} stroke="black" stroke-width="5" />
|
||||
{/each}
|
||||
|
||||
{#each nodes as node}
|
||||
<circle cx={node.x} cy={node.y} r={node.size} fill={node.color} on:mouseenter={handleNodeMouseEnter} on:mouseleave={handleNodeMouseLeave} on:click={handleNodeClick}/>
|
||||
{/each}
|
||||
<h2 class="text-3xl my-4 text-center font-extrabold tracking-tight sm:text-4xl">{currentChild.name}</h2>
|
||||
|
||||
<g>
|
||||
<rect x="120" y="320" width="100" height="100" fill="none" stroke="black" rx="15" />
|
||||
<text x="400" y="150" font-family="Verdana" font-size="85" fill="green" stroke="yellow">Hello</text>
|
||||
</g>
|
||||
|
||||
</ZoomSvg>
|
||||
<div class="flex flex-row justify-center my-4">
|
||||
<span class="relative z-0 inline-flex shadow-sm rounded-md">
|
||||
{#each siblings(currentParent, currentChild) as sibling}
|
||||
<button type="button" on:click="{ e => switchTo(currentParent, sibling) }" class="relative inline-flex items-center px-4 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500">
|
||||
{sibling.name}
|
||||
</button>
|
||||
{/each}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="relative z-10" aria-labelledby="slide-over-title" role="dialog" aria-modal="true">
|
||||
<!-- Background backdrop, show/hide based on slide-over state. -->
|
||||
{#if sidePanelShown}
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
|
||||
{/if}
|
||||
|
||||
<div class="fixed inset-0 overflow-hidden">
|
||||
<div class="absolute inset-0 overflow-hidden">
|
||||
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
|
||||
{#if sidePanelShown}
|
||||
<div class="pointer-events-auto w-screen max-w-md" transition:fly="{{ x: 200, duration: 600 }}">
|
||||
<div class="flex h-full flex-col overflow-y-scroll bg-white py-6 shadow-xl">
|
||||
<div class="px-4 sm:px-6">
|
||||
<div class="flex items-start justify-between">
|
||||
<h2 class="text-lg font-medium text-gray-900" id="slide-over-title">Panel title</h2>
|
||||
<div class="ml-3 flex h-7 items-center">
|
||||
<button on:click="{e => sidePanelShown = false}" type="button" class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
<span class="sr-only">Close panel</span>
|
||||
<!-- Heroicon name: outline/x -->
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative mt-6 flex-1 px-4 sm:px-6">
|
||||
<!-- Replace with your content -->
|
||||
<div class="absolute inset-0 px-4 sm:px-6">
|
||||
<div class="h-full border-2 border-dashed border-gray-200" aria-hidden="true"></div>
|
||||
</div>
|
||||
<!-- /End replace -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-y-0 sm:grid-cols-2 mt-4">
|
||||
{#each currentChild.children as grandchild, i (grandchild)}
|
||||
<button on:click="{ e => switchTo(currentChild, grandchild) }" class="h-96 bg-{grandchild.color} hover:opacity-80 hover:scale-105">
|
||||
<h3>{grandchild.name}</h3>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import TopicMasonryGrid from "./TopicMasonryGrid.svelte"
|
||||
export let alltopics;
|
||||
</script>
|
||||
|
||||
<TopicMasonryGrid />
|
||||
|
||||
<TopicMasonryGrid {alltopics}/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
export let topicname; // undefined for top level
|
||||
let topic;
|
||||
let alltopics = [];
|
||||
export let alltopics;
|
||||
let map = new Map();
|
||||
|
||||
function capitalize(str){
|
||||
|
@ -59,12 +59,7 @@
|
|||
return tempmap;
|
||||
}
|
||||
|
||||
$: fetch(`/learn/topics.json?_shape=array&_size=5000`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
topic = data.find(t => t.name == topicname)
|
||||
alltopics = data;
|
||||
});
|
||||
$: topic = alltopics.find(t => t.name == topicname)
|
||||
|
||||
$: map = hierarchy(alltopics, topic?.name || "")
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
let isNavDrawerOpen = false
|
||||
export let showNotificationBell = false;
|
||||
export let showProfileMenu = false;
|
||||
export let alltopics;
|
||||
import SearchForm from "./SearchForm.svelte"
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -87,12 +89,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="md:pl-64 flex flex-col flex-1">
|
||||
<div class="sticky top-0 z-10 flex-shrink-0 flex h-16 bg-cyan-900 text-white shadow">
|
||||
<div class="sticky top-0 z-10 flex-shrink-0 flex bg-cyan-900 text-white shadow">
|
||||
<button on:click={e => isNavDrawerOpen = true} type="button" class="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 md:hidden">
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<Icon kind="menu"/>
|
||||
</button>
|
||||
<div class="flex-1 px-4 flex justify-between">
|
||||
<div class="flex-1 flex justify-between">
|
||||
<SearchForm {alltopics}/>
|
||||
{#if showNotificationBell || showProfileMenu}
|
||||
<div class="ml-4 flex items-center md:ml-6">
|
||||
{#if showNotificationBell}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<script>
|
||||
export let options = [];
|
||||
export let selected = {};
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="relative ml-2">
|
||||
<input type="text" class="w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" role="combobox" aria-controls="options" aria-expanded="false" value={selected && selected.label}>
|
||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
|
||||
<!-- Heroicon name: solid/selector -->
|
||||
<svg class="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="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<ul class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" id="options" role="listbox">
|
||||
{#each options as option}
|
||||
<!--
|
||||
Combobox option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.
|
||||
|
||||
Active: "text-white bg-indigo-600", Not Active: "text-gray-900"
|
||||
-->
|
||||
<li class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900" role="option" tabindex="-1">
|
||||
<!-- Selected: "font-semibold" -->
|
||||
<span class={(selected && selected.value == option.value ? 'font-semibold' : '') + ' block truncate'}>{option.label}</span>
|
||||
|
||||
<!--
|
||||
Checkmark, only display for selected option.
|
||||
|
||||
Active: "text-white", Not Active: "text-indigo-600"
|
||||
-->
|
||||
{#if selected && selected.value == option.value}
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600">
|
||||
<!-- Heroicon name: solid/check -->
|
||||
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -1,6 +1,15 @@
|
|||
<script>
|
||||
import Icon from "./Icon.svelte"
|
||||
let query;
|
||||
import ComboBox from "./ComboBox.svelte"
|
||||
export let alltopics;
|
||||
|
||||
let query = {
|
||||
text: "some text",
|
||||
topic: "algebra",
|
||||
format: "book",
|
||||
level: "beginner",
|
||||
sortby: ""
|
||||
};
|
||||
let results = [];
|
||||
|
||||
function handleSubmit(ev){
|
||||
|
@ -8,31 +17,35 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<form class="w-full flex md:ml-0" on:submit|preventDefault={handleSubmit}>
|
||||
<label for="search-field" class="sr-only">Search</label>
|
||||
<div class="relative w-full text-gray-400 focus-within:text-gray-600">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pointer-events-none">
|
||||
<Icon kind="search"/>
|
||||
</div>
|
||||
<input bind:value={query} class="block w-full h-full pl-8 pr-3 py-2 border-transparent text-gray-900 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-0 focus:border-transparent sm:text-sm" placeholder="Search" type="search" name="search">
|
||||
</div>
|
||||
<form class="w-full bg-gray-100 p-2 inline-flex" on:submit|preventDefault={handleSubmit}>
|
||||
<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-icon name="search" slot="prefix"></sl-icon>
|
||||
</sl-input>
|
||||
|
||||
<ComboBox options={alltopics.map(t => { return {label: t.display_name, value: t.name}; }).sort((a,b) => a.label.localeCompare(b.label))} selected={null}/>
|
||||
|
||||
<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>
|
||||
|
||||
<sl-select class="ml-2 w-44" on:sl-change="{e => query.level = e.target.value}" value={query.level}>
|
||||
<sl-menu-item value="">Any level</sl-menu-item>
|
||||
<sl-menu-item value="childlike">Childlike</sl-menu-item>
|
||||
<sl-menu-item value="beginner">Beginner</sl-menu-item>
|
||||
<sl-menu-item value="intermediate">Intermediate</sl-menu-item>
|
||||
<sl-menu-item value="advanced">Advanced</sl-menu-item>
|
||||
<sl-menu-item value="research">Research</sl-menu-item>
|
||||
</sl-select>
|
||||
|
||||
<sl-select class="ml-2 w-44" 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="">Sort by</sl-menu-item>
|
||||
<sl-menu-item value="rating">Rating</sl-menu-item>
|
||||
<sl-menu-item value="year">Year</sl-menu-item>
|
||||
<sl-menu-item value="name">Name</sl-menu-item>
|
||||
</sl-select>
|
||||
</form>
|
||||
|
||||
<ul class="max-h-80 scroll-pt-11 scroll-pb-2 space-y-2 overflow-y-auto pb-2" id="options" role="listbox">
|
||||
<li>
|
||||
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Clients</h2>
|
||||
<ul class="mt-2 text-sm text-gray-800">
|
||||
<!-- Active: "bg-indigo-600 text-white" -->
|
||||
<li class="cursor-default select-none px-4 py-2" id="option-1" role="option" tabindex="-1">Workflow Inc.</li>
|
||||
<li class="cursor-default select-none px-4 py-2" id="option-2" role="option" tabindex="-1">Multinational LLC.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Projects</h2>
|
||||
<ul class="mt-2 text-sm text-gray-800">
|
||||
<!-- Active: "bg-indigo-600 text-white" -->
|
||||
<li class="cursor-default select-none px-4 py-2" id="option-3" role="option" tabindex="-1">Workflow Inc. / Website Redesign</li>
|
||||
<li class="cursor-default select-none px-4 py-2" id="option-3" role="option" tabindex="-1">Multinational LLC. / Animation</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
Ładowanie…
Reference in New Issue