Feature: Support closing dialog windows (#233)

Supports closing dialogs in development. Upgrades gas-client to 1.2.0 and uses new API here: Add scriptHostFunctions for google.script.host gas-client#36
Example button to close dialog in Bootstrap sample
Updates integration tests
This commit is contained in:
Elisha Nuchi
2025-03-16 12:13:30 -04:00
committed by GitHub
parent 85223ca42a
commit 248a90023c
17 changed files with 2494 additions and 2502 deletions
@@ -9,9 +9,9 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [macos-12, macos-13, windows-2022] os: [macos-13, windows-2022]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node-version: [18, 20] node-version: [20]
timeout-minutes: 8 timeout-minutes: 8
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -9,9 +9,9 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [macos-12, macos-13, windows-2022] os: [macos-13, windows-2022]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node-version: [18, 20] node-version: [20]
timeout-minutes: 11 timeout-minutes: 11
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+8 -2
View File
@@ -309,8 +309,8 @@ google.script.run
.addSheet(sheetTitle); .addSheet(sheetTitle);
// Using gas-client we can use more familiar promises style like this: // Using gas-client we can use more familiar promises style like this:
import Server from 'gas-client'; import { GASClient } from 'gas-client';
const { serverFunctions } = new Server(); const { serverFunctions, scriptHostFunctions } = new GASClient({});
// We now have access to all our server functions, which return promises! // We now have access to all our server functions, which return promises!
serverFunctions serverFunctions
@@ -327,6 +327,12 @@ async () => {
handleError(err); handleError(err);
} }
}; };
// Use scriptHostFunctions to control dialogs
scriptHostFunctions.close(); // close a dialog or sidebar
scriptHostFunctions.setWidth(400); // set dialog width to 400px
scriptHostFunctions.setHeight(800); // set dialog height to 800px
``` ```
In development, `gas-client` will allow you to call server-side functions from your local environment. In production, it will use Google's underlying `google.script.run` utility. In development, `gas-client` will allow you to call server-side functions from your local environment. In production, it will use Google's underlying `google.script.run` utility.
+8 -2
View File
@@ -20,7 +20,7 @@ Two placeholders are used in this file that will need to be replaced in a build
<base target="_top" /> <base target="_top" />
<title>Dev Server</title> <title>Dev Server</title>
<!-- Load gas-client as external. Exposed global variable is GASClient. --> <!-- Load gas-client as external. Exposed global variable is GASClient. -->
<script src="https://unpkg.com/gas-client@1.1.1/dist/index.js"></script> <script src="https://unpkg.com/gas-client@1.2.0/dist/index.js"></script>
<style> <style>
body, body,
html { html {
@@ -37,7 +37,9 @@ Two placeholders are used in this file that will need to be replaced in a build
const iframe = document.getElementById('iframe'); const iframe = document.getElementById('iframe');
iframe.src = 'https://localhost:' + PORT + '/' + FILE_NAME; iframe.src = 'https://localhost:' + PORT + '/' + FILE_NAME;
const { serverFunctions } = new window.GASClient.GASClient({
const { serverFunctions, scriptHostFunctions } =
new window.GASClient.GASClient({
allowedDevelopmentDomains: (origin) => allowedDevelopmentDomains: (origin) =>
/https:\/\/.*\.googleusercontent\.com$/.test(origin), /https:\/\/.*\.googleusercontent\.com$/.test(origin),
}); });
@@ -46,6 +48,10 @@ Two placeholders are used in this file that will need to be replaced in a build
const request = event.data; const request = event.data;
const { type, functionName, id, args } = request; const { type, functionName, id, args } = request;
if (type === 'SCRIPT_HOST_FUNCTION_REQUEST') {
scriptHostFunctions[functionName](...args);
}
if (type !== 'REQUEST') return; if (type !== 'REQUEST') return;
serverFunctions[functionName](...args) serverFunctions[functionName](...args)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "react-google-apps-script", "name": "react-google-apps-script",
"version": "3.0.0", "version": "3.1.0",
"type": "module", "type": "module",
"description": "Starter project for using React with Google Apps Script", "description": "Starter project for using React with Google Apps Script",
"repository": { "repository": {
@@ -41,7 +41,7 @@
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@mui/material": "^5.11.11", "@mui/material": "^5.11.11",
"gas-client": "^1.1.1", "gas-client": "^1.2.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.4.0", "react-bootstrap": "^2.4.0",
@@ -4,10 +4,14 @@ import { Button, ListGroup } from 'react-bootstrap';
import FormInput from './FormInput'; import FormInput from './FormInput';
// This is a wrapper for google.script.run that lets us use promises. // This is a wrapper for google.script.run that lets us use promises.
import { serverFunctions } from '../../utils/serverFunctions'; import {
serverFunctions,
scriptHostFunctions,
} from '../../utils/serverFunctions';
const SheetEditor = () => { const SheetEditor = () => {
const [names, setNames] = useState([]); const [names, setNames] = useState([]);
const [isExpanded, setIsExpanded] = useState(false);
useEffect(() => { useEffect(() => {
serverFunctions.getSheetsData().then(setNames).catch(alert); serverFunctions.getSheetsData().then(setNames).catch(alert);
@@ -77,6 +81,29 @@ const SheetEditor = () => {
))} ))}
</TransitionGroup> </TransitionGroup>
</ListGroup> </ListGroup>
{names.length > 0 && (
<div className="d-flex justify-content-end py-3">
{!isExpanded ? (
<Button
variant="light"
className="mr-2"
onClick={() => {
scriptHostFunctions.setHeight(1000);
scriptHostFunctions.setWidth(1000);
setIsExpanded(true);
}}
>
Expand Dialog
</Button>
) : null}
<Button
variant="outline-dark"
onClick={() => scriptHostFunctions.close()}
>
Close Dialog Window
</Button>
</div>
)}
</div> </div>
); );
}; };
+1 -1
View File
@@ -21,7 +21,7 @@
></script> ></script>
<script <script
crossorigin crossorigin
src="https://unpkg.com/gas-client@1.1.1/dist/index.js" src="https://unpkg.com/gas-client@1.2.0/dist/index.js"
></script> ></script>
<script <script
crossorigin crossorigin
+1 -1
View File
@@ -17,7 +17,7 @@
></script> ></script>
<script <script
crossorigin crossorigin
src="https://unpkg.com/gas-client@1.1.1/dist/index.js" src="https://unpkg.com/gas-client@1.2.0/dist/index.js"
></script> ></script>
<script <script
crossorigin crossorigin
@@ -17,7 +17,7 @@
></script> ></script>
<script <script
crossorigin crossorigin
src="https://unpkg.com/gas-client@1.1.1/dist/index.js" src="https://unpkg.com/gas-client@1.2.0/dist/index.js"
></script> ></script>
<script <script
crossorigin crossorigin
+1 -1
View File
@@ -17,7 +17,7 @@
></script> ></script>
<script <script
crossorigin crossorigin
src="https://unpkg.com/gas-client@1.1.1/dist/index.js" src="https://unpkg.com/gas-client@1.2.0/dist/index.js"
></script> ></script>
<script <script
crossorigin crossorigin
+4 -2
View File
@@ -1,10 +1,12 @@
import { GASClient } from 'gas-client'; import { GASClient } from 'gas-client';
import * as publicServerFunctions from '../../server'; import * as publicServerFunctions from '../../server';
const { serverFunctions } = new GASClient<typeof publicServerFunctions>({ const { serverFunctions, scriptHostFunctions } = new GASClient<
typeof publicServerFunctions
>({
// this is necessary for local development but will be ignored in production // this is necessary for local development but will be ignored in production
allowedDevelopmentDomains: (origin) => allowedDevelopmentDomains: (origin) =>
/https:\/\/.*\.googleusercontent\.com$/.test(origin), /https:\/\/.*\.googleusercontent\.com$/.test(origin),
}); });
export { serverFunctions }; export { serverFunctions, scriptHostFunctions };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 63 KiB

+3 -3
View File
@@ -26,7 +26,7 @@ const srcTestFile = path.join(
'../src/client/dialog-demo-bootstrap/components/SheetEditor.jsx' '../src/client/dialog-demo-bootstrap/components/SheetEditor.jsx'
); );
const webpackDevServerReady = async (process) => { const viteDevServerReady = async (process) => {
console.log('Waiting for vite to serve...'); console.log('Waiting for vite to serve...');
return new Promise((resolve) => { return new Promise((resolve) => {
process.stdout.on('data', (data) => { process.stdout.on('data', (data) => {
@@ -40,7 +40,7 @@ const webpackDevServerReady = async (process) => {
describe(`Local setup ${isExtended ? '*extended*' : ''}`, () => { describe(`Local setup ${isExtended ? '*extended*' : ''}`, () => {
let page; let page;
let process; let process;
const containerSelector = isExtended ? '.script-app-dialog' : 'body'; const containerSelector = isExtended ? 'div[role="dialog"]' : 'body';
beforeAll(async () => { beforeAll(async () => {
process = exec('yarn dev'); process = exec('yarn dev');
@@ -52,7 +52,7 @@ describe(`Local setup ${isExtended ? '*extended*' : ''}`, () => {
deviceScaleFactor: 1, deviceScaleFactor: 1,
}); });
await webpackDevServerReady(process); await viteDevServerReady(process);
if (isExtended) { if (isExtended) {
await openAddon(page); await openAddon(page);
+3 -2
View File
@@ -1,6 +1,7 @@
export const openAddon = async (page) => { export const openAddon = async (page) => {
await page.goto(process.env.SHEET_URL); await page.goto(process.env.SHEET_URL);
await page.waitForTimeout(5000); // pause for 3 seconds
await page.click('a:nth-child(2)'); // click on signin button await page.click('a:nth-child(2)'); // click on signin button
await page.waitForSelector('input[name="identifier"]', { visible: true }); await page.waitForSelector('input[name="identifier"]', { visible: true });
@@ -97,10 +98,10 @@ export const openAddon = async (page) => {
new MouseEvent('mouseup', { bubbles: true }) new MouseEvent('mouseup', { bubbles: true })
); );
}); });
await page.waitForSelector('.script-app-dialog', { await page.waitForSelector('div[role="dialog"]', {
visible: true, visible: true,
timeout: 10000, timeout: 10000,
}); });
await page.waitForTimeout(3000); await page.waitForTimeout(15000);
}; };
+2427 -2477
View File
File diff suppressed because it is too large Load Diff