<?xml version="1.0"?>
<FMAdd_on version="2.2.3.0" Source="22.0.3" File="fmPromise.fmp12" UUID="914A85CE-ED07-4417-90D2-1CF7E4EB8D2E" locale="English" Has_DDR_INFO="False" timestamp="2025-11-03T13:36:44">
	<Data membercount="1">
		<AddAction membercount="1">
			<Records>
				<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
				<RowList membercount="1">
					<Row membercount="12" id="1">
						<Cell>
							<FieldReference type="Normal" datatype="Text" id="1" name="com.fmi.basetable.field.fmPromiseModule::4B68129F6621C41900B27BF59AB8FD9B" repetition="1" UUID="8B3BC1E7-A85F-49CC-96BA-2F3562FF22A6">
								<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
							</FieldReference>
							<StyledText>
								<Data><![CDATA[C2FC32C2-5417-4641-8F67-EE66FD1798DF]]></Data>
							</StyledText>
						</Cell>
						<Cell>
							<FieldReference type="Normal" datatype="Timestamp" id="11" name="com.fmi.basetable.field.fmPromiseModule::4D23BC46444AF8A65D40FAD262C1ECE8" repetition="1" UUID="E511A9FE-8EEA-42AC-9D43-B12B6A1B59E1">
								<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
							</FieldReference>
							<StyledText>
								<Data><![CDATA[10-24-2025 11:59:54 AM]]></Data>
							</StyledText>
						</Cell>
						<Cell>
							<FieldReference type="Normal" datatype="Text" id="7" name="com.fmi.basetable.field.fmPromiseModule::CAF925C0F6CA25D3A96F84D48240448A" repetition="1" UUID="E147AA02-B110-46F2-B4AA-D528038DF7FD">
								<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
							</FieldReference>
							<StyledText>
								<Data><![CDATA[fm-promise-built-in/fm-promise-select-page.html]]></Data>
							</StyledText>
						</Cell>
						<Cell>
							<FieldReference type="Normal" datatype="Text" id="6" name="com.fmi.basetable.field.fmPromiseModule::406C3A21B8B1B9B99BAC8BE664F8CD44" repetition="1" UUID="6062AB51-E6C0-4613-99F8-697BFDD44DBD">
								<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
							</FieldReference>
							<StyledText>
								<Data><![CDATA[<!doctype html><html lang="en"><head>	<meta charset="utf-8">	<style>		html, body {			height: 100%; margin: 0; padding: 1.5rem;			font-family: -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;			color: #333; background-color: #f8f9fa;		}		a { color: #007bff; }		h3, p { margin-top: 0; margin-bottom: 1rem; }		strong { color: #000; }		#success { color: #28a745; display: none; }		#error, #fm-error-details { color: #dc3545; font-weight: bold; min-height: 1.2em; }		.content-box { padding: 1.5rem; background: #fff; border: 1px solid #dee2e6; border-radius: 0.5rem; }		#setup-instructions, #main-content, #success, #fm-error-instructions { display: none; }		.form-control { display: flex; flex-direction: column; gap: 0.75rem; }		input { width: 100%; padding: 0.5rem; border-radius: 0.25rem; border: 1px solid #ced4da; }		button { padding: 0.5rem 1rem; border: none; border-radius: 0.25rem; background-color: #007bff; color: white; cursor: pointer; }		button:disabled { background-color: #6c757d; cursor: wait; }		code { background-color: #e9ecef; padding: 0.2em 0.4em; border-radius: 3px; }		ol { padding-left: 1.5rem; }		#server-address-warning { color: #dc3545; font-weight: bold; display: none; border: 1px solid; padding: 0.5rem; border-radius: 0.25rem;}	</style>  <script type="module" crossorigin>(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))s(n);new MutationObserver(n=>{for(const o of n)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&s(i)}).observe(document,{childList:!0,subtree:!0});function r(n){const o={};return n.integrity&&(o.integrity=n.integrity),n.referrerPolicy&&(o.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?o.credentials="include":n.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function s(n){if(n.ep)return;n.ep=!0;const o=r(n);fetch(n.href,o)}})();class u extends Error{code;constructor({message:e="Unknown error",code:r}){super(e),this.name="FMPromiseError",this.code=r}toString(){return this.code?`${this.message} (${this.code})`:this.message}}let h=0;const c={},b=Promise.race([new Promise(t=>{if(window.FileMaker)t(window.FileMaker);else{let e;Object.defineProperty(window,"FileMaker",{get:()=>e,set:r=>t(e=r)})}}),new Promise((t,e)=>setTimeout(()=>e(new u({message:"FileMaker object not found within 5 seconds."})),5e3))]),l={get webViewerName(){return window.FMPROMISE_WEB_VIEWER_NAME||new URLSearchParams(window.location.search).get("webViewerName")||"fmPromiseWebViewer"},async performScript(t,e=null,r={}){const s=++h;console.log(`[fmPromise] #${s}: Calling script "${t}"`,e),e&&typeof e!="string"&&(e=JSON.stringify(e));const n=await b;let o=await new Promise((i,a)=>{c[s]={resolve:i,reject:a};const f=JSON.stringify({scriptName:t,promiseId:s,webViewerName:this.webViewerName,ignoreResult:r?.ignoreResult||void 0})+``+(e||""),w=r.runningScript||0;w===0?n.PerformScript("fmPromise",f):n.PerformScriptWithOption("fmPromise",f,w.toString())});if(!r.alwaysReturnString&&typeof o=="string"&&(o.startsWith("{")||o.startsWith("[")))try{o=JSON.parse(o)}catch(i){console.warn(`[fmPromise] #${s}: Unable to parse JSON result.`,{result:o,error:i})}return console.log(`[fmPromise] #${s}: Received result.`,o),o},evaluate(t,e={},r={}){const n=`Let([${Object.entries(e||{}).map(([o,i])=>`${o}=${JSON.stringify(i)}`).join(";")}] ; ${t})`;return this.performScript("fmPromise.evaluate",n,r)},async executeFileMakerDataAPI(t){const e=await this.performScript("fmPromise.executeFileMakerDataAPI",t);if(!e||!e.messages||!e.messages.length)throw new u({code:-1,message:"Empty data API response"});if(e.messages[0].code!=="0")throw new u(e.messages[0]);return e},_processPortalRow(t,e){const r={},s=`${e}::`;for(const n in t)if(!(n==="recordId"||n==="modId"))if(n.startsWith(s)){const o=n.substring(s.length);r[o]=t[n]}else r[n]=t[n];return Object.defineProperties(r,{recordId:{value:t.recordId,enumerable:!1,writable:!0,configurable:!0},modId:{value:t.modId,enumerable:!1,writable:!0,configurable:!0}}),r},async executeFileMakerDataAPIRecords(t){const e=await this.executeFileMakerDataAPI(t);if(!e.response.data){const s=[];return Object.defineProperties(s,{foundCount:{value:0,enumerable:!1},totalRecordCount:{value:0,enumerable:!1}}),s}const r=e.response.data.map(s=>{const n=s.portalData||{},o={};for(const a in n){const m=n[a];o[a]=m.map(f=>this._processPortalRow(f,a))}const i={...s.fieldData,...o};return Object.defineProperties(i,{recordId:{value:s.recordId,enumerable:!1,writable:!0,configurable:!0},modId:{value:s.modId,enumerable:!1,writable:!0,configurable:!0}}),i});return Object.defineProperties(r,{foundCount:{value:e.response.dataInfo.foundCount,enumerable:!1},totalRecordCount:{value:e.response.dataInfo.totalRecordCount,enumerable:!1}}),r},async executeSql(t,...e){let r,s;if(Array.isArray(t)&&Array.isArray(t.raw)){if(e.length!==t.length-1)throw new u({code:-1,message:"Invalid template literal for executeSql"});r=t.join("?").replace(/\n\s*/g," "),s=e}else if(typeof t=="string")r=t,s=e;else throw new u({code:-1,message:"Invalid arguments: executeSql must be called with a SQL string, or as a template literal."});const n=s.map(m=>` ; ${JSON.stringify(m)}`).join(""),o=`|${Math.random()}|`,i=`~${Math.random()}~`,a=await this.evaluate(`ExecuteSQLe(${JSON.stringify(r)} ; "${o}" ; "${i}"${n})`,void 0,{alwaysReturnString:!0});return a===""||a===null||a===void 0?[]:a.split(i).map(m=>m.split(o))},insertFromUrl(t,e=""){return this.performScript("fmPromise.insertFromURL",{url:t,curlOptions:e})},setFieldByName(t,e){return this.performScript("fmPromise.setFieldByName",{fmFieldNameToSet:t,value:e})},async showCustomDialog(t,e,r="OK",s="",n=""){const o=await this.performScript("fmPromise.showCustomDialog",{title:t,body:e,btn1:r,btn2:s,btn3:n});return parseInt(o,10)||0},_resolve(t,e){c[t]&&(c[t].resolve(e),delete c[t])},_reject(t,e){if(c[t]){let r;try{r=JSON.parse(e)}catch{r={message:e}}console.warn(`[fmPromise] #${t}: Rejected.`,r),c[t].reject(new u(r)),delete c[t]}}};globalThis.fmPromise=l;globalThis.fmPromise_Resolve=l._resolve;globalThis.fmPromise_Reject=l._reject;const P=document.getElementById("main-content"),I=document.getElementById("setup-instructions"),v=document.getElementById("fm-error-instructions"),g=document.getElementById("myForm"),E=document.getElementById("success"),y=document.getElementById("error"),S=document.getElementById("existing-files"),x=document.getElementById("filename"),d=g.querySelector("button");function p(t){[P,I,v].forEach(e=>e.style.display="none"),document.getElementById(t).style.display="block"}async function F(t){t.preventDefault();const e=d.textContent;d.disabled=!0,d.textContent="Working...",y.textContent="";try{const r=x.value.trim().toLowerCase();await l.performScript("fmPromise.createOrRefreshModule",{filename:r,webViewerName:l.webViewerName}),g.style.display="none",E.style.display="block"}catch(r){y.textContent="Error: "+(r.message||r),d.disabled=!1,d.textContent=e}}async function M(){let t=[],e="";try{[t,e]=await Promise.all([l.executeSql`select filename from fmPromiseModule where includeInNewFiles=0 and filename not like 'fm-promise%'`,l.evaluate("_fmPromiseServerAddress")]),e=new URL(e+"/ping").toString()}catch(r){console.error("FileMaker bridge error:",r),p("fm-error-instructions");return}if(!e){document.getElementById("server-address-warning").style.display="block",p("setup-instructions");return}try{if(!(await l.insertFromUrl(e)).success)throw new Error("Invalid ping response");S.append(...t.map(s=>{let n=document.createElement("option");return n.value=s[0],n})),p("main-content"),g.addEventListener("submit",F)}catch(r){console.error("Dev server connection error:",r),p("setup-instructions")}}M();</script></head><body>	<h3>360Works fmPromise Add-On</h3>	<!-- View 1: Shown for FileMaker Bridge errors -->	<div id="fm-error-instructions" class="content-box">		<h4>FileMaker Configuration Error</h4>		<p>The web viewer cannot communicate with FileMaker. Please check the following:</p>		<ol>			<li>The web viewer object is configured to <strong>"Allow JavaScript to Perform FileMaker Scripts"</strong>.</li>			<li>The web viewer object name is <code>fmPromiseWebViewer</code>.</li>			<li>The fmPromise scripts and custom functions were installed correctly.</li>		</ol>		<div id="fm-error-details">Could not execute a FileMaker script from JavaScript.</div>	</div>	<!-- View 2: Shown if dev server is offline -->	<div id="setup-instructions" class="content-box">		<h4>Welcome to fmPromise!</h4>		<p>To create new web viewer modules, you need to run the local development server in a directory of your choice. fmPromise will create editable source code for your web viewer modules in this directory.</p>		<p>Please follow these steps in a terminal in the directory you would like to use for web viewer content (this can be any directory on your machine):</p>		<ol>			<li>				<strong>Install the package (you may need to <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" target="_blank">Install NPM</a> first)</strong>				<code>npm install @360works/fmpromise</code>			</li>			<li>				<strong>Start the development server:</strong>				<code>npx fmpromise-dev</code>			</li>			<li>Once the server is running, <strong>Reload this Web Viewer</strong> and call the <strong>fmPromise.toggleDevMode</strong> script to enable devMode.</li>		</ol>		<p>Consult the <a href="https://github.com/shmert/fmPromise" target="_blank">fmPromise documentation</a> for details.</p>		<div id="server-address-warning">			<strong>Warning:</strong> The FileMaker global variable <code>$$fmPromise_Server_Address</code> is not set or is invalid. Please ensure the "fmPromise.OnFirstWindowOpen" script has run correctly.		</div>		<div id="error">Could not connect to the local development server.</div>	</div>	<!-- View 3: Shown on success -->	<div id="main-content" class="content-box">		<p>You have successfully added a fmPromise web viewer to your layout! 😊</p>		<p>Enter a <strong>unique path and/or filename</strong> for a new module:</p>		<form id="myForm" role="form">			<div class="form-control">				<label for="filename">New Module Path (e.g., "invoices/main.html")</label>				<input id="filename" name="filename" list="existing-files" placeholder="my-first-module" required/>				<datalist id="existing-files"></datalist>				<button type="submit">Create Module</button>			</div>		</form>	</div>	<!-- View 4: Shown after successful module creation -->	<div id="success" class="content-box">		<h3>Your fmPromise module has been created!</h3>		<p>The server has created the new files in your project's <code>src</code> directory.</p>		<p>You may now close this setup panel and reload the web viewer to display the new module.</p>	</div></body></html>]]></Data>
							</StyledText>
						</Cell>
						<Cell>
							<FieldReference type="Normal" datatype="Number" id="8" name="com.fmi.basetable.field.fmPromiseModule::6304F8E1826B38DDEB7C95B45D06B8D1" repetition="1" UUID="5C64C0D2-5730-4CC6-BC65-E28057C94A3D">
								<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
							</FieldReference>
							<StyledText>
								<Data><![CDATA[0]]></Data>
							</StyledText>
						</Cell>
						<Cell>
							<FieldReference type="Normal" datatype="Text" id="9" name="com.fmi.basetable.field.fmPromiseModule::4561002D22704A42D003B57FA0E75A33" repetition="1" UUID="981F21E8-1A8D-49FE-8EBC-31558E72C7FA">
								<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
							</FieldReference>
							<StyledText>
								<Data><![CDATA[When a new fmPromise add-on is dragged to a layout, this is the page which lets you configure which module to show in the web viewer.]]></Data>
							</StyledText>
						</Cell>
						<Cell>
							<FieldReference type="Normal" datatype="Text" id="10" name="com.fmi.basetable.field.fmPromiseModule::91F127C4B704FC5AC212BA970EB65F39" repetition="1" UUID="A136C2A8-707A-4481-96A4-4775725BEC80">
								<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
							</FieldReference>
							<StyledText>
								<Data><![CDATA[<!doctype html><html lang="en"><head>	<meta charset="utf-8">	<script type="module">		import fmPromise from "@360works/fmpromise";		// --- DOM Element References ---		const mainContent = document.getElementById('main-content');		const setupInstructions = document.getElementById('setup-instructions');		const fmErrorInstructions = document.getElementById('fm-error-instructions');		const myForm = document.getElementById('myForm');		const successDiv = document.getElementById('success');		const errorDiv = document.getElementById('error');		const datalist = document.getElementById('existing-files');		const filenameInput = document.getElementById('filename');		const submitButton = myForm.querySelector('button');		/** Helper to show a specific view and hide others */		function showView(viewId) {			[mainContent, setupInstructions, fmErrorInstructions].forEach(el => el.style.display = 'none');			document.getElementById(viewId).style.display = 'block';		}		/** Handles the form submission to create a new module. */		async function handleFormSubmit(event) {			event.preventDefault();			const originalButtonText = submitButton.textContent;			submitButton.disabled = true;			submitButton.textContent = 'Working...';			errorDiv.textContent = '';			try {				const filename = filenameInput.value.trim().toLowerCase();				await fmPromise.performScript('fmPromise.createOrRefreshModule', { filename, webViewerName: fmPromise.webViewerName });				myForm.style.display = 'none';				successDiv.style.display = 'block';			} catch (e) {				errorDiv.textContent = 'Error: ' + (e.message || e);				submitButton.disabled = false;				submitButton.textContent = originalButtonText;			}		}		/** Main function to initialize the page and check connections. */		async function main() {			let existingModules = [];			let pingAddress = '';			// --- Stage 1: Check FileMaker Bridge ---			try {				[existingModules, pingAddress] = await Promise.all([					fmPromise.executeSql`select filename from fmPromiseModule where includeInNewFiles=0 and filename not like 'fm-promise%'`,					fmPromise.evaluate('_fmPromiseServerAddress')				]);				pingAddress = new URL(pingAddress + '/ping').toString(); // validate the address			} catch (e) {				console.error("FileMaker bridge error:", e);				showView('fm-error-instructions');				return; // Stop execution			}			// --- Stage 2: Check Dev Server Connection ---			if (!pingAddress) {				// Show a specific part of the setup instructions if the variable isn't set				document.getElementById('server-address-warning').style.display = 'block';				showView('setup-instructions');				return;			}			try {				const pingResult = await fmPromise.insertFromUrl(pingAddress);				if (!pingResult.success) throw new Error('Invalid ping response');				// --- Success State: Everything is working ---				datalist.append(...existingModules.map(f => {					let option = document.createElement('option');					option.value = f[0];					return option;				}));				showView('main-content');				myForm.addEventListener('submit', handleFormSubmit);			} catch (e) {				console.error("Dev server connection error:", e);				showView('setup-instructions');			}		}		main();	</script>	<style>		html, body {			height: 100%; margin: 0; padding: 1.5rem;			font-family: -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;			color: #333; background-color: #f8f9fa;		}		a { color: #007bff; }		h3, p { margin-top: 0; margin-bottom: 1rem; }		strong { color: #000; }		#success { color: #28a745; display: none; }		#error, #fm-error-details { color: #dc3545; font-weight: bold; min-height: 1.2em; }		.content-box { padding: 1.5rem; background: #fff; border: 1px solid #dee2e6; border-radius: 0.5rem; }		#setup-instructions, #main-content, #success, #fm-error-instructions { display: none; }		.form-control { display: flex; flex-direction: column; gap: 0.75rem; }		input { width: 100%; padding: 0.5rem; border-radius: 0.25rem; border: 1px solid #ced4da; }		button { padding: 0.5rem 1rem; border: none; border-radius: 0.25rem; background-color: #007bff; color: white; cursor: pointer; }		button:disabled { background-color: #6c757d; cursor: wait; }		code { background-color: #e9ecef; padding: 0.2em 0.4em; border-radius: 3px; }		ol { padding-left: 1.5rem; }		#server-address-warning { color: #dc3545; font-weight: bold; display: none; border: 1px solid; padding: 0.5rem; border-radius: 0.25rem;}	</style></head><body>	<h3>360Works fmPromise Add-On</h3>	<!-- View 1: Shown for FileMaker Bridge errors -->	<div id="fm-error-instructions" class="content-box">		<h4>FileMaker Configuration Error</h4>		<p>The web viewer cannot communicate with FileMaker. Please check the following:</p>		<ol>			<li>The web viewer object is configured to <strong>"Allow JavaScript to Perform FileMaker Scripts"</strong>.</li>			<li>The web viewer object name is <code>fmPromiseWebViewer</code>.</li>			<li>The fmPromise scripts and custom functions were installed correctly.</li>		</ol>		<div id="fm-error-details">Could not execute a FileMaker script from JavaScript.</div>	</div>	<!-- View 2: Shown if dev server is offline -->	<div id="setup-instructions" class="content-box">		<div id="error">Could not connect to the local development server.</div>		<h4>Welcome to fmPromise!</h4>		<p>To create new web viewer modules, you need to run the local development server in a directory of your choice. fmPromise will create editable source code for your web viewer modules in this directory.</p>		<p>Please follow these steps in a terminal in the directory you would like to use for web viewer content (this can be any directory on your machine):</p>		<ol>			<li>				<strong>Install the package (you may need to <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" target="_blank">Install NPM</a> first)</strong>				<code>npm install @360works/fmpromise</code>			</li>			<li>				<strong>Start the development server:</strong>				<code>npx fmpromise-dev</code>			</li>			<li>Once the server is running, <strong>Reload this Web Viewer</strong> and call the <strong>fmPromise.toggleDevMode</strong> script to enable devMode.</li>		</ol>		<p>Consult the <a href="https://github.com/shmert/fmPromise" target="_blank">fmPromise documentation</a> for details.</p>		<div id="server-address-warning">			<strong>Warning:</strong> The FileMaker global variable <code>$$fmPromise_Server_Address</code> is not set or is invalid. Please ensure the "fmPromise.OnFirstWindowOpen" script has run correctly.		</div>	</div>	<!-- View 3: Shown on success -->	<div id="main-content" class="content-box">		<p>You have successfully added a fmPromise web viewer to your layout! 😊</p>		<p>Enter a <strong>unique path and/or filename</strong> for a new module:</p>		<form id="myForm" role="form">			<div class="form-control">				<label for="filename">New Module Path (e.g., "invoices/main.html")</label>				<input id="filename" name="filename" list="existing-files" placeholder="my-first-module" required/>				<datalist id="existing-files"></datalist>				<button type="submit">Create Module</button>			</div>		</form>	</div>	<!-- View 4: Shown after successful module creation -->	<div id="success" class="content-box">		<h3>Your fmPromise module has been created!</h3>		<p>The server has created the new files in your project's <code>src</code> directory.</p>		<p>You may now close this setup panel and reload the web viewer to display the new module.</p>	</div></body></html>]]></Data>
							</StyledText>
						</Cell>
						<Cell>
							<FieldReference type="Normal" datatype="Number" id="12" name="com.fmi.basetable.field.fmPromiseModule::73F9673850098681390CA23C51976B33" repetition="1" UUID="C632E5DC-43D3-48D6-93C0-543063A3B2E3">
								<BaseTableReference id="129" name="com.fmi.basetable.CE5A8F37EDDA1C956ECC9074C2253BEB" UUID="73DD828A-6FC6-4B6B-8B6D-5ACAE6F523CA"></BaseTableReference>
							</FieldReference>
						</Cell>
					</Row>
				</RowList>
			</Records>
		</AddAction>
	</Data>
</FMAdd_on>
