feat: start implement image upload and cropper
This commit is contained in:
@@ -8,6 +8,7 @@ node_modules
|
|||||||
/.svelte-kit
|
/.svelte-kit
|
||||||
/build
|
/build
|
||||||
/coverage
|
/coverage
|
||||||
|
/.VSCodeCounter
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -22,3 +23,4 @@ Thumbs.db
|
|||||||
# Vite
|
# Vite
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let active = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="crop-canvas-container">
|
||||||
|
<canvas class="crop-canvas"></canvas>
|
||||||
|
<div class="crop-box" class:active>
|
||||||
|
<div class="crop-handle nw"></div>
|
||||||
|
<div class="crop-handle ne"></div>
|
||||||
|
<div class="crop-handle sw"></div>
|
||||||
|
<div class="crop-handle se"></div>
|
||||||
|
<div class="crop-handle n"></div>
|
||||||
|
<div class="crop-handle s"></div>
|
||||||
|
<div class="crop-handle w"></div>
|
||||||
|
<div class="crop-handle e"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.crop-canvas-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 5;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-box {
|
||||||
|
position: absolute;
|
||||||
|
border: 2px solid var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
|
||||||
|
cursor: move;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-box.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: white;
|
||||||
|
border: 2px solid var(--accent-primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle.nw {
|
||||||
|
top: -5px;
|
||||||
|
left: -5px;
|
||||||
|
cursor: nw-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle.ne {
|
||||||
|
top: -5px;
|
||||||
|
right: -5px;
|
||||||
|
cursor: ne-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle.sw {
|
||||||
|
bottom: -5px;
|
||||||
|
left: -5px;
|
||||||
|
cursor: sw-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle.se {
|
||||||
|
bottom: -5px;
|
||||||
|
right: -5px;
|
||||||
|
cursor: se-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle.n {
|
||||||
|
top: -5px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle.s {
|
||||||
|
bottom: -5px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
cursor: s-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle.w {
|
||||||
|
left: -5px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
cursor: w-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-handle.e {
|
||||||
|
right: -5px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
cursor: e-resize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Card from "$components/layout/Card.svelte";
|
||||||
|
import SettingsGrid from "$components/layout/SettingsGrid.svelte";
|
||||||
|
import SettingsRow from "$components/layout/SettingsRow.svelte";
|
||||||
|
import Button from "$components/ui/Button.svelte";
|
||||||
|
import IconEdit from "$components/ui/Icons/IconEdit.svelte";
|
||||||
|
import IconReset from "$components/ui/Icons/IconReset.svelte";
|
||||||
|
import IconUpload from "$components/ui/Icons/IconUpload.svelte";
|
||||||
|
import RangeSlider from "$components/ui/RangeSlider.svelte";
|
||||||
|
import CropInline from "./CropInline.svelte";
|
||||||
|
|
||||||
|
let brightness = $state(100);
|
||||||
|
let contrast = $state(100);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card title="Фоновое изображение">
|
||||||
|
<div class="crop-editor">
|
||||||
|
<CropInline />
|
||||||
|
|
||||||
|
<div class="crop-controls">
|
||||||
|
<Button label="Загрузить" icon={IconUpload} type="secondary" />
|
||||||
|
<Button label="Редактировать" icon={IconEdit} type="outline" />
|
||||||
|
<Button label="Сбросить" icon={IconReset} type="outline" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SettingsGrid>
|
||||||
|
<SettingsRow label="Яркость">
|
||||||
|
<RangeSlider bind:value={brightness} />
|
||||||
|
</SettingsRow>
|
||||||
|
<SettingsRow label="Контраст">
|
||||||
|
<RangeSlider bind:value={contrast} min={50} max={150} />
|
||||||
|
</SettingsRow>
|
||||||
|
</SettingsGrid>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.crop-editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.crop-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,10 +3,6 @@
|
|||||||
import IconSun from "$components/ui/Icons/IconSun.svelte";
|
import IconSun from "$components/ui/Icons/IconSun.svelte";
|
||||||
import { themeState } from "$states/theme.svelte";
|
import { themeState } from "$states/theme.svelte";
|
||||||
|
|
||||||
interface Props {}
|
|
||||||
|
|
||||||
let {}: Props = $props();
|
|
||||||
|
|
||||||
function toggleTheme() {
|
function toggleTheme() {
|
||||||
themeState.toggle();
|
themeState.toggle();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Badge from "$components/ui/Badge.svelte";
|
import ImageManager from "$components/image/ImageManager.svelte";
|
||||||
import Button from "$components/ui/Button.svelte";
|
import PanelPreview from "$components/panel/PanelPreview.svelte";
|
||||||
import IconArrowLeft from "$components/ui/Icons/IconArrowLeft.svelte";
|
|
||||||
import IconArrowRight from "$components/ui/Icons/IconArrowRight.svelte";
|
|
||||||
import IconUpload from "$components/ui/Icons/IconUpload.svelte";
|
|
||||||
import Card from "./Card.svelte";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="panel-bar">
|
<div class="panel-bar">
|
||||||
<Card title="Фоновое изображение">
|
<ImageManager />
|
||||||
<Button label="Загрузить изображение" icon={IconUpload} onclick={() => {}} type="secondary" />
|
<PanelPreview />
|
||||||
</Card>
|
|
||||||
|
|
||||||
{#snippet panelTitle()}
|
|
||||||
Панели <Badge text="0" />
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
{#snippet panelControls()}
|
|
||||||
<button id="prevPanel" class="nav-btn" disabled>
|
|
||||||
<IconArrowLeft />
|
|
||||||
</button>
|
|
||||||
<span id="panelIndicator" class="panel-indicator">0 / 0</span>
|
|
||||||
<button id="nextPanel" class="nav-btn" disabled>
|
|
||||||
<IconArrowRight />
|
|
||||||
</button>
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
<Card title={panelTitle} titleSnippet={panelControls}>Панели</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Card from "$components/layout/Card.svelte";
|
||||||
|
import Badge from "$components/ui/Badge.svelte";
|
||||||
|
import IconArrowLeft from "$components/ui/Icons/IconArrowLeft.svelte";
|
||||||
|
import IconArrowRight from "$components/ui/Icons/IconArrowRight.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#snippet panelTitle()}
|
||||||
|
Панели <Badge text="0" />
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet panelControls()}
|
||||||
|
<button id="prevPanel" class="nav-btn" disabled>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</button>
|
||||||
|
<span id="panelIndicator" class="panel-indicator">0 / 0</span>
|
||||||
|
<button id="nextPanel" class="nav-btn" disabled>
|
||||||
|
<IconArrowRight />
|
||||||
|
</button>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<Card title={panelTitle} titleSnippet={panelControls}>Панели</Card>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
icon: Component;
|
icon: Component;
|
||||||
onclick: MouseEventHandler<HTMLButtonElement>;
|
onclick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
type?: "primary" | "secondary" | "danger" | "outline";
|
type?: "primary" | "secondary" | "danger" | "outline";
|
||||||
}
|
}
|
||||||
|
|
||||||
let { icon: Icon, onclick, disabled = false, label = "", type = "primary" }: Props = $props();
|
let { icon: Icon, onclick = () => {}, disabled = false, label = "", type = "primary" }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M21.2799 6.40005L11.7399 15.94C10.7899 16.89 7.96987 17.33 7.33987 16.7C6.70987 16.07 7.13987 13.25 8.08987 12.3L17.6399 2.75002C17.8754 2.49308 18.1605 2.28654 18.4781 2.14284C18.7956 1.99914 19.139 1.92124 19.4875 1.9139C19.8359 1.90657 20.1823 1.96991 20.5056 2.10012C20.8289 2.23033 21.1225 2.42473 21.3686 2.67153C21.6147 2.91833 21.8083 3.21243 21.9376 3.53609C22.0669 3.85976 22.1294 4.20626 22.1211 4.55471C22.1128 4.90316 22.0339 5.24635 21.8894 5.5635C21.7448 5.88065 21.5375 6.16524 21.2799 6.40005V6.40005Z"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M11 4H6C4.93913 4 3.92178 4.42142 3.17163 5.17157C2.42149 5.92172 2 6.93913 2 8V18C2 19.0609 2.42149 20.0783 3.17163 20.8284C3.92178 21.5786 4.93913 22 6 22H17C19.21 22 20 20.2 20 18V13"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1001 B |
Reference in New Issue
Block a user