{"size": 19254, "ext": "js", "max_stars_repo_path": "modified-tidy5e-sheet.js", "max_stars_repo_name": "lawruble13/modified-tidy5e-sheet", "max_stars_repo_stars_event_min_datetime": null, "max_stars_repo_stars_event_max_datetime": null, "max_issues_repo_path": "modified-tidy5e-sheet.js", "max_issues_repo_name": "lawruble13/modified-tidy5e-sheet", "max_issues_repo_issues_event_min_datetime": null, "max_issues_repo_issues_event_max_datetime": null, "max_forks_repo_path": "modified-tidy5e-sheet.js", "max_forks_repo_name": "lawruble13/modified-tidy5e-sheet", "max_forks_repo_forks_event_min_datetime": null, "max_forks_repo_forks_event_max_datetime": null}
{"max_stars_count": null, "max_issues_count": null, "max_forks_count": null, "avg_line_length": 37.098265896, "max_line_length": 219, "alphanum_fraction": 0.6476056923}
import { DND5E } from "../../systems/dnd5e/module/config.js";
import ActorSheet5e from "../../systems/dnd5e/module/actor/sheets/base.js";
import ActorSheet5eCharacter from "../../systems/dnd5e/module/actor/sheets/character.js";

import { preloadModifiedTidy5eHandlebarsTemplates } from "./templates/modified-tidy5e-templates.js";
import { addFavorites } from "./modified-tidy5e-favorites.js";

let scrollPos = 0;

export class ModifiedTidy5eSheet extends ActorSheet5eCharacter {

	get template() {
		if ( !game.user.isGM && this.actor.limited ) return "modules/modified-tidy5e-sheet/templates/modified-tidy5e-sheet-ltd.html";
		return "modules/modified-tidy5e-sheet/templates/modified-tidy5e-sheet.html";
	}

	static get defaultOptions() {
	  return mergeObject(super.defaultOptions, {
			classes: ["modified-tidy5e", "dnd5e", "sheet", "actor", "character"],
			blockFavTab: true,
			width: 740,
			height: 720
		});
	}

	_createEditor(target, editorOptions, initialContent) {
		editorOptions.min_height = 200;
		super._createEditor(target, editorOptions, initialContent);
	}

