Image download handler

This commit is contained in:
Natsumi
2022-12-13 21:48:34 +13:00
parent 2fd992edf7
commit c630c97c06
6 changed files with 126 additions and 34 deletions

View File

@@ -0,0 +1,36 @@
// Copyright(c) 2019-2022 pypy, Natsumi and individual contributors.
// All rights reserved.
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.
using CefSharp;
namespace VRCX
{
public class CustomDownloadHandler : IDownloadHandler
{
public bool CanDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, string url, string requestMethod)
{
return true;
}
public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
{
if (callback.IsDisposed)
return;
using (callback)
{
callback.Continue(
downloadItem.SuggestedFileName,
showDialog: true
);
}
}
public void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
{
}
}
}

View File

@@ -45,6 +45,7 @@ namespace VRCX
{
DragHandler = new NoopDragHandler(),
MenuHandler = new CustomMenuHandler(),
DownloadHandler = new CustomDownloadHandler(),
BrowserSettings =
{
DefaultEncoding = "UTF-8",

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -84,6 +84,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AssetBundleCacher.cs" />
<Compile Include="CefCustomDownloadHandler.cs" />
<Compile Include="CefCustomMenuHandler.cs" />
<Compile Include="ImageCache.cs" />
<Compile Include="IPCClient.cs" />

View File

@@ -124,6 +124,7 @@ namespace VRCX
}
#pragma warning disable CS4014
public async void Execute(IDictionary<string, object> options, IJavascriptCallback callback)
{
try
@@ -180,7 +181,7 @@ namespace VRCX
request.ContentLength = sentData.Length;
using (System.IO.Stream sendStream = request.GetRequestStream())
{
sendStream.Write(sentData, 0, sentData.Length);
await sendStream.WriteAsync(sentData, 0, sentData.Length);
sendStream.Close();
}
}
@@ -200,7 +201,7 @@ namespace VRCX
{
string item = String.Format(FormDataTemplate, boundary, key, postData[key]);
byte[] itemBytes = System.Text.Encoding.UTF8.GetBytes(item);
requestStream.Write(itemBytes, 0, itemBytes.Length);
await requestStream.WriteAsync(itemBytes, 0, itemBytes.Length);
}
}
var imageData = options["imageData"] as string;
@@ -211,21 +212,21 @@ namespace VRCX
string HeaderTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n";
string header = String.Format(HeaderTemplate, boundary, fileFormKey, fileName, fileMimeType);
byte[] headerbytes = Encoding.UTF8.GetBytes(header);
requestStream.Write(headerbytes, 0, headerbytes.Length);
await requestStream.WriteAsync(headerbytes, 0, headerbytes.Length);
using (MemoryStream fileStream = new MemoryStream(fileToUpload))
{
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
requestStream.Write(buffer, 0, bytesRead);
await requestStream.WriteAsync(buffer, 0, bytesRead);
}
fileStream.Close();
}
byte[] newlineBytes = Encoding.UTF8.GetBytes("\r\n");
requestStream.Write(newlineBytes, 0, newlineBytes.Length);
await requestStream.WriteAsync(newlineBytes, 0, newlineBytes.Length);
byte[] endBytes = System.Text.Encoding.UTF8.GetBytes("--" + boundary + "--");
requestStream.Write(endBytes, 0, endBytes.Length);
await requestStream.WriteAsync(endBytes, 0, endBytes.Length);
requestStream.Close();
}
@@ -242,11 +243,27 @@ namespace VRCX
{
if (callback.CanExecute == true)
{
callback.ExecuteAsync(null, new
if (response.ContentType.Contains("image/") || response.ContentType.Contains("application/octet-stream"))
{
data = await streamReader.ReadToEndAsync(),
status = response.StatusCode
});
// base64 response data for image
using (var memoryStream = new MemoryStream())
{
await stream.CopyToAsync(memoryStream);
callback.ExecuteAsync(null, new
{
data = $"data:image/png;base64,{Convert.ToBase64String(memoryStream.ToArray())}",
status = response.StatusCode
});
}
}
else
{
callback.ExecuteAsync(null, new
{
data = await streamReader.ReadToEndAsync(),
status = response.StatusCode
});
}
}
}
}
@@ -289,6 +306,7 @@ namespace VRCX
callback.Dispose();
}
#pragma warning restore CS4014
}
}
}

