[feat] experimental canvas screenshot
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"fflate": "^0.8.0",
|
"fflate": "^0.8.0",
|
||||||
"gpt-3-encoder": "^1.1.4",
|
"gpt-3-encoder": "^1.1.4",
|
||||||
"gpt3-tokenizer": "^1.1.5",
|
"gpt3-tokenizer": "^1.1.5",
|
||||||
|
"html-to-image": "^1.11.11",
|
||||||
"isomorphic-dompurify": "^1.2.0",
|
"isomorphic-dompurify": "^1.2.0",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@@ -53,6 +53,9 @@ dependencies:
|
|||||||
gpt3-tokenizer:
|
gpt3-tokenizer:
|
||||||
specifier: ^1.1.5
|
specifier: ^1.1.5
|
||||||
version: 1.1.5
|
version: 1.1.5
|
||||||
|
html-to-image:
|
||||||
|
specifier: ^1.11.11
|
||||||
|
version: 1.11.11
|
||||||
isomorphic-dompurify:
|
isomorphic-dompurify:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
@@ -1655,6 +1658,10 @@ packages:
|
|||||||
whatwg-encoding: 2.0.0
|
whatwg-encoding: 2.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/html-to-image@1.11.11:
|
||||||
|
resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/http-errors@2.0.0:
|
/http-errors@2.0.0:
|
||||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|||||||
@@ -346,5 +346,7 @@ export const languageEnglish = {
|
|||||||
createBotInternetAlert: "Please provide the character's name and the corresponding series/game.",
|
createBotInternetAlert: "Please provide the character's name and the corresponding series/game.",
|
||||||
able:"Able",
|
able:"Able",
|
||||||
assetWidth: "Asset Images Max Width",
|
assetWidth: "Asset Images Max Width",
|
||||||
animationSpeed: "Animation Speed"
|
animationSpeed: "Animation Speed",
|
||||||
|
screenshot: "Screenshot",
|
||||||
|
screenshotSaved: "Screenshot Saved",
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
$: displaya(message)
|
$: displaya(message)
|
||||||
</script>
|
</script>
|
||||||
<div class="flex max-w-full justify-center" class:bgc={isLastMemory}>
|
<div class="flex max-w-full justify-center risu-chat" class:bgc={isLastMemory}>
|
||||||
<div class="text-neutral-200 mt-1 ml-4 mr-4 mb-1 p-2 bg-transparent flex-grow border-t-gray-900 border-opacity-30 border-transparent flexium items-start max-w-full" >
|
<div class="text-neutral-200 mt-1 ml-4 mr-4 mb-1 p-2 bg-transparent flex-grow border-t-gray-900 border-opacity-30 border-transparent flexium items-start max-w-full" >
|
||||||
{#await img}
|
{#await img}
|
||||||
<div class="shadow-lg bg-gray-500 mt-2" style={`height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem;min-width:${$DataBase.iconsize * 3.5 / 100}rem`}
|
<div class="shadow-lg bg-gray-500 mt-2" style={`height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem;min-width:${$DataBase.iconsize * 3.5 / 100}rem`}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Suggestion from './Suggestion.svelte';
|
import Suggestion from './Suggestion.svelte';
|
||||||
import { DatabaseIcon, DicesIcon, LanguagesIcon, Laugh, MenuIcon, MicOffIcon, RefreshCcwIcon, ReplyIcon, Send } from "lucide-svelte";
|
import { CameraIcon, DatabaseIcon, DicesIcon, LanguagesIcon, Laugh, MenuIcon, MicOffIcon, RefreshCcwIcon, ReplyIcon, Send } from "lucide-svelte";
|
||||||
import { selectedCharID } from "../../ts/stores";
|
import { selectedCharID } from "../../ts/stores";
|
||||||
import Chat from "./Chat.svelte";
|
import Chat from "./Chat.svelte";
|
||||||
import { DataBase, type Message, type character, type groupChat } from "../../ts/storage/database";
|
import { DataBase, type Message, type character, type groupChat } from "../../ts/storage/database";
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
import { findCharacterbyId, messageForm, sleep } from "../../ts/util";
|
import { findCharacterbyId, messageForm, sleep } from "../../ts/util";
|
||||||
import { language } from "../../lang";
|
import { language } from "../../lang";
|
||||||
import { translate } from "../../ts/translator/translator";
|
import { translate } from "../../ts/translator/translator";
|
||||||
import { alertError } from "../../ts/alert";
|
import { alertError, alertNormal, alertWait } from "../../ts/alert";
|
||||||
import sendSound from '../../etc/send.mp3'
|
import sendSound from '../../etc/send.mp3'
|
||||||
import {cloneDeep} from 'lodash'
|
import {cloneDeep} from 'lodash'
|
||||||
import { processScript } from "src/ts/process/scripts";
|
import { processScript } from "src/ts/process/scripts";
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
import MainMenu from '../UI/MainMenu.svelte';
|
import MainMenu from '../UI/MainMenu.svelte';
|
||||||
import Help from '../Others/Help.svelte';
|
import Help from '../Others/Help.svelte';
|
||||||
import AssetInput from './AssetInput.svelte';
|
import AssetInput from './AssetInput.svelte';
|
||||||
|
import { downloadFile } from 'src/ts/storage/globalApi';
|
||||||
|
|
||||||
let messageInput:string = ''
|
let messageInput:string = ''
|
||||||
let messageInputTranslate:string = ''
|
let messageInputTranslate:string = ''
|
||||||
@@ -222,6 +223,59 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function screenShot(){
|
||||||
|
try {
|
||||||
|
loadPages = Infinity
|
||||||
|
const html2canvas = await import('html-to-image');
|
||||||
|
const chats = document.querySelectorAll('.default-chat-screen .risu-chat')
|
||||||
|
alertWait("Taking screenShot...")
|
||||||
|
let canvases:HTMLCanvasElement[] = []
|
||||||
|
|
||||||
|
for(const chat of chats){
|
||||||
|
const cnv = await html2canvas.toCanvas(chat as HTMLElement)
|
||||||
|
canvases.push(cnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
canvases.reverse()
|
||||||
|
|
||||||
|
let mergedCanvas = document.createElement('canvas');
|
||||||
|
mergedCanvas.width = 0;
|
||||||
|
mergedCanvas.height = 0;
|
||||||
|
let mergedCtx = mergedCanvas.getContext('2d');
|
||||||
|
|
||||||
|
let totalHeight = 0;
|
||||||
|
let maxWidth = 0;
|
||||||
|
for(let i = 0; i < canvases.length; i++) {
|
||||||
|
let canvas = canvases[i];
|
||||||
|
totalHeight += canvas.height;
|
||||||
|
maxWidth = Math.max(maxWidth, canvas.width);
|
||||||
|
|
||||||
|
mergedCanvas.width = maxWidth;
|
||||||
|
mergedCanvas.height = totalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedCtx.fillStyle = '#282a36'
|
||||||
|
mergedCtx.fillRect(0, 0, maxWidth, totalHeight);
|
||||||
|
let indh = 0
|
||||||
|
for(let i = 0; i < canvases.length; i++) {
|
||||||
|
let canvas = canvases[i];
|
||||||
|
indh += canvas.height
|
||||||
|
mergedCtx.drawImage(canvas, 0, indh - canvas.height);
|
||||||
|
canvases[i].remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mergedCanvas){
|
||||||
|
await downloadFile("chat.png", Buffer.from(mergedCanvas.toDataURL('png').split(',').at(-1), 'base64'))
|
||||||
|
mergedCanvas.remove();
|
||||||
|
}
|
||||||
|
alertNormal(language.screenshotSaved)
|
||||||
|
loadPages = 30
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
alertError("Error while taking screenshot")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
currentCharacter = $DataBase.characters[$selectedCharID]
|
currentCharacter = $DataBase.characters[$selectedCharID]
|
||||||
}
|
}
|
||||||
@@ -233,7 +287,7 @@
|
|||||||
{#if $selectedCharID < 0}
|
{#if $selectedCharID < 0}
|
||||||
<MainMenu />
|
<MainMenu />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="h-full w-full flex flex-col-reverse overflow-y-auto relative" on:scroll={(e) => {
|
<div class="h-full w-full flex flex-col-reverse overflow-y-auto relative default-chat-screen" on:scroll={(e) => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const scrolled = (e.target.scrollHeight - e.target.clientHeight + e.target.scrollTop)
|
const scrolled = (e.target.scrollHeight - e.target.clientHeight + e.target.scrollTop)
|
||||||
if(scrolled < 100 && $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].message.length > loadPages){
|
if(scrolled < 100 && $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].message.length > loadPages){
|
||||||
@@ -470,6 +524,16 @@
|
|||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if $DataBase.useExperimental}
|
||||||
|
<div class="flex items-center cursor-pointer hover:text-green-500 transition-colors" on:click={() => {
|
||||||
|
screenShot()
|
||||||
|
}}>
|
||||||
|
<CameraIcon />
|
||||||
|
<span class="ml-2">{language.screenshot} <Help key="experimental"/></span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
<div class={"flex items-center cursor-pointer "+ ($DataBase.useAutoSuggestions ? 'text-green-500':'lg:hover:text-green-500')} on:click={async () => {
|
<div class={"flex items-center cursor-pointer "+ ($DataBase.useAutoSuggestions ? 'text-green-500':'lg:hover:text-green-500')} on:click={async () => {
|
||||||
$DataBase.useAutoSuggestions = !$DataBase.useAutoSuggestions
|
$DataBase.useAutoSuggestions = !$DataBase.useAutoSuggestions
|
||||||
}}>
|
}}>
|
||||||
|
|||||||
Reference in New Issue
Block a user