backend: initial

This commit is contained in:
OleSTEEP 2025-09-28 15:59:46 +03:00
parent e5932844e2
commit b62f6d87c7
33 changed files with 59 additions and 15 deletions

13
frontend/src/app.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

11
frontend/src/app.html Normal file
View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 119 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,5 @@
<script lang="ts">
const { handler } = $props();
</script>
<button class="plus round" onclick={handler}>+</button>

View file

@ -0,0 +1,122 @@
<script lang="ts">
let current: Number = 9;
function set_day(day: Number) {
current = day;
return null;
}
</script>
<div class="calendar rounded">
<div class="month">
<ul>
<li class="prev">&#10094;</li>
<li class="next">&#10095;</li>
<li>Август 2025</li>
</ul>
</div>
<ul class="weekdays">
{#each ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"] as weekday}
<li>{weekday}</li>
{/each}
</ul>
<ul class="days">
{#each [...Array(31).keys()] as day}
<li>
{#if current == day+1}
<button class="active">{day+1}</button>
{:else}
<button class="nonactive" onclick={set_day(day+1)}>{day+1}</button>
{/if}
</li>
{/each}
</ul>
</div>
<style>
ul {list-style-type: none;}
.calendar * {
margin: 0;
}
.month {
width: 100%;
text-align: center;
align-content: center;
}
.month ul {
margin: 0;
padding: 0.5rem;
}
.month ul li {
display: inline;
text-transform: uppercase;
letter-spacing: 3px;
}
.month .prev {
float: inline-start;
cursor: pointer;
}
.month .next {
float: inline-end;
cursor: pointer;
}
.weekdays {
padding: 10px 0 0 0;
}
.weekdays li {
display: inline-block;
width: 13.6%;
text-align: center;
}
.days {
padding: 10px 0;
margin: 0;
}
.days li {
list-style-type: none;
display: inline-block;
width: 13.6%;
text-align: center;
margin-bottom: 5px;
font-size: smaller;
cursor: pointer;
}
.days li .active {
height: 2em;
padding: 5px;
border-radius: 30%;
aspect-ratio: 1/1;
}
.days li .nonactive {
height: 2em;
padding: 5px;
background: none;
aspect-ratio: 1/1;
}
@media screen and (max-width: 720px) {
.weekdays li, .days li {width: 13.1%;}
}
@media screen and (max-width: 420px) {
.weekdays li, .days li {width: 12.5%;}
.days li .active {padding: 2px;}
.days li .nonactive {padding: 2px;}
}
@media screen and (max-width: 290px) {
.weekdays li, .days li {width: 12.2%;}
}
</style>

View file

@ -0,0 +1,8 @@
<!-- Кнопка "Создать" -->
<script lang="ts">
let { onclick } = $props();
</script>
<div class="create">
<button class="rounded" id="create" {onclick}>Создать</button>
</div>

View file

@ -0,0 +1,24 @@
<script lang="ts">
import HorisontalItem from "./horisontal_item.svelte";
import AddButton from "./add_button.svelte";
import Image from "./image.svelte";
let { value = $bindable([]), label = "", type = "text" } = $props();
let items: string[] = $state([]);
</script>
<div class="horisontal-bar {type == "text" ? "input" : ""} rounded">
<p>{label}</p>
{#if type == "text"}
{#each items as item}
<HorisontalItem text={item}/>
{/each}
<AddButton handler={() => {
items.push("test");
value = items;
}} />
{:else if type == "image"}
<Image />
{/if}
</div>

View file

@ -0,0 +1,7 @@
<script>
let { text } = $props();
</script>
<div class="horisontal-item rounded">
{text}
</div>

View file

@ -0,0 +1,11 @@
<script>
import AddButton from "./add_button.svelte";
let { direction = "horisontal", input = true } = $props();
</script>
<div class="{direction} {input ? "input" : ""} image rounded">
<AddButton handler={() => {
// addImage(plus.id, this.id);
// document.getElementById("screenshots").appendChild(new HorisontalImage().html);
}}/>
</div>

View file

@ -0,0 +1,7 @@
<script>
let { style, image, name } = $props();
</script>
<div class="imgbutton {style}">
<img src={image} alt={name}>
</div>

View file

@ -0,0 +1,15 @@
<script>
import { makeid } from "$lib/functions.js"
import edit_btn from '$lib/assets/edit.svg';
import remove_btn from '$lib/assets/delete.svg';
import ImgButton from "./img_button.svelte";
let id = makeid(8);
let { date, name } = $props();
</script>
<div class="queue-item horisontal-bar rounded">
<p>{date}</p>
<p class="ellipsis">{name}</p>
<ImgButton style="edit" name="Изменить" image={edit_btn}/>
<ImgButton style="remove" name="Удалить" image={remove_btn}/>
</div>

View file

@ -0,0 +1,187 @@
@font-face {
font-family: 'Inter';
src: url('/font/Inter-Light.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
/* Шрифт для всей страницы */
* {
font-family: Inter;
font-size: large;
}
body {
margin: 0;
}
button {
border: 0;
}
/* Все элементы для ввода данных (input, textarea, собственные) */
.input {
border: 0;
resize: none;
padding: 0.5rem;
}
/* Круглый элемент */
.round {
border-radius: 100%;
overflow: hidden;
}
/* Закруглённый элемент */
.rounded {
border-radius: 0.75rem;
overflow: hidden;
}
/* Вертикальное изображение */
.vertical {
aspect-ratio: 11 / 16;
}
/* Горизонтальное изображение */
.horisontal {
aspect-ratio: 16 / 11;
}
.image {
margin-left: auto;
margin-right: auto;
align-content: center;
text-align: center;
width: 11rem;
}
/* Кнопка "плюс" */
.plus {
font-family: Arial, Helvetica, sans-serif;
aspect-ratio: 1 / 1;
height: 32px;
font-size: x-large;
cursor: pointer;
}
/* Кнопка "плюс" на изображении должна быть больше */
.image .plus {
height: 48px;
}
/* Поле с обложкой */
.cover {
padding: 2rem;
text-align: center;
align-content: center;
}
/* Поле с кнопкой "Создать" */
.create {
width: 100%;
position: fixed;
bottom: 0;
text-align: center;
display: flex;
flex-direction: column;
}
.create button {
margin: 0.5rem;
padding: 1rem;
cursor: pointer;
}
/* Поле с заполняемыми полями */
.fields {
width: 100%;
height: fit-content;
display: flex;
flex-direction: column;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
padding-bottom: 5rem;
}
/* Отступ между полями */
.fields * {
margin: 1rem;
margin-bottom: 0;
}
/* Вертикальный список */
.horisontal-bar {
display: flex;
overflow-x: scroll;
align-content: center;
padding: 0.5rem;
}
.horisontal-bar * {
margin: 0;
margin-right: 0.5rem;
align-content: center;
}
/* Элемент вертикального списка (тег, бадж, жанр) */
.horisontal-item {
padding: 0.25rem;
cursor: pointer;
}
/* Элемент в очереди */
.queue-item {
margin: 0;
padding: 0;
}
.queue-item + .queue-item {
margin-top: 0.5rem;
}
.queue-item p {
margin: 0.5rem;
}
/* Текст с троеточием в конце */
.ellipsis {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Кнопка с изображением */
.imgbutton {
height: 100%;
display: flex;
flex-direction: column;
margin: 0;
align-content: center;
text-align: center;
font-size: small;
padding: 0.5rem;
}
.imgbutton * {
margin: 0;
}
.imgbutton img {
margin-left: auto;
margin-right: auto;
width: 2rem;
}
/* Кнопка "Удалить" */
.remove {
cursor: pointer;
}
/* Кнопка "Изменить" */
.edit {
cursor: pointer;
margin-left: auto;
}

View file

@ -0,0 +1,233 @@
@media (prefers-color-scheme: light) {
body {
background-color: #EFEFF3;
}
p {
color: black;
}
.input {
background-color: #F4F4F4;
color: black;
}
.input::placeholder {
color: black;
}
.plus {
background-color: #5C8DC0;
color: white;
}
.plus:hover {
background-color: #567aa1;
}
.horisontal-item {
background-color: #5C8DC0;
color: white;
}
.horisontal-item:hover {
background-color: #567aa1;
}
.cover .image {
background-color: white;
}
.create {
background-color: #D9D9D9;
}
.create button {
background-color: #3CAA36;
color: white;
}
.create button:hover {
background-color: #3b8d37;
color: white;
}
.fields {
background-color: white;
}
.queue-item {
background-color: #5C8DC0;
color: white;
}
.queue-item p {
color: white;
}
.imgbutton {
color: white;
}
.month {
background: #5C8DC0;
}
.month ul li {
color: white;
}
.weekdays {
background-color: #F4F4F4;
}
.weekdays li {
color: black;
}
.days {
background: #F4F4F4;
}
.days li {
color: black;
}
.days li .active {
color: white !important;
background: #5C8DC0;
}
.remove {
background-color: #FC1F1C;
}
.remove:hover {
background-color: #d32522;
}
.edit {
background-color: #1BC304;
}
.edit:hover {
background-color: #1c9d0b;
}
}
@media (prefers-color-scheme: dark) {
body {
background-color: #0F1011;
}
p {
color: white;
}
.input {
background-color: #192431;
color: white;
}
.input::placeholder {
color: white;
}
.plus {
background-color: #2791FF;
color: white;
}
.plus:hover {
background-color: #2c7dd4;
}
.horisontal-item {
background-color: #2791FF;
color: white;
}
.horisontal-item:hover {
background-color: #2c7dd4;
}
.cover .image {
background-color: #131A22;
}
.create {
background-color: #192431;
}
.create button {
background-color: #3CAA36;
color: white;
}
.create button:hover {
background-color: #3b8d37;
color: white;
}
.fields {
background-color: #131A22;
}
.queue-item {
background-color: #2791FF;
color: white;
}
.queue-item p {
color: white;
}
.imgbutton {
color: white;
}
.month {
background: #2791FF;
}
.month ul li {
color: white;
}
.weekdays {
background-color: #192431;
}
.weekdays li {
color: white;
}
.days {
background: #192431;
}
.days li {
color: white;
}
.days li .active {
color: white !important;
background: #2791FF;
}
.remove {
background-color: #FC1F1C;
}
.remove:hover {
background-color: #d32522;
}
.edit {
background-color: #1BC304;
}
.edit:hover {
background-color: #1c9d0b;
}
}

View file

@ -0,0 +1,46 @@
export function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
// const imageTypes = {
// types: [
// {
// description: "Images",
// accept: {
// "image/*": [".png", ".gif", ".jpeg", ".jpg"],
// },
// },
// ],
// excludeAcceptAllOption: true,
// multiple: false,
// };
// let fileHandle;
// export async function loadImage() {
// [fileHandle] = await window.showOpenFilePicker(imageTypes);
// return fileHandle;
// }
// export function addImage(initiator, target) {
// loadImage().then(async (result) => {
// let img = document.createElement("img");
// img.src = URL.createObjectURL(await result.getFile());
// document.getElementById(target).appendChild(img);
// initiator = document.getElementById(initiator);
// if (initiator) initiator.remove();
// //document.getElementById(target).addEventListener("click", addImage("", target));
// });
// }
// export function json_filter(key,value)
// {
// if (key=="html") return undefined;
// //else if (key=="id") return undefined;
// else return value;
// }

View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -0,0 +1,87 @@
<script>
import favicon from '$lib/assets/favicon.svg';
import Image from '$lib/components/image.svelte';
import HorisontalBar from '$lib/components/horisontal_bar.svelte';
import CreateButton from '$lib/components/create_button.svelte';
import Calendar from '$lib/components/calendar.svelte';
import QueueItem from '$lib/components/queue_item.svelte';
let { children } = $props();
let page_data = $state({
title: "",
description: "",
hours_to_read: 0,
genres: [],
tags: [],
badges: []
})
function send_json() {
fetch('http://127.0.0.1:8000/new', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': "<origin>"
},
body: JSON.stringify(page_data)
});
}
</script>
<svelte:head>
<link rel="icon" href={favicon} />
</svelte:head>
<!-- Обложка -->
<div class="cover">
<Image direction="vertical"/>
<p>Обложка</p>
</div>
<div class="fields rounded" id="fields">
<!-- Название -->
<input type="text" class="input rounded" placeholder="Название" bind:value={page_data["title"]}>
<!-- Описание -->
<textarea class="input rounded" placeholder="Описание" rows="4" bind:value={page_data["description"]}></textarea>
<!-- Время на чтение -->
<div class="horisontal-bar">
<p>Время на чтение</p>
<select class="input rounded" id="hours-count" bind:value={page_data["hours_to_read"]}>
<option value="2">менее 2-х часов</option>
<option value="10">2-10 часов</option>
<option value="30">10-30 часов</option>
<option value="50">50+ часов</option>
</select>
</div>
<HorisontalBar label="Жанры" bind:value={page_data["genres"]}/>
<HorisontalBar label="Теги" bind:value={page_data["tags"]}/>
<HorisontalBar label="Баджи" bind:value={page_data["badges"]}/>
<p>Скриншоты</p>
<HorisontalBar type="image" />
<!-- Календарь с датой -->
<p>Дата публикации</p>
<Calendar />
<!-- Очередь публикации -->
<p>Очередь публикации</p>
<div class="input rounded" id="queue">
<QueueItem date="28.11.24" name="Fate/Stay Night (Судьба/Ночь схватки)"/>
<QueueItem date="30.11.24" name="Fate/Hollow Ataraxia (Судьба/Святая атараксия)"/>
</div>
</div>
<CreateButton onclick={send_json}/>
<style>
@import '$lib/css/global.css';
@import '$lib/css/themes.css';
</style>
{@render children?.()}

View file

@ -0,0 +1,2 @@
<!-- <h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p> -->