Compare commits
12 Commits
6ff1c40d3c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 86390d27c7 | |||
| 47985a77d9 | |||
| 2be16753bc | |||
| e18608ac2a | |||
| 8cd26c971a | |||
| c9cd9f0da5 | |||
| bc5b6af750 | |||
| d6036109a3 | |||
| a6fa30b19b | |||
| d7a469cf5d | |||
| 0cac048dff | |||
| 6ffc042285 |
Generated
+13
@@ -119,6 +119,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@tinymce/tinymce-svelte": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tinymce/tinymce-svelte/-/tinymce-svelte-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-QA5furKBoloH/fIVNtvNDB/AQfE1YFpPCRI5sRNjKL5rc0Sj0YCYXNGDVh7dIfoX8kd1Ivr9jg53igXGu1xIuw==",
|
||||||
|
"requires": {
|
||||||
|
"rollup-plugin-execute": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@tsconfig/svelte": {
|
"@tsconfig/svelte": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-2.0.1.tgz",
|
||||||
@@ -804,6 +812,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rollup-plugin-execute": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup-plugin-execute/-/rollup-plugin-execute-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-isCNR/VrwlEfWJMwsnmt5TBRod8dW1IjVRxcXCBrxDmVTeA1IXjzeLSS3inFBmRD7KDPlo38KSb2mh5v5BoWgA=="
|
||||||
|
},
|
||||||
"rollup-plugin-livereload": {
|
"rollup-plugin-livereload": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-2.0.5.tgz",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"@tsconfig/svelte": "^2.0.0"
|
"@tsconfig/svelte": "^2.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tinymce/tinymce-svelte": "^0.1.2",
|
||||||
"dompurify": "^2.3.6",
|
"dompurify": "^2.3.6",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"sirv-cli": "^2.0.0"
|
"sirv-cli": "^2.0.0"
|
||||||
|
|||||||
+2
-2
@@ -4,9 +4,9 @@
|
|||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||||
|
|
||||||
<title>Svelte app</title>
|
<title>PF2e card generator</title>
|
||||||
|
|
||||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
<link rel='icon' type='image/webp' href='/imgs/free.webp'>
|
||||||
<link rel='stylesheet' href='/build/bundle.css'>
|
<link rel='stylesheet' href='/build/bundle.css'>
|
||||||
|
|
||||||
<script defer src='/build/bundle.js'></script>
|
<script defer src='/build/bundle.js'></script>
|
||||||
|
|||||||
+72
-15
@@ -2,8 +2,27 @@
|
|||||||
import Card from './Card.svelte';
|
import Card from './Card.svelte';
|
||||||
import CardInput from './CardInput.svelte';
|
import CardInput from './CardInput.svelte';
|
||||||
|
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
import LZString from 'lz-string';
|
import LZString from 'lz-string';
|
||||||
|
|
||||||
|
const NO_CARD_MAGIC_STRING = "---";
|
||||||
|
let saved_cards = {};
|
||||||
|
let saved_cards_keys = [NO_CARD_MAGIC_STRING];
|
||||||
|
let selected_saved_card = NO_CARD_MAGIC_STRING;
|
||||||
|
const saved_cards_store = writable<object>(JSON.parse(localStorage.getItem("saved_cards")) || {} );
|
||||||
|
saved_cards_store.subscribe(v => {
|
||||||
|
if (Object.keys(v).length > 0) {
|
||||||
|
saved_cards = v;
|
||||||
|
saved_cards_keys = Object.keys(v).sort();
|
||||||
|
} else {
|
||||||
|
saved_cards = {};
|
||||||
|
saved_cards_keys = [NO_CARD_MAGIC_STRING]
|
||||||
|
}
|
||||||
|
selected_saved_card = saved_cards_keys[0];
|
||||||
|
})
|
||||||
|
saved_cards_store.update(n => n); // Dummy update
|
||||||
|
saved_cards_store.subscribe((value) => localStorage.setItem("saved_cards", JSON.stringify(value)));
|
||||||
|
|
||||||
const MAX_CARDS = 9;
|
const MAX_CARDS = 9;
|
||||||
let cards = [];
|
let cards = [];
|
||||||
let mhp = {
|
let mhp = {
|
||||||
@@ -12,7 +31,7 @@
|
|||||||
tags: ["Consumable", "Healing", "Magical", "Necromancy", "Positive", "Potion"],
|
tags: ["Consumable", "Healing", "Magical", "Necromancy", "Positive", "Potion"],
|
||||||
attributes: `<p><b>Usage</b> Held in 1 hand; <b>Bulk</b> L</p>
|
attributes: `<p><b>Usage</b> Held in 1 hand; <b>Bulk</b> L</p>
|
||||||
<p><b>Activate (A)</b> Interact</p>`,
|
<p><b>Activate (A)</b> Interact</p>`,
|
||||||
description: `<p>A healing potion is a vial of a ruby-red liquid that imparts a tingling sensation as the drinker’s wounds heal rapidly. When you drink a healing potion, you regain 2d8+5 Hit Points.</p>`
|
description: `<p>A healing potion is a vial of a ruby-red liquid that imparts a tingling sensation as the drinker’s wounds heal rapidly. When you drink a healing potion, you regain 1d8 Hit Points.</p>`
|
||||||
};
|
};
|
||||||
|
|
||||||
if (window.location.hash !== "") {
|
if (window.location.hash !== "") {
|
||||||
@@ -24,16 +43,21 @@
|
|||||||
function get_idx_by_id(id) {
|
function get_idx_by_id(id) {
|
||||||
for(let idx = 0; idx < cards.length; idx++) {
|
for(let idx = 0; idx < cards.length; idx++) {
|
||||||
if(id === cards[idx].id) {
|
if(id === cards[idx].id) {
|
||||||
return idx
|
return idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//let card_selected = 0;
|
function chunk(arr) {
|
||||||
//$: selected_card = cards[card_selected];
|
let larr = [...arr], out = [];
|
||||||
|
while(larr.length) {
|
||||||
|
out = [...out, (larr.splice(0,MAX_CARDS))];
|
||||||
|
};
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
$: pages = chunk(cards);
|
||||||
|
|
||||||
let selector = cards[0].id;
|
let selector = cards[0].id;
|
||||||
//$: selector = cards[card_selected].id;
|
|
||||||
$: selected_card = cards.filter(x => x.id === selector)[0];
|
$: selected_card = cards.filter(x => x.id === selector)[0];
|
||||||
$: selected_idx = get_idx_by_id(selector);
|
$: selected_idx = get_idx_by_id(selector);
|
||||||
|
|
||||||
@@ -45,10 +69,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function add_card() {
|
function add_card() {
|
||||||
if (cards.length < MAX_CARDS) {
|
cards = [...cards, {id: cards[cards.length-1].id+1, ...mhp}];
|
||||||
cards = [...cards, {id: cards[cards.length-1].id+1, ...mhp}];
|
selector = cards[cards.length-1].id;
|
||||||
selector = cards[cards.length-1].id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove_card() {
|
function remove_card() {
|
||||||
@@ -57,33 +79,67 @@
|
|||||||
if (cards.length > 1) { // remove only if there is more than 1 card
|
if (cards.length > 1) { // remove only if there is more than 1 card
|
||||||
cards = cards.filter(x => x.id != selector);
|
cards = cards.filter(x => x.id != selector);
|
||||||
if (selected_idx >= cards.length) {
|
if (selected_idx >= cards.length) {
|
||||||
selector = cards[cards.length-1].id;
|
selector = cards[cards.length-1].id;
|
||||||
} else {
|
} else {
|
||||||
selector = cards[selected_idx].id;
|
selector = cards[selected_idx].id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function save_card() {
|
||||||
|
saved_cards_store.update(n => {
|
||||||
|
let to_save = {};
|
||||||
|
for (let key in selected_card) {
|
||||||
|
if (key !== "id") {
|
||||||
|
to_save[key] = selected_card[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n[to_save["name"]] = to_save;
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_card() {
|
||||||
|
if (selected_saved_card !== NO_CARD_MAGIC_STRING) {
|
||||||
|
let new_card = saved_cards[selected_saved_card];
|
||||||
|
selected_card.name = new_card.name;
|
||||||
|
selected_card.type = new_card.type;
|
||||||
|
selected_card.tags = new_card.tags;
|
||||||
|
selected_card.attributes = new_card.attributes;
|
||||||
|
selected_card.description = new_card.description;
|
||||||
|
cards = cards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_card() {
|
||||||
|
saved_cards_store.update(n => {
|
||||||
|
delete n[selected_saved_card];
|
||||||
|
return n;
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<section class="controls">
|
<section class="controls">
|
||||||
<h1>PF2e card generator</h1>
|
<h1>PF2e card generator</h1>
|
||||||
<CardInput bind:name={selected_card.name} bind:type={selected_card.type} tags={selected_card.tags} bind:attributes={selected_card.attributes} bind:description={selected_card.description} on:change_tags={change_tags} on:add_card={add_card} on:remove_card={remove_card}/>
|
<CardInput bind:name={selected_card.name} bind:type={selected_card.type} tags={selected_card.tags} bind:attributes={selected_card.attributes} bind:description={selected_card.description} bind:saved_cards={saved_cards_keys} bind:selected_saved_card={selected_saved_card} on:change_tags={change_tags} on:add_card={add_card} on:remove_card={remove_card} on:save_card={save_card} on:load_card={load_card} on:delete_card={delete_card}/>
|
||||||
</section>
|
</section>
|
||||||
|
{#each pages as page}
|
||||||
<section class="cards">
|
<section class="cards">
|
||||||
{#each cards as card, idx (card.id)}
|
{#each page as card, idx (card.id)}
|
||||||
<div class="card">
|
<div class="card" style="break-after:avoid;">
|
||||||
<Card {...card}/>
|
<Card {...card}/>
|
||||||
<input class="card-selector" type="radio" bind:group={selector} value={card.id}/>
|
<input class="card-selector" type="radio" bind:group={selector} value={card.id}/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
|
{/each}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@media print {
|
@media print {
|
||||||
@page {
|
@page {
|
||||||
margin: 0;
|
margin: 7mm;
|
||||||
}
|
}
|
||||||
.controls {
|
.controls {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -95,6 +151,7 @@
|
|||||||
.cards {
|
.cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
break-after: always;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|||||||
+16
-9
@@ -15,8 +15,8 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.card {
|
.card {
|
||||||
width: 2.5in;
|
width: calc(2.5in - 4mm);
|
||||||
height: 3.5in;
|
height: calc(3.5in - 4mm);
|
||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
border: 2mm solid black;
|
border: 2mm solid black;
|
||||||
/*background-image: url("/bg.webp");*/
|
/*background-image: url("/bg.webp");*/
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-bottom: 2px solid black;
|
border-bottom: 2px solid black;
|
||||||
|
padding: 0 4pt;
|
||||||
}
|
}
|
||||||
.name, .type {
|
.name, .type {
|
||||||
font-family: serif;
|
font-family: serif;
|
||||||
@@ -35,8 +36,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.tags {
|
.tags {
|
||||||
margin: 1pt 1pt 0pt 1pt;
|
padding: 1pt 4pt;
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,10 @@
|
|||||||
background-color: #54166e;
|
background-color: #54166e;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
margin: 1pt;
|
margin-top: 1pt;
|
||||||
|
}
|
||||||
|
.content div {
|
||||||
|
padding: 0 4pt;
|
||||||
}
|
}
|
||||||
:global(section.content p) {
|
:global(section.content p) {
|
||||||
padding-bottom: 2pt;
|
padding-bottom: 2pt;
|
||||||
@@ -72,6 +75,10 @@
|
|||||||
:global(img.activity) {
|
:global(img.activity) {
|
||||||
max-height: 8pt;
|
max-height: 8pt;
|
||||||
}
|
}
|
||||||
|
.description :global(img :not(.activity)) {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
@@ -94,12 +101,12 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
<p class="attributes">
|
<div class="attributes">
|
||||||
{@html pf_filter(DOMPurify.sanitize(attributes))}
|
{@html pf_filter(DOMPurify.sanitize(attributes))}
|
||||||
</p>
|
</div>
|
||||||
<p class="description">
|
<div class="description">
|
||||||
{@html pf_filter(DOMPurify.sanitize(description))}
|
{@html pf_filter(DOMPurify.sanitize(description))}
|
||||||
</p>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+63
-5
@@ -1,15 +1,63 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import Editor from '@tinymce/tinymce-svelte';
|
||||||
|
|
||||||
export let name, type, tags, attributes, description;
|
let apiKey = "xoifjvd46mymq6y2ev6wyaj7utyykb3tz9pf9v8m98oeiqip";
|
||||||
|
|
||||||
|
let conf = {
|
||||||
|
height: 500,
|
||||||
|
plugins: [ 'autoresize', 'code', 'hr', 'image' ],
|
||||||
|
toolbar: "undo redo | bold italic | hr image | code ",
|
||||||
|
menubar: false,
|
||||||
|
file_picker_types: 'image',
|
||||||
|
/* and here's our custom image picker*/
|
||||||
|
file_picker_callback: function (cb, value, meta) {
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.setAttribute('type', 'file');
|
||||||
|
input.setAttribute('accept', 'image/*');
|
||||||
|
|
||||||
|
input.onchange = function () {
|
||||||
|
var file = this.files[0];
|
||||||
|
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function () {
|
||||||
|
/*
|
||||||
|
Note: Now we need to register the blob in TinyMCEs image blob
|
||||||
|
registry. In the next release this part hopefully won't be
|
||||||
|
necessary, as we are looking to handle it internally.
|
||||||
|
*/
|
||||||
|
var id = 'blobid' + (new Date()).getTime();
|
||||||
|
var blobCache = tinymce.activeEditor.editorUpload.blobCache;
|
||||||
|
var base64 = reader.result.split(',')[1];
|
||||||
|
var blobInfo = blobCache.create(id, file, base64);
|
||||||
|
blobCache.add(blobInfo);
|
||||||
|
|
||||||
|
/* call the callback and populate the Title field with the file name */
|
||||||
|
cb(blobInfo.blobUri(), { title: file.name });
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
input.click();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export let name, type, tags, attributes, description, saved_cards, selected_saved_card;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
function change_tags(e) {
|
function change_tags(e) {
|
||||||
dispatch("change_tags", {tags: e.target.value});
|
dispatch("change_tags", {tags: e.target.value});
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
|
let tiny;
|
||||||
|
|
||||||
|
const tinymceloaded = () => {
|
||||||
|
tiny = window.tinymce.init({
|
||||||
|
selector: "textarea.attributes"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<style>
|
<style>
|
||||||
div.cardInput {
|
div.cardInput {
|
||||||
width: 5in;
|
width: 5in;
|
||||||
@@ -23,13 +71,23 @@
|
|||||||
<div class="row">Type: <input name="type" bind:value={type}/></div>
|
<div class="row">Type: <input name="type" bind:value={type}/></div>
|
||||||
<div class="row">Tags: <input name="tags" on:change={change_tags} value={tags}/></div>
|
<div class="row">Tags: <input name="tags" on:change={change_tags} value={tags}/></div>
|
||||||
<div class="row">Attributes:
|
<div class="row">Attributes:
|
||||||
<textarea bind:value={attributes}></textarea>
|
<Editor {apiKey} {conf} bind:value={attributes}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">Description:
|
<div class="row">Description:
|
||||||
<textarea bind:value={description}></textarea>
|
<Editor {apiKey} {conf} bind:value={description}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<select bind:value={selected_saved_card}>
|
||||||
|
{#each saved_cards as card}
|
||||||
|
<option value={card}>{card}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<button on:click={e => dispatch('add_card')}>+</button>
|
<button on:click={e => dispatch('add_card')}>+</button>
|
||||||
<button on:click={e => dispatch('remove_card')}>-</button>
|
<button on:click={e => dispatch('remove_card')}>-</button>
|
||||||
|
<button on:click={e => dispatch('save_card')}>Save</button>
|
||||||
|
<button on:click={e => dispatch('load_card')}>Load</button>
|
||||||
|
<button on:click={e => dispatch('delete_card')}>Delete selected</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user