Use localdata using jsonlines

pull/53/head
Nilesh 2022-07-10 16:01:35 +01:00
rodzic 33daea98c3
commit 600417f1e6
30 zmienionych plików z 24543 dodań i 26148 usunięć

Wyświetl plik

@ -1,6 +1,6 @@
# CSV format
# JSON format
## topics.csv
## topics.js
`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`.
@ -11,9 +11,9 @@
`sort_index` is an integer that's used for controlling the ordering in which topics are displayed.
## items.csv
## items.js
`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.
`iid` should be a unique UUID. It is needed because `reviews.js` 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.
@ -31,8 +31,8 @@ Currently `ipfs:` and `doi:` identifiers are supported. In future, ISBN can also
`tags` can describe quality: `visual`, `entertaining`, `challenging`, `inspirational`, `interactive`, `oer`. `oer` stands for "Open Educational Resource" and can be used if the linked content does not require payment or user login.
## reviews.csv
## reviews.js
`item_id` is a foreign key to `items.csv`.
`item_id` is a foreign key to `items.js`.
`by` is the name of the person or item.
`blurb` is small description in markdown format.

19048
db/items.csv

Plik diff jest za duży Load Diff

17415
db/items.js 100644

File diff suppressed because one or more lines are too long

50
db/jsonlines.js 100644
Wyświetl plik

@ -0,0 +1,50 @@
import { items } from './items.js';
import { topics } from './topics.js';
import { reviews } from './reviews.js';
const items_db = items.trimStart().trimEnd().split('\n').map(j => JSON.parse(j));
const topics_db = topics.trimStart().trimEnd().split('\n').map(j => JSON.parse(j));
const reviews_db = reviews.trimStart().trimEnd().split('\n').map(j => JSON.parse(j));
export const io_getTopicList = () => {
return [...topics_db];
}
export const io_getRandomTopicName = () => {
let randomId = Math.floor(Math.random() * topics_db.length);
return topics_db[randomId].name;
}
export const io_getTopicByName = (name) => {
return topics_db.filter(t => t.name === name)[0];
}
export const io_getRandomItemId = () => {
let randomId = Math.floor(Math.random() * items_db.length);
return items_db[randomId].iid;
}
export const io_getItemsForTopic = (topicname) => {
return items_db.filter(i => i.topics.split(";").includes(topicname))
}
export const io_getItem = (iid) => {
if(!iid) return null;
return items_db.filter(t => t.iid === iid)[0];
}
export const io_getItemsForTopicAndFormat = (format, topicname) => {
let results = items_db.filter(i => i.topics.includes(topicname)).filter(i => i.links?.includes(format + "|"));
return results.slice(0, 100);
}
export const io_getReviewsForItem = (item_id) => {
if(!item_id) return [];
return reviews_db.filter(r => r.id === item_id);
}
export const io_getItemsWithIDs = (ids) => {
let results = items_db.filter(i => ids.includes(i.iid));
console.log({ids}, {results});
return results;
}

4004
db/reviews.js 100644

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

2950
db/topics.js 100644

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

BIN
dummy.db

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -1,9 +0,0 @@
rm learn.db
sqlite-utils insert learn.db topics db/topics1.csv --csv
sqlite-utils insert learn.db topics db/topics2.csv --csv
sqlite-utils insert learn.db items db/items.csv --csv
sqlite-utils insert learn.db reviews db/reviews1.csv --csv
sqlite-utils insert learn.db reviews db/reviews2.csv --csv

BIN
learn.db

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -1,9 +0,0 @@
{
"title": "LearnAwesome",
"description": "Curated collection of learning resources",
"license": "ISC",
"license_url": "",
"source": "LearnAwesome.org",
"source_url": "https://learnawesome.org/",
"max_returned_rows": 20000
}

Wyświetl plik

@ -6,7 +6,7 @@
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "datasette . -o",
"start": "http-server static -o /",
"publish": "datasette publish vercel learn.db --project learnawesome -m metadata.json --template-dir templates --static static:static --setting max_returned_rows 20000"
},
"devDependencies": {

Wyświetl plik

@ -1,3 +0,0 @@
{
"max_returned_rows": 20000
}

Wyświetl plik

@ -14,32 +14,22 @@
import NavButtonWithLabel from './NavButtonWithLabel.svelte';
import { SearchIcon, LibraryIcon, ViewGridIcon, GiftIcon, CogIcon, BookmarkAltIcon, BookmarkIcon, SupportIcon } from "@rgossiaux/svelte-heroicons/outline";
import Bookmarks from './Bookmarks.svelte';
import { io_getRandomTopicName, io_getTopicByName, io_getTopicList, io_getRandomItemId } from "../db/jsonlines.js"
let currentView = "/topics";
let randomTopicName;
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())
.then(data => {
randomItemId = data[0].rowid;
});
$: alltopics = io_getTopicList();
function getRandomItemId() {
randomItemId = io_getRandomItemId();
}
function getRandomTopicName(){
fetch('/learn.json?_shape=array&sql=select+name+from+topics+order+by+random()+limit+1').then(r => r.json())
.then(data => {
randomTopicName = data[0].name;
});
function getRandomTopicName() {
randomTopicName = io_getRandomTopicName();
}
$: 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);
@ -52,8 +42,8 @@
}
}
onMount(getRandomItemId);
onMount(getRandomTopicName);
onMount(getRandomItemId);
onMount(hashchange);
</script>

Wyświetl plik

@ -113,7 +113,7 @@
<slot name="nav"></slot>
{#if window.location.href.startsWith('http://127.0.0.1')}
<button class="" on:click={themeRandomize}>Randomize</button>
{/if}
{/if}
</nav>
</div>
</div>

Wyświetl plik

@ -4,7 +4,7 @@
</script>
<a class="relative flex flex-col items-center rounded-md overflow-hidden transform transition ease-out duration-300 drop-shadow-lg hover:drop-shadow-2xl hover:scale-105 break-inside-avoid" href="#/item/{item.rowid}">
<a class="relative flex flex-col items-center rounded-md overflow-hidden transform transition ease-out duration-300 drop-shadow-lg hover:drop-shadow-2xl hover:scale-105 break-inside-avoid" href="#/item/{item.iid}">
<img class=" h-36 w-24 md:h-56 md:w-40 shrink-0" src={item.image || randomCover(item.iid)} alt="{item.name}"/>

Wyświetl plik

@ -2,24 +2,11 @@
import { onMount } from 'svelte';
import ItemList from "./ItemList.svelte"
import { bookmarks } from "./stores.js"
import { io_getItemsWithIDs } from "../db/jsonlines.js"
export let kind;
let items = []
onMount(() => {
// items = read();
});
function encodeArray(kind){
return Object.entries($bookmarks).filter(pair => pair[1] == kind).map(pair => pair[0]).join("%2C")
}
$: fetch(`/learn/items.json?_shape=array&rowid__in=${encodeArray(kind)}`)
.then(r => r.json())
.then(data => {
items = data;
});
$: items = io_getItemsWithIDs(Object.entries($bookmarks).filter(pair => pair[1] == kind).map(pair => pair[0]));
</script>

Wyświetl plik

@ -2,6 +2,7 @@
import ItemCard from "./ItemCard.svelte"
import SearchForm from "./SearchForm.svelte"
import { formats } from "./formats.js"
import { io_getItemsForTopicAndFormat } from "../db/jsonlines";
export let format;
export let alltopics;
@ -16,11 +17,8 @@
sortby: "rating"
};
$: query && fetch(`/learn/items.json?_shape=array&_size=100&links__contains=${format}|&topics__contains=${query.topic}`)
.then(r => r.json())
.then(data => {
items = data;
});
$: items = io_getItemsForTopicAndFormat(format, query?.topic)
$: console.log(items.length)
function handleQueryChanged(event){
// console.log("queryChanged: ", event.detail);

Wyświetl plik

@ -1,12 +1,12 @@
<script lang="ts">
export let item: {name: string, creators: string, rowid: number};
export let item: {name: string, creators: string, iid: number};
</script>
<a class="flex flex-wrap p-2 sm:p-8 justify-between rounded break-inside-avoid sm:w-64 max-w-sm bg-primary_light text-primary hover:rounded-3xl border border-secondary ease-in-out duration-300" href="#/item/{item.rowid}">
<a class="flex flex-wrap p-2 sm:p-8 justify-between rounded break-inside-avoid sm:w-64 max-w-sm bg-primary_light text-primary hover:rounded-3xl border border-secondary ease-in-out duration-300" href="#/item/{item.iid}">
<div class="flex flex-col justify-between">
<div class="flex flex-col">
<strong class="font-extrabold text-sm sm:text-lg">{item.name}</strong>
<span class="text-sm font-medium">{item.creators}</span>
{#if item.creators}<span class="text-sm font-medium">{item.creators}</span>{/if}
</div>
</div>
</a>

Wyświetl plik

@ -3,23 +3,12 @@
import { bookmarks } from "./stores.js"
import Review from "./Review.svelte"
import { randomCover } from './utility.js'
import AdvancedSearch from "./AdvancedSearch.svelte";
import { io_getItem, io_getReviewsForItem } from "../db/jsonlines";
export let itemid;
let item;
let reviews = [];
$: fetch(`/learn/items/${itemid}.json?_shape=object`)
.then(r => r.json())
.then(data => {
item = data[itemid];
});
$: item && fetch(`/learn/reviews.json?_shape=array&item_id__exact=${item.iid}`)
.then(r => r.json())
.then(data => {
reviews = data;
});
$: item = io_getItem(itemid);
$: reviews = io_getReviewsForItem(item?.id)
function get_tld(url){
return(new URL(url)).hostname.replace('www.','');

Wyświetl plik

@ -1,7 +1,7 @@
<script>
import ItemList from "./ItemList.svelte"
import TopicMasonryGrid from "./TopicMasonryGrid.svelte"
import { io_getItemsForTopic } from "../db/jsonlines.js"
import SearchForm from "./SearchForm.svelte"
@ -19,12 +19,11 @@
sortby: "rating"
};
$: fetch(`/learn/items.json?_shape=array&topics__contains=${topicname}`)
.then(r => r.json())
.then(data => {
items = data;
filteredItems = data;
});
$: {
let data = io_getItemsForTopic(topicname);
items = data;
filteredItems = data;
}
function handleQueryChanged(event){
// console.log("queryChanged: ", event.detail);

Wyświetl plik

@ -1,7 +1,6 @@
<script>
import TopicMasonryGrid from "./TopicMasonryGrid.svelte"
export let alltopics;
import SearchForm from "./SearchForm.svelte"
</script>
<TopicMasonryGrid {alltopics}/>

Wyświetl plik

@ -23,6 +23,7 @@
let tempmap = new Map();
// first pass to find all top-level objects
let parentids = [];
// console.log({topic_array}, {parent_id});
for(let i = 0; i < topic_array.length; i++){
if(topic_array[i].parent_id == parent_id){
tempmap.set(topic_array[i], []);
@ -61,7 +62,7 @@
$: topic = alltopics.find(t => t.name == topicname)
$: map = hierarchy(alltopics, topic?.name || "")
$: map = hierarchy(alltopics, topic?.name || null)
</script>

Wyświetl plik

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

Wyświetl plik

@ -22,7 +22,7 @@
</script>
<a class="flex flex-wrap overflow-hidden rounded-lg duration-300 break-inside-avoid max-w-lg border-secondary" href="#/item/{item.rowid}">
<a class="flex flex-wrap overflow-hidden rounded-lg duration-300 break-inside-avoid max-w-lg border-secondary" href="#/item/{item.iid}">
<div class="relative w-full max-w-sm w-full md:w-64 ring-black/5 rounded-xl flex flex-col items-start">
<div class="h-36 w-full md:w-64 flex justify-center items-center relative ">
@ -51,7 +51,7 @@
<div class="flex flex-col ml-5 my-5">
<strong class="font-extrabold">{item.name}</strong>
<span class="text-sm font-medium">{item.creators}</span>
{#if item.creators}<span class="text-sm font-medium">{item.creators}</span>{/if}
</div>
</div>

Wyświetl plik

@ -2,13 +2,13 @@
export function randomCover(itemid){
let images = [
'/static/book-cover.png',
'/static/book-cover-2.png',
'/static/book-cover-3.png',
'/static/book-cover-4.png',
'/static/book-cover-5.png',
'/static/book-cover-6.png',
'/static/book-cover-7.png',
'/book-cover.png',
'/book-cover-2.png',
'/book-cover-3.png',
'/book-cover-4.png',
'/book-cover-5.png',
'/book-cover-6.png',
'/book-cover-7.png',
]
return images[itemid.charCodeAt(0) % images.length];

78
static/index.html 100644
Wyświetl plik

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="//cdn.tailwindcss.com/?plugins=forms,typography,aspect-ratio,line-clamp"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#1e3a8a', //blue-900
primary_light: '#FAFAFA', //neutral-50
neutral_light: '#e5e5e5', // neutral-200
neutral_dark: '#262626', // neutral-800
secondary: '#6B21A8', // purple-800
// primary_medium: '#60A5FA', //blue-400
gradOne: '#DBEAFE', //blue-100
gradTwo: '#F3E8FF', //purple-100
},
},
fontFamily: {
sans: ['Gentium Plus', 'sans'],
serif: ['Libre Franklin','serif']
}
}
}
function darkmode(){
return;
tailwind.config.theme.extend.colors.primary = '#787A91';
tailwind.config.theme.extend.colors.primary_light = '#262626';
tailwind.config.theme.extend.colors.neutral_light = '#737373';
tailwind.config.theme.extend.colors.neutral_dark = '#FAFAFA';
tailwind.config.theme.extend.colors.secondary = '#FAFAFA';
tailwind.config.theme.extend.colors.gradOne = '#444444';
tailwind.config.theme.extend.colors.gradTwo = '#171717';
tailwind.config.theme.extend.colors.primary_medium = '#141E61';
}
function lightmode(){
tailwind.config.theme.extend.colors.primary = '#1E3A8A';
tailwind.config.theme.extend.colors.primary_light = '#FAFAFA';
tailwind.config.theme.extend.colors.neutral_light = '#e5e5e5';
tailwind.config.theme.extend.colors.neutral_dark = '#262626';
tailwind.config.theme.extend.colors.secondary = '#6B21A8';
tailwind.config.theme.extend.colors.gradOne = '#DBEAFE';
tailwind.config.theme.extend.colors.gradTwo = '#F3E8FF';
tailwind.config.theme.extend.colors.primary_medium = '#60A5FA';
}
window.matchMedia("(prefers-color-scheme: dark)").matches ? darkmode() : lightmode();
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", e => e.matches ? darkmode() : lightmode() );
</script>
<script src="/bundle.js" defer></script>
<link href="/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 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;400;500&display=swap" rel="stylesheet">
<!-- fonts Gentium Plus -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Gentium+Plus:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
</head>
<body class="max-w-none mx-auto h-full bg-gradient-to-r from-gradOne to-gradTwo text-nutral_dark font-serif">
<div class="h-full" id="app"></div>
</body>
</html>