View File

@@ -23807,6 +23807,42 @@ speechSynthesis.getVoices();
);
};
$app.methods.downloadAndSaveImage = async function (url) {
if (!url) {
return;
}
try {
var response = await webApiService.execute({
url,
method: 'GET'
});
if (
response.status !== 200 ||
!response.data.startsWith('data:image/png')
) {
throw new Error(`Error: ${response.data}`);
}
var link = document.createElement('a');
link.href = response.data;
var fileName = `${extractFileId(url)}.png`;
if (!fileName) {
fileName = `${url.split('/').pop()}.png`;
}
if (!fileName) {
fileName = 'image.png';
}
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch {
new Noty({
type: 'error',
text: escapeTag(`Failed to download image. ${url}`)
}).show();
}
};
$app = new Vue($app);
window.$app = $app;
})();

View File

@@ -82,7 +82,7 @@ html
div(v-if="currentInstanceWorld.ref.id" style="display:flex")
el-popover(placement="right" width="500px" trigger="click" style="height:120px")
img.x-link(slot="reference" v-lazy="currentInstanceWorld.ref.thumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
img.x-link(v-lazy="currentInstanceWorld.ref.imageUrl" style="width:500px;height:375px" @click="openExternalLink(currentInstanceWorld.ref.imageUrl)")
img.x-link(v-lazy="currentInstanceWorld.ref.imageUrl" style="width:500px;height:375px" @click="downloadAndSaveImage(currentInstanceWorld.ref.imageUrl)")
div(style="margin-left:10px;display:flex;flex-direction:column;min-width:320px;width:100%")
div
span.x-link(@click="showWorldDialog(currentInstanceWorld.ref.id)" style="font-weight:bold;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1")
@@ -247,7 +247,7 @@ html
template(v-if="userImage(scope.row.ref)")
el-popover(placement="right" height="500px" trigger="hover")
img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row.ref)")
img.friends-list-avatar(v-lazy="userImageFull(scope.row.ref)" style="height:500px;cursor:pointer" @click="openExternalLink(userImageFull(scope.row.ref))")
img.friends-list-avatar(v-lazy="userImageFull(scope.row.ref)" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(userImageFull(scope.row.ref))")
el-table-column(label="Timer" width="90" prop="timer" sortable)
template(v-once #default="scope")
timer(:epoch="scope.row.timer")
@@ -778,11 +778,11 @@ html
template(v-if="scope.row.details && scope.row.details.imageUrl")
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="scope.row.details.imageUrl" style="flex:none;width:90px;border-radius:4px")
img.x-link(v-lazy="scope.row.details.imageUrl" style="width:500px" @click="openExternalLink(scope.row.details.imageUrl)")
img.x-link(v-lazy="scope.row.details.imageUrl" style="width:500px" @click="downloadAndSaveImage(scope.row.details.imageUrl)")
template(v-else-if="scope.row.imageUrl")
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="scope.row.imageUrl" style="flex:none;width:90px;border-radius:4px")
img.x-link(v-lazy="scope.row.imageUrl" style="width:500px" @click="openExternalLink(scope.row.imageUrl)")
img.x-link(v-lazy="scope.row.imageUrl" style="width:500px" @click="downloadAndSaveImage(scope.row.imageUrl)")
el-table-column(label="Message" prop="message")
template(v-once #default="scope")
span(v-if="scope.row.message" v-text="scope.row.message")
@@ -1015,7 +1015,7 @@ html
template(v-once #default="scope")
el-popover(placement="right" height="500px" trigger="hover")
img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row)")
img.friends-list-avatar(v-lazy="userImageFull(scope.row)" style="height:500px;cursor:pointer" @click="openExternalLink(userImageFull(scope.row))")
img.friends-list-avatar(v-lazy="userImageFull(scope.row)" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(userImageFull(scope.row))")
el-table-column(label="Display Name" min-width="140" prop="displayName" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'displayName')")
template(v-once #default="scope")
span.name(v-if="randomUserColours" v-text="scope.row.displayName" :style="{'color':scope.row.$userColour}")
@@ -1582,10 +1582,10 @@ html
div(style="display:flex")
el-popover(v-if="userDialog.ref.profilePicOverride" placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="userDialog.ref.profilePicOverride" style="flex:none;height:120px;width:213.33px;border-radius:4px;object-fit:cover")
img.x-link(v-lazy="userDialog.ref.profilePicOverride" style="height:400px" @click="openExternalLink(userDialog.ref.profilePicOverride)")
img.x-link(v-lazy="userDialog.ref.profilePicOverride" style="height:400px" @click="downloadAndSaveImage(userDialog.ref.profilePicOverride)")
el-popover(v-else placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="userDialog.ref.currentAvatarThumbnailImageUrl" style="flex:none;height:120px;width:160px;border-radius:4px;object-fit:cover")
img.x-link(v-lazy="userDialog.ref.currentAvatarImageUrl" style="height:500px" @click="openExternalLink(userDialog.ref.currentAvatarImageUrl)")
img.x-link(v-lazy="userDialog.ref.currentAvatarImageUrl" style="height:500px" @click="downloadAndSaveImage(userDialog.ref.currentAvatarImageUrl)")
div(style="flex:1;display:flex;align-items:center;margin-left:15px")
div(style="flex:1")
div
@@ -1628,7 +1628,7 @@ html
div(v-if="userDialog.ref.userIcon" style="flex:none;margin-right:10px")
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="userDialog.ref.userIcon" style="flex:none;width:120px;height:120px;border-radius:4px;object-fit:cover")
img.x-link(v-lazy="userDialog.ref.userIcon" style="height:500px" @click="openExternalLink(userDialog.ref.userIcon)")
img.x-link(v-lazy="userDialog.ref.userIcon" style="height:500px" @click="downloadAndSaveImage(userDialog.ref.userIcon)")
div(style="flex:none")
template(v-if="API.currentUser.id !== userDialog.ref.id")
el-tooltip(v-if="userDialog.isFavorite" placement="top" content="Remove from favorites" :disabled="hideTooltips")
@@ -1734,7 +1734,7 @@ html
div(style="display:inline-block;flex:none;margin-right:5px")
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="userDialog.representedGroup.iconUrl" style="flex:none;width:60px;height:60px;border-radius:4px;object-fit:cover")
img.x-link(v-lazy="userDialog.representedGroup.iconUrl" style="height:500px" @click="openExternalLink(userDialog.representedGroup.iconUrl)")
img.x-link(v-lazy="userDialog.representedGroup.iconUrl" style="height:500px" @click="downloadAndSaveImage(userDialog.representedGroup.iconUrl)")
span(style="vertical-align:top;cursor:pointer" @click="showGroupDialog(userDialog.representedGroup.groupId)")
span(v-if="userDialog.representedGroup.ownerId === userDialog.id" style="margin-right:5px") 👑
span(v-text="userDialog.representedGroup.name" style="margin-right:5px")
@@ -1919,7 +1919,7 @@ html
div(style="display:flex")
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="worldDialog.ref.thumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
img.x-link(v-lazy="worldDialog.ref.imageUrl" style="width:500px;height:375px" @click="openExternalLink(worldDialog.ref.imageUrl)")
img.x-link(v-lazy="worldDialog.ref.imageUrl" style="width:500px;height:375px" @click="downloadAndSaveImage(worldDialog.ref.imageUrl)")
div(style="flex:1;display:flex;align-items:center;margin-left:15px")
div(style="flex:1")
div
@@ -2091,7 +2091,7 @@ html
div(style="display:flex")
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="avatarDialog.ref.thumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
img.x-link(v-lazy="avatarDialog.ref.imageUrl" style="width:500px;height:375px" @click="openExternalLink(avatarDialog.ref.imageUrl)")
img.x-link(v-lazy="avatarDialog.ref.imageUrl" style="width:500px;height:375px" @click="downloadAndSaveImage(avatarDialog.ref.imageUrl)")
div(style="flex:1;display:flex;align-items:center;margin-left:15px")
div(style="flex:1")
div
@@ -2176,12 +2176,12 @@ html
.group-banner-image
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="groupDialog.ref.bannerUrl" style="flex:none;width:100%;aspect-ratio:6/1;object-fit:cover;border-radius:4px")
img.x-link(v-lazy="groupDialog.ref.bannerUrl" style="width:854px;height:480px" @click="openExternalLink(groupDialog.ref.bannerUrl)")
img.x-link(v-lazy="groupDialog.ref.bannerUrl" style="width:854px;height:480px" @click="downloadAndSaveImage(groupDialog.ref.bannerUrl)")
.group-body(v-loading="groupDialog.loading")
div(style="display:flex")
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="groupDialog.ref.iconUrl" style="flex:none;width:120px;height:120px;border-radius:4px")
img.x-link(v-lazy="groupDialog.ref.iconUrl" style="width:500px;height:500px" @click="openExternalLink(groupDialog.ref.iconUrl)")
img.x-link(v-lazy="groupDialog.ref.iconUrl" style="width:500px;height:500px" @click="downloadAndSaveImage(groupDialog.ref.iconUrl)")
div(style="flex:1;display:flex;align-items:center;margin-left:15px")
.group-header(style="flex:1")
span(v-if="groupDialog.ref.ownerId === API.currentUser.id" style="margin-right:5px") 👑
@@ -2257,7 +2257,7 @@ html
.group-banner-image-info
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="groupDialog.ref.bannerUrl" style="flex:none;width:100%;aspect-ratio:6/1;object-fit:cover;border-radius:4px")
img.x-link(v-lazy="groupDialog.ref.bannerUrl" style="width:854px;height:480px" @click="openExternalLink(groupDialog.ref.bannerUrl)")
img.x-link(v-lazy="groupDialog.ref.bannerUrl" style="width:854px;height:480px" @click="downloadAndSaveImage(groupDialog.ref.bannerUrl)")
.x-friend-list(style="max-height:none")
.x-friend-item(v-if="groupDialog.ref.membershipStatus === 'member'" style="width:100%;cursor:default")
.detail
@@ -2266,7 +2266,7 @@ html
div(v-if="groupDialog.announcement.imageUrl" style="display:inline-block;margin-right:5px")
el-popover(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="groupDialog.announcement.imageUrl" style="flex:none;width:60px;height:60px;border-radius:4px;object-fit:cover")
img.x-link(v-lazy="groupDialog.announcement.imageUrl" style="height:500px" @click="openExternalLink(groupDialog.announcement.imageUrl)")
img.x-link(v-lazy="groupDialog.announcement.imageUrl" style="height:500px" @click="downloadAndSaveImage(groupDialog.announcement.imageUrl)")
pre.extra(style="display:inline-block;vertical-align:top;font-family:inherit;font-size:12px;white-space:pre-wrap;margin:0") {{ groupDialog.announcement.text || '-' }}
br
.extra(v-if="groupDialog.announcement.id" style="float:right;margin-left:5px")
@@ -3292,7 +3292,7 @@ html
div(style="display:inline-block" v-for="image in previousImagesTable" :key="image.version" v-if="image.file")
el-popover.x-change-image-item(placement="right" width="500px" trigger="click")
img.x-link(slot="reference" v-lazy="image.file.url")
img.x-link(v-lazy="image.file.url" style="width:500px;height:375px" @click="openExternalLink(image.file.url)")
img.x-link(v-lazy="image.file.url" style="width:500px;height:375px" @click="downloadAndSaveImage(image.file.url)")
//- dialog: Gallery/VRCPlusIcons
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="galleryDialog" :visible.sync="galleryDialogVisible" title="Gallery and Icons" width="100%")
@@ -3311,7 +3311,7 @@ html
.vrcplus-icon(v-if="image.versions[image.versions.length - 1].file.url" @click="setProfilePicOverride(image.id)" :class="{ 'current-vrcplus-icon': compareCurrentProfilePic(image.id) }")
img.avatar(v-lazy="image.versions[image.versions.length - 1].file.url")
div(style="float:right;margin-top:5px")
el-button(type="default" @click="openExternalLink(image.versions[image.versions.length - 1].file.url)" size="mini" icon="el-icon-paperclip" circle)
el-button(type="default" @click="downloadAndSaveImage(image.versions[image.versions.length - 1].file.url)" size="mini" icon="el-icon-paperclip" circle)
el-button(type="default" @click="deleteGalleryImage(image.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
el-tab-pane(v-if="galleryDialogVisible" v-loading="galleryDialogIconsLoading")
span(slot="label") Icons
@@ -3326,7 +3326,7 @@ html
.vrcplus-icon(v-if="image.versions[image.versions.length - 1].file.url" @click="setVRCPlusIcon(image.id)" :class="{ 'current-vrcplus-icon': compareCurrentVRCPlusIcon(image.id) }")
img.avatar(v-lazy="image.versions[image.versions.length - 1].file.url")
div(style="float:right;margin-top:5px")
el-button(type="default" @click="openExternalLink(image.versions[image.versions.length - 1].file.url)" size="mini" icon="el-icon-paperclip" circle)
el-button(type="default" @click="downloadAndSaveImage(image.versions[image.versions.length - 1].file.url)" size="mini" icon="el-icon-paperclip" circle)
el-button(type="default" @click="deleteVRCPlusIcon(image.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
//- dialog Table: Previous Instances User
@@ -3459,7 +3459,7 @@ html
template(v-once #default="scope")
el-popover(placement="right" height="500px" trigger="hover")
img.friends-list-avatar(slot="reference" v-lazy="scope.row.thumbnailImageUrl")
img.friends-list-avatar(v-lazy="scope.row.imageUrl" style="height:500px;cursor:pointer" @click="openExternalLink(scope.row.imageUrl)")
img.friends-list-avatar(v-lazy="scope.row.imageUrl" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(scope.row.imageUrl)")
el-table-column(label="Name" prop="name")
template(v-once #default="scope")
span.x-link(v-text="scope.row.name" @click="showWorldDialog(scope.row.id)")
@@ -3518,7 +3518,7 @@ html
template(v-once #default="scope")
el-popover(placement="right" height="500px" trigger="hover")
img.friends-list-avatar(slot="reference" v-lazy="scope.row.thumbnailImageUrl")
img.friends-list-avatar(v-lazy="scope.row.imageUrl" style="height:500px;cursor:pointer" @click="openExternalLink(scope.row.imageUrl)")
img.friends-list-avatar(v-lazy="scope.row.imageUrl" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(scope.row.imageUrl)")
el-table-column(label="Name" prop="name")
template(v-once #default="scope")
span.x-link(v-text="scope.row.name" @click="showAvatarDialog(scope.row.id)")
@@ -3577,7 +3577,7 @@ html
template(v-once #default="scope")
el-popover(placement="right" height="500px" trigger="hover")
img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row)")
img.friends-list-avatar(v-lazy="userImageFull(scope.row)" style="height:500px;cursor:pointer" @click="openExternalLink(userImageFull(scope.row))")
img.friends-list-avatar(v-lazy="userImageFull(scope.row)" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(userImageFull(scope.row))")
el-table-column(label="Name" prop="displayName")
template(v-once #default="scope")
span.x-link(v-text="scope.row.displayName" @click="showUserDialog(scope.row.id)")
@@ -3609,7 +3609,7 @@ html
template(v-once #default="scope")
el-popover(placement="right" height="500px" trigger="hover")
img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row.ref)")
img.friends-list-avatar(v-lazy="userImageFull(scope.row.ref)" style="height:500px;cursor:pointer" @click="openExternalLink(userImageFull(scope.row.ref))")
img.friends-list-avatar(v-lazy="userImageFull(scope.row.ref)" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(userImageFull(scope.row.ref))")
el-table-column(label="Name" width="170" prop="name")
template(v-once #default="scope")
span.x-link(v-text="scope.row.name" @click="showUserDialog(scope.row.id)")