	activateListeners(html) {
		super.activateListeners(html);

		// store Scroll Pos
		let attributesTab = html.find('.tab.attributes');
		attributesTab.scroll(function(){
			scrollPos = $(this).scrollTop();
		});
		let tabNav = html.find('a.item:not([data-tab="attributes"])');
		tabNav.click(function(){
			scrollPos = 0;
			attributesTab.scrollTop(scrollPos);
		});

		// toggle item delete protection
		html.find('.modified-tidy5e-delete-toggle').click(async (event) => {
			event.preventDefault();
			let actor = this.actor;

			if(actor.getFlag('modified-tidy5e-sheet', 'allow-delete')){
				await actor.unsetFlag('modified-tidy5e-sheet', 'allow-delete');
			} else {
				await actor.setFlag('modified-tidy5e-sheet', 'allow-delete', true);
			}
 		});

		// toggle traits
 		html.find('.traits-toggle').click(async (event) => {
			event.preventDefault();
			let actor = this.actor;

			if(actor.getFlag('modified-tidy5e-sheet', 'traits-compressed')){
				await actor.unsetFlag('modified-tidy5e-sheet', 'traits-compressed');
			} else {
				await actor.setFlag('modified-tidy5e-sheet', 'traits-compressed', true);
			}
 		});

		// toggle favorites
 		html.find('.favorites-toggle').click(async (event) => {
			event.preventDefault();
			let actor = this.actor;

			if(actor.getFlag('modified-tidy5e-sheet', 'favorites-compressed')){
				await actor.unsetFlag('modified-tidy5e-sheet', 'favorites-compressed');
			} else {
				await actor.setFlag('modified-tidy5e-sheet', 'favorites-compressed', true);
			}
 		});

		// set exhaustion level with portrait icon
		html.find('.exhaust-level li').click(async (event) => {
			event.preventDefault();
			let actor = this.actor;
			let data = actor.data.data;
			let target = event.currentTarget;
			let value = target.dataset.elvl;
			await actor.update({"data.attributes.exhaustion": value});
 		});

 		// set input fields via editable elements
 		html.find('[contenteditable]').on('paste', function(e) {
	    //strips elements added to the editable tag when pasting
	    var $self = $(this);
	    setTimeout(function() {$self.html($self.text());}, 0);
		}).on('keypress', function(e) {
	     //ignores enter key
	     // return e.which != 13;
	     // blur field on enter
	     if(e.keyCode===13){
        $(this).blur();
      }
		});

 		html.find('[contenteditable]').blur(async (event) => {
    	let value = event.target.textContent;
    	let target = event.target.dataset.target;
    	html.find('input[type="hidden"][data-input="'+target+'"]').val(value).submit();
 		});

 		// changing item qty and charges values (removing if both value and max are 0)
    html.find('.item:not(.inventory-header) input').change(event => {
    	let value = event.target.value;
    	// console.log(value);
			let actor = this.actor;
      let itemId = $(event.target).parents('.item')[0].dataset.itemId;
      // console.log(itemId);
      let path = event.target.dataset.path;
      let data = {};
      data[path] = Number(event.target.value);
      actor.getOwnedItem(itemId).update(data);
    });

    // creating charges for the item
    html.find('.inventory-list .item .addCharges').click(event => {
			let actor = this.actor;
      let itemId = $(event.target).parents('.item')[0].dataset.itemId;
      let item = actor.getOwnedItem(itemId);

      item.data.uses = { value: 1, max: 1 };
      let data = {};
      data['data.uses.value'] = 1;
      data['data.uses.max'] = 1;

      actor.getOwnedItem(itemId).update(data);
    });

		html.find('.new-ability').click(async (event) => {
		event.preventDefault();
			let actor=this.actor;
			let d = new Dialog({
				title: "Add Ability",
				content: `
				<form id="add-ability" autocomplete="off" onsubmit="event.preventDefault();">
					<div class="form-group">
						<label>Ability Name</label>
						<input type="text" name="long" placeholder="Long name (e.g. Wisdom)">
						<p class="notes">Enter the name for the new skill. The first three letters are the short form, and must be unique.</p>
					</div>
				</form>
				`,
				buttons: {
					confirm: {
						icon: '<i class="fas fa-check"></i>',
						label: "Add",
						callback: async (html) => {
							let l = html.find('input[name="long"]')[0].value;
							let s = l.substr(0,3).toLowerCase();
							let k = "data.abilities." + s;
							let d={};
							d[k] = {
								"checkBonus": 0,
								"mod": 0,
								"prof": 0,
								"proficient": 0,
								"save": 0,
								"saveBonus": 0,
								"value": 10
							};
							if(actor.data.data.hasOwnProperty("xabilities")){
								d["data.xabilities"] = actor.data.data.xabilities;
							} else {
								d["data.xabilities"] = {};
							}

							d["data.xabilities."+s]=l;
							CONFIG.DND5E.abilities[s]=l;
							await actor.update(d);
						}
					},
					cancel: {
						icon: '<i class="fas fa-times"></i>',
						label: "Cancel",
						callback: () => console.log("Cancelled adding sanity.")
					},
				},
				default: 'cancel'
			});
			d.render(true);
		});

		html.find('.add-skill').click(async (event) => {
			console.log("Adding new skill...");
			let actor = this.actor;
			console.log(actor);
			let c = `
			<form id="add-ability" autocomplete="off" onsubmit="event.preventDefault();">
				<div class="form-group">
					<label>Skill Name</label>
					<input type="text" name="name" placeholder="Skill name (e.g. Investigation)">
				</div>
				<div class="form-group">
					<label>Base Ability</label>
					<select name="abl">
			`;
			for (let short in CONFIG.DND5E.abilities){
				c += `<option value="${short}">${CONFIG.DND5E.abilities[short]}</option>`;
			}
			c += `
					</select>
				</div>
			</form>
			`
			let d = new Dialog({
				title: "Add Ability",
				content: c,
				buttons: {
					confirm: {
						icon: '<i class="fas fa-check"></i>',
						label: "Add",
						callback: async (html) => {
							let name = html.find('input[name="name"]')[0].value;
							let abr = name.substr(0,3).toLowerCase();
							let s = html.find('select[name="abl"]')[0].value;
							let k = "data.skills." + abr;
							let d={};
							d[k] = {
								"ability": s,
								"bonus": 0,
								"mod": 0,
								"passive": 10,
								"prof": 0,
								"total": 0,
								"value": 0
							};
							if(actor.data.data.hasOwnProperty("xskills")){
								d["data.xskills"] = actor.data.data.xskills;
							} else {
								d["data.xskills"] = {};
							}

							d["data.xskills."+abr]=name;
							CONFIG.DND5E.skills[abr]=name;
							await actor.update(d);
						}
					},
					cancel: {
						icon: '<i class="fas fa-times"></i>',
						label: "Cancel",
						callback: () => console.log("Cancelled adding sanity.")
					},
				},
				default: 'cancel'
			});
			d.render(true);
			console.log("Done adding new skill.");
		})
	}
}

