Skip to content

Instantly share code, notes, and snippets.

@wmakeev
Last active February 24, 2021 09:06
Show Gist options
  • Save wmakeev/26b6a735054dc616b885bb1b2caf18ec to your computer and use it in GitHub Desktop.
Save wmakeev/26b6a735054dc616b885bb1b2caf18ec to your computer and use it in GitHub Desktop.
[GAS typescript template (rev.2)] #gas #typescript #clasp #template
npm i -D @types/google-apps-script dot-json webpack webpack-cli
npm run build
clasp create --parentId $GAS_PARENT_ID --title "${GAS_APP_TITLE}" --rootDir ./dist
# GAS
/clasp
.clasp.json
/dist/appsscript.json
/dist/bundle
{
"private": true,
"scripts": {
"compile": "rm -rf build/ && tsc --build tsconfig.prod.json",
"compile:prod": null,
"build": "npm run lint:fix && rm -rf ./dist/bundle && npm run compile && webpack",
"build:prod": null,
"push": "npm run build && clasp push",
"version": "npm run push && DESCRIPTION=$(git log --oneline -n 1 HEAD) VERSION=$(dot-json ../../package.json version) && clasp version \"[${VERSION}] ${DESCRIPTION}\""
}
}
module.exports = {
entry: ['./build/src/index.js'],
output: {
path: __dirname,
filename: './dist/bundle/main.js',
library: 'Module',
libraryTarget: 'var'
},
optimization: {
minimize: false
},
target: 'es2019'
}
function onOpen() {
Module.createMenu()
}
// function onEdit(ev) {}
function doGet(ev) {
return Module.getHandler(ev)
}
<!DOCTYPE html>
<html>
<head>
<base target="_top" />
<link
rel="stylesheet"
href="https://ssl.gstatic.com/docs/script/css/add-ons1.css"
/>
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.min.js"></script>
<script>
const isValidString = param =>
typeof param === 'string' && param.length > 0
const startsWith = (string, start) => string[0] === start
const isSelector = param =>
isValidString(param) &&
(startsWith(param, '.') || startsWith(param, '#'))
const helpers = (h, tags) =>
tags.reduce((res, tagName) => {
res[tagName] = (first, ...rest) => {
if (isSelector(first)) {
return h(tagName + first, ...rest)
} else if (typeof first === 'undefined') {
return h(tagName)
} else {
return h(tagName, first, ...rest)
}
}
return res
}, {})
</script>
<style>
body {
margin: 10px;
}
.branding-below {
bottom: 56px;
top: 0;
}
.form-group label {
font-weight: bolder;
}
#status {
font-weight: bold;
}
.ok {
color: green;
}
.error {
color: red;
}
</style>
</head>
<body>
<div id="root" class="branding-below"></div>
<div class="bottom">
<span class="gray">
Введенные данные будут сохранены в настройках пользователя внутри вашего
аккаунта Google.
</span>
</div>
<script>
const { div, p, input, span, label, button } = helpers(m, [
'div',
'p',
'input',
'span',
'label',
'button'
])
const STATUS = {
LOADING: 'LOADING',
READY: 'READY',
SAVING: 'SAVING',
ERROR: 'ERROR',
OK: 'OK'
}
let state = {
status: null,
statusMsg: null,
properties: {}
}
const isPassword = name => {
const val = name.toLowerCase()
return ['password', 'secret', 'token'].some(k => val.includes(k))
}
function handlePropertyInput(name, ev) {
state = {
...state,
properties: {
...state.properties,
[name]: ev.target.value
}
}
}
function getDocumentProperties() {
return new Promise((resolve, reject) => {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
.getDocumentProperties()
})
}
function handleSaveClick() {
state = {
...state,
status: STATUS.SAVING,
statusMsg: 'Обновление ...'
}
google.script.run
.withSuccessHandler(() => {
m.redraw()
state = {
...state,
status: STATUS.OK,
statusMsg: 'Параметры сохранены'
}
})
.withFailureHandler(err => {
m.redraw()
state = {
...state,
status: STATUS.ERROR,
statusMsg: err.message
}
})
.setDocumentProperties(state.properties)
}
// function handleCancelClick() {
// google.script.host.close()
// }
function getStatusClass() {
switch (state.status) {
case STATUS.ERROR:
return '.error'
case STATUS.OK:
return '.ok'
default:
return ''
}
}
const render = () => () => {
return [
div('.block.form-group', [
...Object.keys(state.properties).map(key => {
const prop = state.properties[key]
return div('.block.form-group', [
label({ for: key }, key),
input(`#${key}`, {
type: isPassword(key) ? 'password' : 'text',
style: 'width: 250px;',
value: prop != null ? prop : '',
oninput: ev => handlePropertyInput(key, ev),
disabled:
state.status === STATUS.SAVING ? 'disabled' : undefined
})
])
}),
state.status !== STATUS.LOADING
? div('.block', [
button(
'#save-btn.blue',
{
onclick: handleSaveClick,
disabled:
state.status === STATUS.SAVING ? 'disabled' : undefined
},
['Сохранить']
)
// button(
// '#cancel-btn',
// {
// onclick: handleCancelClick
// },
// ['Отменить']
// )
])
: null,
div('.block', [
span(`#status${getStatusClass()}`, [state.statusMsg])
])
])
]
}
const rootNode = document.getElementById('root')
state.status = STATUS.LOADING
state.statusMsg = 'Загрузка свойств документа ...'
getDocumentProperties({ isPassword }).then(properties => {
m.redraw()
state.properties = properties
state.status = STATUS.READY
state.statusMsg = null
})
m.mount(rootNode, {
view: render()
})
</script>
</body>
</html>
function getDocumentProperties() {
return PropertiesService.getDocumentProperties().getProperties()
}
function setDocumentDefaultProperties() {
PropertiesService.getDocumentProperties().setProperties({
PARAM_1: '',
PARAM_2: ''
})
}
function setDocumentProperties(properties) {
return PropertiesService.getDocumentProperties().setProperties(properties)
}
function requestDocumentProperties() {
const htmlOutput = HtmlService.createHtmlOutputFromFile('properties-form')
// .setWidth(310)
// .setHeight(300)
.setTitle('Свойства документа')
SpreadsheetApp.getUi().showSidebar(htmlOutput)
}
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent()
}
/**
* My custom function.
*
* @param {string} val Value.
* @return Value
* @customfunction
*/
function MY_CUSTOM_FUC(val) {
return Module.myCustomFunc(val)
}
interface GetParams {
method: 'test'
value: string
}
export function getHandler(ev: GoogleAppsScript.Events.DoGet) {
const params = ev.parameter as GetParams
if (params.method === 'test') {
const template = HtmlService.createTemplateFromFile('test')
template.data = {
value: params.value
}
return template.evaluate()
} else if (!params.method) {
throw new Error('Не указан метод запроса')
} else {
throw new Error(`Неизвестный метод запроса - ${params.method}`)
}
}
// export * from './some'
export function myCustomFunc(val: string) {
return val
}
export * from './handlers'
export * from './custom-functions'
export * from './menu'
export function createMenu() {
SpreadsheetApp.getUi()
.createMenu('Actions')
.addItem('Document properties...', 'requestDocumentProperties')
.addToUi()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment