@@ -15,6 +15,7 @@ const { api, fields, handlebars } = foundry.applications;
1515 * @property {boolean } [disabled]
1616 * @property {boolean } [selected]
1717 * @property {boolean } [rule]
18+ * @property {String } img (Custom for PFU dialogs)
1819 * @property {Record<string, string> } [dataset]
1920 */
2021
@@ -85,19 +86,21 @@ export default class FoundryUtils {
8586 /**
8687 * @param {String } title
8788 * @param content
89+ * @param {Object } options
8890 * @returns {Promise<*> }
8991 */
90- static async input ( title , content ) {
91- const result = await foundry . applications . api . DialogV2 . input ( {
92+ static async input ( title , content , options = { } ) {
93+ let defaultOptions = {
9294 window : { title : title , icon : 'fas fa-comment' } ,
9395 content : content ,
9496 classes : [ 'projectfu' , 'sheet' , 'backgroundstyle' , 'fu-dialog' ] ,
9597 rejectClose : false ,
9698 ok : {
9799 label : 'FU.Confirm' ,
98100 } ,
99- } ) ;
100- return result ;
101+ } ;
102+ ObjectUtils . mergeRecursive ( defaultOptions , options ) ;
103+ return await foundry . applications . api . DialogV2 . input ( defaultOptions ) ;
101104 }
102105
103106 /**
@@ -662,10 +665,12 @@ export default class FoundryUtils {
662665 *
663666 * @param html
664667 * @param className
668+ * @param eventName
665669 * @param {ContextMenuEntry[] } entries
666670 */
667- static contextMenu ( html , className , entries ) {
671+ static contextMenu ( html , className , entries , eventName = 'click' ) {
668672 new foundry . applications . ux . ContextMenu ( html , className , entries , {
673+ eventName : eventName ,
669674 fixed : true ,
670675 jQuery : false ,
671676 } ) ;
@@ -790,4 +795,149 @@ export default class FoundryUtils {
790795 }
791796
792797 static PLACEHOLDER_IMG = 'icons/svg/mystery-man.svg' ;
798+
799+ /**
800+ * @desc Instantiates an actor on the current scene, prompting the user to click where to place it.
801+ * @param {FUActor } actor
802+ * @param {Object } data
803+ * @returns {TokenDocument|null }
804+ */
805+ static async instantiateActor ( actor , data ) {
806+ const scene = game . scenes . viewed ;
807+ if ( ! scene ) {
808+ return null ;
809+ }
810+
811+ const gridSize = scene . grid . size ;
812+ const tokenData = await actor . getTokenDocument ( {
813+ x : 0 ,
814+ y : 0 ,
815+ actorLink : false ,
816+ } ) ;
817+ const tokenSize = tokenData . width ;
818+
819+ const notification = ui . notifications . info ( 'Left click to place the token on the active scene, right click to cancel the operation.' , { permanent : true } ) ;
820+
821+ const position = await new Promise ( ( resolve ) => {
822+ const clickHandler = ( event ) => {
823+ const { x, y } = event . getLocalPosition ( canvas . stage ) ;
824+ cleanup ( ) ;
825+ resolve ( { x, y } ) ;
826+ } ;
827+
828+ const rightClickHandler = ( ) => {
829+ cleanup ( ) ;
830+ ui . notifications . warn ( 'Cancelled token placement.' ) ;
831+ resolve ( null ) ;
832+ } ;
833+
834+ const cleanup = ( ) => {
835+ canvas . stage . off ( 'click' , clickHandler ) ;
836+ canvas . stage . off ( 'rightclick' , rightClickHandler ) ;
837+ ui . notifications . remove ( notification ) ;
838+ } ;
839+
840+ canvas . stage . on ( 'click' , clickHandler ) ;
841+ canvas . stage . on ( 'rightclick' , rightClickHandler ) ;
842+ } ) ;
843+
844+ if ( ! position ) {
845+ return null ; // cancelled
846+ }
847+
848+ const snapped = scene . grid . getSnappedPoint ( {
849+ x : position . x - ( tokenSize * gridSize ) / 2 ,
850+ y : position . y - ( tokenSize * gridSize ) / 2 ,
851+ } ) ;
852+
853+ const [ tokenDocument ] = await scene . createEmbeddedDocuments ( 'Token' , [
854+ {
855+ ...tokenData . toObject ( ) ,
856+ x : snapped . x ,
857+ y : snapped . y ,
858+ ...data ,
859+ } ,
860+ ] ) ;
861+ return tokenDocument ;
862+ }
863+
864+ /**
865+ * @param imagePath
866+ * @returns {Promise<Tile> }
867+ */
868+ static async placeTile ( imagePath ) {
869+ const scene = game . scenes . viewed ;
870+
871+ // Get image dimensions to use as default tile size
872+ // eslint-disable-next-line no-undef
873+ const tex = await loadTexture ( imagePath ) ;
874+ let width , height ;
875+
876+ const promptTitle = `${ StringUtils . localize ( 'CONTROLS.TilePlace' ) } - Preset` ;
877+ const preset = await FoundryUtils . selectOptionDialog ( promptTitle , [
878+ {
879+ label : StringUtils . localizeMultiple ( [ 'Token' , 'Scale' ] ) ,
880+ value : 'token' ,
881+ } ,
882+ {
883+ label : StringUtils . localizeMultiple ( [ 'Default' , 'Scale' ] ) ,
884+ value : 'default' ,
885+ } ,
886+ ] ) ;
887+ switch ( preset ) {
888+ case 'token' :
889+ {
890+ const scale = Math . min ( 100 / tex . width , 100 / tex . height ) ;
891+ width = tex . width * scale ;
892+ height = tex . height * scale ;
893+ }
894+ break ;
895+ case 'default' :
896+ width = tex . width ;
897+ height = tex . height ;
898+ break ;
899+ }
900+
901+ if ( ! preset ) {
902+ return ;
903+ }
904+
905+ const notification = ui . notifications . info ( 'Left click to place tile on the active scene, right click to cancel the operation.' , { permanent : true } ) ;
906+
907+ return new Promise ( ( resolve ) => {
908+ const clickHandler = async ( event ) => {
909+ const { x, y } = event . getLocalPosition ( canvas . stage ) ;
910+
911+ cleanup ( ) ;
912+
913+ const [ tileDocument ] = await scene . createEmbeddedDocuments ( 'Tile' , [
914+ {
915+ texture : { src : imagePath } ,
916+ width,
917+ height,
918+ x : x - width / 2 ,
919+ y : y - height / 2 ,
920+ } ,
921+ ] ) ;
922+
923+ canvas . tiles . releaseAll ( ) ;
924+ resolve ( tileDocument ) ;
925+ } ;
926+
927+ const rightClickHandler = ( ) => {
928+ cleanup ( ) ;
929+ ui . notifications . warn ( 'Cancelled tile placement.' ) ;
930+ resolve ( null ) ; // cancelled
931+ } ;
932+
933+ const cleanup = ( ) => {
934+ canvas . stage . off ( 'click' , clickHandler ) ;
935+ canvas . stage . off ( 'rightclick' , rightClickHandler ) ;
936+ ui . notifications . remove ( notification ) ;
937+ } ;
938+
939+ canvas . stage . on ( 'click' , clickHandler ) ;
940+ canvas . stage . on ( 'rightclick' , rightClickHandler ) ;
941+ } ) ;
942+ }
793943}
0 commit comments