// Migrate Traits to default dnd5e data
async function migrateTraits(app, html, data) {
	let actor = game.actors.entities.find(a => a.data._id === data.actor._id);

	if (!actor.getFlag('modified-tidy5e-sheet', 'useCoreTraits')){

		console.log('ModifiedTidy5e Sheet | Data needs migration! Migrating.');

		let coreTrait = (actor.data.data.details.trait !== '') ? actor.data.data.details.trait+"<br>Migrated Content:" : '';
		let coreIdeal = (actor.data.data.details.ideal !== '') ? actor.data.data.details.trait+"<br>Migrated Content:" : '';
		let coreBond = (actor.data.data.details.bond !== '') ? actor.data.data.details.bond+"<br>Migrated Content:" : '';
		let coreFlaw = (actor.data.data.details.flaw !== '') ? actor.data.data.details.flaw+"<br>Migrated Content:" : '';

		let trait = (actor.data.data.details.personality && actor.data.data.details.personality.value) ? coreTrait + actor.data.data.details.personality.value : actor.data.data.details.trait;
		let ideal = (actor.data.data.details.ideals && actor.data.data.details.ideals.value) ? coreIdeal + actor.data.data.details.ideals.value : actor.data.data.details.ideal;
		let bond = (actor.data.data.details.bonds && actor.data.data.details.bonds.value) ? coreBond + actor.data.data.details.bonds.value : actor.data.data.details.bond;
		let flaw = (actor.data.data.details.flaws && actor.data.data.details.flaws.value) ? coreFlaw + actor.data.data.details.flaws.value : actor.data.data.details.flaw;

		await actor.update({
			"data.details.trait": trait,
			"data.details.ideal": ideal,
			"data.details.bond": bond,
			"data.details.flaw": flaw,
			"data.details.personality": null,
			"data.details.-=personality": null,
			"data.details.ideals": null,
			"data.details.-=ideals": null,
			"data.details.bonds": null,
			"data.details.-=bonds": null,
			"data.details.flaws": null,
			"data.details.-=flaws": null,
			"flags.modified-tidy5e-sheet.useCoreTraits":true
		});

		console.log('ModifiedTidy5e Sheet | Data migrated to dnd5e core values.')
	}
}

async function checkDeathSaveStatus(app, html, data){
	var actor = game.actors.entities.find(a => a.data._id === data.actor._id);
	var data = actor.data.data;
	var currentHealth = data.attributes.hp.value;
	var deathSaveSuccess = data.attributes.death.success;
	var deathSaveFailure = data.attributes.death.failure;

	if(currentHealth > 0 && deathSaveSuccess != 0 || currentHealth > 0 && deathSaveFailure != 0){
			await actor.update({"data.attributes.death.success": 0});
			await actor.update({"data.attributes.death.failure": 0});
	}
}

async function addClassList(app, html, data) {
	if (!game.settings.get("modified-tidy5e-sheet", "hideClassList")) {
		let actor = game.actors.entities.find(a => a.data._id === data.actor._id);
		let classList = [];
		let items = data.actor.items;
		for (let item of items) {
			if (item.type === "class") {
				let subclass = (item.data.subclass) ? ` <div class="subclass-info has-note"><span>S</span><div class="note">${item.data.subclass}</div></div>` : ``;
				classList.push(item.name + subclass);
			}
		}
		classList = "<ul class='class-list'><li class='class-item'>" + classList.join("</li><li class='class-item'>") + "</li></ul>";
		mergeObject(actor, {"data.flags.modified-tidy5e-sheet.classlist": classList});
		let classListTarget = html.find('.level-information');
		classListTarget.after(classList);

	}
}

