Compare commits

...

22 Commits

Author SHA1 Message Date
5b74cfe4b1 Bump catalog ID 2024-09-22 13:22:27 -04:00
66b0e4be3e Collapse some function arguments, for consistency 2024-09-22 13:21:43 -04:00
633cd3b94e Allow adding to GMNotes via comments field 2024-09-22 13:21:16 -04:00
f55bed99a2 Bump catalog id 2022-06-05 22:55:06 -04:00
908c6a8ba1 Hackily add some metadata for Arkham Horror LCG SCED deckbuilder 2022-06-05 22:34:31 -04:00
7834b4572e Remove explicit String casts, not needed in SE 3.3 2022-06-05 21:36:47 -04:00
f72d15098e Use less relative imports for new SE version 2022-06-05 20:29:25 -04:00
a21fc55f83 Bump version for release 2022-01-06 17:17:09 -05:00
25a029e69b Refactor to be more object-oriented, move to separate modules 2022-01-06 14:50:13 -05:00
31a367e5fa Remove feature in README that is not actually released yet 2022-01-06 13:58:25 -05:00
ff58e60a95 Move js into subfolder for namespacing 2022-01-04 17:39:21 -05:00
8bdb01f084 Add .log/ to gitignore 2022-01-03 13:18:08 -05:00
4e825e6160 Bump version for release 2022-01-03 13:17:24 -05:00
ce9b34b62f Add and apply prettierrc 2022-01-03 13:15:22 -05:00
4ce42c929b Create a Card class to avoid re-opening members
This has a fairly significant performance improvement
2022-01-03 13:14:59 -05:00
8609f63a7a Add MIT license file 2022-01-01 22:42:19 -05:00
412f45390a Add README and update catalog information 2022-01-01 22:31:53 -05:00
8cf31f4272 Make back image from first card in deck 2022-01-01 22:22:51 -05:00
82fa427305 Restore the default copy count to 1 2022-01-01 22:12:47 -05:00
b121beff5a Add a button to clear the cache 2022-01-01 22:12:47 -05:00
40050580f4 Add settings dialog for image format and resolution 2022-01-01 22:12:47 -05:00
2e68593b30 Move alert() to a Thread.invokeLater, as it is not in the main thread 2021-10-10 10:17:09 -04:00
11 changed files with 584 additions and 296 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.log/

1
.prettierrc.json Normal file
View File

@ -0,0 +1 @@
{}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Adam Goldsmith <contact@adamgoldsmith.name>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

42
README.md Normal file
View File

@ -0,0 +1,42 @@
# SE3 Tabletop Simulator Deck Generator
This plugin for [Strange Eons 3](https://strangeeons.cgjennings.ca/index.html) allows you to generate images and corresponding "Saved Object" JSONs for decks which can be used in [Tabletop Simulator](https://tabletopsimulator.com/).
## Features
- Creates correctly sized deck images for Tabletop Simulator, splitting into multiple pages when necessary.
- Supports multiple copies of cards, without duplicating them in the image.
- Creates a "Saved Object" JSON that joins all of the pages into a single deck, and assigns the name of the card.
## Installation
Paste this URL into the top bar of the Plug-in Catalog in Strange Eons 3: `https://dev.adamgoldsmith.name/se3plugins/`.
You should then be able to just select it from the list of available plugins.
See the [official docs](http://se3docs.cgjennings.ca/um-plugins-catalogue.html) for more details.
Alternatively, you can clone this repository and build/install the plugin following [these instructions from the SE3 docs](http://se3docs.cgjennings.ca/dm-first-plugin.html#building-the-plug-in-bundle).
## Usage
With the plugin installed, right click on a Deck task, then select "Generate TTS Deck" from the dropdown menu.
The plugin will create a JSON file with the same name as the Deck task, as well as a number of JPEG files.
Copy/move/symlink the JSON file to the `Saves/Saved Objects` folder in your [Tabletop Simulator Save Game Data](https://kb.tabletopsimulator.com/getting-started/technical-info/#save-game-data-location).
Do not move the jpg files; their paths are absolute and must be changed in the JSON file if you move them.
The absolute paths to deck images are written into the TTS json, so it will only work locally unless you upload the images, and correct the URLs in the JSON file.
Alternatively, you should in theory be able to use the [Upload All](https://kb.tabletopsimulator.com/custom-content/cloud-manager/#upload-all) feature in Tabletop Simulator to upload them to the Steam Cloud, although I have not have any success with this.
The first time you run the plugin will take a very long time, but future runs will be faster as the cards' images will be cached.
### Copies
This plugin supports the [Copies File](http://se3docs.cgjennings.ca/um-proj-deck-task.html#the-copies-file) for indicating the number of copies of a card to create.
## Limitations
This only supports decks of uniformly sized, single-sided cards (ie all with the same back).
(Note that rotated cards do not count as being the same size for this purpose).
The workaround for this is just to put each size/type of card into it's own deck.
The back image is taken from the first card in the deck.

View File

@ -2,6 +2,12 @@
# TTSDeck Root File
#
id = CATALOGUEID{b601af40-5e79-4dd3-b920-0d8b215b7d77:2021-8-16-20-36-2-56}
id = CATALOGUEID{b601af40-5e79-4dd3-b920-0d8b215b7d77:2024-8-22-17-22-14-302}
res://TTSDeck.js
catalog-name = Tabletop Simulator Deck Generator
catalog-credit = Adam Goldsmith <contact@adamgoldsmith.name>
catalog-homepage = https://github.com/ad1217/SE3-TTSDeck
catalog-description = This plugin for allows you to generate images and \
corresponding "Saved Object" JSONs for decks which can be used in Tabletop Simulator.
res://ttsdeck/plugin.js

View File

@ -1,204 +0,0 @@
/*
* TTSDeck.js
*
* Creates a deck image and corresponding "Saved Object" JSON for use
* in Tabletop Simulator
*/
useLibrary('project');
useLibrary('imageutils');
useLibrary('threads');
useLibrary('uilayout');
importClass(arkham.project.CopiesList);
const TTSJson = require('./TTSJson.js');
// The resolution (in pixels per inch) of the exported images
const RESOLUTION = 200;
// The extension of the image file format to use, e.g., png, jpg
const FORMAT = ImageUtils.FORMAT_JPEG;
const TTS_CARDS_PER_IMAGE = 69;
const TTS_MAX_ROWS = 7;
const getName = () => 'TTSDeck';
const getDescription = () => 'Generates a TTS deck image and JSON file';
const getVersion = () => 1.0;
const getPluginType = () => arkham.plugins.Plugin.INJECTED;
function unload() {
unregisterAll();
}
// Creates a test button during development that calls unload() to clean up.
testProjectScript();
// Hack to override the default return value of 1
function copyCount(copies_list, name) {
const entries = copies_list.getListEntries().map(x => String(x));
if (entries.indexOf(String(name)) == -1) {
return 2;
} else {
return copies_list.getCopyCount(name);
}
}
// export front face, or retrive it from a cached file
// TODO: handle two-sided cards
function makeCardImage(card) {
const component = ResourceKit.getGameComponentFromFile(card.file);
const cache_dir = new File(card.parent.file, '.ttsdeck_cache');
const cached_file = new File(cache_dir, card.file.name + '.' + FORMAT);
if (cached_file.exists() && cached_file.lastModified() > card.file.lastModified()) {
println("Got cached image for card", card);
return ImageUtils.read(cached_file);
} else {
println("Generating image for card ", card);
const sheets = component.createDefaultSheets();
const card_image = sheets[0].paint(arkham.sheet.RenderTarget.EXPORT, RESOLUTION);
cache_dir.mkdir();
ImageUtils.write(card_image, cached_file, FORMAT, -1, false, RESOLUTION);
return card_image;
}
}
function TTSDeckPage(busy_props, page_num, page_cards, copies_list) {
this.rows = Math.min(Math.ceil(Math.sqrt(page_cards.length)), TTS_MAX_ROWS);
this.columns = Math.ceil(page_cards.length / this.rows);
this.deck_image = null;
let deck_graphics;
this.card_jsons = [];
for (let row = 0; row < this.rows; row++) {
for (let col = 0; col < this.columns && row * this.columns + col < page_cards.length; col++) {
if (busy_props.cancelled) return;
let index = row * this.columns + col;
let card = page_cards[index];
busy_props.status = "Processing Card " + card;
busy_props.currentProgress = (page_num - 1) * TTS_CARDS_PER_IMAGE + index;
try {
let component = ResourceKit.getGameComponentFromFile(card.file);
let copies = copyCount(copies_list, card.baseName);
for (let ii = 0; ii < copies; ii++) {
this.card_jsons.push(TTSJson.makeCardJSON(page_num * 100 + index, component.getName()));
}
let card_image = makeCardImage(card);
if (!this.deck_image) {
this.deck_image = ImageUtils.create(
card_image.width * this.columns, card_image.height * this.rows, false);
deck_graphics = this.deck_image.createGraphics();
}
deck_graphics.drawImage(card_image, col * card_image.width, row * card_image.height, null);
} catch (ex) {
alert('Error while processing ' + card + ': ' + ex, true);
}
}
println("End of Row ", row);
}
// TODO: this should either prompt the user or provde automatic uploading somewhere
this.face_url = String((new File(page_cards[0].parent.file,
page_cards[0].parent.getName() + '_' + page_num + '.' + FORMAT)).toPath().toUri());
this.back_url = "TODO";
}
function makeTTSDeck(busy_props, cards, copies_list) {
const pages = [];
busy_props.title = "Processing Cards";
busy_props.maximumProgress = cards.length;
for (let page_num = 0; page_num * TTS_CARDS_PER_IMAGE < cards.length; page_num++) {
let page_cards = cards.slice(page_num * TTS_CARDS_PER_IMAGE, (page_num + 1) * TTS_CARDS_PER_IMAGE);
printf("Making page %d, with %d cards:\n", page_num + 1, page_cards.length);
pages.push(new TTSDeckPage(busy_props, page_num + 1, page_cards, copies_list));
if (busy_props.cancelled) return [,];
}
const deck_json = TTSJson.makeDeckJSON(pages);
return [deck_json, pages.map(page => page.deck_image)];
}
function run() {
const ttsDeckAction = JavaAdapter(TaskAction, {
getLabel: () => 'Generate TTS Deck',
getActionName: () => 'ttsdeck',
// Applies to Deck Tasks
appliesTo: function appliesTo(project, task, member) {
if (member != null || task == null) {
return false;
}
const type = task.settings.get(Task.KEY_TYPE);
if (NewTaskType.DECK_TYPE.equals(type)) {
return true;
}
return false;
},
perform: function perform(project, task, member) {
member = ProjectUtilities.simplify(project, task, member);
Eons.setWaitCursor(true);
try {
Thread.busyWindow(
(busy_props) => this.performImpl(busy_props, member),
'Setting up...',
true);
} catch (ex) {
Error.handleUncaught(ex);
} finally {
Eons.setWaitCursor(false);
}
},
performImpl: function performImpl(busy_props, member) {
let copies_list;
try {
copies_list = new CopiesList(member);
} catch (ex) {
copies_list = new CopiesList();
alert("unable to read copies list, using card count of 2 for all files", true);
}
const children = member.getChildren();
const page_cards = children.filter(child => {
if (ProjectUtilities.matchExtension(child, 'eon')) {
let component = ResourceKit.getGameComponentFromFile(child.file);
return component.isDeckLayoutSupported();
} else {
return false;
}
});
const [deck_json, deck_images] = makeTTSDeck(busy_props, page_cards, copies_list);
if (busy_props.cancelled) return;
const saved_object = TTSJson.makeSavedObjectJSON([deck_json], member.getName());
busy_props.status = "";
busy_props.maximumProgress = -1;
busy_props.title = "Writing JSON";
const json_file = new File(member.file, member.getName() + '.json');
ProjectUtilities.writeTextFile(json_file, JSON.stringify(saved_object, null, 4));
busy_props.title = "Writing Images";
busy_props.maximumProgress = deck_images.length;
deck_images.forEach((deck_image, index) => {
busy_props.currentProgress = index;
const image_file = new File(member.file, member.getName() + '_' + (index + 1) + '.' + FORMAT);
ImageUtils.write(deck_image, image_file, FORMAT, -1, false, RESOLUTION);
});
member.synchronize();
}
});
ActionRegistry.register(ttsDeckAction, Actions.PRIORITY_IMPORT_EXPORT);
}

View File

@ -1,90 +0,0 @@
// Helper functions for Tabletop Simulator JSON output
exports.makeCardJSON = function makeCardJSON(card_id, nickname, description) {
return {
Name: "Card",
Transform: {
posX: 0,
posY: 0,
posZ: 0,
rotX: 0,
rotY: 0,
rotZ: 0,
scaleX: 1.0,
scaleY: 1.0,
scaleZ: 1.0,
},
Nickname: String(nickname),
CardID: card_id,
Description: String(description || ""),
ColorDiffuse: {
r: 0.713235259,
g: 0.713235259,
b: 0.713235259,
},
Locked: false,
Grid: true,
Snap: true,
Autoraise: true,
Sticky: true,
Tooltip: true,
SidewaysCard: false,
};
};
exports.makeDeckJSON = function makeDeckJSON(pages, nickname, description) {
return {
Name: "DeckCustom",
Transform: {
posX: 0,
posY: 0,
posZ: 0,
rotX: 0,
rotY: 0.0,
rotZ: 0.0,
scaleX: 1.0,
scaleY: 1.0,
scaleZ: 1.0,
},
Nickname: String(nickname || ""),
Description: String(description || ""),
ColorDiffuse: {
r: 0.713239133,
g: 0.713239133,
b: 0.713239133,
},
Grid: true,
Locked: false,
SidewaysCard: false,
DeckIDs: pages
.map(page => page.card_jsons.map(card => card.CardID))
.reduce((acc, val) => acc.concat(val), []),
CustomDeck: pages.reduce((acc, page, index) => {
acc[String(index + 1)] = {
FaceURL: String(page.face_url),
BackURL: String(page.back_url),
NumWidth: page.columns,
NumHeight: page.rows,
BackIsHidden: true,
};
return acc;
}, {}),
ContainedObjects: pages
.map(page => page.card_jsons)
.reduce((acc, val) => acc.concat(val), []),
};
};
exports.makeSavedObjectJSON = function makeSavedObjectJSON(objects, save_name) {
return {
SaveName: String(save_name || ""),
GameMode: "",
Date: "",
Table: "",
Sky: "",
Note: "",
Rules: "",
PlayerTurn: "",
ObjectStates: objects,
};
};

115
resources/ttsdeck/Card.js Normal file
View File

@ -0,0 +1,115 @@
useLibrary("project");
function Card(member, arkhamdb_cycle_prefix, copies_list) {
this.member = member;
this.arkhamdb_cycle_prefix = arkhamdb_cycle_prefix;
this.copies_list = copies_list;
this.component = ResourceKit.getGameComponentFromFile(member.file);
}
Card.getImageFile = function getImageFile(parent, format, page_num) {
return new File(
parent.file,
parent.getName() + "_" + page_num + "." + format
);
};
Card.prototype.makeImageUncached = function makeImageUncached(resolution, back) {
println("Generating image for card ", this.member);
const sheets = this.component.createDefaultSheets();
const card_image = sheets[back ? 1 : 0].paint(
arkham.sheet.RenderTarget.EXPORT,
resolution
);
return card_image;
};
// export front face, or retrive it from a cached file
// TODO: handle two-sided cards
Card.prototype.makeImage = function makeImage(format, resolution) {
const cache_dir = new File(this.member.parent.file, ".ttsdeck_cache");
const cached_file = new File(cache_dir, this.member.file.name + "." + format);
if (
cached_file.exists() &&
cached_file.lastModified() > this.member.file.lastModified()
) {
println("Got cached image for card", this.member);
return ImageUtils.read(cached_file);
} else {
const card_image = this.makeImageUncached(resolution);
cache_dir.mkdir();
ImageUtils.write(card_image, cached_file, format, -1, false, resolution);
return card_image;
}
};
// TODO: allow setting a default copy count
// Hack to override the default return value of 1
Card.prototype.copyCount = function copyCount() {
const entries = this.copies_list.getListEntries().map((x) => String(x));
if (entries.indexOf(String(this.member.baseName)) == -1) {
return 1;
} else {
return this.copies_list.getCopyCount(this.member.baseName);
}
};
Card.prototype.makeJSON = function makeJSON(card_id, description) {
let card = {
Name: "Card",
Transform: {
posX: 0,
posY: 0,
posZ: 0,
rotX: 0,
rotY: 0,
rotZ: 0,
scaleX: 1.0,
scaleY: 1.0,
scaleZ: 1.0,
},
Nickname: this.component.getName(),
CardID: card_id,
Description: description || "",
ColorDiffuse: {
r: 0.713235259,
g: 0.713235259,
b: 0.713235259,
},
Locked: false,
Grid: true,
Snap: true,
Autoraise: true,
Sticky: true,
Tooltip: true,
SidewaysCard: false,
};
// TODO: could also do other fields, like "uses"
// Hack for AHLCG SCED deckbuilder
if (this.arkhamdb_cycle_prefix) {
let arkhamdb_id =
this.arkhamdb_cycle_prefix +
String(this.component.settings.get("CollectionNumber")).padStart(3, "0");
let overrides = {};
try {
const comments_json = JSON.parse(this.component.comment);
if ("tts_gmnotes_override" in comments_json) {
overrides = comments_json["tts_gmnotes_override"];
}
} catch (e) {}
let gmnotes = Object.assign({ id: arkhamdb_id }, overrides);
card.GMNotes = JSON.stringify(gmnotes);
}
return card;
};
module.exports = Card;

View File

@ -0,0 +1,174 @@
useLibrary("threads");
const Card = require("ttsdeck/Card.js");
const TTS_CARDS_PER_IMAGE = 69;
const TTS_MAX_ROWS = 7;
function TTSDeckPage(image_format, image_resolution, page_num, cards) {
this.image_format = image_format;
this.image_resolution = image_resolution;
this.page_num = page_num;
this.cards = cards;
this.rows = Math.min(Math.ceil(Math.sqrt(cards.length)), TTS_MAX_ROWS);
this.columns = Math.ceil(cards.length / this.rows);
this.deck_image = null;
this.card_jsons = [];
this.face_url = String(
Card.getImageFile(cards[0].member.parent, image_format, this.page_num)
.toPath()
.toUri()
);
this.back_url = String(
Card.getImageFile(cards[0].member.parent, image_format, "back")
.toPath()
.toUri()
);
}
TTSDeckPage.prototype.build = function build(busy_props) {
let deck_graphics;
for (let row = 0; row < this.rows; row++) {
for (
let col = 0;
col < this.columns && row * this.columns + col < this.cards.length;
col++
) {
if (busy_props.cancelled) return this;
let index = row * this.columns + col;
let card = this.cards[index];
busy_props.status = "Processing Card " + card.member;
busy_props.currentProgress =
(this.page_num - 1) * TTS_CARDS_PER_IMAGE + index;
try {
let copies = card.copyCount();
for (let ii = 0; ii < copies; ii++) {
this.card_jsons.push(card.makeJSON(this.page_num * 100 + index));
}
let card_image = card.makeImage(
this.image_format,
this.image_resolution
);
if (!this.deck_image) {
this.deck_image = ImageUtils.create(
card_image.width * this.columns,
card_image.height * this.rows,
false
);
deck_graphics = this.deck_image.createGraphics();
}
deck_graphics.drawImage(
card_image,
col * card_image.width,
row * card_image.height,
null
);
} catch (ex) {
Thread.invokeLater(() =>
alert("Error while processing " + card + ": " + ex, true)
);
}
}
println("End of Row ", row);
}
return this;
};
function TTSDeck(image_format, image_resolution, cards) {
this.image_format = image_format;
this.image_resolution = image_resolution;
this.cards = cards;
this.pages = [];
}
TTSDeck.prototype.build = function build(busy_props) {
busy_props.title = "Processing Cards";
busy_props.maximumProgress = this.cards.length;
for (
let page_num = 0;
page_num * TTS_CARDS_PER_IMAGE < this.cards.length;
page_num++
) {
if (busy_props.cancelled) return this;
let page_cards = this.cards.slice(
page_num * TTS_CARDS_PER_IMAGE,
(page_num + 1) * TTS_CARDS_PER_IMAGE
);
printf("Making page %d, with %d cards:\n", page_num + 1, page_cards.length);
this.pages.push(
new TTSDeckPage(
this.image_format,
this.image_resolution,
page_num + 1,
page_cards
).build(busy_props)
);
}
return this;
};
TTSDeck.prototype.getImages = function getImages() {
return this.pages.map((page) => page.deck_image);
};
TTSDeck.prototype.makeJSON = function makeJSON(nickname, description) {
return {
Name: "DeckCustom",
Transform: {
posX: 0,
posY: 0,
posZ: 0,
rotX: 0,
rotY: 0.0,
rotZ: 0.0,
scaleX: 1.0,
scaleY: 1.0,
scaleZ: 1.0,
},
Nickname: nickname || "",
Description: description || "",
ColorDiffuse: {
r: 0.713239133,
g: 0.713239133,
b: 0.713239133,
},
Grid: true,
Locked: false,
SidewaysCard: false,
DeckIDs: this.pages
.map((page) => page.card_jsons.map((card) => card.CardID))
.reduce((acc, val) => acc.concat(val), []),
CustomDeck: this.pages.reduce((acc, page, index) => {
acc[String(index + 1)] = {
FaceURL: page.face_url,
BackURL: page.back_url,
NumWidth: page.columns,
NumHeight: page.rows,
BackIsHidden: true,
};
return acc;
}, {}),
ContainedObjects: this.pages
.map((page) => page.card_jsons)
.reduce((acc, val) => acc.concat(val), []),
};
};
module.exports = {
TTSDeckPage: TTSDeckPage,
TTSDeck: TTSDeck,
};

View File

@ -0,0 +1,15 @@
// Helper functions for Tabletop Simulator JSON output
exports.makeSavedObjectJSON = function makeSavedObjectJSON(objects, save_name) {
return {
SaveName: save_name || "",
GameMode: "",
Date: "",
Table: "",
Sky: "",
Note: "",
Rules: "",
PlayerTurn: "",
ObjectStates: objects,
};
};

207
resources/ttsdeck/plugin.js Normal file
View File

@ -0,0 +1,207 @@
/*
* TTSDeck.js
*
* Creates a deck image and corresponding "Saved Object" JSON for use
* in Tabletop Simulator
*/
useLibrary("project");
useLibrary("imageutils");
useLibrary("threads");
useLibrary("uilayout");
useLibrary("uicontrols");
importClass(arkham.project.CopiesList);
const Card = require("ttsdeck/Card.js");
const { TTSDeck } = require("ttsdeck/TTSDeck.js");
const TTSJson = require("ttsdeck/TTSJson.js");
const getName = () => "TTSDeck";
const getDescription = () => "Generates a TTS deck image and JSON file";
const getVersion = () => 1.0;
const getPluginType = () => arkham.plugins.Plugin.INJECTED;
function unload() {
unregisterAll();
}
// Creates a test button during development that calls unload() to clean up.
testProjectScript();
function settingsDialog(deck_task) {
const task_settings = deck_task.getSettings();
const image_format_field = comboBox([
ImageUtils.FORMAT_JPEG,
ImageUtils.FORMAT_PNG,
]);
image_format_field.setSelectedItem(
task_settings.get("tts_image_format", "jpg")
);
const resolution_field = textField(
task_settings.get("tts_image_resolution", "200"),
15
);
const clear_cache_button = button("Clear Cache", undefined, function (e) {
const cache_dir = new File(deck_task.file, ".ttsdeck_cache");
cache_dir.listFiles().forEach((file) => file.delete());
});
const panel = new Grid();
// prettier-ignore
panel.place(
"Image Format", "",
image_format_field, "grow,span",
"Resolution", "",
resolution_field, "grow,span",
clear_cache_button, "grow,span"
);
const close_button = panel.createDialog("TTS Export").showDialog();
return [
close_button,
image_format_field.getSelectedItem(),
Number(resolution_field.text),
];
}
function run() {
const ttsDeckAction = JavaAdapter(TaskAction, {
getLabel: () => "Generate TTS Deck",
getActionName: () => "ttsdeck",
// Applies to Deck Tasks
appliesTo: function appliesTo(project, task, member) {
if (member != null || task == null) {
return false;
}
const type = task.settings.get(Task.KEY_TYPE);
if (NewTaskType.DECK_TYPE.equals(type)) {
return true;
}
return false;
},
perform: function perform(project, task, member) {
let deck_task = ProjectUtilities.simplify(project, task, member);
const [close_button, image_format, image_resolution] =
settingsDialog(deck_task);
// User canceled the dialog or closed it without pressing ok
if (close_button != 1) {
return;
}
// persist settings
const task_settings = deck_task.getSettings();
task_settings.set("tts_image_format", image_format);
task_settings.set("tts_image_resolution", image_resolution);
deck_task.writeTaskSettings();
// If used with arkhamdb, get cycle prefix from settings
const arkhamdb_cycle_prefix = task_settings.get("arkhamdb_cycle_prefix");
Eons.setWaitCursor(true);
try {
Thread.busyWindow(
(busy_props) =>
this.performImpl(
busy_props,
image_format,
image_resolution,
arkhamdb_cycle_prefix,
deck_task
),
"Setting up...",
true
);
} catch (ex) {
Error.handleUncaught(ex);
} finally {
Eons.setWaitCursor(false);
}
},
performImpl: function performImpl(
busy_props,
image_format,
image_resolution,
arkhamdb_cycle_prefix,
member
) {
let copies_list;
try {
copies_list = new CopiesList(member);
} catch (ex) {
copies_list = new CopiesList();
alert(
"unable to read copies list, using card count of 2 for all files",
true
);
}
const children = member.getChildren();
const cards = children
.map((child) => {
if (ProjectUtilities.matchExtension(child, "eon")) {
let card = new Card(child, arkhamdb_cycle_prefix, copies_list);
if (card.component.isDeckLayoutSupported()) {
return card;
}
}
return undefined;
})
.filter((card) => card !== undefined);
const deck = new TTSDeck(
image_format,
image_resolution,
cards,
copies_list
).build(busy_props);
if (busy_props.cancelled) return;
const saved_object = TTSJson.makeSavedObjectJSON(
[deck.makeJSON()],
member.getName()
);
busy_props.status = "";
busy_props.maximumProgress = -1;
busy_props.title = "Writing JSON";
const json_file = new File(member.file, member.getName() + ".json");
ProjectUtilities.writeTextFile(
json_file,
JSON.stringify(saved_object, null, 4)
);
const deck_images = deck.getImages();
busy_props.title = "Writing Images";
busy_props.maximumProgress = deck_images.length;
deck_images.forEach((deck_image, index) => {
busy_props.currentProgress = index;
const image_file = Card.getImageFile(member, image_format, index + 1);
ImageUtils.write(
deck_image,
image_file,
image_format,
-1,
false,
image_resolution
);
});
let back_image = cards[0].makeImageUncached(image_resolution, true);
const back_image_file = Card.getImageFile(member, image_format, "back");
ImageUtils.write(
back_image,
back_image_file,
image_format,
-1,
false,
image_resolution
);
member.synchronize();
},
});
ActionRegistry.register(ttsDeckAction, Actions.PRIORITY_IMPORT_EXPORT);
}