все кроме редактирования есть
This commit is contained in:
parent
a2fd7f7318
commit
5f94ff5e29
9 changed files with 810 additions and 356 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -11,6 +11,7 @@
|
||||||
"@sveltestrap/sveltestrap": "^6.2.4",
|
"@sveltestrap/sveltestrap": "^6.2.4",
|
||||||
"@themesberg/flowbite": "^1.3.0",
|
"@themesberg/flowbite": "^1.3.0",
|
||||||
"cropperjs": "^1.6.2",
|
"cropperjs": "^1.6.2",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
"sass": "^1.70.0",
|
"sass": "^1.70.0",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.5",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
|
@ -1626,6 +1627,15 @@
|
||||||
"type": "^1.0.1"
|
"type": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dayjs": {
|
"node_modules/dayjs": {
|
||||||
"version": "1.11.10",
|
"version": "1.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
"@sveltestrap/sveltestrap": "^6.2.4",
|
"@sveltestrap/sveltestrap": "^6.2.4",
|
||||||
"@themesberg/flowbite": "^1.3.0",
|
"@themesberg/flowbite": "^1.3.0",
|
||||||
"cropperjs": "^1.6.2",
|
"cropperjs": "^1.6.2",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
"sass": "^1.70.0",
|
"sass": "^1.70.0",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.5",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
|
|
19
src/app.html
19
src/app.html
|
@ -87,7 +87,15 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
height: 10%;
|
height: 10%;
|
||||||
|
transition: 0.15s ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button:disabled{
|
||||||
|
color: rgba(255, 255, 255, 0.329);
|
||||||
|
text-shadow: 0 0 20px rgba(0, 0, 0, 0.322);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
button,
|
button,
|
||||||
select,
|
select,
|
||||||
option {
|
option {
|
||||||
|
@ -169,5 +177,16 @@
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.backgroundBlur{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
export async function getLastMessages(chatId,msgLoaded){
|
// import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
||||||
|
// import ruLocale from 'date-fns/locale/ru';
|
||||||
|
|
||||||
|
export async function getLastMessages(chatId,msgLoaded){
|
||||||
|
|
||||||
let token = localStorage.getItem('BPChat')
|
let token = localStorage.getItem('BPChat')
|
||||||
const response = await fetch(`https://docs.black-phoenix.ru/api/chat/get_some_messages/${chatId}?messages_loaded=${msgLoaded}&messages_to_get=15`,
|
const response = await fetch(`https://docs.black-phoenix.ru/api/chat/get_some_messages/${chatId}?messages_loaded=${msgLoaded}&messages_to_get=15`,
|
||||||
|
@ -20,12 +23,16 @@
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
msgMassive.reverse();
|
msgMassive.reverse();
|
||||||
let utc = msgMassive[0].created_at
|
|
||||||
console.log(utc)
|
|
||||||
let newutc = new Date(utc)
|
|
||||||
console.log(utc)
|
|
||||||
console.log(newutc)
|
|
||||||
|
|
||||||
|
// for(let i = 0; i < msgMassive.length; i++){
|
||||||
|
// let utc = msgMassive[i].created_at
|
||||||
|
// let newutc = new Date(utc)
|
||||||
|
// //console.log(utc)
|
||||||
|
// const formattedDate = formatDistanceToNow(new Date(newutc), { addSuffix: true, locale: ruLocale });
|
||||||
|
// //console.log(newutc)
|
||||||
|
// console.log(formattedDate)
|
||||||
|
// msgMassive[i].created_at = formattedDate
|
||||||
|
// }
|
||||||
return msgMassive
|
return msgMassive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,3 +183,57 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAllUsers(nameToFind){
|
||||||
|
|
||||||
|
const response = await fetch(`https://docs.black-phoenix.ru/api/users?username=${nameToFind}`,{
|
||||||
|
method:"GET",
|
||||||
|
credentials:'include'
|
||||||
|
})
|
||||||
|
|
||||||
|
if(response.ok){
|
||||||
|
let data = await response.json()
|
||||||
|
let anotherData = data.users
|
||||||
|
|
||||||
|
if(anotherData == null){
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return data.users
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createNewChat(chatName, excludeUser, token){
|
||||||
|
console.log(token, "token")
|
||||||
|
const response = await fetch(`https://docs.black-phoenix.ru/api/chat/create_chat?user_to_exclude=${excludeUser}&chat_name=${chatName}`,{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {'Authorization': token },
|
||||||
|
})
|
||||||
|
|
||||||
|
if(response.ok){
|
||||||
|
let data = await response.json()
|
||||||
|
let id = data.chat_id
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
console.log(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteChat(chatId, token){
|
||||||
|
|
||||||
|
const response = await fetch(`https://docs.black-phoenix.ru/api/chat/delete_chat/${chatId}`,{
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {'Authorization': token },
|
||||||
|
})
|
||||||
|
|
||||||
|
if(response.ok){
|
||||||
|
return "Чат удален"
|
||||||
|
}
|
||||||
|
|
||||||
|
else{
|
||||||
|
console.log(response)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,89 +1,46 @@
|
||||||
// import io from 'socket.io-client';
|
export default function createWebSocket(url, token, onMessageCallback, maxRetries = 10) {
|
||||||
|
let socket;
|
||||||
|
let retries = 0;
|
||||||
|
let messageQueue = [];
|
||||||
|
token = token.split(" ")[1];
|
||||||
|
|
||||||
// export default function createWebSocket(url, token, onMessageCallback) {
|
function connect() {
|
||||||
// const socket = io(url, {
|
socket = new WebSocket(url, [token]);
|
||||||
// auth: {
|
|
||||||
// token: "123"
|
|
||||||
// },
|
|
||||||
// //withCredentials: true,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// socket.on('message', (message) => {
|
socket.addEventListener('message', (event) => {
|
||||||
// console.log('Получено сообщение:', message);
|
const jsonData = JSON.parse(event.data)
|
||||||
// });
|
onMessageCallback(jsonData)
|
||||||
|
});
|
||||||
|
|
||||||
// function sendMessage(message) {
|
socket.onopen = () => {
|
||||||
// socket.emit('message', message);
|
console.log('WebSocket is open now.')
|
||||||
// }
|
retries = 0 //сброс попыток
|
||||||
|
|
||||||
// return socket;
|
while (messageQueue.length > 0) {
|
||||||
// }
|
socket.send(messageQueue.shift());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// export default function createWebSocket(url, token , onMessageCallback) {
|
socket.onclose = (event) => {
|
||||||
|
console.log('WebSocket is closed now.', event)
|
||||||
|
if (retries < maxRetries) {
|
||||||
|
retries++
|
||||||
|
const timeout = Math.min(1000 * Math.pow(2, retries), 30000)
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(`Reconnecting... attempt ${retries}`)
|
||||||
|
connect()
|
||||||
|
}, timeout)
|
||||||
|
} else {
|
||||||
|
console.log('Max retries reached. Could not reconnect.')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// const socket = new WebSocket(url, {
|
socket.onerror = (error) => {
|
||||||
// headers:{ Authorization: token }
|
console.error('WebSocket error observed:', error)
|
||||||
// })
|
};
|
||||||
|
|
||||||
// socket.addEventListener('message', (event) => {
|
|
||||||
// const jsonData = JSON.parse(event.data);
|
|
||||||
// onMessageCallback(jsonData);
|
|
||||||
// //console.log(jsonData)
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return socket;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// import SockJS from 'sockjs-client';
|
|
||||||
|
|
||||||
// export default function createWebSocket(url,token , onMessageCallback) {
|
|
||||||
// const sock = new SockJS(url, null, {
|
|
||||||
// headers: {
|
|
||||||
// 'Authorization': token
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// //socket.onmessage = (event) => {
|
|
||||||
// // const jsonData = JSON.parse(event.data);
|
|
||||||
// // onMessageCallback(jsonData);
|
|
||||||
// // //console.log(jsonData)
|
|
||||||
// //};
|
|
||||||
|
|
||||||
// sock.onopen = function() {
|
|
||||||
// console.log('open');
|
|
||||||
// sock.send('test');
|
|
||||||
// };
|
|
||||||
|
|
||||||
// sock.onmessage = function(e) {
|
|
||||||
// console.log('message', e.data);
|
|
||||||
// sock.close();
|
|
||||||
// };
|
|
||||||
|
|
||||||
// sock.onclose = function() {
|
|
||||||
// console.log('close');
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return sock;
|
|
||||||
// }
|
|
||||||
|
|
||||||
export default function createWebSocket(url, token, onMessageCallback) {
|
|
||||||
token = token.split(" ")[1]
|
|
||||||
//console.log(token)
|
|
||||||
const socket = new WebSocket(url, [token])
|
|
||||||
|
|
||||||
socket.addEventListener('message', (event) => {
|
|
||||||
const jsonData = JSON.parse(event.data);
|
|
||||||
onMessageCallback(jsonData);
|
|
||||||
//console.log(jsonData)S
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.onopen = (event) => {
|
|
||||||
console.log('WebSocket is open now.');
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.addEventListener('close', (event) =>{
|
|
||||||
console.log(event)
|
|
||||||
})
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connect()
|
||||||
|
|
||||||
|
return socket
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import { fade, slide, fly, scale } from 'svelte/transition';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Select from 'svelte-select';
|
import Select from 'svelte-select';
|
||||||
|
|
||||||
|
@ -38,8 +40,8 @@
|
||||||
<div><img class="userAvatar" src={userImage} alt="аватарка"/></div>
|
<div><img class="userAvatar" src={userImage} alt="аватарка"/></div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen }
|
||||||
<div class="dropdown">
|
<div class="dropdown" transition:slide={{ duration: 300, delay: 150 }}>
|
||||||
<a href="/profile" class="listItem">Профиль</a>
|
<a href="/profile" class="listItem">Профиль</a>
|
||||||
<a href="/settings" class="listItem">Настройки</a>
|
<a href="/settings" class="listItem">Настройки</a>
|
||||||
<a on:click={handleLogout} class="listItem">Выйти</a>
|
<a on:click={handleLogout} class="listItem">Выйти</a>
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { fade, slide, fly, scale } from 'svelte/transition';
|
||||||
|
|
||||||
|
import { getAvatarHistory, changeUserData, getConfirmationCode } from '$lib/settings'
|
||||||
|
import { UserCheck } from '$lib/userFunction';
|
||||||
|
import { uploadImages } from '$lib/chat';
|
||||||
|
import Header from './../Header.svelte';
|
||||||
|
|
||||||
import Cropper from 'cropperjs';
|
import Cropper from 'cropperjs';
|
||||||
import 'cropperjs/dist/cropper.css';
|
import 'cropperjs/dist/cropper.css';
|
||||||
import { uploadImages } from '$lib/chat';
|
|
||||||
import { blur } from 'svelte/transition';
|
|
||||||
import Header from './../Header.svelte';
|
|
||||||
import { UserCheck } from '$lib/userFunction';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { getAvatarHistory, changeUserData, getConfirmationCode } from '$lib/settings'
|
|
||||||
|
|
||||||
|
let urlChecker = "https://"
|
||||||
let token
|
let token
|
||||||
let confCode
|
let confCode
|
||||||
let userData = []
|
let userData = []
|
||||||
|
@ -58,8 +62,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit(){
|
async function submit(){
|
||||||
newAvatar = await uploadImages(newAvatar)
|
if(pickedImg != ""){
|
||||||
console.log(newAvatar)
|
newAvatar = pickedImg
|
||||||
|
} else {
|
||||||
|
newAvatar = await uploadImages(newAvatar)
|
||||||
|
}
|
||||||
|
|
||||||
let changing = afterChanging = await changeUserData(token, newName, newEmail, newPassword, newAvatar, code)
|
let changing = afterChanging = await changeUserData(token, newName, newEmail, newPassword, newAvatar, code)
|
||||||
console.log(changing)
|
console.log(changing)
|
||||||
if (changing == true){
|
if (changing == true){
|
||||||
|
@ -129,22 +137,34 @@
|
||||||
showCroppingImgDiv = false
|
showCroppingImgDiv = false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function cancelCroppedImage(){
|
||||||
|
showCroppingImgDiv = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let pickedImg = ""
|
||||||
|
function PickOldAvatar(oldAvatarUrl){
|
||||||
|
pickedImg = oldAvatarUrl
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
{#if showCroppingImgDiv}
|
{#if showCroppingImgDiv}
|
||||||
<div class="backgroundBlur"></div>
|
<div class="backgroundBlur" transition:fade={{ duration: 500 }}></div>
|
||||||
|
|
||||||
<div class="croppingDiv">
|
<div class="croppingDiv" transition:fly={{y: -800, duration: 500 }}>
|
||||||
|
<!-- svelte-ignore a11y-img-redundant-alt -->
|
||||||
<img class="cropAvatar" bind:this={imageElement} alt="Image to crop" />
|
<img class="cropAvatar" bind:this={imageElement} alt="Image to crop" />
|
||||||
<button class="cropButton" on:click={getCroppedImage}>Подтвердить</button>
|
<div class="cropButtonsDiv">
|
||||||
|
<button class="cropButton" on:click={getCroppedImage}>Подтвердить</button>
|
||||||
|
<button class="cropButton" on:click={cancelCroppedImage}>Отменить</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showSubmitDiv}
|
{#if showSubmitDiv}
|
||||||
<div class="backgroundBlur"></div>
|
<div class="backgroundBlur" transition:fade={{ duration: 500 }}></div>
|
||||||
<div class="submitPassword">
|
<div class="submitPassword" transition:fly={{y: -800, duration: 500 }}>
|
||||||
<h2>Подтвердите кодом из почты</h2>
|
<h2>Подтвердите кодом из почты</h2>
|
||||||
<input bind:value={code} type="password" id="oldPassword" placeholder="Код">
|
<input bind:value={code} type="password" id="oldPassword" placeholder="Код">
|
||||||
<button on:click={submit}>Отправить</button>
|
<button on:click={submit}>Отправить</button>
|
||||||
|
@ -175,12 +195,17 @@
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="avatarsHistory">
|
<div class="avatarHistoryDiv">
|
||||||
{#each avatars as avatar}
|
<h3>старые фотки</h3>
|
||||||
<img class="avatarHistory" src="{avatar.avatar_url}" alt="ава">
|
<div class="avatarsHistory">
|
||||||
{/each}
|
{#each avatars as avatar}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
|
<img class="avatarHistory" class:avatarHistoryPicked={avatar.avatar_url == pickedImg} src="{avatar.avatar_url}" alt="ава"
|
||||||
|
on:click={PickOldAvatar(avatar.avatar_url)}>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="file" accept="image/*" id="fileInput"
|
<input type="file" accept="image/*" id="fileInput"
|
||||||
on:change={handleFileChange} style="display: none;">
|
on:change={handleFileChange} style="display: none;">
|
||||||
</div>
|
</div>
|
||||||
|
@ -214,11 +239,21 @@
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
||||||
.cropper-modal {
|
|
||||||
|
.cropper-modal {
|
||||||
background-color: #101010;
|
background-color: #101010;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cropButtonsDiv{
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.cropButton{
|
.cropButton{
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
}
|
}
|
||||||
|
@ -239,9 +274,7 @@
|
||||||
|
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%; top: 50%;
|
left: 33.33%; top: 10%;
|
||||||
transform: (-50%,-50%);
|
|
||||||
transform: translate(-50%,-50%);
|
|
||||||
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
max-height: 90%;
|
max-height: 90%;
|
||||||
|
@ -272,8 +305,33 @@
|
||||||
.avatarHistory{
|
.avatarHistory{
|
||||||
width: 75px;
|
width: 75px;
|
||||||
height: 75px;
|
height: 75px;
|
||||||
padding: 3px;
|
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
|
padding: 3px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarHistoryPicked{
|
||||||
|
width: 75px;
|
||||||
|
height: 75px;
|
||||||
|
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background:
|
||||||
|
linear-gradient(#101010, #101010) padding-box,
|
||||||
|
var(--gradient) border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarHistoryDiv{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.changeAvatar{
|
.changeAvatar{
|
||||||
|
@ -296,17 +354,6 @@ button{
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backgroundBlur{
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
z-index: 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
#oldPassword{
|
#oldPassword{
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -51,8 +51,8 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="crop-container">
|
<div class="crop-container">
|
||||||
<input type="file" accept="image/*" on:change={handleFileChange} />
|
<input type="file" accept="image/*" id="fileInput" on:change={handleFileChange} />
|
||||||
<img bind:this={imageElement} alt="Image to crop" />
|
<img class="CropAvatar" bind:this={imageElement} alt="Image to crop" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<button on:click={getCroppedImage}>Crop Image</button>
|
<button on:click={getCroppedImage}>Crop Image</button>
|
||||||
|
|
Loading…
Add table
Reference in a new issue