async function setSheetClasses(app, html, data) {
	if (game.settings.get("modified-tidy5e-sheet", "useRoundPortraits")) {
		html.find('.modified-tidy5e-sheet .profile').addClass('roundPortrait');
	}
	if (game.settings.get("modified-tidy5e-sheet", "disableHpOverlay")) {
		html.find('.modified-tidy5e-sheet .profile').addClass('disable-hp-overlay');
	}
	if (game.settings.get("modified-tidy5e-sheet", "noInspirationAnimation")) {
		html.find('.modified-tidy5e-sheet .inspiration label i').addClass('disable-animation');
	}
	if (game.settings.get("modified-tidy5e-sheet", "hpOverlayBorder") > 0) {
		html.find('.modified-tidy5e-sheet .profile .hp-overlay').css({'border-width':game.settings.get("modified-tidy5e-sheet", "hpOverlayBorder")+'px'});
	}
}

async function hidePortraitButtons(app, html, data){
	if (game.settings.get("modified-tidy5e-sheet", "exhaustionOnHover")) {
		html.find('.modified-tidy5e-sheet .profile').addClass('exhaustionOnHover');
	}
	if (game.settings.get("modified-tidy5e-sheet", "restOnHover")) {
		html.find('.modified-tidy5e-sheet .profile').addClass('restOnHover');
	}
	if (game.settings.get("modified-tidy5e-sheet", "inspirationOnHover")) {
		html.find('.modified-tidy5e-sheet .profile').addClass('inspirationOnHover');
	}
}

async function updateExtraNames(){
	for (let actor of game.actors._source){
		if(actor.data.hasOwnProperty("xabilities")){
			for (let short in actor.data.xabilities){
				CONFIG.DND5E.abilities[short] = actor.data.xabilities[short];
			}
		}
		if(actor.data.hasOwnProperty("xskills")){
			for(let abr in actor.data.xskills){
				CONFIG.DND5E.skills[abr] = actor.data.xskills[abr];
			}
		}
	}
}

// Preload modified-tidy5e Handlebars Templates
Hooks.once("init", () => {
  preloadModifiedTidy5eHandlebarsTemplates();

	game.settings.register("modified-tidy5e-sheet", "useDarkMode", {
		name: "Use alternate Dark Mode version of the sheet",
		hint: "Checking this option will enable an alternate Dark Mode version of the ModifiedTidy5e Sheet. Goes well with D&D5E Dark Mode or as a Standalone.",
		scope: "user",
		config: true,
		default: false,
		type: Boolean,
		onChange: data => {
      data === true ? document.body.classList.add("modified-tidy5eDark"):document.body.classList.remove("modified-tidy5eDark");
     }
	});

	game.settings.register("modified-tidy5e-sheet", "primaryAccent", {
		name: "Custom Primary Accent Color.",
		hint: "Overwrite the default primary accent color (#48BB78) for Dark Mode used to highlight e. g. buttons, input field borders or hover states. Use any valid css value like red/#ff0000/rgba(255,0,0)/rgba(255,0,0,1)",
		scope: "user",
		config: true,
		default: "",
		type: String,
		onChange: data => {
      data === true ? document.documentElement.style.setProperty('--darkmode-primary-accent',primaryAccentColor)
  :document.documentElement.style.setProperty('--darkmode-primary-accent',"#48BB78");
  ;
     }
	});

	game.settings.register("modified-tidy5e-sheet", "secondaryAccent", {
		name: "Custom Secondary Accent Color.",
		hint: "Overwrite the default secondary accent color (rgba(0,150,150,.325)) for Dark Mode used to highlight preparation states. Use any valid css value like red/#ff0000/rgba(255,0,0)/rgba(255,0,0,1)",
		scope: "user",
		config: true,
		default: "",
		type: String,
		onChange: data => {
      data === true ? document.documentElement.style.setProperty('--darkmode-secondary-accent',secondaryAccentColor)
  :document.documentElement.style.setProperty('--darkmode-secondary-accent',"rgba(0,150,150,.325)");
     }
	});

  const useDarkMode = game.settings.get('modified-tidy5e-sheet', "useDarkMode");
  if (useDarkMode === true) {
    document.body.classList.add("modified-tidy5eDark");
  }
  const primaryAccentColor = game.settings.get('modified-tidy5e-sheet', "primaryAccent");
  const secondaryAccentColor = game.settings.get('modified-tidy5e-sheet', "secondaryAccent");
  if(useDarkMode === true && primaryAccentColor !==  '') {
  	document.documentElement.style.setProperty('--darkmode-primary-accent',primaryAccentColor);
  }
  if(useDarkMode === true && secondaryAccentColor !==  '') {
   	document.documentElement.style.setProperty('--darkmode-secondary-accent',secondaryAccentColor);
  }
});

