Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor @tomic/svelte to Svelte 5 and update sveltekit-site template #1010

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions browser/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,31 @@ This changelog covers all five packages, as they are (for now) updated as a whol
- [#992](https://github.com/atomicdata-dev/atomic-server/issues/992) Fix Searchbox overflowing when displaying long names.
- [#999](https://github.com/atomicdata-dev/atomic-server/issues/999) Fix parseMetaTags character escape issue.

### @tomic/lib

- `resource.props` is now writable: `resource.props.name = 'New Name'`.
- Added `store.preloadResourceTree()` method.
- Fix generated ontologies not working in a Next.js server context.

### @tomic/cli

- [#983](https://github.com/atomicdata-dev/atomic-server/issues/983) Give clear error when name collisions are found in an ontology.
- Generates class definitions that enables doing: `resource.props.name = 'New Name'`;

### @tomic/svelte

- [#700](https://github.com/atomicdata-dev/atomic-server/issues/700) Update to Svelte 5. There are significant changes to the API.
- BREAKING CHANGE: Dropped support for Svelte 4 and below.
- BREAKING CHANGE: `getResource()` now returns a reactive proxy instead of a readable store.
- BREAKING CHANGE: `getResource()` now takes a function returning a subject instead of the subject directly. e.g. `getResource(() => 'https://my-atomicserver.com/my-resource');`.
- BREAKING CHANGE: Removed `getValue()`. It is no longer needed. Instead use `resource.props.name` directly or do `const name = $derived(resource.get(core.properties.name));`.
- BREAKING CHANGE: Removed `initStore()`. You now need to set your store on a context using `createAtomicStoreContext()`.
- BREAKING CHANGE: Removed `loadResourceTree()`. It is now a method on `store`: `store.preloadResourceTree()`.
- Added `createAtomicStoreContext()` and `getStoreFromContext()`.

### @tomic/create-template

- [#700](https://github.com/atomicdata-dev/atomic-server/issues/700) Update SvelteKit-site template to Svelte 5.

## [v0.40.0] - 2024-10-07

Expand Down
36 changes: 34 additions & 2 deletions browser/cli/src/generateBaseObject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Resource, type Core } from '@tomic/lib';
import { Resource, type Core, core } from '@tomic/lib';
import { store } from './store.js';
import { camelCaseify, dedupe } from './utils.js';
import chalk from 'chalk';
Expand All @@ -8,6 +8,7 @@ export type ReverseMapping = Record<string, string>;
type BaseObject = {
classes: Record<string, string>;
properties: Record<string, string>;
__classDefs: Record<string, string[]>;
};

export const generateBaseObject = async (
Expand All @@ -24,12 +25,14 @@ export const generateBaseObject = async (
const baseObj = {
classes: await listToObj(classes, 'classes'),
properties: await listToObj(properties, 'properties'),
__classDefs: await createClassDefs(classes),
};

const objStr = `export const ${name} = {
classes: ${recordToString(baseObj.classes)},
properties: ${recordToString(baseObj.properties)},
} as const`;
__classDefs: ${stringifyClassDefs(baseObj.__classDefs)}
} as const satisfies OntologyBaseObject`;

return [objStr, createReverseMapping(name, baseObj)];
};
Expand Down Expand Up @@ -71,6 +74,26 @@ const listToObj = async (
return Object.fromEntries(entries);
};

const createClassDefs = async (
classes: string[],
): Promise<Record<string, string[]>> => {
const classResources = await Promise.all(
classes.map(async c => await store.getResource(c)),
);

const entries = classResources.map(resource => {
return [
resource.subject,
[
...resource.getArray(core.properties.requires),
...resource.getArray(core.properties.recommends),
],
];
});

return Object.fromEntries(entries);
};

const recordToString = (obj: Record<string, string>): string => {
const innerSting = Object.entries(obj).reduce(
(acc, [key, value]) => `${acc}\n\t${key}: '${value}',`,
Expand All @@ -80,6 +103,15 @@ const recordToString = (obj: Record<string, string>): string => {
return `{${innerSting}\n }`;
};

const stringifyClassDefs = (obj: Record<string, string[]>) => {
const innerString = Object.entries(obj).reduce(
(acc, [key, value]) => `${acc}\n\t["${key}"]: ${JSON.stringify(value)},`,
'',
);

return `{${innerString}\n }`;
};

const createReverseMapping = (
ontologyTitle: string,
obj: BaseObject,
Expand Down
2 changes: 1 addition & 1 deletion browser/cli/src/generateOntology.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const TEMPLATE = `
* For more info on how to use ontologies: https://github.com/atomicdata-dev/atomic-server/blob/develop/browser/cli/readme.md
* -------------------------------- */

import type { BaseProps } from '${Inserts.MODULE_ALIAS}'
import type { OntologyBaseObject, BaseProps } from '${Inserts.MODULE_ALIAS}'

${Inserts.BASE_OBJECT}

Expand Down
9 changes: 8 additions & 1 deletion browser/cli/src/generatePropTypeMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,12 @@ const generateLine = (subject: string, reverseMapping: ReverseMapping) => {
const resource = store.getResourceLoading<Core.Property>(subject);
const datatype = resource.props.datatype as Datatype;

return `[${reverseMapping[subject]}]: ${DatatypeToTSTypeMap[datatype]}`;
const type = DatatypeToTSTypeMap[datatype];

if (!type) {
console.error(`Unknown datatype ${datatype} on property ${resource.title}`);
process.exit(1);
}

return `[${reverseMapping[subject]}]: ${type}`;
};
2 changes: 2 additions & 0 deletions browser/create-template/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/bin

/templates/**/ontologies/**/*
16 changes: 8 additions & 8 deletions browser/create-template/templates/sveltekit-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
"update-ontologies": "ad-generate ontologies"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.3.0",
"@sveltejs/adapter-node": "^5.2.8",
"@sveltejs/kit": "^2.7.2",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/kit": "^2.7.3",
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
"@tomic/cli": "^0.39.0",
"@types/eslint": "^9.6.1",
"eslint": "^9.13.0",
Expand All @@ -26,17 +26,17 @@
"globals": "^15.11.0",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"svelte": "^4.2.19",
"svelte-check": "^3.8.6",
"svelte": "^5.1.4",
"svelte-check": "^4.0.5",
"typescript": "^5.6.3",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10",
"vitest": "^2.1.3"
},
"type": "module",
"dependencies": {
"@tomic/lib": "^0.39.0",
"@tomic/svelte": "^0.39.0",
"@tomic/lib": "^0.40.0",
"@tomic/svelte": "^0.40.0",
"svelte-markdown": "^0.4.1"
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { website } from '$lib/ontologies/website';
import { CollectionBuilder, core } from '@tomic/lib';
import { store as storeStore } from '@tomic/svelte';
import { get } from 'svelte/store';
import { getStore } from './getStore';
export async function getAllBlogposts(): Promise<string[]> {
const store = get(storeStore);
const store = getStore();

const collection = new CollectionBuilder(store)
.setProperty(core.properties.isA)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export async function getCurrentResource(
url: URL
): Promise<Resource | undefined> {
const store = getStore();
// Svelte uses a special fetch function that inlines responses during server-side rendering. To make sure the store can make use of this we need to inject the fetch function into the store.
// Svelte uses a special fetch function that inlines responses during server-side rendering.
// To make sure the store can make use of this we need to inject the fetch function into the store.
store.injectFetch(fetchOverride);

const path = url.pathname;
Expand All @@ -32,5 +33,9 @@ export async function getCurrentResource(

const currentResourceSubject = await collection.getMemberWithIndex(0);

if (currentResourceSubject === undefined) {
return undefined;
}

return await store.getResource(currentResourceSubject);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { get } from 'svelte/store';
import { initStore, store as atomicStore } from '@tomic/svelte';
import { Store } from '@tomic/lib';
import { PUBLIC_ATOMIC_SERVER_URL } from '$env/static/public';
import { initOntologies } from '$lib/ontologies';

// We use a global store. Keep in mind that this means the cache is shared between sessions. Don't do this if you have data that should only be available to certain agents.
let store: Store | undefined;

const init = () => {
const atomicStore = new Store({
store = new Store({
serverUrl: PUBLIC_ATOMIC_SERVER_URL
});
initStore(atomicStore);

initOntologies();
};

export const getStore = () => {
let store = get(atomicStore);

export const getStore = (): Store => {
if (store === undefined) {
init();
store = get(atomicStore);
}

return store;
return store!;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { website } from '$lib/ontologies/website';
import type { Resource } from '@tomic/lib';
import { loadResourceTree } from '@tomic/svelte';
import { getStore } from './getStore';

/**
* Due to how sveltekit works we sometimes need to preload resources for them to show up in the serverside rendered html.
Expand All @@ -9,16 +9,26 @@ import { loadResourceTree } from '@tomic/svelte';
* If you do not preload the referenced resources they will not show up when the page is hydrated client side. This should not be a big problem but could cause issues with SEO or users that have javascript disabled.
*/
export async function preloadResources(resource: Resource): Promise<void> {
const store = getStore();

if (resource.hasClasses(website.classes.website)) {
await store.preloadResourceTree(resource.subject, {
[website.properties.menuItems]: {
[website.properties.subItems]: true
}
});
}

if (resource.hasClasses(website.classes.page)) {
await loadResourceTree(resource.subject, {
await store.preloadResourceTree(resource.subject, {
[website.properties.blocks]: {
[website.properties.images]: true
}
});
}

if (resource.hasClasses(website.classes.blogpost)) {
await loadResourceTree(resource.subject, {
await store.preloadResourceTree(resource.subject, {
[website.properties.coverImage]: true
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
align-items: var(--hstack-align);
justify-content: var(--hstack-justify);

& > h1,
& > h2,
& > h3,
& > h4,
& > h5,
& > h6 {
:global(& > h1),
:global(& > h2),
:global(& > h3),
:global(& > h4),
:global(& > h5),
:global(& > h6) {
margin: 0;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
justify-content: var(--vstack-justify);
height: var(--vstack-height);
min-height: var(--vstack-min-height);
& > h1,
& > h2,
& > h3,
& > h4,
& > h5,
& > h6 {
:global(& > h1),
:global(& > h2),
:global(& > h3),
:global(& > h4),
:global(& > h5),
:global(& > h6) {
margin: 0;
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
<script lang="ts">
import { PUBLIC_WEBSITE_RESOURCE } from '$env/static/public';
import { type Website } from '$lib/ontologies/website';
import { website, type Website } from '$lib/ontologies/website';
import { getResource } from '@tomic/svelte';
import MenuItem from '../views/MenuItem/MenuItem.svelte';
import Container from './Layout/Container.svelte';
import HStack from './Layout/HStack.svelte';
import Loader from './Loader.svelte';

$: site = getResource<Website>(PUBLIC_WEBSITE_RESOURCE);
let site = getResource<Website>(() => PUBLIC_WEBSITE_RESOURCE);
let menuItems = $derived(site.props.menuItems ?? []);
</script>

<Container>
<nav>
<HStack align="center" justify="space-between" wrap>
<Loader resource={$site}>
<a class="site-title" href="/">
{$site.title}
</a>
<ul>
{#each $site.props.menuItems ?? [] as menuItem}
<li>
<MenuItem subject={menuItem} />
</li>
{/each}
</ul>
</Loader>
<a class="site-title" href="/">
{site.title}
</a>
<ul>
{#each menuItems as menuItem (menuItem)}
<li>
<MenuItem subject={menuItem} />
</li>
{/each}
</ul>
</HStack>
</nav>
</Container>
Expand Down
Loading
Loading