This package is an admin generator for your JS/TS/Svelte projects. It can consume a distant API, a localStorage, or even an RPC-based data storage (like when using Tauri).
List | View | Edit |
---|---|---|
There is a roadmap at the end of this documentation to know what features are soon coming!
You can install SvelteAdmin with one of these commands, depending on your package manager:
pnpm add "@orbitale/svelte-admin"
npm install --save "@orbitale/svelte-admin"
If you want to use the latest version committed to Github, you can run one of these commands to add it to your project:
pnpm add "@orbitale/svelte-admin@git://github.com/Orbitale/SvelteAdmin.git"
npm install --save "@orbitale/svelte-admin@git://github.com/Orbitale/SvelteAdmin.git"
This comes with no warranty of working perfectly (again, it's a prototype).
An administration panel is composed of three main components:
Dashboard
definition: it provides some metadata, the links you will add to your menus.Crud
definitions: added to your Dashboard, they define the resource type you will manipulate in each operation of your Crud. All Cruds also define how to fetch and persist your data from whatever data source you want (usually it might be some kind of API).Dashboard
definition, and called by user input (like URL parameters).Usually, you will have one dashboard and set one Crud per entity to your administration panel.
First, create a classic SvelteKit project, as an empty application, and when prompted whether to use Typescript, say yes (we highly recommend using Typescript).
Then, create a src/lib/Dashboard.ts
file, and start writing the necessary data to create a Dasbhoard object:
// src/lib/Dashboard.ts
// Some fancy icons for a neat UI:
import Book from 'carbon-icons-svelte/lib/Book.svelte';
import Home from 'carbon-icons-svelte/lib/Home.svelte';
// SvelteAdmin classes:
import { DashboardDefinition, UrlAction } from '@orbitale/svelte-admin';
// See later:
import booksCrud from './booksCrud';
// The dashboard setup:
export const dashboard = new DashboardDefinition({
// Some metadata related to your admin panel
admin: {
defaultLocale: 'en',
rootUrl: '/admin',
head: {
brandName: '{ Your company/brand name }',
appName: '{ Your app name }'
}
},
// The main menu on the left side of the page
sideMenu: [
new UrlAction('Homepage', '/', Home),
new UrlAction('Book', '/admin/books/list', Book)
],
// Here you set all the Crud configurations of your admin panel
// For organization purposes, we recommend you to define your Crud configs
// in separate typescript files, it makes it easier to read and maintain.
cruds: [booksCrud]
});
Then, create the src/lib/booksCrud.ts
file, it will contain your "Books" Crud configuration:
// src/lib/booksCrud.ts
// Again, some icons to pop your admin!
import Pen from 'carbon-icons-svelte/lib/Pen.svelte';
import TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte';
// SvelteAdmin classes:
import {
TextField,
TextareaField,
UrlAction,
List,
Edit,
Delete,
CallbackStateProcessor,
CallbackStateProvider
} from '@orbitale/svelte-admin';
// These are the most common fields you want for your "Book" entity:
const fields = [
new TextField('title', 'Title', { placeholder: "Enter the book's title" }),
new TextareaField('description', 'description', {
placeholder: "Enter the book's descrption",
help: "Please don't make a summary of the book, remember to not spoil your readers!"
})
];
// Note: these fields can of course change based on different pages/actions,
// so feel free to spread this out if you have more complex admins!
// Finally: the actual Crud object!
export const booksCrud = new CrudDefinition(
// This is a unique name that will serve as identifier for the Crud:
'books',
{
// These labels are used to display titles or operation messages,
// If you add them to your own translation dictionary, they can be translated too!
// (see more about translations below)
label: { singular: 'Book', plural: 'Books' },
// The "operations" are the actual Crud pages you will use.
// SvelteAdmin has some generic ones for you, but you can also create your own!
operations: [
// Basic data grid listing all your Books.
// Will display "edit" and "delete" buttons on the right of the list.
new List(fields, [
new UrlAction('Edit', '/admin/books/edit', Pen),
new UrlAction('Delete', '/admin/books/delete', TrashCan)
]),
// Straightforward "edit" form, can be accessed via the "edit" link above.
new Edit(fields, []),
// Deletion page with confirmation message and buttons,
// accessed via the "delete" link configured above
new Delete(fields, new UrlAction('List', '/admin/books/list', null))
],
// See below about state processors and providers.
stateProvider: new CallbackStateProvider(function (operation, requestParameters = {}) {
console.info('TODO: return actual data, like from an API');
return Promise.resolve(null);
}),
// See below about state processors and providers.
stateProcessor: new CallbackStateProcessor(function (data, operation, requestParameters = {}) {
console.info('TODO: process new, edit or delete data based on the current operation');
return Promise.resolve();
})
}
);
Voilà!
Well, your Crud is not totally ready, because it needs two more actions: being rendered, and fetching the real data.
About the "fetching" part, we will leave this for below chapters.
However, let's render our dashboard!
If you have a common SvelteKit project, you can directly create a new route page for your base admin.
Since we will mostly use the URL to determine what Crud and operations we are looking at, let's use SvelteKit's router to make our URLs shine with as less code as possible!
Create a src/routes/admin/[crud]/[operation]/+page.svelte
file with the following code:
<script lang="ts">
// src/routes/admin/[crud]/[operation]/+page.svelte
// This is a custom Svelte store created by SvelteKit,
// it points to an instance of a Page object,
// which can allow us to gather data from the current page,
// such as the window.URL object, or the dynamic parameters of your route.
// More details about the "page" store there:
// https://kit.svelte.dev/docs/modules#$app-stores-page
import { page } from '$app/stores';
// Same here as the previous "page" store, but the "browser" var contains
// a boolean that is set to "false" during server-side rendering, and to
// "true" when the Svelte component is mounted to the DOM.
import { browser } from '$app/environment';
// This function helps retrieving the [crud] and [operation] variables from the URL,
// as well as the potential query string params like "?id=..."
import { getRequestParams } from '@orbitale/svelte-admin';
// That's your custom dashboard!
// The "$lib" alias is configured by SvelteKit,
// it always points to your "src/lib/" directory.
import { dashboard } from '$lib/Dashboard';
// The "$:" syntax is valid Javascript code that tells Svelte
// that the following code is reactive, based on the values it depends on.
// On this code, reactivity depends on "page" and "browser" variables.
$: crud = $page.params.crud;
$: operation = $page.params.operation;
$: requestParameters = getRequestParams($page, browser);
// Note: prefixing "page" with "$" makes sure we refer to the store's actual value.
// If we didn't add this "$" prefix, we would use an object containing a method ".subscribe()", but we can use it to execude code whenever the store data changes. The "$" prefix sort of shortens everything for us automatically.
</script>
<!--
The "key" block makes sure its content is re-rendered whenever its arguments change.
Then, if the "$page" changes, the <svelte:component> component is re-rendered,
which is exactly what we expect when the current web page actually changes!
-->
{#key $page}
<svelte:component
this="{dashboard.theme.dashboard}"
{dashboard}
{crud}
{operation}
{requestParameters}
/>
{/key}
That's it!
Now, your admin is prepared.
Here is the *shorter version with no comments, if you want to copy-paste for a quick setup:
<script lang="ts">
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { getRequestParams } from '@orbitale/svelte-admin';
import { dashboard } from '$lib/Dashboard';
$: crud = $page.params.crud;
$: operation = $page.params.operation;
$: requestParameters = getRequestParams($page, browser);
</script>
{#key $page}
<svelte:component
this="{dashboard.theme.dashboard}"
{dashboard}
{crud}
{operation}
{requestParameters}
/>
{/key}
As we have seen before, there are two entrypoints to your data source, one of them is the state provider, and there is one per Crud.
A SateProvider is a class that implements this interface:
export interface StateProvider<T> {
provide(action: CrudOperation, requestParameters: RequestParameters): StateProviderResult<T>;
}
operation
object is the same as one of the Crud operations, the ones you configure in your CrudDefinition
objects.List
and ̀Edit
operations, with an if
statement to discriminate both.requestParameters
is a key=>value object, matching this typescript type: {[key: string]: string}
.[crud]
and [operation]
parameters extracted from the URL, but also the entity ID if you add it via ?id=...
for example.The return type StateProviderResult<T>
resolves to Promise<T | PaginatedResults<T> | Array<T> | null>
, so you could return almost anything that represents an entity, or a list of entities.
It must though be a Promise
object.
If your code is synchronous, you can use the Promise.resolve(your_value)
method in order to return a "semi-synchronous" promise.
The only implemented provider so far is the CallbackStateProvider
, with which you define an external callback function that returns whatever you want.
The second entrypoint to your data source is the state processor, and as for the state provider, there is only one processor per Crud.
A StateProcessor is a class that implements this interface:
export interface StateProcessor<T> {
process(
data: StateProcessorInput<T>,
operation: CrudOperation,
requestParameters: RequestParameters
): void;
}
The StateProcessorInput<T>
type resolves to T | Array<T> | null
, similarly to StateProviderResult
you have seen in the previous section. This way, it can resolve to most of your entity types.
Very useful to send HTTP requests to your API to save data!
The only implemented provider so far is the CallbackStateProcessor
, it allows you to define an external callback function that does whatever you want.
SvelteAdmin is translated, by using the svelte-i18n package.
Most labels you will provide to your dashboard (fields labels, Crud operations labels, etc.) will be translated with svelte-i18n, whether or not they have an associated translation.
Fallback will be to the en
locale anyway.
If you want your own translations, or if you need to override existing translations, you can provide your own translation dictionaries to SvelteAdmin.
To do so, provide the localeDictionaries
option to your DashboardDefinition
creation:
export const dashboard = new DashboardDefinition({
// ...
localeDictionaries: {
en: {
my_text: 'My Translation'
}
}
});
This dictionary will append non-existing, and override existing translations. If the translation key exists in SvelteAdmin, your custom translations will override it.
To avoid having a big dashboard definition file, you can put your translations in an external file.
For the rest of translations, you can still use the same dictionary for a local svelte-i18n
-based setup in your own application.
This project is still incomplete, and if you want to contribute, feel free to submit an issue or a pull-request!
In the meantime, here is the roadmap for future features:
Edit
and New
operations).StateProvider
s and StateProcessor
s (especially for API endpoints).This project is published under the GNU-LGPL license, feel free to check the LICENSE file for more details.