@nyaruka/temba-components 0.76.0 → 0.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sw.js CHANGED
@@ -1,2 +1,2 @@
1
- if(!self.define){let e,t={};const o=(o,n)=>(o=new URL(o+".js",n).href,t[o]||new Promise((t=>{if("document"in self){const e=document.createElement("script");e.src=o,e.onload=t,document.head.appendChild(e)}else e=o,importScripts(o),t()})).then((()=>{let e=t[o];if(!e)throw new Error(`Module ${o} didn’t register its module`);return e})));self.define=(n,s)=>{const i=e||("document"in self?document.currentScript.src:"")||location.href;if(t[i])return;let r={};const d=e=>o(e,i),l={module:{uri:i},exports:r,require:d};t[i]=Promise.all(n.map((e=>l[e]||d(e)))).then((e=>(s(...e),r)))}}define(["./workbox-919adfb7"],(function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"3c275eb2.js",revision:"2846b1048f3f0f0896d088caca6cb22b"},{url:"templates/components-body.html",revision:"6a880bab9e5dc5ef32839ad3a340fd23"},{url:"templates/components-head.html",revision:"43d59176beaf04d5ed6e2d714ad0815a"}],{}),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"))),e.registerRoute("polyfills/*.js",new e.CacheFirst,"GET")}));
1
+ if(!self.define){let e,t={};const o=(o,n)=>(o=new URL(o+".js",n).href,t[o]||new Promise((t=>{if("document"in self){const e=document.createElement("script");e.src=o,e.onload=t,document.head.appendChild(e)}else e=o,importScripts(o),t()})).then((()=>{let e=t[o];if(!e)throw new Error(`Module ${o} didn’t register its module`);return e})));self.define=(n,s)=>{const i=e||("document"in self?document.currentScript.src:"")||location.href;if(t[i])return;let r={};const l=e=>o(e,i),d={module:{uri:i},exports:r,require:l};t[i]=Promise.all(n.map((e=>d[e]||l(e)))).then((e=>(s(...e),r)))}}define(["./workbox-919adfb7"],(function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"105420e8.js",revision:"db002f361d5c351ec662dc2052b884bf"},{url:"templates/components-body.html",revision:"7cfe3e1345be036343e287dd98f7fbb7"},{url:"templates/components-head.html",revision:"55a46ded2a81db082c9223793a993434"}],{}),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"))),e.registerRoute("polyfills/*.js",new e.CacheFirst,"GET")}));
2
2
  //# sourceMappingURL=sw.js.map
package/dist/sw.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sw.js","sources":["../../tmp/c4a636b426ba507bda2690c94adcf5c1/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/workspaces/temba-components/node_modules/workbox-routing/registerRoute.mjs';\nimport {CacheFirst as workbox_strategies_CacheFirst} from '/workspaces/temba-components/node_modules/workbox-strategies/CacheFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/workspaces/temba-components/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/workspaces/temba-components/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/workspaces/temba-components/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/workspaces/temba-components/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"3c275eb2.js\",\n \"revision\": \"2846b1048f3f0f0896d088caca6cb22b\"\n },\n {\n \"url\": \"templates/components-body.html\",\n \"revision\": \"6a880bab9e5dc5ef32839ad3a340fd23\"\n },\n {\n \"url\": \"templates/components-head.html\",\n \"revision\": \"43d59176beaf04d5ed6e2d714ad0815a\"\n }\n], {});\n\nworkbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"/index.html\")));\n\n\nworkbox_routing_registerRoute(\"polyfills/*.js\", new workbox_strategies_CacheFirst(), 'GET');\n\n\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox","registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","workbox_strategies_CacheFirst"],"mappings":"0nBAwBAA,KAAKC,cAELC,EAAAA,eAQAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,qCAEb,CAAE,GAEwBC,EAAAC,cAAC,IAAIC,EAAAA,gBAAgCC,EAAAA,wBAA2C,iBAGhFH,EAAAC,cAAC,iBAAkB,IAAIG,aAAiC"}
1
+ {"version":3,"file":"sw.js","sources":["../../tmp/e7a28fad5c5a31b17ea265abfd66c661/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/workspaces/temba-components/node_modules/workbox-routing/registerRoute.mjs';\nimport {CacheFirst as workbox_strategies_CacheFirst} from '/workspaces/temba-components/node_modules/workbox-strategies/CacheFirst.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/workspaces/temba-components/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/workspaces/temba-components/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/workspaces/temba-components/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/workspaces/temba-components/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"105420e8.js\",\n \"revision\": \"db002f361d5c351ec662dc2052b884bf\"\n },\n {\n \"url\": \"templates/components-body.html\",\n \"revision\": \"7cfe3e1345be036343e287dd98f7fbb7\"\n },\n {\n \"url\": \"templates/components-head.html\",\n \"revision\": \"55a46ded2a81db082c9223793a993434\"\n }\n], {});\n\nworkbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"/index.html\")));\n\n\nworkbox_routing_registerRoute(\"polyfills/*.js\", new workbox_strategies_CacheFirst(), 'GET');\n\n\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox","registerRoute","workbox_routing_NavigationRoute","workbox_precaching_createHandlerBoundToURL","workbox_strategies_CacheFirst"],"mappings":"0nBAwBAA,KAAKC,cAELC,EAAAA,eAQAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,qCAEb,CAAE,GAEwBC,EAAAC,cAAC,IAAIC,EAAAA,gBAAgCC,EAAAA,wBAA2C,iBAGhFH,EAAAC,cAAC,iBAAkB,IAAIG,aAAiC"}
@@ -1 +1 @@
1
- <script type="module" src="{{STATIC_URL}}@nyaruka/temba-components/dist/3c275eb2.js"></script><script>window.TEMBA_COMPONENTS_VERSION="0.76.0"</script>
1
+ <script type="module" src="{{STATIC_URL}}@nyaruka/temba-components/dist/105420e8.js"></script><script>window.TEMBA_COMPONENTS_VERSION="0.77.0"</script>
@@ -1 +1 @@
1
- <script src="{{STATIC_URL}}croppie/croppie.js"></script><link rel="modulepreload" href="{{STATIC_URL}}@nyaruka/temba-components/dist/3c275eb2.js" crossorigin="anonymous">
1
+ <script src="{{STATIC_URL}}croppie/croppie.js"></script><link rel="modulepreload" href="{{STATIC_URL}}@nyaruka/temba-components/dist/105420e8.js" crossorigin="anonymous">
@@ -3,17 +3,10 @@ import { property } from 'lit/decorators.js';
3
3
  import { FormElement } from '../FormElement';
4
4
  import { html, css } from 'lit';
5
5
  import { CustomEventType } from '../interfaces';
6
- const KEY_HEADER = 'header';
7
- const KEY_BODY = 'body';
8
- const KEY_FOOTER = 'footer';
9
- const KEY_BUTTONS = 'button';
10
6
  export class TemplateEditor extends FormElement {
11
7
  constructor() {
12
8
  super(...arguments);
13
9
  this.lang = 'eng-US';
14
- this.buttonKeys = [];
15
- this.contentKeys = [];
16
- this.otherKeys = [];
17
10
  }
18
11
  static get styles() {
19
12
  return css `
@@ -81,9 +74,18 @@ export class TemplateEditor extends FormElement {
81
74
  border-radius: var(--curvature);
82
75
  min-height: 23px;
83
76
  display: flex;
77
+ flex-direction: row;
84
78
  align-items: center;
85
79
  margin-right: 0.5em;
86
80
  margin-top: 0.5em;
81
+ align-items: center;
82
+ }
83
+
84
+ .button .display {
85
+ margin-right: 0.5em;
86
+ background: #f9f9f9;
87
+ padding: 0.25em 1em;
88
+ border-radius: var(--curvature);
87
89
  }
88
90
 
89
91
  temba-textinput,
@@ -117,29 +119,13 @@ export class TemplateEditor extends FormElement {
117
119
  if (translation.locale === this.lang ||
118
120
  (!loc && translation.locale.split('-')[0] === lang)) {
119
121
  this.translation = translation;
120
- this.buttonKeys = [];
121
- this.contentKeys = [];
122
- this.otherKeys = [];
123
- const keys = Object.keys(translation.components);
124
- for (const key of keys) {
125
- if (key.startsWith(KEY_BUTTONS)) {
126
- this.buttonKeys.push(key);
127
- }
128
- else if (key === KEY_HEADER ||
129
- key === KEY_BODY ||
130
- key === KEY_FOOTER) {
131
- this.contentKeys.push(key);
132
- }
133
- else {
134
- this.otherKeys.push(key);
135
- }
136
- const compParams = translation.components[key].params || [];
122
+ for (const comp of translation.components) {
123
+ const compParams = comp.params || [];
137
124
  if (compParams.length > 0) {
138
125
  // create an array for the length of params
139
- newParams[key] = new Array(compParams.length).fill('');
126
+ newParams[comp.name] = new Array(compParams.length).fill('');
140
127
  }
141
128
  }
142
- this.buttonKeys.sort();
143
129
  // if we are looking at the same template copy our params on top
144
130
  if (this.template === this.selectedTemplate.uuid) {
145
131
  for (const key of Object.keys(this.params || {})) {
@@ -165,95 +151,77 @@ export class TemplateEditor extends FormElement {
165
151
  }
166
152
  handleVariableChanged(event) {
167
153
  const target = event.target;
168
- const key = target.getAttribute('key');
169
154
  const index = parseInt(target.getAttribute('index'));
170
- this.params[key][index - 1] = target.value;
155
+ this.params[target.name][index - 1] = target.value;
171
156
  this.fireCustomEvent(CustomEventType.ContentChanged, {
172
157
  template: this.selectedTemplate,
173
158
  translation: this.translation,
174
159
  params: this.params,
175
160
  });
176
161
  }
177
- renderVariables(key, component) {
162
+ renderVariables(component) {
178
163
  const parts = component.content.split(/{{(\d+)}}/g);
179
164
  if (parts.length > 0) {
180
165
  const variables = parts.map((part, index) => {
181
- const keyIndex = Math.round(index / 2);
166
+ const paramIndex = Math.round(index / 2);
182
167
  if (index % 2 === 0) {
183
168
  return html `<span class="text">${part}</span>`;
184
169
  }
185
170
  return html `<temba-completion
186
171
  class="variable"
187
172
  type="text"
188
- value=${this.params[key][keyIndex - 1]}
173
+ value=${this.params[component.name][paramIndex - 1]}
189
174
  @change=${this.handleVariableChanged}
190
- key="${key}"
191
- index="${keyIndex}}"
175
+ name="${component.name}"
176
+ index="${paramIndex}"
192
177
  placeholder="variable.."
193
178
  ></temba-completion>`;
194
179
  });
195
180
  return html `<div class="content">${variables}</div>`;
196
181
  }
197
182
  }
198
- renderComponent(key, component) {
199
- return html ` <div class="component">
200
- <div>${key}</div>
201
- ${this.renderVariables(key, component)}
202
- </div>`;
203
- }
204
- renderContent(components) {
205
- let header = null;
206
- let body = null;
207
- let footer = null;
208
- if (components[KEY_HEADER]) {
209
- header = html `<div class="header">
210
- ${this.renderVariables(KEY_HEADER, components[KEY_HEADER])}
183
+ renderComponents(components) {
184
+ const nonButtons = components
185
+ .filter(comp => !comp.type.startsWith('button/'))
186
+ .map(component => html `<div class="${component['name']}">
187
+ ${this.renderVariables(component)}
188
+ </div>`);
189
+ const buttonComponents = components.filter(comp => comp.type.startsWith('button/'));
190
+ const buttons = buttonComponents.length > 0 ? this.renderButtons(buttonComponents) : null;
191
+ return html `<div class="main">${nonButtons}</div>
192
+ <div class="buttons">
193
+ ${buttons}
194
+ <div></div>
211
195
  </div>`;
212
- }
213
- if (components[KEY_BODY]) {
214
- body = html `<div class="body">
215
- ${this.renderVariables(KEY_BODY, components[KEY_BODY])}
216
- </div>`;
217
- }
218
- if (components[KEY_FOOTER]) {
219
- footer = html `<div class="footer">
220
- ${this.renderVariables(KEY_FOOTER, components[KEY_FOOTER])}
221
- </div>`;
222
- }
223
- if (header || body || footer) {
224
- return html `<div class="content">${header}${body}${footer}</div>`;
225
- }
226
- return null;
227
196
  }
228
197
  renderButtons(components) {
229
- if (this.buttonKeys.length > 0) {
230
- const buttons = this.buttonKeys.map(key => {
231
- const component = components[key];
232
- return html `<div class="button">
233
- ${this.renderVariables(key, component)}
234
- </div>`;
235
- });
236
- return html `<div class="button-wrapper">
237
- <div class="button-header">Template Buttons</div>
238
- <div class="buttons">${buttons}</div>
239
- </div>`;
240
- }
241
- return null;
198
+ const buttons = components.map(component => {
199
+ if (component.display) {
200
+ return html `
201
+ <div class="button">
202
+ <div class="display">${component.display}</div>
203
+ ${this.renderVariables(component)}
204
+ </div>
205
+ `;
206
+ }
207
+ else {
208
+ return html `
209
+ <div class="button">${this.renderVariables(component)}</div>
210
+ `;
211
+ }
212
+ });
213
+ return html `<div class="button-wrapper">
214
+ <div class="button-header">Template Buttons</div>
215
+ <div class="buttons">${buttons}</div>
216
+ </div>`;
242
217
  }
243
218
  render() {
244
219
  let content = null;
245
- let buttons = null;
246
- let otherComponents = null;
247
220
  if (this.translation) {
248
- content = this.renderContent(this.translation.components);
249
- buttons = this.renderButtons(this.translation.components);
250
- otherComponents = this.otherKeys.map(key => {
251
- const component = this.translation.components[key];
252
- return this.renderComponent(key, component);
253
- });
221
+ content = this.renderComponents(this.translation.components);
254
222
  }
255
223
  else {
256
- otherComponents = html `<div class="error-message">
224
+ content = html `<div class="error-message">
257
225
  No approved translation was found for current language.
258
226
  </div>`;
259
227
  }
@@ -266,7 +234,7 @@ export class TemplateEditor extends FormElement {
266
234
  valuekey="uuid"
267
235
  class="picker"
268
236
  value="${this.template}"
269
- endpoint=${this.url}
237
+ endpoint="${this.url}?comps_as_list=true"
270
238
  shouldExclude=${template => template.status !== 'approved'}
271
239
  placeholder="Select a template"
272
240
  @temba-content-changed=${this.swallowEvent}
@@ -274,11 +242,7 @@ export class TemplateEditor extends FormElement {
274
242
  >
275
243
  </temba-select>
276
244
 
277
- ${this.template
278
- ? html ` <div class="template">
279
- ${content} ${buttons} ${otherComponents}
280
- </div>`
281
- : null}
245
+ ${this.template ? html ` <div class="template">${content}</div>` : null}
282
246
  </div>
283
247
  `;
284
248
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TemplateEditor.js","sourceRoot":"","sources":["../../../src/templates/TemplateEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\nimport { FormElement } from '../FormElement';\nimport { TemplateResult, html, css, PropertyValueMap } from 'lit';\nimport { CustomEventType } from '../interfaces';\n\nconst KEY_HEADER = 'header';\nconst KEY_BODY = 'body';\nconst KEY_FOOTER = 'footer';\nconst KEY_BUTTONS = 'button';\n\ninterface Component {\n content: string;\n params: { type: string }[];\n}\n\ninterface Translation {\n locale: string;\n status: string;\n channel: { uuid: string; name: string };\n components: { [key: string]: Component };\n}\n\ninterface Template {\n created_on: string;\n modified_on: string;\n name: string;\n translations: Translation[];\n uuid: string;\n}\n\nexport class TemplateEditor extends FormElement {\n static get styles() {\n return css`\n .component {\n background: #fff;\n border: 1px solid var(--color-widget-border);\n border-radius: var(--curvature);\n padding: 1em;\n margin-top: 1em;\n }\n .picker {\n margin-bottom: 0.5em;\n display: block;\n }\n .param {\n display: flex;\n margin-bottom: 0.5em;\n align-items: center;\n }\n label {\n margin-right: 0.5em;\n }\n\n .content span {\n margin-right: 0.25em;\n }\n\n .error-message {\n padding-left: 0.5em;\n }\n\n .variable {\n display: inline-block;\n margin: 0.25em 0em;\n margin-right: 0.25em;\n }\n\n .button-wrapper {\n margin-top: 1em;\n background: #f9f9f9;\n border-radius: var(--curvature);\n padding: 0.5em;\n display: flex;\n flex-direction: column;\n }\n\n .button-header {\n font-weight: normal;\n margin-left: 0.25em;\n margin-bottom: -0.5em;\n font-size: 0.9em;\n color: #777;\n }\n\n .buttons {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n }\n\n .button {\n background: #fff;\n padding: 0.3em 1em;\n border: 1px solid #e6e6e6;\n border-radius: var(--curvature);\n min-height: 23px;\n display: flex;\n align-items: center;\n margin-right: 0.5em;\n margin-top: 0.5em;\n }\n\n temba-textinput,\n temba-completion {\n --temba-textinput-padding: 5px 5px;\n --temba-textinput-font-size: 0.9em;\n line-height: initial;\n }\n\n .template {\n background: #fff;\n border-radius: var(--curvature);\n border: 1px solid var(--color-widget-border);\n padding: 1em;\n line-height: 2.2em;\n }\n `;\n }\n\n @property({ type: String })\n url: string;\n\n // initial template uuid\n @property({ type: String })\n template: string;\n\n @property({ type: Object })\n selectedTemplate: Template;\n\n @property({ type: String })\n lang = 'eng-US';\n\n // component key to array of strings for variables\n @property({ type: Object })\n params: { [key: string]: string[] };\n\n @property({ type: Object, attribute: false })\n translation: Translation;\n\n @property({ type: Boolean })\n translating: boolean;\n\n buttonKeys = [];\n contentKeys = [];\n otherKeys = [];\n\n public firstUpdated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(changes);\n }\n\n public updated(changedProperties: Map<string, any>): void {\n super.updated(changedProperties);\n }\n\n private handleTemplateChanged(event: CustomEvent) {\n this.selectedTemplate = (event.target as any).values[0] as Template;\n const [lang, loc] = this.lang.split('-');\n\n const newParams = {};\n if (this.selectedTemplate) {\n this.selectedTemplate.translations.forEach(translation => {\n if (\n translation.locale === this.lang ||\n (!loc && translation.locale.split('-')[0] === lang)\n ) {\n this.translation = translation;\n this.buttonKeys = [];\n this.contentKeys = [];\n this.otherKeys = [];\n const keys = Object.keys(translation.components);\n for (const key of keys) {\n if (key.startsWith(KEY_BUTTONS)) {\n this.buttonKeys.push(key);\n } else if (\n key === KEY_HEADER ||\n key === KEY_BODY ||\n key === KEY_FOOTER\n ) {\n this.contentKeys.push(key);\n } else {\n this.otherKeys.push(key);\n }\n\n const compParams = translation.components[key].params || [];\n if (compParams.length > 0) {\n // create an array for the length of params\n newParams[key] = new Array(compParams.length).fill('');\n }\n }\n this.buttonKeys.sort();\n\n // if we are looking at the same template copy our params on top\n if (this.template === this.selectedTemplate.uuid) {\n for (const key of Object.keys(this.params || {})) {\n if (newParams[key]) {\n for (let i = 0; i < this.params[key].length; i++) {\n newParams[key][i] = this.params[key][i];\n }\n }\n }\n }\n }\n });\n } else {\n this.translation = null;\n }\n\n this.params = newParams;\n this.fireCustomEvent(CustomEventType.ContextChanged, {\n template: this.selectedTemplate,\n translation: this.translation,\n params: this.params,\n });\n }\n\n private handleVariableChanged(event: CustomEvent) {\n const target = event.target as HTMLInputElement;\n const key = target.getAttribute('key');\n const index = parseInt(target.getAttribute('index'));\n this.params[key][index - 1] = target.value;\n this.fireCustomEvent(CustomEventType.ContentChanged, {\n template: this.selectedTemplate,\n translation: this.translation,\n params: this.params,\n });\n }\n\n private renderVariables(key: string, component: Component) {\n const parts = component.content.split(/{{(\\d+)}}/g);\n if (parts.length > 0) {\n const variables = parts.map((part, index) => {\n const keyIndex = Math.round(index / 2);\n if (index % 2 === 0) {\n return html`<span class=\"text\">${part}</span>`;\n }\n return html`<temba-completion\n class=\"variable\"\n type=\"text\"\n value=${this.params[key][keyIndex - 1]}\n @change=${this.handleVariableChanged}\n key=\"${key}\"\n index=\"${keyIndex}}\"\n placeholder=\"variable..\"\n ></temba-completion>`;\n });\n return html`<div class=\"content\">${variables}</div>`;\n }\n }\n\n private renderComponent(key: string, component: Component) {\n return html` <div class=\"component\">\n <div>${key}</div>\n ${this.renderVariables(key, component)}\n </div>`;\n }\n\n public renderContent(components: {\n [key: string]: Component;\n }): TemplateResult {\n let header = null;\n let body = null;\n let footer = null;\n\n if (components[KEY_HEADER]) {\n header = html`<div class=\"header\">\n ${this.renderVariables(KEY_HEADER, components[KEY_HEADER])}\n </div>`;\n }\n\n if (components[KEY_BODY]) {\n body = html`<div class=\"body\">\n ${this.renderVariables(KEY_BODY, components[KEY_BODY])}\n </div>`;\n }\n\n if (components[KEY_FOOTER]) {\n footer = html`<div class=\"footer\">\n ${this.renderVariables(KEY_FOOTER, components[KEY_FOOTER])}\n </div>`;\n }\n\n if (header || body || footer) {\n return html`<div class=\"content\">${header}${body}${footer}</div>`;\n }\n return null;\n }\n\n public renderButtons(components): TemplateResult {\n if (this.buttonKeys.length > 0) {\n const buttons = this.buttonKeys.map(key => {\n const component = components[key];\n return html`<div class=\"button\">\n ${this.renderVariables(key, component)}\n </div>`;\n });\n return html`<div class=\"button-wrapper\">\n <div class=\"button-header\">Template Buttons</div>\n <div class=\"buttons\">${buttons}</div>\n </div>`;\n }\n return null;\n }\n public render(): TemplateResult {\n let content = null;\n let buttons = null;\n let otherComponents = null;\n if (this.translation) {\n content = this.renderContent(this.translation.components);\n buttons = this.renderButtons(this.translation.components);\n otherComponents = this.otherKeys.map(key => {\n const component = this.translation.components[key];\n return this.renderComponent(key, component);\n });\n } else {\n otherComponents = html`<div class=\"error-message\">\n No approved translation was found for current language.\n </div>`;\n }\n\n return html`\n <div>\n <temba-select\n searchable\n ?clearable=${!this.translating}\n ?disabled=${this.translating}\n valuekey=\"uuid\"\n class=\"picker\"\n value=\"${this.template}\"\n endpoint=${this.url}\n shouldExclude=${template => template.status !== 'approved'}\n placeholder=\"Select a template\"\n @temba-content-changed=${this.swallowEvent}\n @change=${this.handleTemplateChanged}\n >\n </temba-select>\n\n ${this.template\n ? html` <div class=\"template\">\n ${content} ${buttons} ${otherComponents}\n </div>`\n : null}\n </div>\n `;\n }\n}\n"]}
1
+ {"version":3,"file":"TemplateEditor.js","sourceRoot":"","sources":["../../../src/templates/TemplateEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { property } from 'lit/decorators.js';\nimport { FormElement } from '../FormElement';\nimport { TemplateResult, html, css, PropertyValueMap } from 'lit';\nimport { CustomEventType } from '../interfaces';\n\ninterface Component {\n name: string;\n type: string;\n content: string;\n params: { type: string }[];\n}\n\ninterface Translation {\n locale: string;\n status: string;\n channel: { uuid: string; name: string };\n components: Component[];\n}\n\ninterface Template {\n created_on: string;\n modified_on: string;\n name: string;\n translations: Translation[];\n uuid: string;\n}\n\nexport class TemplateEditor extends FormElement {\n static get styles() {\n return css`\n .component {\n background: #fff;\n border: 1px solid var(--color-widget-border);\n border-radius: var(--curvature);\n padding: 1em;\n margin-top: 1em;\n }\n .picker {\n margin-bottom: 0.5em;\n display: block;\n }\n .param {\n display: flex;\n margin-bottom: 0.5em;\n align-items: center;\n }\n label {\n margin-right: 0.5em;\n }\n\n .content span {\n margin-right: 0.25em;\n }\n\n .error-message {\n padding-left: 0.5em;\n }\n\n .variable {\n display: inline-block;\n margin: 0.25em 0em;\n margin-right: 0.25em;\n }\n\n .button-wrapper {\n margin-top: 1em;\n background: #f9f9f9;\n border-radius: var(--curvature);\n padding: 0.5em;\n display: flex;\n flex-direction: column;\n }\n\n .button-header {\n font-weight: normal;\n margin-left: 0.25em;\n margin-bottom: -0.5em;\n font-size: 0.9em;\n color: #777;\n }\n\n .buttons {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n }\n\n .button {\n background: #fff;\n padding: 0.3em 1em;\n border: 1px solid #e6e6e6;\n border-radius: var(--curvature);\n min-height: 23px;\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-right: 0.5em;\n margin-top: 0.5em;\n align-items: center;\n }\n\n .button .display {\n margin-right: 0.5em;\n background: #f9f9f9;\n padding: 0.25em 1em;\n border-radius: var(--curvature);\n }\n\n temba-textinput,\n temba-completion {\n --temba-textinput-padding: 5px 5px;\n --temba-textinput-font-size: 0.9em;\n line-height: initial;\n }\n\n .template {\n background: #fff;\n border-radius: var(--curvature);\n border: 1px solid var(--color-widget-border);\n padding: 1em;\n line-height: 2.2em;\n }\n `;\n }\n\n @property({ type: String })\n url: string;\n\n // initial template uuid\n @property({ type: String })\n template: string;\n\n @property({ type: Object })\n selectedTemplate: Template;\n\n @property({ type: String })\n lang = 'eng-US';\n\n // component key to array of strings for variables\n @property({ type: Object })\n params: { [key: string]: string[] };\n\n @property({ type: Object, attribute: false })\n translation: Translation;\n\n @property({ type: Boolean })\n translating: boolean;\n\n public firstUpdated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.firstUpdated(changes);\n }\n\n public updated(changedProperties: Map<string, any>): void {\n super.updated(changedProperties);\n }\n\n private handleTemplateChanged(event: CustomEvent) {\n this.selectedTemplate = (event.target as any).values[0] as Template;\n const [lang, loc] = this.lang.split('-');\n\n const newParams = {};\n if (this.selectedTemplate) {\n this.selectedTemplate.translations.forEach(translation => {\n if (\n translation.locale === this.lang ||\n (!loc && translation.locale.split('-')[0] === lang)\n ) {\n this.translation = translation;\n for (const comp of translation.components) {\n const compParams = comp.params || [];\n if (compParams.length > 0) {\n // create an array for the length of params\n newParams[comp.name] = new Array(compParams.length).fill('');\n }\n }\n\n // if we are looking at the same template copy our params on top\n if (this.template === this.selectedTemplate.uuid) {\n for (const key of Object.keys(this.params || {})) {\n if (newParams[key]) {\n for (let i = 0; i < this.params[key].length; i++) {\n newParams[key][i] = this.params[key][i];\n }\n }\n }\n }\n }\n });\n } else {\n this.translation = null;\n }\n\n this.params = newParams;\n this.fireCustomEvent(CustomEventType.ContextChanged, {\n template: this.selectedTemplate,\n translation: this.translation,\n params: this.params,\n });\n }\n\n private handleVariableChanged(event: CustomEvent) {\n const target = event.target as HTMLInputElement;\n const index = parseInt(target.getAttribute('index'));\n this.params[target.name][index - 1] = target.value;\n this.fireCustomEvent(CustomEventType.ContentChanged, {\n template: this.selectedTemplate,\n translation: this.translation,\n params: this.params,\n });\n }\n\n private renderVariables(component: Component) {\n const parts = component.content.split(/{{(\\d+)}}/g);\n if (parts.length > 0) {\n const variables = parts.map((part, index) => {\n const paramIndex = Math.round(index / 2);\n if (index % 2 === 0) {\n return html`<span class=\"text\">${part}</span>`;\n }\n return html`<temba-completion\n class=\"variable\"\n type=\"text\"\n value=${this.params[component.name][paramIndex - 1]}\n @change=${this.handleVariableChanged}\n name=\"${component.name}\"\n index=\"${paramIndex}\"\n placeholder=\"variable..\"\n ></temba-completion>`;\n });\n return html`<div class=\"content\">${variables}</div>`;\n }\n }\n\n public renderComponents(components: Component[]): TemplateResult {\n const nonButtons = components\n .filter(comp => !comp.type.startsWith('button/'))\n .map(\n component =>\n html`<div class=\"${component['name']}\">\n ${this.renderVariables(component)}\n </div>`\n );\n const buttonComponents = components.filter(comp =>\n comp.type.startsWith('button/')\n );\n const buttons =\n buttonComponents.length > 0 ? this.renderButtons(buttonComponents) : null;\n return html`<div class=\"main\">${nonButtons}</div>\n <div class=\"buttons\">\n ${buttons}\n <div></div>\n </div>`;\n }\n\n public renderButtons(components): TemplateResult {\n const buttons = components.map(component => {\n if (component.display) {\n return html`\n <div class=\"button\">\n <div class=\"display\">${component.display}</div>\n ${this.renderVariables(component)}\n </div>\n `;\n } else {\n return html`\n <div class=\"button\">${this.renderVariables(component)}</div>\n `;\n }\n });\n return html`<div class=\"button-wrapper\">\n <div class=\"button-header\">Template Buttons</div>\n <div class=\"buttons\">${buttons}</div>\n </div>`;\n }\n\n public render(): TemplateResult {\n let content = null;\n if (this.translation) {\n content = this.renderComponents(this.translation.components);\n } else {\n content = html`<div class=\"error-message\">\n No approved translation was found for current language.\n </div>`;\n }\n\n return html`\n <div>\n <temba-select\n searchable\n ?clearable=${!this.translating}\n ?disabled=${this.translating}\n valuekey=\"uuid\"\n class=\"picker\"\n value=\"${this.template}\"\n endpoint=\"${this.url}?comps_as_list=true\"\n shouldExclude=${template => template.status !== 'approved'}\n placeholder=\"Select a template\"\n @temba-content-changed=${this.swallowEvent}\n @change=${this.handleTemplateChanged}\n >\n </temba-select>\n\n ${this.template ? html` <div class=\"template\">${content}</div>` : null}\n </div>\n `;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.76.0",
3
+ "version": "0.77.0",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
@@ -3,12 +3,9 @@ import { FormElement } from '../FormElement';
3
3
  import { TemplateResult, html, css, PropertyValueMap } from 'lit';
4
4
  import { CustomEventType } from '../interfaces';
5
5
 
6
- const KEY_HEADER = 'header';
7
- const KEY_BODY = 'body';
8
- const KEY_FOOTER = 'footer';
9
- const KEY_BUTTONS = 'button';
10
-
11
6
  interface Component {
7
+ name: string;
8
+ type: string;
12
9
  content: string;
13
10
  params: { type: string }[];
14
11
  }
@@ -17,7 +14,7 @@ interface Translation {
17
14
  locale: string;
18
15
  status: string;
19
16
  channel: { uuid: string; name: string };
20
- components: { [key: string]: Component };
17
+ components: Component[];
21
18
  }
22
19
 
23
20
  interface Template {
@@ -95,9 +92,18 @@ export class TemplateEditor extends FormElement {
95
92
  border-radius: var(--curvature);
96
93
  min-height: 23px;
97
94
  display: flex;
95
+ flex-direction: row;
98
96
  align-items: center;
99
97
  margin-right: 0.5em;
100
98
  margin-top: 0.5em;
99
+ align-items: center;
100
+ }
101
+
102
+ .button .display {
103
+ margin-right: 0.5em;
104
+ background: #f9f9f9;
105
+ padding: 0.25em 1em;
106
+ border-radius: var(--curvature);
101
107
  }
102
108
 
103
109
  temba-textinput,
@@ -140,10 +146,6 @@ export class TemplateEditor extends FormElement {
140
146
  @property({ type: Boolean })
141
147
  translating: boolean;
142
148
 
143
- buttonKeys = [];
144
- contentKeys = [];
145
- otherKeys = [];
146
-
147
149
  public firstUpdated(
148
150
  changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
149
151
  ): void {
@@ -166,30 +168,13 @@ export class TemplateEditor extends FormElement {
166
168
  (!loc && translation.locale.split('-')[0] === lang)
167
169
  ) {
168
170
  this.translation = translation;
169
- this.buttonKeys = [];
170
- this.contentKeys = [];
171
- this.otherKeys = [];
172
- const keys = Object.keys(translation.components);
173
- for (const key of keys) {
174
- if (key.startsWith(KEY_BUTTONS)) {
175
- this.buttonKeys.push(key);
176
- } else if (
177
- key === KEY_HEADER ||
178
- key === KEY_BODY ||
179
- key === KEY_FOOTER
180
- ) {
181
- this.contentKeys.push(key);
182
- } else {
183
- this.otherKeys.push(key);
184
- }
185
-
186
- const compParams = translation.components[key].params || [];
171
+ for (const comp of translation.components) {
172
+ const compParams = comp.params || [];
187
173
  if (compParams.length > 0) {
188
174
  // create an array for the length of params
189
- newParams[key] = new Array(compParams.length).fill('');
175
+ newParams[comp.name] = new Array(compParams.length).fill('');
190
176
  }
191
177
  }
192
- this.buttonKeys.sort();
193
178
 
194
179
  // if we are looking at the same template copy our params on top
195
180
  if (this.template === this.selectedTemplate.uuid) {
@@ -217,9 +202,8 @@ export class TemplateEditor extends FormElement {
217
202
 
218
203
  private handleVariableChanged(event: CustomEvent) {
219
204
  const target = event.target as HTMLInputElement;
220
- const key = target.getAttribute('key');
221
205
  const index = parseInt(target.getAttribute('index'));
222
- this.params[key][index - 1] = target.value;
206
+ this.params[target.name][index - 1] = target.value;
223
207
  this.fireCustomEvent(CustomEventType.ContentChanged, {
224
208
  template: this.selectedTemplate,
225
209
  translation: this.translation,
@@ -227,21 +211,21 @@ export class TemplateEditor extends FormElement {
227
211
  });
228
212
  }
229
213
 
230
- private renderVariables(key: string, component: Component) {
214
+ private renderVariables(component: Component) {
231
215
  const parts = component.content.split(/{{(\d+)}}/g);
232
216
  if (parts.length > 0) {
233
217
  const variables = parts.map((part, index) => {
234
- const keyIndex = Math.round(index / 2);
218
+ const paramIndex = Math.round(index / 2);
235
219
  if (index % 2 === 0) {
236
220
  return html`<span class="text">${part}</span>`;
237
221
  }
238
222
  return html`<temba-completion
239
223
  class="variable"
240
224
  type="text"
241
- value=${this.params[key][keyIndex - 1]}
225
+ value=${this.params[component.name][paramIndex - 1]}
242
226
  @change=${this.handleVariableChanged}
243
- key="${key}"
244
- index="${keyIndex}}"
227
+ name="${component.name}"
228
+ index="${paramIndex}"
245
229
  placeholder="variable.."
246
230
  ></temba-completion>`;
247
231
  });
@@ -249,72 +233,54 @@ export class TemplateEditor extends FormElement {
249
233
  }
250
234
  }
251
235
 
252
- private renderComponent(key: string, component: Component) {
253
- return html` <div class="component">
254
- <div>${key}</div>
255
- ${this.renderVariables(key, component)}
256
- </div>`;
257
- }
258
-
259
- public renderContent(components: {
260
- [key: string]: Component;
261
- }): TemplateResult {
262
- let header = null;
263
- let body = null;
264
- let footer = null;
265
-
266
- if (components[KEY_HEADER]) {
267
- header = html`<div class="header">
268
- ${this.renderVariables(KEY_HEADER, components[KEY_HEADER])}
269
- </div>`;
270
- }
271
-
272
- if (components[KEY_BODY]) {
273
- body = html`<div class="body">
274
- ${this.renderVariables(KEY_BODY, components[KEY_BODY])}
275
- </div>`;
276
- }
277
-
278
- if (components[KEY_FOOTER]) {
279
- footer = html`<div class="footer">
280
- ${this.renderVariables(KEY_FOOTER, components[KEY_FOOTER])}
236
+ public renderComponents(components: Component[]): TemplateResult {
237
+ const nonButtons = components
238
+ .filter(comp => !comp.type.startsWith('button/'))
239
+ .map(
240
+ component =>
241
+ html`<div class="${component['name']}">
242
+ ${this.renderVariables(component)}
243
+ </div>`
244
+ );
245
+ const buttonComponents = components.filter(comp =>
246
+ comp.type.startsWith('button/')
247
+ );
248
+ const buttons =
249
+ buttonComponents.length > 0 ? this.renderButtons(buttonComponents) : null;
250
+ return html`<div class="main">${nonButtons}</div>
251
+ <div class="buttons">
252
+ ${buttons}
253
+ <div></div>
281
254
  </div>`;
282
- }
283
-
284
- if (header || body || footer) {
285
- return html`<div class="content">${header}${body}${footer}</div>`;
286
- }
287
- return null;
288
255
  }
289
256
 
290
257
  public renderButtons(components): TemplateResult {
291
- if (this.buttonKeys.length > 0) {
292
- const buttons = this.buttonKeys.map(key => {
293
- const component = components[key];
294
- return html`<div class="button">
295
- ${this.renderVariables(key, component)}
296
- </div>`;
297
- });
298
- return html`<div class="button-wrapper">
299
- <div class="button-header">Template Buttons</div>
300
- <div class="buttons">${buttons}</div>
301
- </div>`;
302
- }
303
- return null;
258
+ const buttons = components.map(component => {
259
+ if (component.display) {
260
+ return html`
261
+ <div class="button">
262
+ <div class="display">${component.display}</div>
263
+ ${this.renderVariables(component)}
264
+ </div>
265
+ `;
266
+ } else {
267
+ return html`
268
+ <div class="button">${this.renderVariables(component)}</div>
269
+ `;
270
+ }
271
+ });
272
+ return html`<div class="button-wrapper">
273
+ <div class="button-header">Template Buttons</div>
274
+ <div class="buttons">${buttons}</div>
275
+ </div>`;
304
276
  }
277
+
305
278
  public render(): TemplateResult {
306
279
  let content = null;
307
- let buttons = null;
308
- let otherComponents = null;
309
280
  if (this.translation) {
310
- content = this.renderContent(this.translation.components);
311
- buttons = this.renderButtons(this.translation.components);
312
- otherComponents = this.otherKeys.map(key => {
313
- const component = this.translation.components[key];
314
- return this.renderComponent(key, component);
315
- });
281
+ content = this.renderComponents(this.translation.components);
316
282
  } else {
317
- otherComponents = html`<div class="error-message">
283
+ content = html`<div class="error-message">
318
284
  No approved translation was found for current language.
319
285
  </div>`;
320
286
  }
@@ -328,7 +294,7 @@ export class TemplateEditor extends FormElement {
328
294
  valuekey="uuid"
329
295
  class="picker"
330
296
  value="${this.template}"
331
- endpoint=${this.url}
297
+ endpoint="${this.url}?comps_as_list=true"
332
298
  shouldExclude=${template => template.status !== 'approved'}
333
299
  placeholder="Select a template"
334
300
  @temba-content-changed=${this.swallowEvent}
@@ -336,11 +302,7 @@ export class TemplateEditor extends FormElement {
336
302
  >
337
303
  </temba-select>
338
304
 
339
- ${this.template
340
- ? html` <div class="template">
341
- ${content} ${buttons} ${otherComponents}
342
- </div>`
343
- : null}
305
+ ${this.template ? html` <div class="template">${content}</div>` : null}
344
306
  </div>
345
307
  `;
346
308
  }