Add svelte sample app and expose it on main route
This commit is contained in:
parent
da35c25544
commit
fb6ab6a07e
26 changed files with 2488 additions and 3 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -36,3 +36,6 @@ out/
|
|||
.vscode/
|
||||
.env
|
||||
wulkanowy-*.json
|
||||
|
||||
node_modules/
|
||||
src/main/resources/app
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
FROM node:21
|
||||
WORKDIR /home/gradle/src/app
|
||||
RUN npm install && npm run build
|
||||
|
||||
FROM gradle:7-jdk11 AS build
|
||||
COPY --chown=gradle:gradle . /home/gradle/src
|
||||
WORKDIR /home/gradle/src
|
||||
|
|
10
app/.gitignore
vendored
Normal file
10
app/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
1
app/.npmrc
Normal file
1
app/.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
engine-strict=true
|
38
app/README.md
Normal file
38
app/README.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
17
app/jsconfig.json
Normal file
17
app/jsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
1674
app/package-lock.json
generated
Normal file
1674
app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
24
app/package.json
Normal file
24
app/package.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"svelte": "^4.0.5",
|
||||
"svelte-check": "^3.4.3",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.4.2"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-static": "^2.0.3"
|
||||
}
|
||||
}
|
12
app/src/app.d.ts
vendored
Normal file
12
app/src/app.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
12
app/src/app.html
Normal file
12
app/src/app.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Schools - Wulkanowy</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
1
app/src/lib/index.js
Normal file
1
app/src/lib/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
2
app/src/routes/+layout.js
Normal file
2
app/src/routes/+layout.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
// This can be false if you're using a fallback (i.e. SPA mode)
|
||||
export const prerender = true;
|
7
app/src/routes/+page.svelte
Normal file
7
app/src/routes/+page.svelte
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import RemoteTable from "./RemoteTable.svelte";
|
||||
</script>
|
||||
|
||||
<h1>Schools</h1>
|
||||
|
||||
<RemoteTable/>
|
106
app/src/routes/Pagination.svelte
Normal file
106
app/src/routes/Pagination.svelte
Normal file
|
@ -0,0 +1,106 @@
|
|||
<script context="module">
|
||||
let globalLabels;
|
||||
|
||||
export function setLabels(labels) {
|
||||
globalLabels = labels;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
const stateContext = getContext("state");
|
||||
|
||||
export let buttons = [-2, -1, 0, 1, 2];
|
||||
export let count;
|
||||
export let page = 0;
|
||||
export let pageSize;
|
||||
export let serverSide = false;
|
||||
|
||||
export let labels = {
|
||||
first: "First",
|
||||
last: "Last",
|
||||
next: "Next",
|
||||
previous: "Previous",
|
||||
...globalLabels
|
||||
};
|
||||
|
||||
$: pageCount = Math.floor(count / pageSize);
|
||||
|
||||
function onChange(event, page) {
|
||||
const state = stateContext.getState();
|
||||
const detail = {
|
||||
originalEvent: event,
|
||||
page,
|
||||
pageIndex: serverSide ? 0 : page * state.pageSize,
|
||||
pageSize: state.pageSize
|
||||
};
|
||||
dispatch("pageChange", detail);
|
||||
|
||||
if (detail.preventDefault !== true) {
|
||||
stateContext.setPage(detail.page, detail.pageIndex);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.active {
|
||||
background-color: rgb(150, 150, 235);
|
||||
color: white;
|
||||
}
|
||||
|
||||
ul {
|
||||
flex: 1;
|
||||
float: right;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: 1px solid #ccc;
|
||||
padding: 5px 10px;
|
||||
margin-left: 3px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<button disabled={page === 0} on:click={e => onChange(e, 0)}>
|
||||
{labels.first}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button disabled={page === 0} on:click={e => onChange(e, page - 1)}>
|
||||
{labels.previous}
|
||||
</button>
|
||||
</li>
|
||||
{#each buttons as button}
|
||||
{#if page + button >= 0 && page + button <= pageCount}
|
||||
<li>
|
||||
<button
|
||||
class:active={page === page + button}
|
||||
on:click={e => onChange(e, page + button)}>
|
||||
{page + button + 1}
|
||||
</button>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
<li>
|
||||
<button
|
||||
disabled={page > pageCount - 1}
|
||||
on:click={e => onChange(e, page + 1)}>
|
||||
{labels.next}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button disabled={page >= pageCount} on:click={e => onChange(e, pageCount)}>
|
||||
{labels.last}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
84
app/src/routes/RemoteTable.svelte
Normal file
84
app/src/routes/RemoteTable.svelte
Normal file
|
@ -0,0 +1,84 @@
|
|||
<script>
|
||||
//Row component is optional and only serves to render odd/even row, you can use <tr> instead.
|
||||
//Sort component is optional
|
||||
import { onMount } from "svelte";
|
||||
import Table, { Pagination, Row, Search, Sort } from "./Table.svelte";
|
||||
import { getData } from "./server.js";
|
||||
import { sortNumber, sortString } from "./sorting.js";
|
||||
|
||||
let rows = [];
|
||||
let page = 0; //first page
|
||||
let pageIndex = 0; //first row
|
||||
let pageSize = 3; //optional, 10 by default
|
||||
|
||||
let loading = true;
|
||||
let rowsCount = 0;
|
||||
let text;
|
||||
let sorting;
|
||||
|
||||
onMount(async () => {
|
||||
await load(page);
|
||||
});
|
||||
|
||||
async function load(_page) {
|
||||
loading = true;
|
||||
const data = await getData(_page, pageSize, text, sorting);
|
||||
rows = data.rows;
|
||||
rowsCount = data.rowsCount;
|
||||
loading = false;
|
||||
}
|
||||
|
||||
function onCellClick(row) {
|
||||
alert(JSON.stringify(row));
|
||||
}
|
||||
|
||||
function onPageChange(event) {
|
||||
load(event.detail.page);
|
||||
page = event.detail.page;
|
||||
}
|
||||
|
||||
async function onSearch(event) {
|
||||
text = event.detail.text;
|
||||
await load(page);
|
||||
page = 0;
|
||||
}
|
||||
|
||||
async function onSort(event) {
|
||||
sorting = { dir: event.detail.dir, key: event.detail.key };
|
||||
await load(page);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Table {loading} {rows} {pageIndex} {pageSize} let:rows={rows2}>
|
||||
<div slot="top">
|
||||
<Search on:search={onSearch} />
|
||||
</div>
|
||||
<thead slot="head">
|
||||
<tr>
|
||||
<th>
|
||||
Name
|
||||
<Sort key="name" on:sort={onSort} />
|
||||
</th>
|
||||
<th>
|
||||
Lastname
|
||||
<Sort key="lastName" on:sort={onSort} />
|
||||
</th>
|
||||
<th>
|
||||
Age
|
||||
<Sort key="age" on:sort={onSort} />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each rows2 as row, index (row)}
|
||||
<Row {index} on:click={() => onCellClick(row)}>
|
||||
<td data-label="Name">{row.name}</td>
|
||||
<td data-label="Lastname">{row.lastName}</td>
|
||||
<td data-label="Age">{row.age}</td>
|
||||
</Row>
|
||||
{/each}
|
||||
</tbody>
|
||||
<div slot="bottom">
|
||||
<Pagination {page} {pageSize} count={rowsCount} serverSide={true} on:pageChange={onPageChange} />
|
||||
</div>
|
||||
</Table>
|
24
app/src/routes/Row.svelte
Normal file
24
app/src/routes/Row.svelte
Normal file
|
@ -0,0 +1,24 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let index = 0;
|
||||
|
||||
function onClick(event) {
|
||||
dispatch("click", event);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.odd {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
</style>
|
||||
|
||||
<tr
|
||||
on:click={onClick}
|
||||
class={$$props.class}
|
||||
class:odd={index % 2 !== 0}
|
||||
class:even={index % 2 === 0}>
|
||||
<slot />
|
||||
</tr>
|
91
app/src/routes/Search.svelte
Normal file
91
app/src/routes/Search.svelte
Normal file
|
@ -0,0 +1,91 @@
|
|||
<script context="module">
|
||||
let globalLabels;
|
||||
|
||||
export function setLabels(labels) {
|
||||
globalLabels = labels;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
const stateContext = getContext("state");
|
||||
|
||||
export let filter = (row, text, index) => {
|
||||
text = text.toLowerCase();
|
||||
for (let i in row) {
|
||||
if (
|
||||
row[i]
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(text) > -1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
export let index = -1;
|
||||
export let text = "";
|
||||
|
||||
export let labels = {
|
||||
placeholder: "Search",
|
||||
...globalLabels
|
||||
};
|
||||
|
||||
async function onSearch(event) {
|
||||
const state = stateContext.getState();
|
||||
const detail = {
|
||||
originalEvent: event,
|
||||
filter,
|
||||
index,
|
||||
text,
|
||||
page: state.page,
|
||||
pageIndex: state.pageIndex,
|
||||
pageSize: state.pageSize,
|
||||
rows: state.filteredRows
|
||||
};
|
||||
dispatch("search", detail);
|
||||
|
||||
if (detail.preventDefault !== true) {
|
||||
if (detail.text.length === 0) {
|
||||
stateContext.setRows(state.rows);
|
||||
} else {
|
||||
stateContext.setRows(
|
||||
detail.rows.filter(r => detail.filter(r, detail.text, index))
|
||||
);
|
||||
}
|
||||
stateContext.setPage(0, 0);
|
||||
} else {
|
||||
stateContext.setRows(detail.rows);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.search {
|
||||
width: 33.3%;
|
||||
float: right;
|
||||
}
|
||||
.search input {
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 3px;
|
||||
padding: 5px 3px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.search {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="search">
|
||||
<input
|
||||
type="search"
|
||||
title={labels.placeholder}
|
||||
placeholder={labels.placeholder}
|
||||
bind:value={text}
|
||||
on:keyup={onSearch} />
|
||||
</div>
|
67
app/src/routes/Sort.svelte
Normal file
67
app/src/routes/Sort.svelte
Normal file
|
@ -0,0 +1,67 @@
|
|||
<script context="module">
|
||||
let globalLabels;
|
||||
|
||||
export function setLabels(labels) {
|
||||
globalLabels = labels;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
const stateContext = getContext("state");
|
||||
|
||||
export let dir = "none";
|
||||
export let key;
|
||||
export let labels = {
|
||||
asc: { title: "Ascending", html: "↑" },
|
||||
desc: { title: "Desceding", html: "↓" },
|
||||
unsorted: { title: "Unsorted", html: "⇅" },
|
||||
...globalLabels
|
||||
};
|
||||
|
||||
function onClick(event) {
|
||||
const state = stateContext.getState();
|
||||
let rows;
|
||||
|
||||
const detail = {
|
||||
originalEvent: event,
|
||||
key,
|
||||
dir: dir !== "desc" ? "desc" : "asc",
|
||||
rows: state.filteredRows
|
||||
};
|
||||
|
||||
dispatch("sort", detail);
|
||||
|
||||
if (detail.preventDefault !== true) {
|
||||
dir = detail.dir;
|
||||
}
|
||||
stateContext.setRows(detail.rows);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sort {
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
padding: 0 0.25em;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<span class="sort" on:click={onClick}>
|
||||
{#if dir === 'asc'}
|
||||
<span title={labels.asc.title}>
|
||||
{@html labels.asc.html}
|
||||
</span>
|
||||
{:else if dir === 'desc'}
|
||||
<span title={labels.desc.title}>
|
||||
{@html labels.desc.html}
|
||||
</span>
|
||||
{:else}
|
||||
<span title={labels.unsorted.title}>
|
||||
{@html labels.unsorted.html}
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
200
app/src/routes/Table.svelte
Normal file
200
app/src/routes/Table.svelte
Normal file
|
@ -0,0 +1,200 @@
|
|||
<script context="module">
|
||||
import Pagination, {
|
||||
setLabels as _setPaginationLabels
|
||||
} from "./Pagination.svelte";
|
||||
import Row from "./Row.svelte";
|
||||
import Search, { setLabels as _setSearchLabels } from "./Search.svelte";
|
||||
import Sort, { setLabels as _setSortLabels } from "./Sort.svelte";
|
||||
export { Pagination, Row, Search, Sort };
|
||||
|
||||
let globalLabels;
|
||||
|
||||
export function setTableLabels(labels) {
|
||||
globalLabels = labels;
|
||||
}
|
||||
|
||||
export const setPaginationLabels = _setPaginationLabels;
|
||||
export const setSearchLabels = _setSearchLabels;
|
||||
export const setSortLabels = _setSortLabels;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher, setContext } from "svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let loading = false;
|
||||
export let page = 0;
|
||||
export let pageIndex = 0;
|
||||
export let pageSize = 10;
|
||||
export let responsive = true;
|
||||
export let rows;
|
||||
export let serverSide = false;
|
||||
export let labels = {
|
||||
empty: "No records available",
|
||||
loading: "Loading data",
|
||||
...globalLabels
|
||||
};
|
||||
|
||||
let buttons = [-2, -1, 0, 1, 2];
|
||||
let pageCount = 0;
|
||||
|
||||
$: filteredRows = rows;
|
||||
$: visibleRows = filteredRows.slice(pageIndex, pageIndex + pageSize);
|
||||
|
||||
setContext("state", {
|
||||
getState: () => ({
|
||||
page,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
rows,
|
||||
filteredRows
|
||||
}),
|
||||
setPage: (_page, _pageIndex) => {
|
||||
page = _page;
|
||||
pageIndex = _pageIndex;
|
||||
},
|
||||
setRows: _rows => (filteredRows = _rows)
|
||||
});
|
||||
|
||||
function onPageChange(event) {
|
||||
dispatch("pageChange", event.detail);
|
||||
}
|
||||
|
||||
function onSearch(event) {
|
||||
dispatch("search", event.detail);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table :global(td) {
|
||||
position: relative;
|
||||
}
|
||||
.table :global(th) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table :global(td) {
|
||||
padding: 0.3em 0.3em;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.center > span {
|
||||
padding: 10px 10px;
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slot-top,
|
||||
.slot-bottom {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
table.responsive {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
table.responsive :global(thead) {
|
||||
border: none;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
table.responsive :global(tr) {
|
||||
border-bottom: 2px solid #ddd;
|
||||
display: block;
|
||||
padding-bottom: 0.3em;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
table.responsive :global(td) {
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table.responsive :global(td::before) {
|
||||
/*
|
||||
* aria-label has no advantage, it won't be read inside a table content: attr(aria-label);
|
||||
*/
|
||||
content: attr(data-label);
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.responsive :global(td[data-label-normal]::before) {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
table.responsive :global(td[data-label-upper]::before) {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
table.responsive :global(td:last-child) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<slot name="top">
|
||||
<div class="slot-top">
|
||||
<svelte:component this={Search} on:search={onSearch} />
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<table class={'table ' + $$props.class} class:responsive>
|
||||
<slot name="head" />
|
||||
{#if loading}
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="center" colspan="100%">
|
||||
<span>
|
||||
{@html labels.loading}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{:else if visibleRows.length === 0}
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="center" colspan="100%">
|
||||
<span>
|
||||
{@html labels.empty}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{:else}
|
||||
<slot rows={visibleRows} />
|
||||
{/if}
|
||||
<slot name="foot" />
|
||||
</table>
|
||||
|
||||
<slot name="bottom">
|
||||
<div class="slot-bottom">
|
||||
<svelte:component
|
||||
this={Pagination}
|
||||
{page}
|
||||
{pageSize}
|
||||
{serverSide}
|
||||
count={filteredRows.length - 1}
|
||||
on:pageChange={onPageChange} />
|
||||
</div>
|
||||
</slot>
|
64
app/src/routes/server.js
Normal file
64
app/src/routes/server.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { sortNumber, sortString } from "./sorting.js";
|
||||
|
||||
function generateData() {
|
||||
const rand = Math.floor(Math.random() * 1000);
|
||||
return [
|
||||
{ name: "a-" + rand.toString(), lastName: "o", age: 12 },
|
||||
{ name: "b", lastName: "n", age: 1 },
|
||||
{ name: "c", lastName: "m", age: 13 },
|
||||
{ name: "d", lastName: "l", age: 21 },
|
||||
{ name: "e", lastName: "k", age: 2 },
|
||||
{ name: "f", lastName: "j", age: 4 }
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
export function getAll(text) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(function() {
|
||||
resolve(generateData());
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
export function getData(page, pageSize, text, sorting) {
|
||||
let originalData = generateData();
|
||||
|
||||
if (sorting) {
|
||||
if (sorting.key === "age") {
|
||||
originalData = sortNumber(originalData, sorting.dir, sorting.key);
|
||||
} else {
|
||||
originalData = sortString(originalData, sorting.dir, sorting.key);
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(function() {
|
||||
let rowsCount = originalData.length;
|
||||
const originalRows = originalData;
|
||||
let rows = [];
|
||||
|
||||
if (text && text.length > 0) {
|
||||
for (let i in originalRows) {
|
||||
for (let j in originalRows[i]) {
|
||||
if (
|
||||
originalRows[i][j]
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(text) > -1
|
||||
) {
|
||||
rows.push(originalRows[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rowsCount = rows.length;
|
||||
} else {
|
||||
rows = originalRows;
|
||||
}
|
||||
|
||||
resolve({ rows: rows.slice(0, pageSize), rowsCount: rowsCount - 1 });
|
||||
}, 500);
|
||||
});
|
||||
}
|
13
app/src/routes/sorting.js
Normal file
13
app/src/routes/sorting.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export function sortString(rows, dir, key) {
|
||||
return rows.sort((a, b) =>
|
||||
dir === "asc"
|
||||
? ("" + a[key]).localeCompare(b[key])
|
||||
: ("" + b[key]).localeCompare(a[key])
|
||||
);
|
||||
}
|
||||
|
||||
export function sortNumber(rows, dir, key) {
|
||||
return rows.sort((a, b) =>
|
||||
dir === "asc" ? a[key] - b[key] : b[key] - a[key]
|
||||
);
|
||||
}
|
0
app/static/favicon.ico
Normal file
0
app/static/favicon.ico
Normal file
15
app/svelte.config.js
Normal file
15
app/svelte.config.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import adapter from '@sveltejs/adapter-static';
|
||||
|
||||
export default {
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
// default options are shown. On some platforms
|
||||
// these options are set automatically — see below
|
||||
pages: '../src/main/resources/app',
|
||||
assets: '../src/main/resources/app',
|
||||
fallback: undefined,
|
||||
precompress: false,
|
||||
strict: true
|
||||
})
|
||||
}
|
||||
};
|
6
app/vite.config.js
Normal file
6
app/vite.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
|
@ -8,8 +8,12 @@ import io.ktor.server.engine.*
|
|||
import io.ktor.server.netty.*
|
||||
|
||||
fun main() {
|
||||
embeddedServer(Netty, port = 3002, host = "0.0.0.0", module = Application::module)
|
||||
.start(wait = true)
|
||||
embeddedServer(
|
||||
factory = Netty,
|
||||
port = 3002,
|
||||
host = "0.0.0.0",
|
||||
module = Application::module
|
||||
).start(wait = true)
|
||||
}
|
||||
|
||||
fun Application.module() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.github.wulkanowy.schools.integrity.*
|
|||
import io.github.wulkanowy.schools.model.LoginEvent
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.http.content.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
|
@ -36,8 +37,13 @@ fun Application.configureRouting() {
|
|||
}
|
||||
}
|
||||
}
|
||||
get("/") {
|
||||
get("/log/list") {
|
||||
call.respond(loginEventDao.allLoginEvents())
|
||||
}
|
||||
singlePageApplication {
|
||||
useResources = true
|
||||
filesPath = "app"
|
||||
defaultPage = "index.html"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue