|
| 1 | +/* |
| 2 | + React Studio plugin for a styled image tag. |
| 3 | +
|
| 4 | + - 2020 / Nitin Khanna / @nitinthewiz / automedia.ai |
| 5 | +
|
| 6 | +. v1.0 |
| 7 | +. Icon from https://icon-library.com/icon/width-icon-3.html |
| 8 | +
|
| 9 | + */ |
| 10 | + |
| 11 | + |
| 12 | +// -- plugin info requested by host app -- |
| 13 | + |
| 14 | +this.describePlugin = function(id, lang) { |
| 15 | + switch (id) { |
| 16 | + case 'displayName': |
| 17 | + return "Styled Image"; |
| 18 | + |
| 19 | + case 'shortDisplayText': |
| 20 | + return "component for CSS styled images"; |
| 21 | + |
| 22 | + case 'defaultNameForNewInstance': |
| 23 | + return "styled-img"; |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +// This is all data for the plugin |
| 28 | +// -- private variables -- |
| 29 | + |
| 30 | +this._data = { |
| 31 | + src: '', |
| 32 | + style: '{"display": "block"}' |
| 33 | +}; |
| 34 | + |
| 35 | + |
| 36 | + |
| 37 | +// OFFICIAL dont touch |
| 38 | +// -- persistence, i.e. saving and loading -- |
| 39 | + |
| 40 | +this.persist = function() { |
| 41 | + return this._data; |
| 42 | +} |
| 43 | + |
| 44 | +this.unpersist = function(data) { |
| 45 | + this._data = data; |
| 46 | +} |
| 47 | + |
| 48 | + |
| 49 | +// WHAT you see in settings in React Studio |
| 50 | +// -- inspector UI -- |
| 51 | + |
| 52 | +this.reactWebDataLinkKeys = [ |
| 53 | + "source", |
| 54 | + "style" |
| 55 | +]; |
| 56 | + |
| 57 | +this.inspectorUIDefinition = [ |
| 58 | + { |
| 59 | + "type": "label", |
| 60 | + "text": "You can either select a data slot that contains the \nsource URL of the image and manually enter the style \nof the image below, or set these values in the data \nlinkage tab. The data linkage in the data tab \noverrides the plugin parameters below. \n", |
| 61 | + "height": 80, |
| 62 | + }, |
| 63 | + { |
| 64 | + "type": "label", |
| 65 | + "text": "Please ensure style follows the React in-line style \nJSON format, e.g.:\n{\n 'display': 'block',\n 'maxHeight': '100%',\n}", |
| 66 | + "height": 80, |
| 67 | + }, |
| 68 | + { |
| 69 | + "type": "label", |
| 70 | + "text": "Style:", |
| 71 | + "height": 20, |
| 72 | + }, |
| 73 | + { |
| 74 | + "type": "textinput", |
| 75 | + "id": "style", // MAKE SURE THIS is same as the variable name in this._data{} |
| 76 | + "label": "Style of the image", |
| 77 | + "actionBinding": "this.onUIChange", |
| 78 | + "multiline": true, |
| 79 | + "height": 100, // HEIGHT of component in RS |
| 80 | + }, |
| 81 | + { |
| 82 | + "type": "label", |
| 83 | + "text": "Source URL:", |
| 84 | + "height": 10, |
| 85 | + "paddingTop": 20, |
| 86 | + }, |
| 87 | + { |
| 88 | + "type": "dataslot-picker", |
| 89 | + "id": "src", |
| 90 | + "label": "source URL or dataSlot of image", |
| 91 | + "actionBinding": "this.onUIChange" |
| 92 | + } |
| 93 | +]; |
| 94 | + |
| 95 | +// ACTUAL Settings declared |
| 96 | +this._uiTextFields = [ 'style']; |
| 97 | +this._uiCheckboxes = []; |
| 98 | +// this._uiCheckboxes = ['loop', 'play']; |
| 99 | +this._uiNumberFields = []; |
| 100 | +this._uiColorPickers = []; |
| 101 | +this._uiComponentPickers = []; |
| 102 | +this._uiDataSlotPickers = [ 'src' ]; |
| 103 | + |
| 104 | +this._accessorForDataKey = function(key) { |
| 105 | + if (this._uiTextFields.includes(key)) return 'text'; |
| 106 | + else if (this._uiCheckboxes.includes(key)) return 'checked'; |
| 107 | + else if (this._uiNumberFields.includes(key)) return 'numberValue'; |
| 108 | + else if (this._uiColorPickers.includes(key)) return 'rgbaArrayValue'; |
| 109 | + else if (this._uiComponentPickers.includes(key)) return 'componentName'; |
| 110 | + else if (this._uiDataSlotPickers.includes(key)) return 'dataSlotName'; |
| 111 | + return null; |
| 112 | +} |
| 113 | + |
| 114 | +this.onCreateUI = function() { |
| 115 | + var ui = this.getUI(); |
| 116 | + for (var controlId in this._data) { |
| 117 | + var prop = this._accessorForDataKey(controlId); |
| 118 | + if (prop) { |
| 119 | + try { |
| 120 | + ui.getChildById(controlId)[prop] = this._data[controlId]; |
| 121 | + } catch (e) { |
| 122 | + console.log("** can't set ui value for key "+controlId+", prop "+prop); |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +this.onUIChange = function(controlId) { |
| 129 | + var ui = this.getUI(); |
| 130 | + var prop = this._accessorForDataKey(controlId); |
| 131 | + if (prop) { |
| 132 | + this._data[controlId] = ui.getChildById(controlId)[prop]; |
| 133 | + } else { |
| 134 | + console.log("** no data property found for controlId "+controlId); |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +// -- plugin preview -- |
| 139 | + |
| 140 | +this.renderIcon = function(canvas) { |
| 141 | + var ctx = canvas.getContext('2d'); |
| 142 | + var w = canvas.width; |
| 143 | + var h = canvas.height; |
| 144 | + ctx.save(); |
| 145 | + if (this.icon == null) { |
| 146 | + // got the YouTube logo online |
| 147 | + var path = Plugin.getPathForResource("logo.png"); // LOGO image |
| 148 | + this.icon = Plugin.loadImage(path); |
| 149 | + } |
| 150 | + var iconW = this.icon.width; |
| 151 | + var iconH = this.icon.height; |
| 152 | + var aspectScale = Math.min(w/iconW, h/iconH); |
| 153 | + var scale = 0.9 * aspectScale; // add some margin around icon |
| 154 | + iconW *= scale; |
| 155 | + iconH *= scale; |
| 156 | + ctx.drawImage(this.icon, (w-iconW)*0.5, (h-iconH)*0.5, iconW, iconH); |
| 157 | + ctx.restore(); |
| 158 | +}; |
| 159 | + |
| 160 | +// WHAT shows in the RS area after dragging component |
| 161 | +this.renderEditingCanvasPreview = function(canvas, controller) { |
| 162 | + this._renderPreview(canvas, controller); |
| 163 | +} |
| 164 | + |
| 165 | +// REAL preview if needed to show while in dev |
| 166 | +this._renderPreview = function(canvas, controller) { |
| 167 | + var ctx = canvas.getContext('2d'); |
| 168 | + var w = canvas.width; |
| 169 | + var h = canvas.height; |
| 170 | + ctx.save(); |
| 171 | + |
| 172 | + if (this.icon == null) { |
| 173 | + var path = Plugin.getPathForResource("logo.png"); |
| 174 | + this.icon = Plugin.loadImage(path); |
| 175 | + } |
| 176 | + var iconW = this.icon.width; |
| 177 | + var iconH = this.icon.height; |
| 178 | + var aspectScale = Math.min(w/iconW, h/iconH); |
| 179 | + var scale = 0.9 * aspectScale; // add some margin around icon |
| 180 | + iconW *= scale; |
| 181 | + iconH *= scale; |
| 182 | + ctx.drawImage(this.icon, (w-iconW)*0.5, (h-iconH)*0.5, iconW, iconH); |
| 183 | + ctx.restore(); |
| 184 | + |
| 185 | +} |
| 186 | + |
| 187 | + |
| 188 | +// ACTUALLY TELLING REACT WHERE TO PULL COMPONENT FROM |
| 189 | + |
| 190 | +// -- code generation, React web -- |
| 191 | + |
| 192 | +this.getReactWebPackages = function() { |
| 193 | + // Return dependencies that need to be included in the exported project's package.json file. |
| 194 | + // Each key is an npm package name that must be imported, and the value is the package version. |
| 195 | + |
| 196 | + return; |
| 197 | +} |
| 198 | + |
| 199 | +this.getReactWebImports = function(exporter) { |
| 200 | + var arr = []; |
| 201 | + |
| 202 | + return arr; |
| 203 | +} |
| 204 | + |
| 205 | +this.writesCustomReactWebComponent = false; |
| 206 | + |
| 207 | +this.getReactWebJSXCode = function(exporter) { |
| 208 | + const src = this._data.src; // FROM Variable declared at top |
| 209 | + const style = this._data.style; // FROM Variable declared at top |
| 210 | + var jsx = `<img `; |
| 211 | + |
| 212 | + var sourceLinkage = exporter.getExpressionForLinkKey('source'); |
| 213 | + var styleLinkage = exporter.getExpressionForLinkKey('style'); |
| 214 | + |
| 215 | + if (sourceLinkage) { |
| 216 | + jsx += `src={${sourceLinkage}} `; |
| 217 | + } |
| 218 | + else { |
| 219 | + jsx += `src={this.props.appActions.dataSlots['${src}']} `; |
| 220 | + } |
| 221 | + |
| 222 | + if (styleLinkage) { |
| 223 | + jsx += `style={${styleLinkage}} `; |
| 224 | + } |
| 225 | + else { |
| 226 | + jsx += `style={${style}} `; |
| 227 | + } |
| 228 | + jsx += ` />`; |
| 229 | + return jsx; |
| 230 | +} |
| 231 | + |
0 commit comments