Skip to content

Commit

Permalink
Merge branch 'refactor-export-wl-dialog' of github.com:candela97/Augm…
Browse files Browse the repository at this point in the history
…entedSteam into candela97-refactor-export-wl-dialog
  • Loading branch information
tfedor committed Aug 28, 2024
2 parents 10c076c + e9d19b0 commit e501178
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 92 deletions.
162 changes: 70 additions & 92 deletions src/js/Content/Features/Store/Wishlist/FExportWishlist.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Downloader from "@Core/Downloader";
import {L} from "@Core/Localization/Localization";
import {
__cancel,
__export_copyClipboard,
__export_download,
__export_format,
Expand All @@ -14,9 +15,9 @@ import HTML from "@Core/Html/Html";
import SteamFacade from "@Content/Modules/Facades/SteamFacade";
import UserNotes from "@Content/Features/Store/Common/UserNotes";
import Clipboard from "@Content/Modules/Clipboard";
import TimeUtils from "@Core/Utils/TimeUtils";
import DOMHelper from "@Content/Modules/DOMHelper";

type Wishlist = Array<[string, {
type WishlistData = Array<[string, {
name: string,
type: string,
release_string: string,
Expand All @@ -28,16 +29,22 @@ type Wishlist = Array<[string, {
}]>;

enum ExportMethod {
download,
copyToClipboard
download = "download",
copy = "copy"
}

type ExportForm = {
method: ExportMethod,
type: "text"|"json",
format: string
};

class WishlistExporter {

private readonly wishlist: Wishlist;
private readonly wishlist: WishlistData;
private readonly notes: Promise<Map<number, string|null>>;

constructor(wishlist: Wishlist) {
constructor(wishlist: WishlistData) {
this.wishlist = wishlist;

const userNotes = new UserNotes()
Expand Down Expand Up @@ -117,99 +124,70 @@ export default class FExportWishlist extends Feature<CWishlist> {
override apply(): void {
HTML.afterBegin("#cart_status_data", `<div class="es-wbtn" id="es_export_wishlist"><div>${L(__export_wishlist)}</div></div>`);

document.querySelector("#es_export_wishlist")!.addEventListener("click", async() => {
const appInfo = await SteamFacade.global("g_rgAppInfo");
const wl: Wishlist = (await SteamFacade.global<{rgVisibleApps: string[]}>("g_Wishlist")).rgVisibleApps.map(
appid => [appid, appInfo[appid]]
);
this._showDialog(wl);
document.querySelector("#es_export_wishlist")!.addEventListener("click", () => {
this.showDialog();
});

// @ts-expect-error
document.addEventListener("as_exportWishlist", (e: CustomEvent<ExportForm>) => {
this.exportWishlist(e.detail);
});
}

/*
* Using Valve's CModal API here is very hard, since, when trying to copy data to the clipboard, it has to originate from
* a short-lived event handler for a user action.
* Since we'd use our Messenger class to pass information in between these two contexts, we would "outrange" this specific event
* handler, resulting in a denial of access to the clipboard function.
* This could be circumvented by adding the appropriate permissions, but doing so would prompt users to explicitly accept the
* changed permissions on an update.
*
* If we don't use the Messenger, we'd have to move the whole handler part (including WishlistExporter) to
* the page context side.
*
* Final solution is to query the action buttons of the dialog and adding some extra click handlers on the content script side.
*/
async _showDialog(wl: Array<[string, any]>): Promise<void> {

async function exportWishlist(method: ExportMethod): Promise<void> {
const type = document.querySelector<HTMLInputElement>("input[name='es_wexport_type']:checked")!.value;
const format = document.querySelector<HTMLInputElement>("#es-wexport-format")!.value;

const wishlist = new WishlistExporter(wl);

let result = "";
let filename = "";
let filetype = "";
if (type === "json") {
result = await wishlist.toJson();
filename = "wishlist.json";
filetype = "application/json";
} else if (type === "text" && format) {
result = await wishlist.toText(format);
filename = "wishlist.txt";
filetype = "text/plain";
}
private showDialog(): void {

const template
= `<div class="es-wexport">
<h2>${L(__export_type)}</h2>
<div>
<label class="es-wexport__label"><input type="radio" name="es_wexport_type" value="text" checked> ${L(__export_text)}</label>
<label class="es-wexport__label"><input type="radio" name="es_wexport_type" value="json"> JSON</label>
</div>
</div>
<div class="es-wexport es-wexport__format">
<h2>${L(__export_format)}</h2>
<div>
<input type="text" id="es-wexport-format" class="es-wexport__input" value="%title%"><br>
<div class="es-wexport__symbols">%title%, %id%, %appid%, %url%, %release_date%, %price%, %discount%, %base_price%, %type%, %note%</div>
</div>
</div>`;

DOMHelper.insertScript("scriptlets/Store/Wishlist/exportWishlistModal.js", {
title: L(__export_wishlist),
template,
strSave: L(__export_download),
strCancel: L(__cancel),
strSaveSecondary: L(__export_copyClipboard),
ExportMethod
});
}

if (method === ExportMethod.copyToClipboard) {
Clipboard.set(result);
} else if (method === ExportMethod.download) {
Downloader.download(new Blob([result], {"type": `${filetype};charset=UTF-8`}), filename);
}
}
private async exportWishlist(options: ExportForm): Promise<void> {

SteamFacade.showConfirmDialog(
L(__export_wishlist),
`<div id="es_export_form">
<div class="es-wexport">
<h2>${L(__export_type)}</h2>
<div>
<label class="es-wexport__label"><input type="radio" name="es_wexport_type" value="text" checked> ${L(__export_text)}</label>
<label class="es-wexport__label"><input type="radio" name="es_wexport_type" value="json"> JSON</label>
</div>
</div>
<div class="es-wexport es-wexport__format">
<h2>${L(__export_format)}</h2>
<div>
<input type="text" id="es-wexport-format" class="es-wexport__input" value="%title%"><br>
<div class="es-wexport__symbols">%title%, %id%, %appid%, %url%, %release_date%, %price%, %discount%, %base_price%, %type%, %note%</div>
</div>
</div>
</div>`,
L(__export_download),
null, // use default "Cancel"
L(__export_copyClipboard)
const {method, type, format} = options;
const appInfo = await SteamFacade.global("g_rgAppInfo");
const wl: WishlistData = (await SteamFacade.global<{rgVisibleApps: string[]}>("g_Wishlist")).rgVisibleApps.map(
appid => [appid, appInfo[appid]]
);
const wishlist = new WishlistExporter(wl);

let data = "";
let filename = "";
let filetype = "";
if (type === "json") {
data = await wishlist.toJson();
filename = "wishlist.json";
filetype = "application/json";
} else if (type === "text" && format) {
data = await wishlist.toText(format);
filename = "wishlist.txt";
filetype = "text/plain";
}

for (let i=0; i<10; i++) {
const [dlBtn, copyBtn] = document.querySelectorAll(".newmodal_buttons > .btn_medium");

if (!dlBtn || !copyBtn) {
// wait for popup to show up to apply events
await TimeUtils.timer(10);
continue;
}

// Capture this s.t. the CModal doesn't get destroyed before we can grab this information
dlBtn!.addEventListener("click", () => exportWishlist(ExportMethod.download), true);
copyBtn!.addEventListener("click", () => exportWishlist(ExportMethod.copyToClipboard), true);

const format = document.querySelector<HTMLElement>(".es-wexport__format");
for (const el of document.getElementsByName("es_wexport_type")) {
el.addEventListener("click", e => {
const target = e.target as HTMLInputElement;
format!.classList.toggle("es-grayout", target.value === "json");
});
}
if (method === ExportMethod.copy) {
Clipboard.set(data);
} else if (method === ExportMethod.download) {
Downloader.download(new Blob([data], {"type": `${filetype};charset=UTF-8`}), filename);
}
}
}
52 changes: 52 additions & 0 deletions src/scriptlets/Store/Wishlist/exportWishlistModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
(function(){
const params = JSON.parse(document.currentScript.dataset.params);
const {title, template, strSave, strCancel, strSaveSecondary, ExportMethod} = params;

const deferred = new jQuery.Deferred();

function fnSelect(method) {
deferred.resolve({
method,
type: document.querySelector('input[name="es_wexport_type"]:checked').value,
format: document.querySelector("#es-wexport-format").value
});
}

function fnCancel() {
deferred.reject();
}

const buttons = [];

const okBtn = _BuildDialogButton(strSave, true);
okBtn.click(() => fnSelect(ExportMethod.download));
buttons.push(okBtn);

const secondaryBtn = _BuildDialogButton(strSaveSecondary, false, {strClassName: "btn_blue_steamui btn_medium"});
secondaryBtn.click(() => fnSelect(ExportMethod.copy));
buttons.push(secondaryBtn);

const cancelBtn = _BuildDialogButton(strCancel, false);
cancelBtn.click(fnCancel);
buttons.push(cancelBtn);

const modal = _BuildDialog(title, template, buttons, fnCancel);

deferred.always(() => modal.Dismiss());

// Gray-out formats when "JSON" is selected
const format = document.querySelector(".es-wexport__format");
for (const el of document.getElementsByName("es_wexport_type")) {
el.addEventListener("click", ({target}) => {
format.classList.toggle("es-grayout", target.value === "json");
});
}

deferred.promise(modal);

modal.done(value => {
document.dispatchEvent(new CustomEvent("as_exportWishlist", {detail: value}));
});

modal.Show();
})();

0 comments on commit e501178

Please sign in to comment.