// Register ModifiedTidy5e Sheet and make default character sheet
Actors.registerSheet("dnd5e", ModifiedTidy5eSheet, {
	types: ["character"],
	makeDefault: true
});

Hooks.on("renderModifiedTidy5eSheet", (app, html, data) => {
	addFavorites(app, html, data, scrollPos);
	migrateTraits(app, html, data);
	addClassList(app, html, data);
	setSheetClasses(app, html, data);
	checkDeathSaveStatus(app, html, data);
	hidePortraitButtons(app, html, data);
	updateExtraNames();
	// console.log(data);
});

Hooks.once("ready", () => {

	if (window.BetterRolls) {
	  window.BetterRolls.hooks.addActorSheet("ModifiedTidy5eSheet");
	}

	game.settings.register("modified-tidy5e-sheet", "useRoundPortraits", {
		name: "Character sheet uses round portraits.",
		hint: "You should check this if you use round portraits. It will adapt the hp overlay and portait buttons to make it look nicer. Also looks nice on square portraits without a custom frame.",
		scope: "world",
		config: true,
		default: false,
		type: Boolean
	});
	game.settings.register("modified-tidy5e-sheet", "disableHpOverlay", {
		name: "Disable the Hit Point Overlay.",
		hint: "If you don't like the video game style Hit Point overlay on your character's portrait you can disable it.",
		scope: "user",
		config: true,
		default: false,
		type: Boolean
	});
	game.settings.register("modified-tidy5e-sheet", "hpOverlayBorder", {
		name: "Border width for the Hit Point overlay",
		hint: "If your portrait has a frame you can adjust the Hit Point overlay to compensate the frame width. It might look nicer if the overlay doesn't tint the border.",
		scope: "world",
		config: true,
		default: 0,
		type: Number
	});
	game.settings.register("modified-tidy5e-sheet", "noInspirationAnimation", {
		name: "No inspiration indicator animation.",
		hint: "If it's too distracting, you can disable the subtle animation of the glowing inspiration indicator.",
		scope: "user",
		config: true,
		default: false,
		type: Boolean
	});
	game.settings.register("modified-tidy5e-sheet", "exhaustionOnHover", {
		name: "Show exhaustion tracker only on Hover",
		hint: "If you check this option the exhaustion tracker will only be visible when you hover over the portrait",
		scope: "user",
		config: true,
		default: false,
		type: Boolean
	});
	game.settings.register("modified-tidy5e-sheet", "restOnHover", {
		name: "Show rest button only on Hover",
		hint: "If you check this option the rest button will only be visible when you hover over the portrait",
		scope: "user",
		config: true,
		default: false,
		type: Boolean
	});
	game.settings.register("modified-tidy5e-sheet", "inspirationOnHover", {
		name: "Show inspiration indicator only on Hover",
		hint: "If you check this option the inspiration indicator will only be visible when you hover over the portrait",
		scope: "user",
		config: true,
		default: false,
		type: Boolean
	});
	game.settings.register("modified-tidy5e-sheet", "hideClassList", {
		name: "Hide Character Class List",
		hint: "Checking this option will hide the character's class list next to the level label. The sheet can handle 3 classes well, more than that will work but things get shifty ;)",
		scope: "world",
		config: true,
		default: false,
		type: Boolean
	});
	updateExtraNames();
});
