+1
-1
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"rootDir": "dist",
|
"rootDir": "dist",
|
||||||
"scriptId":"*** scriptId from GAS Script Editor, File -> Project properties ***"
|
"scriptId":"17yhkDaoyccM467KaeWLmLG3DndMgoeUCawf9SEkMq1-AogZ6l1c9e3Qe"
|
||||||
}
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"parser": "babel-eslint",
|
|
||||||
"extends": ["eslint:recommended", "google", "plugin:prettier/recommended"],
|
|
||||||
"plugins": ["prettier", "googleappsscript"],
|
|
||||||
"env": {
|
|
||||||
"googleappsscript/googleappsscript": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"prettier/prettier": "error",
|
|
||||||
"import/prefer-default-export": 0,
|
|
||||||
"no-console": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-1
@@ -1 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
.clasp.json
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"printWidth": 120,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"ecmaVersion": 7,
|
||||||
|
"libs": [
|
||||||
|
],
|
||||||
|
"plugins": {
|
||||||
|
"complete_strings": {},
|
||||||
|
"googleappsscript": {},
|
||||||
|
"es_modules": {},
|
||||||
|
"node": {},
|
||||||
|
"doc_comment": {
|
||||||
|
"fullDocs": true,
|
||||||
|
"strong": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
#React + Google Apps Script#
|
## React + Google Apps Script
|
||||||
|
*Use this repo as your boilerplate React app for use with HTML dialogs in Google Sheets, Docs and Forms.*
|
||||||
|
|
||||||
Use this repo as your boilerplate React app for use with Google Apps Script html dialogs.
|
This project uses labnol's excellent [apps-script-starter](https://github.com/labnol/apps-script-starter) as a starting point, adding support for client-side dialogs built with React. It demonstrates how easy it is to build React apps that interact with Google Apps server-side scripts.
|
||||||
|
|
||||||
It uses labnol's excellent [apps-script-starter](https://github.com/labnol/apps-script-starter) project as a starting point, but adds support for client-side dialogs built with React, and shows how React pages can interact with Google Apps server-side script.
|
## Installation
|
||||||
|
|
||||||
**Installation**
|
|
||||||
|
|
||||||
Set up the sample project:
|
Set up the sample project:
|
||||||
```
|
```
|
||||||
mkdir my-gas-react-app && cd my-gas-react-app
|
mkdir my-gas-react-app && cd my-gas-react-app
|
||||||
clone https://github.com/enuchi/React-Google-Apps-Script.git
|
git clone https://github.com/enuchi/React-Google-Apps-Script.git
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
Create a new Google Sheets spreadsheet. Open the Script Editor (Tools --> Script Editor) and copy the ScriptId (File --> Project properties).
|
Create a new Google Sheets spreadsheet. Open the Script Editor (Tools --> Script Editor) and copy the ScriptId (File --> Project properties).
|
||||||
@@ -17,17 +16,72 @@ Create a new Google Sheets spreadsheet. Open the Script Editor (Tools --> Script
|
|||||||
Add the ScriptId to the .clasp.json file:
|
Add the ScriptId to the .clasp.json file:
|
||||||
```
|
```
|
||||||
// .clasp.json
|
// .clasp.json
|
||||||
{
|
{"rootDir": "dist",
|
||||||
"rootDir": "dist",
|
"scriptId":"...paste scriptId here..."}
|
||||||
"scriptId":"...paste scriptId here..."
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
Log into CLASP, which lets you develop Apps Script projects locally, and follow the authorization flow:
|
Log into CLASP, a tool that lets you develop Apps Script projects locally, and follow the authorization flow:
|
||||||
```
|
```
|
||||||
npx clasp login
|
npx clasp login
|
||||||
```
|
```
|
||||||
Build and deploy the app!
|
Modify your server-side and client-side code in the `src` folder using ES6/7 and React. Change the scopes in `appsscript.json`. When you're ready, build the app!
|
||||||
```
|
```
|
||||||
npm run build
|
npm run build
|
||||||
npm run deploy
|
|
||||||
```
|
```
|
||||||
|
Webpack will bundle your files in `dist`. Push your files to Google's servers using CLASP:
|
||||||
|
```
|
||||||
|
clasp push
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## The sample app
|
||||||
|
Insert new sheets and delete sheets through a simple dialog, built with React:
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
Code is written in the `src` directory and bundled into the `dist` directory when `npm run build` is run. CLASP pushes files from `dist` to Script Editor files.
|
||||||
|
|
||||||
|
Multiple Webpack entry points are used to generate code that is compatible with the Google Apps Script environment on both server and client. On the server side that means ending up as (basically) ES5. On the client side, the challenge is that GAS does not easily support multiple pages, so a single HTML page is created. This is done with the help of some Webpack plugins that use HTML templates and add the necessary CSS and JavaScript/React assets inline. (You'll see the original output bundle, `main.js`, in the `dist` directory, but it's ignored through .clasp.ignore). The appsscript.json manifest file is simply copied into `dist`.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Support for JSX syntax:
|
||||||
|
```
|
||||||
|
render() {
|
||||||
|
return (<div className="formBlock"><span>Add a sheet: </span>
|
||||||
|
<form onSubmit={(e) => this.handleSubmit(e)}>
|
||||||
|
<input onChange={this.handleChange} value={this.state.text} />
|
||||||
|
</form>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Support for npm packages. Simply install with npm and `import`:
|
||||||
|
```
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||||
|
```
|
||||||
|
- `import` CSS from another file:
|
||||||
|
```
|
||||||
|
import "./styles.css";
|
||||||
|
```
|
||||||
|
- Shows how server calls are made in React using `google.script.run`:
|
||||||
|
```
|
||||||
|
componentDidMount() {
|
||||||
|
google.script.run
|
||||||
|
.withSuccessHandler((data) => this.setState({names: data}))
|
||||||
|
.withFailureHandler((error) => alert(error))
|
||||||
|
.getSheetsData()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extending this app
|
||||||
|
- You can split up server-side code into multiple files and folders . Simply use `import` and `export`.
|
||||||
|
- Make sure to expose all public functions (functions called from the client with `google.script.run`) as well as onOpen using e.g.:
|
||||||
|
```
|
||||||
|
const onOpen = () => {
|
||||||
|
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
|
||||||
|
.createMenu('Dialog')
|
||||||
|
.addItem('Add sheets', 'openDialog')
|
||||||
|
.addToUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
global.onOpen = onOpen
|
||||||
|
```
|
||||||
Vendored
+108
-171
@@ -7,175 +7,112 @@ function getSheetsData() {
|
|||||||
function addSheet() {
|
function addSheet() {
|
||||||
}
|
}
|
||||||
function deleteSheet() {
|
function deleteSheet() {
|
||||||
}/******/ (function(modules) { // webpackBootstrap
|
|
||||||
/******/ // The module cache
|
|
||||||
/******/ var installedModules = {};
|
|
||||||
/******/
|
|
||||||
/******/ // The require function
|
|
||||||
/******/ function __webpack_require__(moduleId) {
|
|
||||||
/******/
|
|
||||||
/******/ // Check if module is in cache
|
|
||||||
/******/ if(installedModules[moduleId]) {
|
|
||||||
/******/ return installedModules[moduleId].exports;
|
|
||||||
/******/ }
|
|
||||||
/******/ // Create a new module (and put it into the cache)
|
|
||||||
/******/ var module = installedModules[moduleId] = {
|
|
||||||
/******/ i: moduleId,
|
|
||||||
/******/ l: false,
|
|
||||||
/******/ exports: {}
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // Execute the module function
|
|
||||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
||||||
/******/
|
|
||||||
/******/ // Flag the module as loaded
|
|
||||||
/******/ module.l = true;
|
|
||||||
/******/
|
|
||||||
/******/ // Return the exports of the module
|
|
||||||
/******/ return module.exports;
|
|
||||||
/******/ }
|
|
||||||
/******/
|
|
||||||
/******/
|
|
||||||
/******/ // expose the modules object (__webpack_modules__)
|
|
||||||
/******/ __webpack_require__.m = modules;
|
|
||||||
/******/
|
|
||||||
/******/ // expose the module cache
|
|
||||||
/******/ __webpack_require__.c = installedModules;
|
|
||||||
/******/
|
|
||||||
/******/ // define getter function for harmony exports
|
|
||||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
|
||||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
|
||||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
|
||||||
/******/ }
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // define __esModule on exports
|
|
||||||
/******/ __webpack_require__.r = function(exports) {
|
|
||||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
|
||||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
||||||
/******/ }
|
|
||||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // create a fake namespace object
|
|
||||||
/******/ // mode & 1: value is a module id, require it
|
|
||||||
/******/ // mode & 2: merge all properties of value into the ns
|
|
||||||
/******/ // mode & 4: return value when already ns object
|
|
||||||
/******/ // mode & 8|1: behave like require
|
|
||||||
/******/ __webpack_require__.t = function(value, mode) {
|
|
||||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
|
||||||
/******/ if(mode & 8) return value;
|
|
||||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
|
||||||
/******/ var ns = Object.create(null);
|
|
||||||
/******/ __webpack_require__.r(ns);
|
|
||||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
|
||||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
|
||||||
/******/ return ns;
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
|
||||||
/******/ __webpack_require__.n = function(module) {
|
|
||||||
/******/ var getter = module && module.__esModule ?
|
|
||||||
/******/ function getDefault() { return module['default']; } :
|
|
||||||
/******/ function getModuleExports() { return module; };
|
|
||||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
|
||||||
/******/ return getter;
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // Object.prototype.hasOwnProperty.call
|
|
||||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
|
||||||
/******/
|
|
||||||
/******/ // __webpack_public_path__
|
|
||||||
/******/ __webpack_require__.p = "";
|
|
||||||
/******/
|
|
||||||
/******/
|
|
||||||
/******/ // Load entry module and return exports
|
|
||||||
/******/ return __webpack_require__(__webpack_require__.s = 0);
|
|
||||||
/******/ })
|
|
||||||
/************************************************************************/
|
|
||||||
/******/ ([
|
|
||||||
/* 0 */
|
|
||||||
/***/ (function(module, exports, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
/* WEBPACK VAR INJECTION */(function(global) {
|
|
||||||
|
|
||||||
// Use ES6/7 code
|
|
||||||
var onOpen = function onOpen() {
|
|
||||||
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
|
|
||||||
.createMenu('Dialog').addItem('Add sheets', 'openDialog').addToUi();
|
|
||||||
};
|
|
||||||
|
|
||||||
var openDialog = function openDialog() {
|
|
||||||
var html = HtmlService.createHtmlOutputFromFile('dialog');
|
|
||||||
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
|
|
||||||
.showModalDialog(html, 'Sheet Editor');
|
|
||||||
};
|
|
||||||
|
|
||||||
var getSheets = function getSheets() {
|
|
||||||
return SpreadsheetApp.getActive().getSheets();
|
|
||||||
};
|
|
||||||
|
|
||||||
var getActiveSheetName = function getActiveSheetName() {
|
|
||||||
return SpreadsheetApp.getActive().getSheetName();
|
|
||||||
};
|
|
||||||
|
|
||||||
var getSheetsData = function getSheetsData() {
|
|
||||||
var activeSheetName = getActiveSheetName();
|
|
||||||
return getSheets().map(function (sheet, index) {
|
|
||||||
var sheetName = sheet.getName();
|
|
||||||
return {
|
|
||||||
text: sheetName,
|
|
||||||
sheetIndex: index,
|
|
||||||
isActive: sheetName === activeSheetName
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var addSheet = function addSheet(sheetTitle) {
|
|
||||||
SpreadsheetApp.getActive().insertSheet(sheetTitle);
|
|
||||||
return getSheetsData();
|
|
||||||
};
|
|
||||||
|
|
||||||
var deleteSheet = function deleteSheet(sheetIndex) {
|
|
||||||
var sheets = getSheets();
|
|
||||||
SpreadsheetApp.getActive().deleteSheet(sheets[sheetIndex]);
|
|
||||||
return getSheetsData();
|
|
||||||
};
|
|
||||||
|
|
||||||
//Must expose public functions
|
|
||||||
global.onOpen = onOpen;
|
|
||||||
global.openDialog = openDialog;
|
|
||||||
global.getSheetsData = getSheetsData;
|
|
||||||
global.addSheet = addSheet;
|
|
||||||
global.deleteSheet = deleteSheet;
|
|
||||||
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
/* 1 */
|
|
||||||
/***/ (function(module, exports) {
|
|
||||||
|
|
||||||
var g;
|
|
||||||
|
|
||||||
// This works in non-strict mode
|
|
||||||
g = (function() {
|
|
||||||
return this;
|
|
||||||
})();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// This works if eval is allowed (see CSP)
|
|
||||||
g = g || Function("return this")() || (1, eval)("this");
|
|
||||||
} catch (e) {
|
|
||||||
// This works if the window reference is available
|
|
||||||
if (typeof window === "object") g = window;
|
|
||||||
}
|
}
|
||||||
|
function setActiveSheet() {
|
||||||
// g can still be undefined, but nothing to do about it...
|
}!function(e, a) {
|
||||||
// We return undefined, instead of nothing here, so it's
|
for (var i in a) e[i] = a[i];
|
||||||
// easier to handle this case. if(!global) { ...}
|
}(this, function(modules) {
|
||||||
|
var installedModules = {};
|
||||||
module.exports = g;
|
function __webpack_require__(moduleId) {
|
||||||
|
if (installedModules[moduleId]) return installedModules[moduleId].exports;
|
||||||
|
var module = installedModules[moduleId] = {
|
||||||
/***/ })
|
i: moduleId,
|
||||||
/******/ ]);
|
l: !1,
|
||||||
|
exports: {}
|
||||||
|
};
|
||||||
|
return modules[moduleId].call(module.exports, module, module.exports, __webpack_require__),
|
||||||
|
module.l = !0, module.exports;
|
||||||
|
}
|
||||||
|
return __webpack_require__.m = modules, __webpack_require__.c = installedModules,
|
||||||
|
__webpack_require__.d = function(exports, name, getter) {
|
||||||
|
__webpack_require__.o(exports, name) || Object.defineProperty(exports, name, {
|
||||||
|
enumerable: !0,
|
||||||
|
get: getter
|
||||||
|
});
|
||||||
|
}, __webpack_require__.r = function(exports) {
|
||||||
|
"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(exports, Symbol.toStringTag, {
|
||||||
|
value: "Module"
|
||||||
|
}), Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: !0
|
||||||
|
});
|
||||||
|
}, __webpack_require__.t = function(value, mode) {
|
||||||
|
if (1 & mode && (value = __webpack_require__(value)), 8 & mode) return value;
|
||||||
|
if (4 & mode && "object" == typeof value && value && value.__esModule) return value;
|
||||||
|
var ns = Object.create(null);
|
||||||
|
if (__webpack_require__.r(ns), Object.defineProperty(ns, "default", {
|
||||||
|
enumerable: !0,
|
||||||
|
value: value
|
||||||
|
}), 2 & mode && "string" != typeof value) for (var key in value) __webpack_require__.d(ns, key, function(key) {
|
||||||
|
return value[key];
|
||||||
|
}.bind(null, key));
|
||||||
|
return ns;
|
||||||
|
}, __webpack_require__.n = function(module) {
|
||||||
|
var getter = module && module.__esModule ? function() {
|
||||||
|
return module["default"];
|
||||||
|
} : function() {
|
||||||
|
return module;
|
||||||
|
};
|
||||||
|
return __webpack_require__.d(getter, "a", getter), getter;
|
||||||
|
}, __webpack_require__.o = function(object, property) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(object, property);
|
||||||
|
}, __webpack_require__.p = "", __webpack_require__(__webpack_require__.s = 1);
|
||||||
|
}([ function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
"use strict";
|
||||||
|
__webpack_require__.d(__webpack_exports__, "d", function() {
|
||||||
|
return onOpen;
|
||||||
|
}), __webpack_require__.d(__webpack_exports__, "e", function() {
|
||||||
|
return openDialog;
|
||||||
|
}), __webpack_require__.d(__webpack_exports__, "c", function() {
|
||||||
|
return getSheetsData;
|
||||||
|
}), __webpack_require__.d(__webpack_exports__, "a", function() {
|
||||||
|
return addSheet;
|
||||||
|
}), __webpack_require__.d(__webpack_exports__, "b", function() {
|
||||||
|
return deleteSheet;
|
||||||
|
}), __webpack_require__.d(__webpack_exports__, "f", function() {
|
||||||
|
return setActiveSheet;
|
||||||
|
});
|
||||||
|
var onOpen = function() {
|
||||||
|
SpreadsheetApp.getUi().createMenu("Custom scripts").addItem("Edit sheets [sample React project]", "openDialog").addToUi();
|
||||||
|
}, openDialog = function() {
|
||||||
|
var html = HtmlService.createHtmlOutputFromFile("dialog").setWidth(400).setHeight(600);
|
||||||
|
SpreadsheetApp.getUi().showModalDialog(html, "Sheet Editor");
|
||||||
|
}, getSheets = function() {
|
||||||
|
return SpreadsheetApp.getActive().getSheets();
|
||||||
|
}, getSheetsData = function() {
|
||||||
|
var activeSheetName = SpreadsheetApp.getActive().getSheetName();
|
||||||
|
return getSheets().map(function(sheet, index) {
|
||||||
|
var sheetName = sheet.getName();
|
||||||
|
return {
|
||||||
|
text: sheetName,
|
||||||
|
sheetIndex: index,
|
||||||
|
isActive: sheetName === activeSheetName
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, addSheet = function(sheetTitle) {
|
||||||
|
return SpreadsheetApp.getActive().insertSheet(sheetTitle), getSheetsData();
|
||||||
|
}, deleteSheet = function(sheetIndex) {
|
||||||
|
var sheets = getSheets();
|
||||||
|
return SpreadsheetApp.getActive().deleteSheet(sheets[sheetIndex]), getSheetsData();
|
||||||
|
}, setActiveSheet = function(sheetName) {
|
||||||
|
return SpreadsheetApp.getActive().getSheetByName(sheetName).activate(), getSheetsData();
|
||||||
|
};
|
||||||
|
}, function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
"use strict";
|
||||||
|
__webpack_require__.r(__webpack_exports__), function(global) {
|
||||||
|
var _sheets_utilities_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
|
||||||
|
global.onOpen = _sheets_utilities_js__WEBPACK_IMPORTED_MODULE_0__["d"], global.openDialog = _sheets_utilities_js__WEBPACK_IMPORTED_MODULE_0__["e"],
|
||||||
|
global.getSheetsData = _sheets_utilities_js__WEBPACK_IMPORTED_MODULE_0__["c"], global.addSheet = _sheets_utilities_js__WEBPACK_IMPORTED_MODULE_0__["a"],
|
||||||
|
global.deleteSheet = _sheets_utilities_js__WEBPACK_IMPORTED_MODULE_0__["b"], global.setActiveSheet = _sheets_utilities_js__WEBPACK_IMPORTED_MODULE_0__["f"];
|
||||||
|
}.call(this, __webpack_require__(2));
|
||||||
|
}, function(module, exports) {
|
||||||
|
var g;
|
||||||
|
g = function() {
|
||||||
|
return this;
|
||||||
|
}();
|
||||||
|
try {
|
||||||
|
g = g || Function("return this")() || (0, eval)("this");
|
||||||
|
} catch (e) {
|
||||||
|
"object" == typeof window && (g = window);
|
||||||
|
}
|
||||||
|
module.exports = g;
|
||||||
|
} ]));
|
||||||
Vendored
+5125
-26
File diff suppressed because one or more lines are too long
Vendored
+5125
-22
File diff suppressed because one or more lines are too long
Generated
+117
-32
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "react-google-apps-script-2",
|
"name": "react-google-apps-script",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
@@ -3378,13 +3378,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "5.3.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.6.0.tgz",
|
||||||
"integrity": "sha512-N/tCqlMKkyNvAvLu+zI9AqDasnSLt00K+Hu8kdsERliC9jYEc8ck12XtjvOXrBKu8fK6RrBcN9bat6Xk++9jAg==",
|
"integrity": "sha512-/eVYs9VVVboX286mBK7bbKnO1yamUy2UCRjiY6MryhQL2PaaXCExsCQ2aO83OeYRhU2eCU/FMFP+tVMoOrzNrA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "6.5.2",
|
"@babel/code-frame": "7.0.0",
|
||||||
"babel-code-frame": "6.26.0",
|
"ajv": "6.5.3",
|
||||||
"chalk": "2.4.1",
|
"chalk": "2.4.1",
|
||||||
"cross-spawn": "6.0.5",
|
"cross-spawn": "6.0.5",
|
||||||
"debug": "3.1.0",
|
"debug": "3.1.0",
|
||||||
@@ -3401,7 +3401,7 @@
|
|||||||
"globals": "11.7.0",
|
"globals": "11.7.0",
|
||||||
"ignore": "4.0.6",
|
"ignore": "4.0.6",
|
||||||
"imurmurhash": "0.1.4",
|
"imurmurhash": "0.1.4",
|
||||||
"inquirer": "5.2.0",
|
"inquirer": "6.2.0",
|
||||||
"is-resolvable": "1.1.0",
|
"is-resolvable": "1.1.0",
|
||||||
"js-yaml": "3.12.0",
|
"js-yaml": "3.12.0",
|
||||||
"json-stable-stringify-without-jsonify": "1.0.1",
|
"json-stable-stringify-without-jsonify": "1.0.1",
|
||||||
@@ -3416,12 +3416,113 @@
|
|||||||
"progress": "2.0.0",
|
"progress": "2.0.0",
|
||||||
"regexpp": "2.0.0",
|
"regexpp": "2.0.0",
|
||||||
"require-uncached": "1.0.3",
|
"require-uncached": "1.0.3",
|
||||||
"semver": "5.5.0",
|
"semver": "5.5.1",
|
||||||
"string.prototype.matchall": "2.0.0",
|
|
||||||
"strip-ansi": "4.0.0",
|
"strip-ansi": "4.0.0",
|
||||||
"strip-json-comments": "2.0.1",
|
"strip-json-comments": "2.0.1",
|
||||||
"table": "4.0.3",
|
"table": "4.0.3",
|
||||||
"text-table": "0.2.0"
|
"text-table": "0.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/highlight": "7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/highlight": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"chalk": "2.4.1",
|
||||||
|
"esutils": "2.0.2",
|
||||||
|
"js-tokens": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ajv": {
|
||||||
|
"version": "6.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz",
|
||||||
|
"integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fast-deep-equal": "2.0.1",
|
||||||
|
"fast-json-stable-stringify": "2.0.0",
|
||||||
|
"json-schema-traverse": "0.4.1",
|
||||||
|
"uri-js": "4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chardet": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
||||||
|
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"external-editor": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"chardet": "0.7.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"tmp": "0.0.33"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": "2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inquirer": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-escapes": "3.1.0",
|
||||||
|
"chalk": "2.4.1",
|
||||||
|
"cli-cursor": "2.1.0",
|
||||||
|
"cli-width": "2.2.0",
|
||||||
|
"external-editor": "3.0.3",
|
||||||
|
"figures": "2.0.0",
|
||||||
|
"lodash": "4.17.10",
|
||||||
|
"mute-stream": "0.0.7",
|
||||||
|
"run-async": "2.3.0",
|
||||||
|
"rxjs": "6.3.2",
|
||||||
|
"string-width": "2.1.1",
|
||||||
|
"strip-ansi": "4.0.0",
|
||||||
|
"through": "2.3.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"js-tokens": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"rxjs": {
|
||||||
|
"version": "6.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz",
|
||||||
|
"integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"tslib": "1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "5.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
|
||||||
|
"integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-config-airbnb": {
|
"eslint-config-airbnb": {
|
||||||
@@ -7540,15 +7641,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"regexp.prototype.flags": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"define-properties": "1.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"regexpp": {
|
"regexpp": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz",
|
||||||
@@ -8309,19 +8401,6 @@
|
|||||||
"strip-ansi": "4.0.0"
|
"strip-ansi": "4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string.prototype.matchall": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"define-properties": "1.1.2",
|
|
||||||
"es-abstract": "1.12.0",
|
|
||||||
"function-bind": "1.1.1",
|
|
||||||
"has-symbols": "1.0.0",
|
|
||||||
"regexp.prototype.flags": "1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string.prototype.padend": {
|
"string.prototype.padend": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz",
|
||||||
@@ -8404,7 +8483,7 @@
|
|||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz",
|
"resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz",
|
||||||
"integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==",
|
"integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -8422,6 +8501,12 @@
|
|||||||
"integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==",
|
"integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"tern-googleappsscript": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tern-googleappsscript/-/tern-googleappsscript-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-f8iw7eu62bWeZyxWQj8vZP4wQRCbs7IXe4H6+JKWLHOuNvYAeSXzL10sPW/6bh6u8fPGTYS6z38OEDt1B1GdDw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"text-table": {
|
"text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
|
|||||||
+4
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "react-google-apps-script",
|
"name": "react-google-apps-script",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Boilerplate sample project for using React with Google Apps Scipt",
|
"description": "Starter project for using React with Google Apps Script",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "test",
|
"test": "test",
|
||||||
"start": "webpack --mode development",
|
"start": "webpack --mode development",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"clean-webpack-plugin": "^0.1.19",
|
"clean-webpack-plugin": "^0.1.19",
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"eslint": "^5.3.0",
|
"eslint": "^5.6.0",
|
||||||
"eslint-config-airbnb": "^17.0.0",
|
"eslint-config-airbnb": "^17.0.0",
|
||||||
"eslint-config-airbnb-base": "^13.0.0",
|
"eslint-config-airbnb-base": "^13.0.0",
|
||||||
"eslint-config-google": "^0.9.1",
|
"eslint-config-google": "^0.9.1",
|
||||||
@@ -56,11 +56,13 @@
|
|||||||
"html-webpack-inline-source-plugin": "0.0.10",
|
"html-webpack-inline-source-plugin": "0.0.10",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"prettier": "^1.13.7",
|
"prettier": "^1.13.7",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
"react-addons-css-transition-group": "^15.6.2",
|
"react-addons-css-transition-group": "^15.6.2",
|
||||||
"react-dom": "^16.4.2",
|
"react-dom": "^16.4.2",
|
||||||
"react-transition-group": "^2.4.0",
|
"react-transition-group": "^2.4.0",
|
||||||
"style-loader": "^0.22.1",
|
"style-loader": "^0.22.1",
|
||||||
|
"tern-googleappsscript": "^1.0.0",
|
||||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||||
"webpack": "^4.16.1",
|
"webpack": "^4.16.1",
|
||||||
"webpack-cli": "^3.1.0"
|
"webpack-cli": "^3.1.0"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"presets": ["react"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"extends": ["plugin:react/recommended", "eslint:recommended", "google"],
|
||||||
|
"plugins": ["react"],
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"google": false,
|
||||||
|
"alert": false,
|
||||||
|
"css": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"import/prefer-default-export": 0,
|
||||||
|
"no-console": 0,
|
||||||
|
"require-jsdoc" : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<base target="_top">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="index">
|
||||||
|
<!-- bundled js and css will get inlined here -->
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,26 +1,27 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from 'react-dom';
|
||||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||||
import css from "./styles.css";
|
import PropTypes from 'prop-types';
|
||||||
|
import './styles.css';
|
||||||
|
|
||||||
class FormInput extends React.Component {
|
class FormInput extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {text: ""};
|
this.state = {text: ''};
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.handleChange = this.handleChange.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(e) {
|
handleChange(e) {
|
||||||
this.setState({ text: e.target.value });
|
this.setState({text: e.target.value});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!this.state.text.length) return;
|
if (!this.state.text.length) return;
|
||||||
this.props.newSheetFormHandler(e, this.state.text)
|
this.props.newSheetFormHandler(e, this.state.text);
|
||||||
this.setState(prevState => ({
|
this.setState((prevState) => ({
|
||||||
text: ""
|
text: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,21 +30,23 @@ class FormInput extends React.Component {
|
|||||||
<div className="formBlock">
|
<div className="formBlock">
|
||||||
<span>Add a sheet: </span>
|
<span>Add a sheet: </span>
|
||||||
<form onSubmit={(e) => this.handleSubmit(e)}>
|
<form onSubmit={(e) => this.handleSubmit(e)}>
|
||||||
<input
|
<input onChange={this.handleChange} value={this.state.text} />
|
||||||
onChange={this.handleChange}
|
|
||||||
value={this.state.text}
|
|
||||||
/>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FormInput.propTypes = {
|
||||||
|
newSheetFormHandler: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
class SheetEditor extends React.Component {
|
class SheetEditor extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {names: []};
|
this.state = {names: []};
|
||||||
this.deleteButtonHandler = this.deleteButtonHandler.bind(this);
|
this.deleteButtonHandler = this.deleteButtonHandler.bind(this);
|
||||||
|
this.clickSheetNameHandler = this.clickSheetNameHandler.bind(this);
|
||||||
this.newSheetFormHandler = this.newSheetFormHandler.bind(this);
|
this.newSheetFormHandler = this.newSheetFormHandler.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,43 +54,50 @@ class SheetEditor extends React.Component {
|
|||||||
google.script.run
|
google.script.run
|
||||||
.withSuccessHandler((data) => this.setState({names: data}))
|
.withSuccessHandler((data) => this.setState({names: data}))
|
||||||
.withFailureHandler((error) => alert(error))
|
.withFailureHandler((error) => alert(error))
|
||||||
.getSheetsData()
|
.getSheetsData();
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteButtonHandler(e, sheetIndex) {
|
deleteButtonHandler(e, sheetIndex) {
|
||||||
return google.script.run
|
return google.script.run
|
||||||
.withSuccessHandler((data) => this.setState({names: data}))
|
.withSuccessHandler((data) => this.setState({names: data}))
|
||||||
.withFailureHandler((error) => alert(error))
|
.withFailureHandler((error) => alert(error))
|
||||||
.deleteSheet(sheetIndex)
|
.deleteSheet(sheetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
clickSheetNameHandler(e, sheetName) {
|
||||||
|
return google.script.run
|
||||||
|
.withSuccessHandler((data) => this.setState({names: data}))
|
||||||
|
.withFailureHandler((error) => alert(error))
|
||||||
|
.setActiveSheet(sheetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
newSheetFormHandler(e, newSheetTitle) {
|
newSheetFormHandler(e, newSheetTitle) {
|
||||||
google.script.run
|
return google.script.run
|
||||||
.withSuccessHandler((data) => this.setState({names: data}))
|
.withSuccessHandler((data) => this.setState({names: data}))
|
||||||
.withFailureHandler((error) => alert(error))
|
.withFailureHandler((error) => alert(error))
|
||||||
.addSheet(newSheetTitle);
|
.addSheet(newSheetTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let names = this.state.names
|
let names = this.state.names;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormInput newSheetFormHandler={this.newSheetFormHandler}/>
|
<FormInput newSheetFormHandler={this.newSheetFormHandler} />
|
||||||
<span>Clicking on the X will delete the respective sheet. This cannot be undoed. It is recommended you use this sample tool on a new, empty Spreadsheet.</span>
|
|
||||||
<ReactCSSTransitionGroup
|
<ReactCSSTransitionGroup
|
||||||
transitionName="sheetNames"
|
transitionName="sheetNames"
|
||||||
transitionAppear={true}
|
transitionAppear={true}
|
||||||
transitionEnterTimeout={100}
|
transitionEnterTimeout={100}
|
||||||
transitionLeaveTimeout={100}>
|
transitionLeaveTimeout={100}
|
||||||
{
|
>
|
||||||
names.length ?
|
{names.length ? names.map((name) => {
|
||||||
names.map((name) => {
|
return <SheetButton
|
||||||
return (
|
name={name}
|
||||||
<SheetButton name={name} deleteButtonHandler={this.deleteButtonHandler}/>
|
deleteButtonHandler={this.deleteButtonHandler}
|
||||||
)
|
clickSheetNameHandler={this.clickSheetNameHandler}
|
||||||
})
|
key={name.sheetName}
|
||||||
: null
|
/>;
|
||||||
}
|
})
|
||||||
|
: null}
|
||||||
</ReactCSSTransitionGroup>
|
</ReactCSSTransitionGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -102,19 +112,23 @@ class SheetButton extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="sheetLine">
|
<div className="sheetLine">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => this.props.deleteButtonHandler(e,sheetIndex)}
|
onClick={(e) => this.props.deleteButtonHandler(e, sheetIndex)}
|
||||||
>
|
>X
|
||||||
X
|
|
||||||
</button>
|
</button>
|
||||||
<span className={"sheetNameText " + (isActiveSheet ? "active-sheet" : "")}>
|
<span
|
||||||
{sheetName}
|
onClick={(e) => this.props.clickSheetNameHandler(e, sheetName)}
|
||||||
|
className={'sheetNameText ' + (isActiveSheet ? 'active-sheet' : '')}
|
||||||
|
>{sheetName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
SheetButton.propTypes = {
|
||||||
<SheetEditor />,
|
name: PropTypes.object,
|
||||||
document.getElementById("index")
|
deleteButtonHandler: PropTypes.func,
|
||||||
);
|
clickSheetNameHandler: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<SheetEditor />, document.getElementById('index'));
|
||||||
@@ -45,8 +45,8 @@ button:focus {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.sheetLine {
|
.sheetLine {
|
||||||
|
cursor: pointer;
|
||||||
line-height: 3;
|
line-height: 3;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
@@ -60,8 +60,6 @@ button:focus {
|
|||||||
border-bottom: 3px solid #338236;
|
border-bottom: 3px solid #338236;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ReactCSSTransitionGroup styling
|
ReactCSSTransitionGroup styling
|
||||||
*/
|
*/
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
// Use ES6/7 code
|
|
||||||
const onOpen = () => {
|
|
||||||
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
|
|
||||||
.createMenu('Dialog')
|
|
||||||
.addItem('Add sheets', 'openDialog')
|
|
||||||
.addToUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
const openDialog = () => {
|
|
||||||
var html = HtmlService.createHtmlOutputFromFile('dialog');
|
|
||||||
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
|
|
||||||
.showModalDialog(html, 'Sheet Editor');
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSheets = () => SpreadsheetApp
|
|
||||||
.getActive()
|
|
||||||
.getSheets();
|
|
||||||
|
|
||||||
const getActiveSheetName = () => SpreadsheetApp
|
|
||||||
.getActive()
|
|
||||||
.getSheetName();
|
|
||||||
|
|
||||||
const getSheetsData = () => {
|
|
||||||
let activeSheetName = getActiveSheetName();
|
|
||||||
return getSheets()
|
|
||||||
.map((sheet,index) => {
|
|
||||||
let sheetName = sheet.getName();
|
|
||||||
return {
|
|
||||||
text: sheetName,
|
|
||||||
sheetIndex: index,
|
|
||||||
isActive: sheetName === activeSheetName
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const addSheet = (sheetTitle) => {
|
|
||||||
SpreadsheetApp.getActive().insertSheet(sheetTitle);
|
|
||||||
return getSheetsData();
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteSheet = (sheetIndex) => {
|
|
||||||
var sheets = getSheets();
|
|
||||||
SpreadsheetApp.getActive().deleteSheet(sheets[sheetIndex]);
|
|
||||||
return getSheetsData();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Must expose public functions
|
|
||||||
global.onOpen = onOpen;
|
|
||||||
global.openDialog = openDialog;
|
|
||||||
global.getSheetsData = getSheetsData;
|
|
||||||
global.addSheet = addSheet;
|
|
||||||
global.deleteSheet = deleteSheet;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<base target="_top">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<!-- remove/comment out these two script tags and uncomment the react imports from sidebar.html if you don't want to load react from a cdn -->
|
|
||||||
|
|
||||||
<!-- <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
|
|
||||||
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> -->
|
|
||||||
|
|
||||||
<section id="index"></section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"modules": false
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"transform-es3-property-literals",
|
||||||
|
"transform-es3-member-expression-literals",
|
||||||
|
"transform-class-properties",
|
||||||
|
"transform-object-rest-spread",
|
||||||
|
"transform-object-assign"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"extends": ["eslint:recommended", "google"],
|
||||||
|
"plugins": ["googleappsscript"],
|
||||||
|
"env": {
|
||||||
|
"googleappsscript/googleappsscript": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"SpreadsheetApp": true,
|
||||||
|
"HtmlService": true,
|
||||||
|
"global": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"import/prefer-default-export": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import * as publicFunctions from './sheets-utilities.js';
|
||||||
|
|
||||||
|
// Expose public functions
|
||||||
|
global.onOpen = publicFunctions.onOpen;
|
||||||
|
global.openDialog = publicFunctions.openDialog;
|
||||||
|
global.getSheetsData = publicFunctions.getSheetsData;
|
||||||
|
global.addSheet = publicFunctions.addSheet;
|
||||||
|
global.deleteSheet = publicFunctions.deleteSheet;
|
||||||
|
global.setActiveSheet = publicFunctions.setActiveSheet;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Use ES6/7 code
|
||||||
|
const onOpen = () => {
|
||||||
|
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
|
||||||
|
.createMenu('Custom scripts')
|
||||||
|
.addItem('Edit sheets [sample React project]', 'openDialog')
|
||||||
|
.addToUi();
|
||||||
|
};
|
||||||
|
|
||||||
|
const openDialog = () => {
|
||||||
|
let html = HtmlService.createHtmlOutputFromFile('dialog')
|
||||||
|
.setWidth(400)
|
||||||
|
.setHeight(600);
|
||||||
|
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
|
||||||
|
.showModalDialog(html, 'Sheet Editor');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSheets = () => SpreadsheetApp.getActive().getSheets();
|
||||||
|
|
||||||
|
const getActiveSheetName = () => SpreadsheetApp.getActive().getSheetName();
|
||||||
|
|
||||||
|
const getSheetsData = () => {
|
||||||
|
let activeSheetName = getActiveSheetName();
|
||||||
|
return getSheets().map((sheet, index) => {
|
||||||
|
let sheetName = sheet.getName();
|
||||||
|
return {
|
||||||
|
text: sheetName,
|
||||||
|
sheetIndex: index,
|
||||||
|
isActive: sheetName === activeSheetName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSheet = (sheetTitle) => {
|
||||||
|
SpreadsheetApp.getActive().insertSheet(sheetTitle);
|
||||||
|
return getSheetsData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSheet = (sheetIndex) => {
|
||||||
|
let sheets = getSheets();
|
||||||
|
SpreadsheetApp.getActive().deleteSheet(sheets[sheetIndex]);
|
||||||
|
return getSheetsData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setActiveSheet = (sheetName) => {
|
||||||
|
SpreadsheetApp.getActive().getSheetByName(sheetName).activate();
|
||||||
|
return getSheetsData();
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
onOpen,
|
||||||
|
openDialog,
|
||||||
|
getSheetsData,
|
||||||
|
addSheet,
|
||||||
|
deleteSheet,
|
||||||
|
setActiveSheet,
|
||||||
|
};
|
||||||
+52
-21
@@ -2,7 +2,7 @@ const path = require('path');
|
|||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||||
const GasPlugin = require('gas-webpack-plugin');
|
const GasPlugin = require('gas-webpack-plugin');
|
||||||
// const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
||||||
@@ -10,43 +10,65 @@ const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin
|
|||||||
const destination = 'dist';
|
const destination = 'dist';
|
||||||
|
|
||||||
const htmlPlugin = new HtmlWebpackPlugin({
|
const htmlPlugin = new HtmlWebpackPlugin({
|
||||||
template: "./src/dialog.html",
|
template: "./src/client/dialog-template.html",
|
||||||
filename: "./dialog.html",
|
filename: "dialog.html",
|
||||||
inlineSource: '.(js|css)$' // embed all javascript and css inline
|
inlineSource: '.(js|css)$' // embed all javascript and css inline
|
||||||
});
|
});
|
||||||
|
|
||||||
const htmlWebpackInlineSourcePlugin = new HtmlWebpackInlineSourcePlugin();
|
const htmlWebpackInlineSourcePlugin = new HtmlWebpackInlineSourcePlugin();
|
||||||
|
|
||||||
const config = {
|
const sharedConfigSettings = {
|
||||||
optimization: {
|
optimization: {
|
||||||
// Set to true to minimize code (e.g. for production).
|
minimizer: [
|
||||||
minimize: true
|
new UglifyJSPlugin({
|
||||||
|
uglifyOptions: {
|
||||||
|
ie8: true,
|
||||||
|
mangle: false,
|
||||||
|
compress: {
|
||||||
|
properties: false
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
beautify: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
},
|
},
|
||||||
module: {},
|
module: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const appsscriptConfig = {
|
const appsscriptConfig = {
|
||||||
name: "appsscript copy json",
|
name: "COPY APPSSCRIPT.JSON",
|
||||||
entry: "./appsscript.json",
|
entry: "./appsscript.json",
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin([destination]),
|
new CleanWebpackPlugin([destination]),
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin([
|
||||||
{
|
{
|
||||||
from: './appsscript.json'
|
from: './appsscript.json'
|
||||||
// to: path.resolve(__dirname, destination)
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
const clientConfig = Object.assign({}, config, {
|
const clientConfig = Object.assign({}, sharedConfigSettings, {
|
||||||
name: "CLIENT",
|
name: "CLIENT",
|
||||||
entry: "./src/index.jsx",
|
entry: "./src/client/index.jsx",
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, destination),
|
path: path.resolve(__dirname, destination),
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
{
|
||||||
|
enforce: 'pre',
|
||||||
|
test: /\.jsx$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'eslint-loader',
|
||||||
|
options: {
|
||||||
|
cache: false,
|
||||||
|
failOnError: false,
|
||||||
|
fix: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.jsx$/,
|
test: /\.jsx$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
@@ -66,17 +88,29 @@ const clientConfig = Object.assign({}, config, {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
const serverConfig = Object.assign({}, config, {
|
const serverConfig = Object.assign({}, sharedConfigSettings, {
|
||||||
name: "SERVER",
|
name: "SERVER",
|
||||||
entry: "./src/code.gs",
|
entry: "./src/server/code.js",
|
||||||
output: {
|
output: {
|
||||||
filename: 'code.gs',
|
filename: 'code.js',
|
||||||
path: path.resolve(__dirname, destination),
|
path: path.resolve(__dirname, destination),
|
||||||
|
libraryTarget: 'this'
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.gs$/,
|
enforce: 'pre',
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'eslint-loader',
|
||||||
|
options: {
|
||||||
|
cache: false,
|
||||||
|
failOnError: false,
|
||||||
|
fix: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: {
|
use: {
|
||||||
loader: "babel-loader"
|
loader: "babel-loader"
|
||||||
@@ -84,19 +118,16 @@ const serverConfig = Object.assign({}, config, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
optimization: {
|
// optimization: {
|
||||||
//don't minimize .gs server code
|
// //don't minimize .js server code
|
||||||
minimize: false
|
// minimize: true
|
||||||
},
|
// },
|
||||||
plugins: [
|
plugins: [
|
||||||
new GasPlugin()
|
new GasPlugin()
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Return Array of Configurations
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
// initialConfig,
|
|
||||||
appsscriptConfig,
|
appsscriptConfig,
|
||||||
clientConfig,
|
clientConfig,
|
||||||
serverConfig,
|
serverConfig,
|
||||||
|
|||||||
Reference in New Issue
Block a user