Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ui_base.html 170KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324
  1. <style>
  2. :root {
  3. --nr-db-dark-text: #444;
  4. --nr-db-light-text: #eee;
  5. --nr-db-disabled-text: #999;
  6. --nr-db-mid-grey: #7f7f7f;
  7. }
  8. .nr-db-sb {
  9. position: absolute;
  10. top: 1px;
  11. bottom: 2px;
  12. left: 1px;
  13. right: 1px;
  14. overflow-y: scroll;
  15. padding: 10px;
  16. }
  17. .nr-db-sb .form-row label {
  18. display: block;
  19. width: auto;
  20. }
  21. .nr-db-sb .form-row input,
  22. .nr-db-sb .form-row select {
  23. width: calc(100% - 100px);
  24. margin-bottom:0;
  25. }
  26. .nr-db-sb .compact {
  27. margin-bottom: 8px !important;
  28. }
  29. .nr-db-sb .red-ui-editableList-container {
  30. padding: 0;
  31. min-height: 250px;
  32. height: auto;
  33. }
  34. .nr-db-sb-tab-list {
  35. min-height: 250px;
  36. height: auto;
  37. }
  38. .nr-db-sb-tab-list li {
  39. padding: 0;
  40. }
  41. .nr-db-sb-tab-list-item {
  42. border-radius: 4px;
  43. color: var(--red-ui-primary-text-color, var(--nr-db-dark-text));
  44. }
  45. .nr-db-sb-list-header {
  46. cursor: pointer;
  47. position:relative;
  48. color: var(--red-ui-header-text-color, var(--nr-db-dark-text));
  49. padding:3px;
  50. white-space: nowrap;
  51. }
  52. .nr-db-sb-list-header:hover {
  53. color: var(--red-ui-secondary-text-color-hover, var(--nr-db-dark-text));
  54. }
  55. .nr-db-sb-title-hidden {
  56. text-decoration: line-through;
  57. }
  58. .nr-db-sb-title-disabled {
  59. color: var(--red-ui-secondary-text-color-disabled, var(--nr-db-disabled-text));
  60. }
  61. .nr-db-sb-tab-list-header {
  62. background: var(--red-ui-secondary-background-selected, var(--nr-db-light-text));
  63. padding:5px;
  64. }
  65. .nr-db-sb-group-list-header:hover,
  66. .nr-db-sb-widget-list-header:hover {
  67. background: var(--red-ui-secondary-background-hover, var(--nr-db-light-text));
  68. }
  69. .nr-db-sb-list-chevron {
  70. width: 15px;
  71. text-align: center;
  72. margin: 3px 5px 3px 5px;
  73. }
  74. .nr-db-sb-tab-list-item .red-ui-editableList-container {
  75. border-radius: 0;
  76. border: none;
  77. height: auto !important;
  78. min-height: unset;
  79. }
  80. .nr-db-sb-list-handle {
  81. vertical-align: top;
  82. opacity: 0;
  83. cursor: move;
  84. }
  85. .nr-db-sb-list-header:hover>.nr-db-sb-list-handle,
  86. .nr-db-sb-list-header:hover>.nr-db-sb-list-header-button-group {
  87. opacity: 1;
  88. }
  89. .nr-db-sb-list-header-button-group {
  90. opacity: 0;
  91. }
  92. .nr-db-sb-list-handle {
  93. color: var(--red-ui-tertiary-text-color, var(--nr-db-light-text));
  94. padding:5px;
  95. }
  96. .nr-db-sb-tab-list-header>.nr-db-sb-list-chevron {
  97. margin-left: 0px;
  98. transition: transform 0.2s ease-in-out;
  99. }
  100. .nr-db-sb-group-list-header>.nr-db-sb-list-chevron {
  101. margin-left: 20px;
  102. transition: transform 0.2s ease-in-out;
  103. }
  104. .nr-db-sb-group-list {
  105. min-height: 10px;
  106. }
  107. .nr-db-sb-group-list li {
  108. border-bottom-color: var(--red-ui-secondary-border-color, var(--nr-db-light-text));
  109. }
  110. .nr-db-sb-group-list>li.ui-sortable-helper {
  111. border-top: 1px solid var(--red-ui-secondary-border-color, var(--nr-db-light-text));
  112. }
  113. .nr-db-sb-group-list>li:last-child {
  114. border-bottom: none;
  115. }
  116. .nr-db-sb-widget-list>li {
  117. border: none !important;
  118. }
  119. .nr-db-sb-group-list>li>.red-ui-editableList-item-handle {
  120. left: 10px;
  121. }
  122. .nr-db-sb-list-button-group {
  123. position: absolute;
  124. right: 3px;
  125. top: 0px;
  126. z-index: 2;
  127. }
  128. .nr-db-sb-list-header-button-group {
  129. position: absolute;
  130. right: 3px;
  131. top: 4px;
  132. }
  133. .nr-db-sb-list-header-button {
  134. margin-left: 5px;
  135. }
  136. .nr-db-sb li.ui-sortable-helper {
  137. opacity: 0.9;
  138. }
  139. .nr-db-sb-widget-icon {
  140. margin-left: 56px;
  141. }
  142. .nr-db-sb-icon {
  143. margin-right: 10px;
  144. }
  145. .nr-db-sb-link {
  146. display: inline-block;
  147. padding-left: 20px;
  148. }
  149. .nr-db-sb-link-name-container .fa-external-link {
  150. margin-right: 10px;
  151. }
  152. .nr-db-sb-link-url {
  153. font-size: 0.8em;
  154. color: var(--red-ui-secondary-text-color, var(--nr-db-mid-grey));
  155. }
  156. span.nr-db-color-pick-container {
  157. max-width: 50px;
  158. border-radius: 3px;
  159. margin-left: 15px;
  160. }
  161. input.nr-db-field-themeColor[type="color"] {
  162. width: 60px !important;
  163. padding: 0px;
  164. height: 20px;
  165. box-shadow: none;
  166. position: absolute;
  167. right: 36px;
  168. border-radius: 3px !important;
  169. border: solid 1px #ccc;
  170. -webkit-appearance: none;
  171. font-size: smaller;
  172. text-align: center;
  173. }
  174. input.nr-db-field-themeColor::-webkit-color-swatch {
  175. border: none;
  176. }
  177. .red-ui-tabs {
  178. margin-bottom: 15px;
  179. }
  180. .red-ui-tab.hidden {
  181. display: none;
  182. }
  183. #dashboard-tabs-list li a:hover {
  184. cursor: pointer;
  185. }
  186. #dash-link-button {
  187. background: none;
  188. border: none;
  189. margin-top: 3px;
  190. display: inline-block;
  191. margin: 3px 0px 0px 3px;
  192. height: 32px;
  193. line-height: 29px;
  194. max-width: 200px;
  195. overflow: hidden;
  196. white-space: nowrap;
  197. position: relative;
  198. padding: 0px 7px 0px 7px;
  199. }
  200. ul.red-ui-dashboard-theme-styles {
  201. list-style: none;
  202. }
  203. ul.red-ui-dashboard-theme-styles li {
  204. margin-bottom: 6px;
  205. }
  206. .nr-db-resetIcon {
  207. margin: 3px 6px 0px 6px;
  208. float: right;
  209. color: var(--red-ui-secondary-text-color, var(--nr-db-mid-grey));
  210. opacity: 0.8;
  211. display: block;
  212. }
  213. .nr-db-resetIcon:hover {
  214. cursor: pointer;
  215. }
  216. #nr-db-field-font {
  217. margin-left: 2em;
  218. width: calc(100% - 81px);
  219. }
  220. .nr-db-theme-label {
  221. font-weight: bold;
  222. }
  223. #custom-theme-library-container .btn-group {
  224. margin-bottom: 10px;
  225. }
  226. </style>
  227. <!-- Dashboard layout tool -->
  228. <link rel="stylesheet" href="./ui_base/gs/gridstack.min.css">
  229. <link rel="stylesheet" href="./ui_base/css/gridstack-extra.min.css">
  230. <style>
  231. .grid-stack {
  232. background-color: #f8f8f8;
  233. border: solid 2px #C0C0C0;
  234. margin: auto;
  235. min-height: 42px;
  236. display: table-cell;
  237. background-image: linear-gradient(#C0C0C0 1px, transparent 0),
  238. linear-gradient(90deg, #C0C0C0 1px, transparent 0);
  239. background-size: 40px 43px;
  240. }
  241. .grid-stack>.grid-stack-item>.grid-stack-item-content {
  242. top: 3px;
  243. left: 5px;
  244. right: 5px;
  245. bottom: 3px;
  246. }
  247. .grid-stack-item-content {
  248. color: #2c3e50;
  249. text-align: center;
  250. background-color: #b0dfe3;
  251. border-radius: 2px;
  252. font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif;
  253. white-space: nowrap;
  254. font-size: 12px;
  255. opacity: 0.7;
  256. }
  257. .grid-stack-item {
  258. cursor: move;
  259. }
  260. .nr-dashboard-layout-container-fluid {
  261. width: 100%;
  262. padding-right: 0px;
  263. padding-left: 0px;
  264. margin-right: 0px;
  265. margin-left: 0px;
  266. }
  267. .nr-dashboard-layout-row {
  268. width: 100%;
  269. display: -ms-flexbox;
  270. display: flex;
  271. -ms-flex-wrap: wrap;
  272. flex-wrap: wrap;
  273. margin-right: 0px;
  274. margin-left: 0px;
  275. }
  276. .nr-dashboard-layout-span12 {
  277. width: 98.4%;
  278. padding: 2px;
  279. margin-left: 2px;
  280. }
  281. .nr-dashboard-layout-span6 {
  282. width: 49.2%;
  283. padding: 2px;
  284. margin-left: 2px;
  285. }
  286. .nr-dashboard-layout-span4 {
  287. width: 32.7%;
  288. padding: 2px;
  289. margin-left: 2px;
  290. }
  291. .nr-dashboard-layout-span3 {
  292. width: 24.3%;
  293. padding: 2px;
  294. margin-left: 2px;
  295. }
  296. .nr-dashboard-layout-span2 {
  297. width: 16.0%;
  298. padding: 2px;
  299. margin-left: 2px;
  300. }
  301. .nr-dashboard-layout-resize-disable {
  302. cursor: pointer;
  303. float: right;
  304. position: relative;
  305. z-index: 90;
  306. margin-right: 4px;
  307. }
  308. .nr-dashboard-layout-resize-enable {
  309. cursor: pointer;
  310. float: right;
  311. position: relative;
  312. z-index: 90;
  313. margin-right: 1px;
  314. }
  315. .grid-stack>.ui-state-disabled {
  316. opacity: 1;
  317. background-image: none;
  318. }
  319. .grid-stack>.grid-stack-item>.ui-resizable-handle {
  320. z-index: 90;
  321. margin-right: -7px;
  322. }
  323. </style>
  324. <script type="text/javascript">
  325. (function($) {
  326. var editSaveEventHandler;
  327. var nodesAddEventHandler;
  328. var nodesRemoveEventHandler;
  329. var layoutUpdateEventHandler; // Dashboard layout tool
  330. var uip = 'ui';
  331. var attemptedVendorLoad = false;
  332. var ensureDashboardNode;
  333. var loadTinyColor = function(path) {
  334. $.ajax({ url: path,
  335. success: function (data) {
  336. var jsScript = document.createElement("script");
  337. jsScript.type = "application/javascript";
  338. jsScript.src = path;
  339. document.body.appendChild(jsScript);
  340. //console.log('Tiny Color Loaded:',path);
  341. },
  342. error: function (xhr, ajaxOptions, thrownError) {
  343. if (xhr.status === 404 && !attemptedVendorLoad) {
  344. loadTinyColor('/'+uip+'/vendor/tinycolor2/dist/tinycolor-min.js');
  345. attemptedVendorLoad = true;
  346. }
  347. //console.log('Tiny Color Failed to load:',path);
  348. }
  349. });
  350. }
  351. // convert to i18 text
  352. function c_(x) {
  353. return RED._("node-red-dashboard/ui_base:ui_base."+x);
  354. }
  355. // Try to load dist version first
  356. // then if fails, load non dist version
  357. loadTinyColor('ui_base/js/tinycolor-min.js');
  358. //loadTinyColor('ui_base/tinycolor2/dist/tinycolor-min.js');
  359. // Dashboard layout tool
  360. // Load gridstack library
  361. var loadGsLib = function(path, callback) {
  362. $.ajax({ url: path,
  363. success: function (data) {
  364. var jsScript = document.createElement("script");
  365. jsScript.type = "application/javascript";
  366. jsScript.src = path;
  367. document.body.appendChild(jsScript);
  368. if (callback) { callback(); }
  369. },
  370. error: function (xhr, ajaxOptions, thrownError) {
  371. // TODO
  372. }
  373. });
  374. };
  375. loadGsLib('ui_base/gs/gridstack.min.js', function() {
  376. loadGsLib('ui_base/gs/gridstack.jQueryUI.min.js', null)
  377. });
  378. var tabDatas; // Layout editing tab data
  379. var oldSpacer; // Spacer not needed after editing
  380. var widthChange; // Group width change
  381. var widgetResize; // Change widget event
  382. var widgetDrag; // Drag wiget event
  383. var MAX_GROUP_WIDTH = 50; // The maximum width is 30
  384. /////////////////////////////////////////////////////////
  385. // Get widget under specified tab from node information
  386. /////////////////////////////////////////////////////////
  387. function getTabDataFromNodes(tabID) {
  388. var nodes = RED.nodes.createCompleteNodeSet(false);
  389. var tab = {};
  390. // Tab information
  391. for (var cnt = 0; cnt < nodes.length; cnt++) {
  392. if (nodes[cnt].type == "ui_tab" && nodes[cnt].id == tabID) {
  393. tab = {
  394. id: nodes[cnt].id,
  395. name: nodes[cnt].name,
  396. type: nodes[cnt].type,
  397. order: nodes[cnt].order,
  398. groups: []
  399. };
  400. break;
  401. }
  402. }
  403. // Group information
  404. for (var cnt = 0; cnt < nodes.length; cnt++) {
  405. if (nodes[cnt].type == "ui_group" && nodes[cnt].tab == tabID) {
  406. var group = {
  407. id: nodes[cnt].id,
  408. name: nodes[cnt].name,
  409. type: nodes[cnt].type,
  410. order: nodes[cnt].order,
  411. width: nodes[cnt].width,
  412. widgets: []
  413. };
  414. tab.groups.push(group);
  415. }
  416. }
  417. // Widget information
  418. var groupsIdx = {};
  419. for (var cnt = 0; cnt < tab.groups.length; cnt++) {
  420. groupsIdx[tab.groups[cnt].id] = tab.groups[cnt];
  421. }
  422. for (var cnt = 0; cnt < nodes.length; cnt++) {
  423. var group = groupsIdx[nodes[cnt].group];
  424. if (group != null && (/^ui_/.test(nodes[cnt].type) && nodes[cnt].type !== 'ui_link' && nodes[cnt].type !== 'ui_toast' && nodes[cnt].type !== 'ui_ui_control' && nodes[cnt].type !== 'ui_audio' && nodes[cnt].type !== 'ui_base' && nodes[cnt].type !== 'ui_group' && nodes[cnt].type !== 'ui_tab')) {
  425. var widget = {
  426. id: nodes[cnt].id,
  427. type: nodes[cnt].type,
  428. order: nodes[cnt].order,
  429. width: nodes[cnt].width,
  430. height: nodes[cnt].height,
  431. auto: nodes[cnt].width == 0 ? true : false
  432. };
  433. group.widgets.push(widget);
  434. if (!isLayoutToolSupported(nodes[cnt].type)) {
  435. console.log("LayoutTool warning: Unsupported widget. Widget="+JSON.stringify(widget));
  436. }
  437. }
  438. }
  439. return tab;
  440. }
  441. //////////////////////////////////////////////////
  442. // Update node information in the edited widget
  443. ////////////////////////////////////////////////////
  444. function putTabDataToNodes() {
  445. // Delete old flow spacer node
  446. for (var cnt = 0; cnt < oldSpacer.length; cnt++) {
  447. RED.nodes.remove(oldSpacer[cnt]);
  448. RED.nodes.dirty(true);
  449. RED.view.redraw(true);
  450. }
  451. var t_groups = tabDatas.groups;
  452. for (var cnt1 = 0; cnt1 < t_groups.length; cnt1++) {
  453. var n_group = RED.nodes.node(t_groups[cnt1].id);
  454. n_group.width = t_groups[cnt1].width;
  455. var t_widgets = t_groups[cnt1].widgets;
  456. for (var cnt2 = 0; cnt2 < t_widgets.length; cnt2++) {
  457. var n_widget = RED.nodes.node(t_widgets[cnt2].id);
  458. if (n_widget != null) {
  459. if (n_widget.group !== n_group.id) {
  460. var oldGroupNode = RED.nodes.node(n_widget.group);
  461. if (oldGroupNode) {
  462. var index = oldGroupNode.users.indexOf(n_widget);
  463. oldGroupNode.users.splice(index,1);
  464. }
  465. n_widget.group = n_group.id;
  466. n_group.users.push(n_widget);
  467. }
  468. n_widget.order = t_widgets[cnt2].order;
  469. if (t_widgets[cnt2].auto === true ) {
  470. n_widget.width = 0;
  471. n_widget.height = 0;
  472. } else {
  473. n_widget.width = t_widgets[cnt2].width;
  474. n_widget.height = t_widgets[cnt2].height;
  475. }
  476. n_widget.changed = true;
  477. n_widget.dirty = true;
  478. RED.editor.validateNode(n_widget);
  479. RED.events.emit("layout:update",n_widget);
  480. RED.nodes.dirty(true);
  481. RED.view.redraw(true);
  482. }
  483. else {
  484. // Add a spacer node
  485. if (t_widgets[cnt2].type === 'ui_spacer') {
  486. var spaceNode = {
  487. _def: RED.nodes.getType("ui_spacer"),
  488. type: "ui_spacer",
  489. hasUsers: false,
  490. users: [],
  491. id: RED.nodes.id(),
  492. tab: tabDatas.id,
  493. group: n_group.id,
  494. order: t_widgets[cnt2].order,
  495. name: "spacer",
  496. width: t_widgets[cnt2].width,
  497. height: t_widgets[cnt2].height,
  498. z: RED.workspaces.active(),
  499. label: function() { return this.name + " " + this.width + "x" + this.height; }
  500. };
  501. RED.nodes.add(spaceNode);
  502. RED.editor.validateNode(spaceNode);
  503. RED.nodes.dirty(true);
  504. RED.view.redraw(true);
  505. }
  506. }
  507. };
  508. }
  509. RED.sidebar.info.refresh();
  510. }
  511. ////////////////////////////////////////
  512. // Sort by order
  513. ////////////////////////////////////////
  514. function compareOrder(a, b) {
  515. var r = 0;
  516. if (a.order < b.order) { r = -1; }
  517. else if (a.order > b.order) { r = 1; }
  518. return r;
  519. }
  520. ////////////////////////////////////////
  521. // Sort by XY
  522. ////////////////////////////////////////
  523. function compareXY(a, b) {
  524. var r = 0;
  525. if (a.y < b.y) { r = -1; }
  526. else if (a.y > b.y) { r = 1; }
  527. else if (a.x < b.x) { r = -1; }
  528. else if (a.x > b.x) { r = 1; }
  529. return r;
  530. }
  531. ///////////////////////////////////////////////////////
  532. // Placeable location search (placed in the upper left)
  533. ///////////////////////////////////////////////////////
  534. function search_point(width, height, maxWidth, maxHeight, tbl) {
  535. for (var y=0; y < maxHeight; y++) {
  536. for (var x=0; x < maxWidth; x++) {
  537. if (check_matrix(x, y, width, height, maxWidth, tbl)) {
  538. fill_matrix(x, y, width, height, maxWidth, tbl);
  539. return {x:x, y:y};
  540. }
  541. }
  542. }
  543. return false;
  544. }
  545. // Check placement position
  546. function check_matrix(px, py, width, height, maxWidth, tbl) {
  547. if (px+width > maxWidth) return false;
  548. for (var y=py; y < py+height; y++) {
  549. for (var x=px; x<px+width; x++) {
  550. if (tbl[maxWidth*y+x]) return false;
  551. }
  552. }
  553. return true;
  554. }
  555. // Mark the placement position
  556. function fill_matrix(px, py, width, height, maxWidth, tbl) {
  557. for (var y=py; y < py+height; y++) {
  558. for (var x=px; x < px+width; x++) {
  559. tbl[maxWidth*y+x] = 1;
  560. }
  561. }
  562. }
  563. ////////////////////////////////////////////////////
  564. // Apply edit result to tab information for editing
  565. ////////////////////////////////////////////////////
  566. function saveGridDatas() {
  567. var groups = tabDatas.groups;
  568. for (var cnt = 0; cnt < groups.length; cnt++) {
  569. // Get layout editing results
  570. var gridID = '#grid'+cnt;
  571. var serializedData = [];
  572. $(gridID+'.grid-stack > .grid-stack-item:visible').each( function (index) {
  573. el = $(this);
  574. var node = el.data('_gridstack_node');
  575. serializedData.push({
  576. id: el[0].dataset.noderedid,
  577. type: el[0].dataset.noderedtype,
  578. group: groups[cnt].id,
  579. width: Number(node.width),
  580. height: Number(node.height),
  581. x: node.x,
  582. y: node.y,
  583. auto: (el[0].dataset.noderedsizeauto == 'true') ? true : false
  584. });
  585. });
  586. var width = Number(groups[cnt].width);
  587. var height = 0;
  588. // Search group height
  589. for (var i=0; i < serializedData.length; i++) {
  590. var wd = serializedData[i];
  591. if (height < wd.y + wd.height) {
  592. height = wd.y + wd.height;
  593. }
  594. }
  595. // Place widget on table
  596. var tbl = new Array(width * height);
  597. for (var i = 0; i< tbl.length; i++) {
  598. tbl[i]=0;
  599. }
  600. for (var i = 0; i < serializedData.length; i++) {
  601. var wd = serializedData[i];
  602. for (var y = wd.y; y < wd.y+wd.height; y++) {
  603. for (var x = wd.x; x < wd.x+wd.width; x++) {
  604. tbl[width*y+x]=1;
  605. }
  606. }
  607. }
  608. // Add Spacer to Blank
  609. for (var y = 0; y < height; y++) {
  610. var spacerAdded = false;
  611. for (var x = 0; x < width; x++) {
  612. if (tbl[width*y+x]===0) {
  613. if (!spacerAdded) {
  614. // Add 1x1 spacer
  615. serializedData.push({
  616. x: x,
  617. y: y,
  618. z: RED.workspaces.active(),
  619. width: 1,
  620. height: 1,
  621. name: 'spacer',
  622. type: 'ui_spacer'
  623. });
  624. spacerAdded = true;
  625. } else {
  626. // Extend the spacer width by 1
  627. serializedData[serializedData.length-1].width += 1;
  628. }
  629. } else {
  630. spacerAdded = false;
  631. }
  632. }
  633. }
  634. // Sort Gridstack objects by x, y information
  635. serializedData.sort(compareXY);
  636. // Delete x and y elements as information for sorting, and give order
  637. var order = 0;
  638. for (i in serializedData) {
  639. order++;
  640. delete serializedData[i].x;
  641. delete serializedData[i].y;
  642. serializedData[i].order = order;
  643. }
  644. // Update widget information in group with edited data
  645. var group = groups[cnt];
  646. delete group.widgets;
  647. group.widgets = serializedData;
  648. }
  649. // Save process call
  650. putTabDataToNodes();
  651. };
  652. ////////////////////////////////////////////////////
  653. // Get default height for automatic settings
  654. ////////////////////////////////////////////////////
  655. function getDefaultHeight(nodeID, groupWidth) {
  656. var redNode = RED.nodes.node(nodeID);
  657. var height = 1;
  658. if (redNode.type === 'ui_gauge') {
  659. if (redNode.gtype === 'gage') {
  660. height = Math.round(groupWidth/2)+1;
  661. } else if (redNode.gtype === 'wave') {
  662. if (groupWidth < 3) {
  663. height = 1;
  664. } else {
  665. height = Math.round(groupWidth*0.75);
  666. }
  667. } else { // donut or compass
  668. if (groupWidth < 3) {
  669. height = 1;
  670. } else if (groupWidth < 11) {
  671. height = groupWidth - 1;
  672. } else {
  673. height = Math.round(groupWidth*0.95);
  674. }
  675. }
  676. } else if (redNode.type === 'ui_chart') {
  677. height = Math.floor(groupWidth/2)+1;
  678. } else if (redNode.type === 'ui_form') {
  679. // var optNum = redNode.options.length; // Sub widget number
  680. // if (redNode.label) {
  681. // height = optNum + 2; // Label and Button
  682. // } else {
  683. // height = optNum + 1; // Button only
  684. // }
  685. height = redNode.rowCount
  686. } else if (redNode.type === 'ui_lineargauge') {
  687. if (redNode.unit && redNode.name) {
  688. height = 5;
  689. } else {
  690. height = 4;
  691. }
  692. } else if (redNode.type === 'ui_list') {
  693. height = 5;
  694. } else if (redNode.type === 'ui_vega') {
  695. height = 5;
  696. }
  697. return height;
  698. }
  699. /////////////////////////////
  700. // Grid width change
  701. ////////////////////////////
  702. var changeGroupWidth = function(id) {
  703. var widthID = '#change-width'+id;
  704. var gridID = '#grid'+id;
  705. $(widthID).spinner( {
  706. min: 1,
  707. max: MAX_GROUP_WIDTH,
  708. spin: function(event, ui) {
  709. // Search current maximum width
  710. var serializedData = [];
  711. $(gridID+'.grid-stack > .grid-stack-item:visible').each( function (index) {
  712. el = $(this);
  713. var node = el.data('_gridstack_node');
  714. serializedData.push({
  715. width: Number(node.width),
  716. x: node.x,
  717. auto: (el[0].dataset.noderedsizeauto == 'true') ? true : false
  718. });
  719. });
  720. var maxWidth = 0;
  721. for (var i=0; i < serializedData.length; i++) {
  722. var wd = serializedData[i];
  723. if (wd.auto == false) {
  724. if (maxWidth < wd.x + wd.width) {
  725. maxWidth = wd.x + wd.width;
  726. }
  727. }
  728. }
  729. var width = ui.value;
  730. if (width < maxWidth) {
  731. width = maxWidth;
  732. }
  733. var grid = $(gridID+'.grid-stack').data('gridstack');
  734. $(gridID+'.grid-stack').css("width", width * 40);
  735. $(gridID+'.grid-stack').css("background-size", 100/width+"% 43px");
  736. grid.setColumn(tabDatas.groups[id].width, true);
  737. grid.setColumn(width, true);
  738. tabDatas.groups[id].width = width;
  739. $(gridID+'.grid-stack > .grid-stack-item:visible').each( function(idx, el) {
  740. el = $(el);
  741. var node = el.data('_gridstack_node');
  742. var auto = (el[0].dataset.noderedsizeauto == 'true') ? true : false;
  743. var type = el[0].dataset.noderedtype;
  744. grid.resizable(el, !auto);
  745. if (auto === true) {
  746. grid.resize(el, width, getDefaultHeight(node.id, width));
  747. }
  748. });
  749. if (width !== ui.value) {
  750. event.stopPropagation();
  751. event.preventDefault();
  752. }
  753. }
  754. });
  755. };
  756. //////////////////////////////////
  757. // Move between groups of widgets
  758. //////////////////////////////////
  759. function handleMove(grid) {
  760. return function(ev, prevWidget, newWidget) {
  761. var elem = newWidget.el[0];
  762. if (elem.getAttribute("data-noderedsizeauto") === "true") {
  763. var id = elem.getAttribute("data-noderedid");
  764. var width = grid.grid.column;
  765. var height = getDefaultHeight(id, width);
  766. grid.move(elem, 0, newWidget.y);
  767. grid.resize(elem, width, height);
  768. var en = $(elem).find('.nr-dashboard-layout-resize-enable');
  769. en.off('click');
  770. en.on('click',layoutResizeEnable);
  771. en[0].setAttribute("title",c_("layout.auto"));
  772. }
  773. else {
  774. var ds = $(elem).find('.nr-dashboard-layout-resize-disable');
  775. ds.off('click');
  776. ds.on('click',layoutResizeDisable);
  777. ds[0].setAttribute("title",c_("layout.manual"));
  778. }
  779. };
  780. }
  781. //////////////////////////////////////////
  782. // Widget size change (start event)
  783. //////////////////////////////////////////
  784. var resizeGroupWidget = function(id) {
  785. var gridID = '#grid'+id;
  786. var grid = $(gridID+'.grid-stack').data('gridstack');
  787. $(gridID+'.grid-stack').on('resizestart', function(event, ui) {
  788. // Reset group width
  789. grid.setColumn(tabDatas.groups[id].width, true);
  790. });
  791. }
  792. //////////////////////////////////////////
  793. // Widget drag (start event)
  794. //////////////////////////////////////////
  795. var dragGroupWidget = function(id) {
  796. var gridID = '#grid'+id;
  797. var grid = $(gridID+'.grid-stack').data('gridstack');
  798. $(gridID+'.grid-stack').on('dragstart', function(event, ui) {
  799. // Reset group width
  800. grid.setColumn(tabDatas.groups[id].width, true);
  801. });
  802. }
  803. //////////////////////////////////////////
  804. // Layout resize Disable (Auto:false)
  805. //////////////////////////////////////////
  806. var layoutResizeDisable = function(e) {
  807. var target = $(e.target);
  808. var el = target.parents('.grid-stack-item:visible');
  809. var grid = target.parents('.grid-stack').data('gridstack');
  810. var node = el.data('_gridstack_node');
  811. var id = Number(target.parents('.grid-stack').attr('id').slice(4));
  812. var width = Number(tabDatas.groups[id].width);
  813. var nodeID = el[0].dataset.noderedid;
  814. var height = getDefaultHeight(nodeID, width);
  815. grid.move(el, 0, node.y);
  816. grid.resize(el, width, height);
  817. grid.resizable(el, false);
  818. el.find('.nr-dashboard-layout-resize-disable').off('click');
  819. el.attr({'data-noderedsizeauto':'true'});
  820. target.removeClass().addClass('fa fa-unlock nr-dashboard-layout-resize-enable');
  821. el.find('.nr-dashboard-layout-resize-enable')[0].setAttribute("title",c_("layout.auto"));
  822. el.find('.nr-dashboard-layout-resize-enable').on('click',layoutResizeEnable);
  823. }
  824. //////////////////////////////////////////
  825. // Layout resize Enable (Auto:true)
  826. //////////////////////////////////////////
  827. var layoutResizeEnable = function(e) {
  828. var target = $(e.target);
  829. var el = target.parents('.grid-stack-item:visible');
  830. var grid = target.parents('.grid-stack').data('gridstack');
  831. grid.resizable(el, true);
  832. el.find('.nr-dashboard-layout-resize-enable').off('click');
  833. el.attr({'data-noderedsizeauto':'false'});
  834. target.removeClass().addClass('fa fa-lock nr-dashboard-layout-resize-disable');
  835. el.find('.nr-dashboard-layout-resize-disable')[0].setAttribute("title",c_("layout.manual"));
  836. el.find('.nr-dashboard-layout-resize-disable').on('click',layoutResizeDisable);
  837. }
  838. //////////////////////////////////////////
  839. // Check dashboard layout tool supported
  840. //////////////////////////////////////////
  841. function isLayoutToolSupported(nodeType) {
  842. if (nodeType.indexOf("ui_") !== 0) {
  843. return false;
  844. }
  845. else {
  846. return true;
  847. }
  848. }
  849. RED.nodes.registerType('ui_base', {
  850. category: 'config',
  851. defaults: {
  852. name: {},
  853. theme: {},
  854. site: {}
  855. },
  856. hasUsers: false,
  857. paletteLabel: 'Dashboard',
  858. label: function() { return this.name || 'Node-RED Dashboard'; },
  859. labelStyle: function() { return this.name ? "node_label_italic" : ""; },
  860. onpaletteremove: function() {
  861. RED.sidebar.removeTab("dashboard");
  862. RED.events.off("editor:save",editSaveEventHandler);
  863. RED.events.off("nodes:add",nodesAddEventHandler);
  864. RED.events.off("nodes:remove",nodesRemoveEventHandler);
  865. RED.events.off("layout:update",layoutUpdateEventHandler); // Dashboard layout tool
  866. },
  867. onpaletteadd: function() {
  868. var globalDashboardNode = null;
  869. var editor;
  870. var baseStyles = ['base-color'];
  871. var configurableStyles = ['page-titlebar-backgroundColor', 'page-backgroundColor', 'page-sidebar-backgroundColor',
  872. 'group-textColor', 'group-borderColor', 'group-backgroundColor',
  873. 'widget-textColor', 'widget-backgroundColor','widget-borderColor'];
  874. var baseFontName = "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif";
  875. var aTheme = {primary:"indigo", accents:"blue", warn:"red", background:"grey", palette:"light"};
  876. // tiny colour implementation
  877. var colours = {
  878. leastReadable: function(base, colours) {
  879. var least = tinycolor.readability(base, colours[0]);
  880. var leastColor = colours[0];
  881. for (var i=1; i<colours.length; i++) {
  882. var readability = tinycolor.readability(base, colours[i]);
  883. if (readability < least) {
  884. least = readability;
  885. leastColor = colours[i];
  886. }
  887. }
  888. return leastColor;
  889. },
  890. whiteGreyMostReadable: function (base) {
  891. var rgb = tinycolor(base).toRgb();
  892. var level = ((rgb.r*299) + (rgb.g*587) + (rgb.b*114))/1000;
  893. var readable = (level >= 128) ? '#111111' : '#eeeeee';
  894. return readable;
  895. },
  896. whiteBlackLeastReadable: function(base) {
  897. return this.leastReadable(base, ["#000000", "#ffffff"]);
  898. },
  899. calculate_page_backgroundColor: function(base) {
  900. var pageBackground = "#fafafa";
  901. var theme = "light";
  902. if (globalDashboardNode && globalDashboardNode.hasOwnProperty("theme") && globalDashboardNode.theme.hasOwnProperty("name")) {
  903. theme = globalDashboardNode.theme.name.split('-')[1];
  904. }
  905. if (theme === "dark") {
  906. pageBackground = "#111111";
  907. }
  908. else if (theme === "custom") {
  909. var whiteOrBlack = this.whiteBlackLeastReadable(base);
  910. if (whiteOrBlack === "#000000") { pageBackground = "#111111"; }
  911. }
  912. return pageBackground;
  913. },
  914. calculate_page_sidebar_backgroundColor: function(base) {
  915. if (this.whiteBlackLeastReadable(base) === "#000000") { return "#333333"; }
  916. else { return "#ffffff"; }
  917. },
  918. calculate_page_titlebar_backgroundColor: function(base) {
  919. return base;
  920. },
  921. calculate_group_textColor: function(base) {
  922. var groupTextColour = tinycolor(base).lighten(15).toHexString();
  923. //if (this.whiteBlackLeastReadable(base) === "#ffffff") { groupTextColour = "#000000"; }
  924. return groupTextColour;
  925. },
  926. calculate_group_backgroundColor: function(base) {
  927. var groupBackground = "#ffffff";
  928. var theme = "light";
  929. if (globalDashboardNode && globalDashboardNode.hasOwnProperty("theme") && globalDashboardNode.theme.hasOwnProperty("name")) {
  930. theme = globalDashboardNode.theme.name.split('-')[1];
  931. }
  932. if (theme === "dark") {
  933. groupBackground = "#333333";
  934. }
  935. else if (theme === "custom") {
  936. var whiteOrBlack = this.whiteBlackLeastReadable(base);
  937. if (whiteOrBlack === "#000000") { groupBackground = "#333333"; }
  938. }
  939. return groupBackground;
  940. },
  941. calculate_group_borderColor: function(base) {
  942. var groupBackground = this.calculate_group_backgroundColor(base);
  943. return this.leastReadable(groupBackground, ["#ffffff", "#555555"]);
  944. },
  945. calculate_widget_textColor: function(base) {
  946. //most readable against group background
  947. var groupBackground = this.calculate_group_backgroundColor(base);
  948. return tinycolor.mostReadable(groupBackground, ["#111111", "#eeeeee"]).toHexString();
  949. },
  950. calculate_widget_backgroundColor: function(base) {
  951. //return tinycolor(base).darken(5).toHexString()
  952. return tinycolor(base).toHexString();
  953. },
  954. calculate_widget_borderColor: function(base) {
  955. var widgetBorder = "#ffffff";
  956. var theme = "light";
  957. if (globalDashboardNode && globalDashboardNode.hasOwnProperty("theme") && globalDashboardNode.theme.hasOwnProperty("name")) {
  958. theme = globalDashboardNode.theme.name.split('-')[1];
  959. }
  960. if (theme === "dark") {
  961. widgetBorder = "#333333";
  962. }
  963. else if (theme === "custom") {
  964. var whiteOrBlack = this.whiteBlackLeastReadable(base);
  965. if (whiteOrBlack === "#000000") { widgetBorder = "#333333"; }
  966. }
  967. return widgetBorder;
  968. },
  969. calculate_base_font: function(base) {
  970. return baseFontName;
  971. }
  972. }
  973. var sizes = {
  974. sx: 48, // width of <1> grid square
  975. sy: 48, // height of <1> grid square
  976. gx: 6, // gap between groups
  977. gy: 6, // gap between groups
  978. cx: 6, // gap between components
  979. cy: 6, // gap between components
  980. px: 0, // padding of group's cards
  981. py: 0 // padding of group's cards
  982. };
  983. ensureDashboardNode = function(createMissing) {
  984. if (globalDashboardNode !== null) {
  985. // Check if it has been deleted beneath us
  986. var n = RED.nodes.node(globalDashboardNode.id);
  987. if (n === null) { globalDashboardNode = null; }
  988. }
  989. // Find the old dashboard node
  990. if (globalDashboardNode === null) {
  991. var bases = [];
  992. RED.nodes.eachConfig(function(n) {
  993. if (n.type === 'ui_base') { bases.push(n); }
  994. });
  995. // make sure we only have one ui_base node
  996. // at the moment this will just use our existing one - deleting any new base node and theme
  997. // at some point we may want to make this an option to select one or the other.
  998. while (bases.length > 1) {
  999. var n = bases.pop();
  1000. console.log("Removing ui_base node "+n.id);
  1001. RED.nodes.remove(n.id);
  1002. RED.nodes.dirty(true);
  1003. }
  1004. if (bases.length === 1) { globalDashboardNode = bases[0]; }
  1005. // If there is no dashboard node, ensure we create it after
  1006. // initialising
  1007. var noDashboardNode = (globalDashboardNode === null);
  1008. // set up theme state
  1009. var themeState = {};
  1010. var baseColor = "#0094CE"
  1011. for (var i=0; i<baseStyles.length; i++) {
  1012. themeState[baseStyles[i]] = { default:baseColor, value:baseColor, edited:false };
  1013. }
  1014. for (var j = 0; j < configurableStyles.length; j++) {
  1015. var underscore = configurableStyles[j].split("-").join("_");
  1016. var colour = colours['calculate_'+underscore](baseColor);
  1017. themeState[configurableStyles[j]] = {value:colour, edited:false};
  1018. }
  1019. themeState["base-font"] = {value:baseFontName};
  1020. var missingFields = (!globalDashboardNode || !globalDashboardNode.theme || !globalDashboardNode.site || !globalDashboardNode.site.sizes );
  1021. if (missingFields && createMissing) {
  1022. var lightTheme = {
  1023. default: baseColor,
  1024. baseColor: baseColor,
  1025. baseFont: baseFontName,
  1026. edited: false
  1027. }
  1028. var darkTheme = {
  1029. default: "#097479",
  1030. baseColor: "#097479",
  1031. baseFont: baseFontName,
  1032. edited: false
  1033. }
  1034. var customTheme = {
  1035. name: 'Untitled Theme 1',
  1036. default: "#4B7930",
  1037. baseColor: "#4B7930",
  1038. baseFont: baseFontName
  1039. }
  1040. var oldThemeName;
  1041. if (globalDashboardNode && typeof(globalDashboardNode.theme === 'string')) { oldThemeName = globalDashboardNode.theme; }
  1042. var theme = {
  1043. name: oldThemeName || "theme-light",
  1044. lightTheme: lightTheme,
  1045. darkTheme: darkTheme,
  1046. customTheme: customTheme,
  1047. themeState: themeState,
  1048. angularTheme: aTheme
  1049. }
  1050. var site_name = c_("site.title");
  1051. var site_date_format = c_("site.date-format");
  1052. var site = { name:site_name, hideToolbar:"false", allowSwipe:"false", lockMenu:"false", allowTempTheme:"true", dateFormat:site_date_format, sizes:sizes };
  1053. if (globalDashboardNode !== null) {
  1054. if (typeof globalDashboardNode.site !== "undefined") {
  1055. site = {
  1056. name: globalDashboardNode.site.name || globalDashboardNode.name,
  1057. hideToolbar: globalDashboardNode.site.hideToolbar,
  1058. lockMenu: globalDashboardNode.site.lockMenu,
  1059. allowSwipe: globalDashboardNode.site.allowSwipe,
  1060. allowTempTheme: globalDashboardNode.site.allowTempTheme,
  1061. dateFormat: globalDashboardNode.site.dateFormat,
  1062. sizes: globalDashboardNode.site.sizes
  1063. }
  1064. }
  1065. if (globalDashboardNode.theme.hasOwnProperty("angularTheme")) {
  1066. aTheme = globalDashboardNode.theme.angularTheme;
  1067. }
  1068. else { globalDashboardNode.theme.angularTheme = aTheme; }
  1069. }
  1070. if (noDashboardNode) {
  1071. globalDashboardNode = {
  1072. id: RED.nodes.id(),
  1073. _def: RED.nodes.getType("ui_base"),
  1074. type: "ui_base",
  1075. site: site,
  1076. theme: theme,
  1077. users: []
  1078. }
  1079. RED.nodes.add(globalDashboardNode);
  1080. RED.editor.validateNode(globalDashboardNode);
  1081. }
  1082. else {
  1083. globalDashboardNode["_def"] = RED.nodes.getType("ui_base");
  1084. globalDashboardNode.site = site;
  1085. globalDashboardNode.theme = theme;
  1086. delete globalDashboardNode.name;
  1087. }
  1088. $("#nr-db-field-font").val(baseFontName);
  1089. RED.nodes.dirty(true);
  1090. }
  1091. }
  1092. }
  1093. var content = $("<div>").css({"position":"relative","height":"100%"});
  1094. var mainContent = $("<div>",{class:"nr-db-sb"}).appendTo(content);
  1095. var form = $('<form class="dialog-form">').appendTo(mainContent);
  1096. // Dashboard Tabs markup
  1097. var divTab = $('<div class="red-ui-tabs">').appendTo(form);
  1098. var ulDashboardTabs = $('<ul id="dashboard-tabs-list"></ul>').appendTo(divTab);
  1099. var layout_label = c_("label.layout");
  1100. var site_label = c_("label.site");
  1101. var theme_label = c_("label.theme");
  1102. var angular_label = c_("label.angular");
  1103. var liLayoutTab = $('<li class="red-ui-tab" style="width:70px;"><a class="red-ui-tab-label" title="Layout"><span>'+layout_label+'</span></a></li>').appendTo(ulDashboardTabs);
  1104. var liSiteTab = $('<li class="red-ui-tab" style="width:70px;"><a class="red-ui-tab-label" title="Site" style="width:60px;"><span>'+site_label+'</span></a></li>').appendTo(ulDashboardTabs);
  1105. var liThemeTab = $('<li class="red-ui-tab" style="width:70px;"><a class="red-ui-tab-label" title="Theme" style="width:80px;"><span>'+theme_label+'</span></a></li>').appendTo(ulDashboardTabs);
  1106. var liAngularTab = $('<li class="red-ui-tab" style="width:70px;"><a class="red-ui-tab-label" title="Angular" style="width:80px;"><span>'+angular_label+'</span></a></li>').appendTo(ulDashboardTabs);
  1107. // Link out to dashboard
  1108. $.getJSON('uisettings',function(data) {
  1109. if (data.hasOwnProperty("path")) { uip = data.path; }
  1110. var lnk = document.location.host+RED.settings.httpNodeRoot+"/"+uip;
  1111. var re = new RegExp('\/{1,}','g');
  1112. lnk = lnk.replace(re,'/');
  1113. if (!RED.hasOwnProperty("actions")) {
  1114. RED.keyboard.add("*",/* d */ 68,{ctrl:true, shift:true},function() { window.open(document.location.protocol+"//"+lnk, "nr-dashboard") });
  1115. }
  1116. else {
  1117. RED.actions.add("dashboard:show-dashboard",function() { window.open(document.location.protocol+"//"+lnk, "nr-dashboard") });
  1118. RED.keyboard.add("*","ctrl-shift-d","dashboard:show-dashboard");
  1119. }
  1120. $('<span id="dash-link-button" class="editor-button" style="position:absolute; right:0px;"><i class="fa fa-external-link"></i></span>')
  1121. .click(function(evt) {
  1122. window.open(document.location.protocol+"//"+lnk);
  1123. evt.preventDefault();
  1124. })
  1125. .appendTo(ulDashboardTabs);
  1126. });
  1127. // Dashboard Tab containers
  1128. var layoutTab = $('<div id="dashboard-layout" style="height:calc(100% - 48px)">').appendTo(form);
  1129. var siteTab = $('<div id="dashboard-site" style="display:none;">').appendTo(form);
  1130. var themeTab = $('<div id="dashboard-theme" style="display:none;">').appendTo(form);
  1131. var angularTab = $('<div id="dashboard-angular" style="display:none;">').appendTo(form);
  1132. ulDashboardTabs.children().first().addClass("active");
  1133. // Tab logic
  1134. var onTabClick = function() {
  1135. //Toggle tabs
  1136. ulDashboardTabs.children().removeClass("active");
  1137. ulDashboardTabs.children().css({"transition": "width 100ms"});
  1138. $(this).parent().addClass("active");
  1139. var selectedTab = $(this)[0].title;
  1140. if (selectedTab === 'Layout') {
  1141. themeTab.hide();
  1142. siteTab.hide();
  1143. angularTab.hide();
  1144. layoutTab.show();
  1145. }
  1146. else if (selectedTab === 'Angular') {
  1147. themeTab.hide();
  1148. siteTab.hide();
  1149. angularTab.show();
  1150. layoutTab.hide();
  1151. }
  1152. else if (selectedTab === 'Theme') {
  1153. layoutTab.hide();
  1154. siteTab.hide();
  1155. angularTab.hide();
  1156. themeTab.show();
  1157. if ($("#nr-db-field-theme option:selected").val() === 'theme-custom') { themeSettingsContainer.show(); }
  1158. else { themeSettingsContainer.hide(); }
  1159. }
  1160. else {
  1161. layoutTab.hide();
  1162. themeTab.hide();
  1163. angularTab.hide();
  1164. siteTab.show();
  1165. }
  1166. }
  1167. ulDashboardTabs.find("li.red-ui-tab a").on("click",onTabClick)
  1168. // Site Tab
  1169. var divTitle = $('<div>',{class:"form-row compact"}).appendTo(siteTab);
  1170. $('<div>').html('<b>'+c_("label.title")+'</b>').appendTo(divTitle);
  1171. $('<input type="text" id="nr-db-field-title">').val(site_name).css("width","100%")
  1172. .on("change", function() {
  1173. if (!globalDashboardNode || globalDashboardNode.site.name !== $(this).val()) {
  1174. //ensureDashboardNode(true);
  1175. globalDashboardNode.site.name = $(this).val();
  1176. }
  1177. RED.nodes.dirty(true);
  1178. })
  1179. .appendTo(divTitle);
  1180. var divHideToolbar = $('<div>',{class:"form-row compact"}).appendTo(siteTab);
  1181. $('<div>').html('<b>'+c_("label.options")+'</b>').appendTo(divHideToolbar);
  1182. $('<select id="nr-db-field-hideToolbar">')
  1183. .css("width","100%")
  1184. .append($('<option>', { value:"false", text:c_("title-bar.show"), selected:true }))
  1185. .append($('<option>', { value:"true", text:c_("title-bar.hide") }))
  1186. .val("false")
  1187. .on("change", function() {
  1188. if (!globalDashboardNode || globalDashboardNode.site.hideToolbar !== $(this).val()) {
  1189. //ensureDashboardNode(true);
  1190. globalDashboardNode.site.hideToolbar = $(this).val();
  1191. }
  1192. RED.nodes.dirty(true);
  1193. })
  1194. .appendTo(divHideToolbar);
  1195. var divLockMenu = $('<div>',{class:"form-row compact"}).appendTo(siteTab);
  1196. $('<select id="nr-db-field-lockMenu">')
  1197. .css("width","100%")
  1198. .append($('<option>', { value:"false", text:c_("lock.clicked"), selected:true }))
  1199. .append($('<option>', { value:"true", text:c_("lock.locked") }))
  1200. .append($('<option>', { value:"icon", text:c_("lock.locked-icon") }))
  1201. .val("false")
  1202. .on("change", function() {
  1203. if (!globalDashboardNode || globalDashboardNode.site.lockMenu !== $(this).val()) {
  1204. //ensureDashboardNode(true);
  1205. globalDashboardNode.site.lockMenu = $(this).val();
  1206. }
  1207. RED.nodes.dirty(true);
  1208. })
  1209. .appendTo(divLockMenu);
  1210. var divAllowSwipe = $('<div>',{class:"form-row compact"}).appendTo(siteTab);
  1211. $('<select id="nr-db-field-allowSwipe">')
  1212. .css("width","100%")
  1213. .append($('<option>', { value:"false", text:c_("swipe.no-swipe"), selected:true }))
  1214. .append($('<option>', { value:"true", text:c_("swipe.allow-swipe") }))
  1215. .append($('<option>', { value:"mouse", text:c_("swipe.allow-swipe-mouse") }))
  1216. .append($('<option>', { value:"menu", text:c_("swipe.show-menu") }))
  1217. .val("false")
  1218. .on("change", function() {
  1219. if (!globalDashboardNode || globalDashboardNode.site.allowSwipe !== $(this).val()) {
  1220. //ensureDashboardNode(true);
  1221. globalDashboardNode.site.allowSwipe = $(this).val();
  1222. RED.nodes.dirty(true);
  1223. }
  1224. })
  1225. .appendTo(divAllowSwipe);
  1226. var divAllowTempTheme = $('<div>',{class:"form-row compact"}).appendTo(siteTab);
  1227. $('<select id="nr-db-field-allowTempTheme">')
  1228. .css("width","100%")
  1229. .append($('<option>', { value:"true", text:c_("temp.allow-theme"), selected:true }))
  1230. .append($('<option>', { value:"false", text:c_("temp.no-theme") }))
  1231. .append($('<option>', { value:"none", text:c_("temp.none") }))
  1232. .val("true")
  1233. .on("change", function() {
  1234. if (!globalDashboardNode || globalDashboardNode.site.allowTempTheme !== $(this).val()) {
  1235. //ensureDashboardNode(true);
  1236. globalDashboardNode.site.allowTempTheme = $(this).val();
  1237. }
  1238. if ($('#nr-db-field-allowTempTheme').val() === "none") {
  1239. ulDashboardTabs.children().eq(2).addClass("hidden");
  1240. ulDashboardTabs.children().eq(3).removeClass("hidden");
  1241. }
  1242. else {
  1243. ulDashboardTabs.children().eq(2).removeClass("hidden");
  1244. ulDashboardTabs.children().eq(3).addClass("hidden");
  1245. }
  1246. RED.nodes.dirty(true);
  1247. })
  1248. .appendTo(divAllowTempTheme);
  1249. var site_name = c_("site.title");
  1250. var site_date_format = c_("site.date-format");
  1251. var divDateFormat = $('<div>',{class:"form-row"}).appendTo(siteTab);
  1252. $('<div>').html('<b>'+c_("label.date-format")+'</b>')
  1253. .css("width","80%")
  1254. .css("display","inline-block")
  1255. .appendTo(divDateFormat);
  1256. $('<div>').html("<a href='https://momentjs.com/docs/#/displaying/format/' target='_new'><i class='fa fa-info-circle' style='color:grey;'></i></a>")
  1257. .css("display","inline-block")
  1258. .css("margin-right","6px")
  1259. .css("float","right")
  1260. .appendTo(divDateFormat);
  1261. $('<input type="text" id="nr-db-field-dateFormat">').val(site_date_format).css("width","100%")
  1262. .on("change", function() {
  1263. if (!globalDashboardNode || globalDashboardNode.site.dateFormat !== $(this).val()) {
  1264. //ensureDashboardNode(true);
  1265. globalDashboardNode.site.dateFormat = $(this).val();
  1266. }
  1267. RED.nodes.dirty(true);
  1268. })
  1269. .appendTo(divDateFormat);
  1270. var divSetSizes = $('<div>',{class:"form-row"}).appendTo(siteTab);
  1271. $('<span style="width:45%; display:inline-block">').html('<b>'+c_("label.sizes")+'</b>').appendTo(divSetSizes);
  1272. $('<span style="width:25%; display:inline-block; font-size:smaller">').text(c_("label.horizontal")).appendTo(divSetSizes);
  1273. $('<span style="width:20%; display:inline-block; font-size:smaller">').text(c_("label.vertical")).appendTo(divSetSizes);
  1274. $('<i id="sizes-reset" class="fa fa-undo nr-db-resetIcon"></i>')
  1275. .css({opacity:1.0})
  1276. .click(function(e) {
  1277. $("#nr-db-field-sx").val(sizes.sx); globalDashboardNode.site.sizes.sx = sizes.sx;
  1278. $("#nr-db-field-sy").val(sizes.sy); globalDashboardNode.site.sizes.sy = sizes.sy;
  1279. $("#nr-db-field-px").val(sizes.px); globalDashboardNode.site.sizes.px = sizes.px;
  1280. $("#nr-db-field-py").val(sizes.py); globalDashboardNode.site.sizes.py = sizes.py;
  1281. $("#nr-db-field-gx").val(sizes.gx); globalDashboardNode.site.sizes.gx = sizes.gx;
  1282. $("#nr-db-field-gy").val(sizes.gy); globalDashboardNode.site.sizes.gy = sizes.gy;
  1283. $("#nr-db-field-cx").val(sizes.cx); globalDashboardNode.site.sizes.cx = sizes.cx;
  1284. $("#nr-db-field-cy").val(sizes.cy); globalDashboardNode.site.sizes.cy = sizes.cy;
  1285. RED.nodes.dirty(true);
  1286. })
  1287. .appendTo(divSetSizes);
  1288. $('<br/><span style="width:45%; display:inline-block">').text(c_("label.widget-size")).appendTo(divSetSizes);
  1289. $('<input type="number" name="sx" min="24" id="nr-db-field-sx">').val(48).css("width","20%")
  1290. .on("change", function() {
  1291. //ensureDashboardNode(true);
  1292. globalDashboardNode.site.sizes.sx=Number($(this).val()); RED.nodes.dirty(true); } )
  1293. .appendTo(divSetSizes);
  1294. $('<span style="width:5%; display:inline-block">').text(' ').appendTo(divSetSizes);
  1295. $('<input type="number" name="sy" min="24" id="nr-db-field-sy">').val(48).css("width","20%")
  1296. .on("change", function() {
  1297. //ensureDashboardNode(true);
  1298. globalDashboardNode.site.sizes.sy=Number($(this).val()); RED.nodes.dirty(true); } )
  1299. .appendTo(divSetSizes);
  1300. $('<br/><span style="width:45%; display:inline-block">').text(c_("label.widget-spacing")).appendTo(divSetSizes);
  1301. $('<input type="number" name="cx" min="0" id="nr-db-field-cx">').val(6).css("width","20%")
  1302. .on("change", function() {
  1303. //ensureDashboardNode(true);
  1304. globalDashboardNode.site.sizes.cx=Number($(this).val()); RED.nodes.dirty(true); } )
  1305. .appendTo(divSetSizes);
  1306. $('<span style="width:5%; display:inline-block">').text(' ').appendTo(divSetSizes);
  1307. $('<input type="number" name="cy" min="0" id="nr-db-field-cy">').val(6).css("width","20%")
  1308. .on("change", function() {
  1309. //ensureDashboardNode(true);
  1310. globalDashboardNode.site.sizes.cy=Number($(this).val()); RED.nodes.dirty(true); } )
  1311. .appendTo(divSetSizes);
  1312. $('<br/><span style="width:45%; display:inline-block">').text(c_("label.group-padding")).appendTo(divSetSizes);
  1313. $('<input type="number" name="px" min="0" id="nr-db-field-px">').val(0).css("width","20%")
  1314. .on("change", function() {
  1315. //ensureDashboardNode(true);
  1316. globalDashboardNode.site.sizes.px=Number($(this).val()); RED.nodes.dirty(true); } )
  1317. .appendTo(divSetSizes);
  1318. $('<span style="width:5%; display:inline-block">').text(' ').appendTo(divSetSizes);
  1319. $('<input type="number" name="py" min="0" id="nr-db-field-py">').val(0).css("width","20%")
  1320. .on("change", function() {
  1321. //ensureDashboardNode(true);
  1322. globalDashboardNode.site.sizes.py=Number($(this).val()); RED.nodes.dirty(true); } )
  1323. .appendTo(divSetSizes);
  1324. $('<br/><span style="width:45%; display:inline-block">').text(c_("label.group-spacing")).appendTo(divSetSizes);
  1325. $('<input type="number" name="gx" min="0" id="nr-db-field-gx">').val(6).css("width","20%")
  1326. .on("change", function() {
  1327. //ensureDashboardNode(true);
  1328. globalDashboardNode.site.sizes.gx=Number($(this).val()); RED.nodes.dirty(true); } )
  1329. .appendTo(divSetSizes);
  1330. $('<span style="width:5%; display:inline-block">').text(' ').appendTo(divSetSizes);
  1331. $('<input type="number" name="gy" min="0" id="nr-db-field-gy">').val(6).css("width","20%")
  1332. .on("change", function() {
  1333. //ensureDashboardNode(true);
  1334. globalDashboardNode.site.sizes.gy=Number($(this).val()); RED.nodes.dirty(true); } )
  1335. .appendTo(divSetSizes);
  1336. // Angular Theme Tab
  1337. var changed = function() {
  1338. ensureDashboardNode(true);
  1339. globalDashboardNode.theme.angularTheme = aTheme;
  1340. RED.nodes.dirty(true);
  1341. }
  1342. var angColorList = ["red", "pink", "purple", "deep-purple", "indigo", "blue", "light-blue", "cyan", "teal", "green", "light-green", "lime", "yellow", "amber", "orange", "deep-orange", "brown", "grey", "blue-grey"];
  1343. var angColors = "";
  1344. angColorList.forEach(function(c) { angColors += '<option value="' + c + '">' + c + '</option>'; });
  1345. var divPrimStyle = $('<div>',{class:"form-row"}).appendTo(angularTab);
  1346. $('<span style="width:45%; display:inline-block">')
  1347. .html('<b>'+c_("style.primary")+'</b>')
  1348. .appendTo(divPrimStyle);
  1349. $('<i id="ang-reset" class="fa fa-undo nr-db-resetIcon"></i>')
  1350. .css({opacity:1.0})
  1351. .click(function(e) {
  1352. $("#nr-db-field-angPrimary").val("indigo");
  1353. globalDashboardNode.theme.angularTheme.primary = "indigo";
  1354. RED.nodes.dirty(true);
  1355. })
  1356. .appendTo(divPrimStyle);
  1357. $('<select id="nr-db-field-angPrimary">'+angColors+'</select>')
  1358. .css("width","100%")
  1359. .val(aTheme.primary)
  1360. .on("change", function() { aTheme.primary = $(this).val(); changed(); })
  1361. .appendTo(divPrimStyle);
  1362. var divAccStyle = $('<div>',{class:"form-row"}).appendTo(angularTab);
  1363. $('<span style="width:45%; display:inline-block">')
  1364. .html('<b>'+c_("style.accents")+'</b>')
  1365. .appendTo(divAccStyle);
  1366. $('<i id="ang-reset" class="fa fa-undo nr-db-resetIcon"></i>')
  1367. .css({opacity:1.0})
  1368. .click(function(e) {
  1369. $("#nr-db-field-angAccents").val("blue");
  1370. globalDashboardNode.theme.angularTheme.accents = "blue";
  1371. RED.nodes.dirty(true);
  1372. })
  1373. .appendTo(divAccStyle);
  1374. $('<select id="nr-db-field-angAccents">'+angColors+'</select>')
  1375. .css("width","100%")
  1376. .val(aTheme.accents)
  1377. .on("change", function() { aTheme.accents = $(this).val(); changed(); })
  1378. .appendTo(divAccStyle);
  1379. var divWarnStyle = $('<div>',{class:"form-row"}).appendTo(angularTab);
  1380. $('<span style="width:45%; display:inline-block">')
  1381. .html('<b>'+c_("style.warnings")+'</b>')
  1382. .appendTo(divWarnStyle);
  1383. $('<i id="ang-reset" class="fa fa-undo nr-db-resetIcon"></i>')
  1384. .css({opacity:1.0})
  1385. .click(function(e) {
  1386. $("#nr-db-field-angWarn").val("red");
  1387. globalDashboardNode.theme.angularTheme.warn = "red";
  1388. RED.nodes.dirty(true);
  1389. })
  1390. .appendTo(divWarnStyle);
  1391. $('<select id="nr-db-field-angWarn">'+angColors+'</select>')
  1392. .css("width","100%")
  1393. .val(aTheme.warn)
  1394. .on("change", function() { aTheme.warn = $(this).val(); changed(); })
  1395. .appendTo(divWarnStyle);
  1396. var divBackStyle = $('<div>',{class:"form-row"}).appendTo(angularTab);
  1397. $('<span style="width:45%; display:inline-block">')
  1398. .html('<b>'+c_("style.background")+'</b>')
  1399. .appendTo(divBackStyle);
  1400. $('<i id="ang-reset" class="fa fa-undo nr-db-resetIcon"></i>')
  1401. .css({opacity:1.0})
  1402. .click(function(e) {
  1403. $("#nr-db-field-angBackground").val("grey");
  1404. globalDashboardNode.theme.angularTheme.background = "grey";
  1405. RED.nodes.dirty(true);
  1406. })
  1407. .appendTo(divBackStyle);
  1408. $('<select id="nr-db-field-angBackground">'+angColors+'</select>')
  1409. .css("width","100%")
  1410. .val(aTheme.background)
  1411. .on("change", function() { aTheme.background = $(this).val(); changed(); })
  1412. .appendTo(divBackStyle);
  1413. var divPalStyle = $('<div>',{class:"form-row"}).appendTo(angularTab);
  1414. $('<span style="width:45%; display:inline-block">')
  1415. .html('<b>'+c_("style.palette")+'</b>')
  1416. .appendTo(divPalStyle);
  1417. var lightdark = '<option value="light">' +c_("style.light")+ '</option>';
  1418. lightdark += '<option value="dark">' +c_("style.dark")+ '</option>';
  1419. $('<select id="nr-db-field-angLook">'+lightdark+'</select>')
  1420. .css("width","100%")
  1421. .val(aTheme.palette)
  1422. .on("change", function() { aTheme.palette = $(this).val(); changed(); })
  1423. .appendTo(divPalStyle);
  1424. // Theme Tab
  1425. // For all customisable styles, generate and apply the css
  1426. var generateColours = function(base) {
  1427. var theme = globalDashboardNode.theme.name.split('-')[1];
  1428. if (!globalDashboardNode.theme.themeState.hasOwnProperty["base-font"]) {
  1429. if (globalDashboardNode.theme[theme+"Theme"].baseFont === "Helvetica Neue") {
  1430. globalDashboardNode.theme[theme+"Theme"].baseFont = baseFontName;
  1431. }
  1432. globalDashboardNode.theme.themeState["base-font"] = {value:globalDashboardNode.theme[theme+"Theme"].baseFont};
  1433. $("#nr-db-field-font").val(globalDashboardNode.theme[theme+"Theme"].baseFont);
  1434. }
  1435. for (var i=0; i<configurableStyles.length; i++) {
  1436. var styleID = configurableStyles[i];
  1437. var underscore = styleID.split("-").join("_");
  1438. if (!globalDashboardNode.theme.themeState.hasOwnProperty(styleID)) {
  1439. globalDashboardNode.theme.themeState[styleID] = {value:"#fff",edited:false};
  1440. }
  1441. if (!globalDashboardNode.theme.themeState[styleID].edited || globalDashboardNode.theme[theme+'Theme'].reset) {
  1442. var colour = colours['calculate_'+underscore](base);
  1443. globalDashboardNode.theme.themeState[styleID].value = colour;
  1444. }
  1445. setColourPickerColour(styleID, globalDashboardNode.theme.themeState[styleID].value, globalDashboardNode.theme.themeState[styleID].edited);
  1446. }
  1447. globalDashboardNode.theme[theme+'Theme'].reset = false;
  1448. }
  1449. var divThemeStyle = $('<div>',{class:"form-row"}).appendTo(themeTab);
  1450. $('<label class="nr-db-theme-label">').text(c_("theme.style")).appendTo(divThemeStyle);
  1451. var themeSelection = $('<select id="nr-db-field-theme">'+
  1452. '<option value="theme-light">'+c_("style.light")+'</option>'+
  1453. '<option value="theme-dark">'+c_("style.dark")+'</option>'+
  1454. '<option value="theme-custom">'+c_("style.custom")+'</option>'+
  1455. '</select>')
  1456. .css("width","100%")
  1457. .on("change", function() {
  1458. if (!globalDashboardNode || globalDashboardNode.theme.name !== $(this).val()) {
  1459. //ensureDashboardNode(true);
  1460. var theme = globalDashboardNode.theme.name.split('-')[1];
  1461. var baseColour = globalDashboardNode.theme[theme+'Theme'].baseColor;
  1462. var baseFont = globalDashboardNode.theme[theme+'Theme'].baseFont;
  1463. globalDashboardNode.theme.name = $(this).val();
  1464. theme = globalDashboardNode.theme.name.split('-')[1];
  1465. if (theme !== "custom") {
  1466. baseColour = globalDashboardNode.theme[theme+'Theme'].default;
  1467. }
  1468. else { baseColour = globalDashboardNode.theme[theme+'Theme'].baseColor; }
  1469. setColourPickerColour("base-color", baseColour);
  1470. globalDashboardNode.theme.themeState['base-color'].value = baseColour;
  1471. globalDashboardNode.theme.themeState['base-color'].default = baseColour;
  1472. globalDashboardNode.theme.themeState['base-font'] = {value:baseFont};
  1473. $("#nr-db-field-font").val(baseFont);
  1474. globalDashboardNode.theme[theme+'Theme'].reset = true;
  1475. //generate colours for all colour settings from base colour
  1476. generateColours(baseColour);
  1477. RED.nodes.dirty(true);
  1478. }
  1479. $('#base-color-reset').remove();
  1480. if ($(this).val() === 'theme-custom') {
  1481. $("#custom-theme-library-container").show(); //TODO undo this at some point
  1482. $("#custom-theme-settings").show();
  1483. //addResetButton('base-color', baseSettingsUl.children());
  1484. }
  1485. else {
  1486. $("#custom-theme-library-container").hide();
  1487. $("#custom-theme-settings").hide();
  1488. addLightAndDarkResetButton('base-color', baseSettingsUl.children().first());
  1489. }
  1490. })
  1491. .appendTo(divThemeStyle);
  1492. var customThemeLibraryContainer = $('<div id="custom-theme-library-container">').appendTo(themeTab);
  1493. $('<label class="nr-db-theme-label">').text(c_("theme.custom-profile")).appendTo(customThemeLibraryContainer);
  1494. $('<input type="text" id="ui-sidebar-name" style="vertical-align:top;" placeholder="profile name (not blank)">')
  1495. .val(c_("theme.custom-profile-name"))
  1496. .on("change", function() {
  1497. if (!globalDashboardNode || globalDashboardNode.theme.customTheme.name !== $(this).val()) {
  1498. //ensureDashboardNode(true);
  1499. globalDashboardNode.theme.customTheme.name = $(this).val();
  1500. if (editor) {
  1501. editor.setValue(JSON.stringify({theme:globalDashboardNode.theme.themeState, site:globalDashboardNode.site}),1);
  1502. RED.nodes.dirty(true);
  1503. }
  1504. }
  1505. })
  1506. .keyup(function() {
  1507. if ($(this).val().length === 0) {
  1508. $("#custom-theme-library-container div").css("pointer-events","none");
  1509. }
  1510. else { $("#custom-theme-library-container div").css("pointer-events","inherit"); }
  1511. })
  1512. .appendTo(customThemeLibraryContainer);
  1513. $('<input type="hidden" id="nr-db-field-format">').appendTo(customThemeLibraryContainer);
  1514. $('<div style="display:none;" class="node-text-editor" id="nr-db-field-format-editor"></div>').appendTo(customThemeLibraryContainer);
  1515. var baseThemeSettingsContainer = $('<div id="base-theme-settings">').appendTo(themeTab);
  1516. var baseSettings = $('<div>',{class:"form-row"}).appendTo(baseThemeSettingsContainer);
  1517. $('<label class="nr-db-theme-label">').text(c_("theme.base-settings")).appendTo(baseSettings);
  1518. var baseSettingsUl = $('<ul id="base-settings-ul" class="red-ui-dashboard-theme-styles"></ul>').appendTo(baseSettings);
  1519. var baseColourItem = $('<li class="red-ui-dashboard-theme-item"><span>'+c_("base.colour")+'</span></li>').appendTo(baseSettingsUl);
  1520. var spanColorContainer = $('<span class="nr-db-color-pick-container"></span>').appendTo(baseColourItem);
  1521. $('<input id="base-color" class="nr-db-field-themeColor" type="color" value="#ffffff"/>')
  1522. .on("change", function() {
  1523. //ensureDashboardNode(true);
  1524. var value = $(this).val();
  1525. var lightThemeMatch = globalDashboardNode.theme.lightTheme.baseColor === value;
  1526. var darkThemeMatch = globalDashboardNode.theme.darkTheme.baseColor === value;
  1527. var customThemeMatch = globalDashboardNode.theme.customTheme.baseColor === value;
  1528. if (!globalDashboardNode || !lightThemeMatch || !darkThemeMatch || !customThemeMatch) {
  1529. var theme = globalDashboardNode.theme.name.split('-')[1];
  1530. globalDashboardNode.theme[theme+'Theme'].baseColor = value;
  1531. if (globalDashboardNode.theme.name === 'theme-light' || globalDashboardNode.theme.name === 'theme-dark') {
  1532. //for light and dark themes, reset the colours
  1533. globalDashboardNode.theme[theme+'Theme'].reset = true;
  1534. }
  1535. generateColours(value);
  1536. editor.setValue(JSON.stringify({theme:globalDashboardNode.theme.themeState, site:globalDashboardNode.site}),1);
  1537. colourPickerChangeHandler($(this).attr('id'), value);
  1538. }
  1539. })
  1540. .appendTo(spanColorContainer);
  1541. var baseFontItem = $('<li class="red-ui-dashboard-theme-item"><span>'+c_("base.font")+'</span></li>').appendTo(baseSettingsUl);
  1542. var fontSelector = $('<select id="nr-db-field-font">'+
  1543. '<option value="'+baseFontName+'" style="font-family:'+baseFontName+'">'+c_("font.system")+'</option>'+
  1544. '<option value="Arial,Arial,Helvetica,sans-serif" style="font-family:Arial,Arial,Helvetica,sans-serif">Arial</option>'+
  1545. '<option value="Arial Black,Arial Black,Gadget,sans-serif" style="font-family:Arial Black,Arial Black,Gadget,sans-serif">Arial Black</option>'+
  1546. '<option value="Arial Narrow,Nimbus Sans L,sans-serif" style="font-family:Arial Narrow,Nimbus Sans L,sans-serif">Arial Narrow</option>'+
  1547. '<option value="Century Gothic,CenturyGothic,AppleGothic,sans-serif" style="font-family:Century Gothic,CenturyGothic,AppleGothic,sans-serif">Century Gothic</option>'+
  1548. '<option value="Copperplate,Copperplate Gothic Light,fantasy" style="font-family:Copperplate,Copperplate Gothic Light,fantasy;">Copperplate</option>'+
  1549. '<option value="Courier,monospace" style="font-family:Courier,monospace;">Courier</option>'+
  1550. '<option value="Georgia,Georgia,serif" style="font-family:Georgia,Georgia,serif">Georgia</option>'+
  1551. '<option value="Gill Sans,Geneva,sans-serif" style="font-family:Gill Sans,Geneva,sans-serif;">Gill Sans</option>'+
  1552. //'<option value="Helvetica Neue,Helvetica,sans-serif" style="font-family:Helvetica Neue,Helvetica,sans-serif">Helvetica Neue</option>'+
  1553. '<option value="Impact,Impact,Charcoal,sans-serif" style="font-family:Impact,Impact,Charcoal,sans-serif">Impact</option>'+
  1554. '<option value="Lucida Sans Typewriter,Lucida Console,Monaco,monospace" style="font-family:Lucida Console,Monaco,monospace">Lucida Console</option>'+
  1555. '<option value="Lucida Sans Unicode,Lucida Grande,sans-serif" style="font-family:Lucida Sans Unicode,Lucida Grande,sans-serif">Lucida Sans</option>'+
  1556. '<option value="Palatino Linotype,Palatino,Book Antiqua,serif" style="font-family:Palatino Linotype,Palatino,Book Antiqua,serif">Palatino Linotype</option>'+
  1557. '<option value="Tahoma,Geneva,sans-serif" style="font-family:Tahoma,Geneva,sans-serif">Tahoma</optionstyle="font-family:>'+
  1558. '<option value="Times New Roman,Times,serif" style="font-family:Times New Roman,Times,serif">Times New Roman</option>'+
  1559. '<option value="Trebuchet MS,Helvetica,sans-serif" style="font-family:Trebuchet MS,Helvetica,sans-serif">Trebuchet MS</option>'+
  1560. '<option value="Verdana,Verdana,Geneva,sans-serif" style="font-family:Verdana,Verdana,Geneva,sans-serif">Verdana</option>'+
  1561. '</select>')
  1562. .on("change", function() {
  1563. //ensureDashboardNode(true);
  1564. var theme = globalDashboardNode.theme.name.split('-')[1];
  1565. globalDashboardNode.theme[theme+'Theme'].baseFont = $(this).val();
  1566. globalDashboardNode.theme.themeState['base-font'] = {value:$(this).val()};
  1567. RED.nodes.dirty(true);
  1568. })
  1569. .appendTo(baseFontItem);
  1570. var themeSettingsContainer = $('<div id="custom-theme-settings">').appendTo(themeTab);
  1571. // Markup
  1572. // Page styles
  1573. var divPageStyle = $('<div>',{class:"form-row"}).appendTo(themeSettingsContainer);
  1574. $('<label class="nr-db-theme-label">').text(c_("theme.page-settings")).appendTo(divPageStyle);
  1575. var pageStyles = $('<ul class="red-ui-dashboard-theme-styles"></ul>').appendTo(themeSettingsContainer);
  1576. addCustomisableStyle('page-titlebar-backgroundColor', c_("theme.page.title"), pageStyles);
  1577. addCustomisableStyle('page-backgroundColor', c_("theme.page.page"), pageStyles);
  1578. addCustomisableStyle('page-sidebar-backgroundColor', c_("theme.page.side"), pageStyles);
  1579. // Group styles
  1580. var divGroupStyle = $('<div>',{class:"form-row"}).appendTo(themeSettingsContainer);
  1581. $('<label class="nr-db-theme-label">').text(c_("theme.group-settings")).appendTo(divGroupStyle);
  1582. var groupStyles = $('<ul class="red-ui-dashboard-theme-styles"></ul>').appendTo(themeSettingsContainer);
  1583. addCustomisableStyle('group-textColor', c_("theme.group.text"), groupStyles);
  1584. addCustomisableStyle('group-borderColor', c_("theme.group.border"), groupStyles);
  1585. addCustomisableStyle('group-backgroundColor', c_("theme.group.background"), groupStyles);
  1586. // Widget styles
  1587. var divWidgetStyle = $('<div>',{class:"form-row"}).appendTo(themeSettingsContainer);
  1588. $('<label class="nr-db-theme-label">').text(c_("theme.widget-settings")).appendTo(divWidgetStyle);
  1589. var widgetStyles = $('<ul class="red-ui-dashboard-theme-styles"></ul>').appendTo(themeSettingsContainer);
  1590. addCustomisableStyle('widget-textColor', c_("theme.widget.text"), widgetStyles);
  1591. addCustomisableStyle('widget-backgroundColor', c_("theme.widget.colour"), widgetStyles);
  1592. addCustomisableStyle('widget-borderColor', c_("theme.widget.background"), widgetStyles);
  1593. function addCustomisableStyle(id, name, parentUl) {
  1594. var styleLi = $('<li class="red-ui-dashboard-theme-item"><span>'+name+'</span></li>').appendTo(parentUl);
  1595. var spanColorContainer = $('<span class="nr-db-color-pick-container"></span>').appendTo(styleLi);
  1596. $('<input id="'+id+'" class="nr-db-field-themeColor" type="color" value="#ffffff"/>')
  1597. .on("change", function() {
  1598. colourPickerChangeHandler($(this).attr('id'), $(this).val());
  1599. })
  1600. .appendTo(spanColorContainer);
  1601. addResetButton(id, styleLi);
  1602. }
  1603. function colourPickerChangeHandler(id, value) {
  1604. $("#"+id).css("background-color", value);
  1605. $("#"+id+"-reset").css({opacity:1});
  1606. globalDashboardNode.theme.themeState[id].edited = true;
  1607. globalDashboardNode.theme.themeState[id].value = value;
  1608. if (editor) {
  1609. editor.setValue(JSON.stringify({theme:globalDashboardNode.theme.themeState, site:globalDashboardNode.site}),1);
  1610. }
  1611. RED.nodes.dirty(true);
  1612. }
  1613. function addResetButton(id, parent) {
  1614. var resetToDefault = $('<i id="'+id+'-reset" class="fa fa-undo nr-db-resetIcon"></i>')
  1615. .css({opacity:0.2})
  1616. .click(function(e) { resetClick(e); })
  1617. .appendTo(parent);
  1618. }
  1619. function addLightAndDarkResetButton(id, parent) {
  1620. if ($("#" + id + "-reset").length === 0) {
  1621. var resetToDefault = $('<i id="'+id+'-reset" class="fa fa-undo nr-db-resetIcon"></i>')
  1622. .css({opacity:1})
  1623. .click(function(e) { lightAndDarkResetClick(e); })
  1624. .appendTo(parent);
  1625. globalDashboardNode.theme[globalDashboardNode.theme.name.split('-')[1] + 'Theme'].edited = true;
  1626. }
  1627. }
  1628. function lightAndDarkResetClick(e) {
  1629. var elementID = e.target.id.split('-reset')[0];
  1630. var key = globalDashboardNode.theme.name.split('-')[1] + 'Theme';
  1631. //sanity check - light and dark only allow base-color-reset
  1632. if (elementID === 'base-color') { // && globalDashboardNode.theme[key].edited) {
  1633. var defaultColor = globalDashboardNode.theme[key].default;
  1634. globalDashboardNode.theme[key].reset = true;
  1635. generateColours(defaultColor);
  1636. setColourPickerColour(elementID, defaultColor);
  1637. $("#"+elementID+"-reset").css({opacity:0.2});
  1638. globalDashboardNode.theme.themeState[elementID].value = defaultColor;
  1639. globalDashboardNode.theme[key].baseColor = defaultColor;
  1640. globalDashboardNode.theme[key].edited = false;
  1641. RED.nodes.dirty(true);
  1642. }
  1643. }
  1644. function resetClick(e) {
  1645. //take off -reset
  1646. var elementID = e.target.id.split('-reset')[0];
  1647. if (globalDashboardNode.theme.themeState[elementID].edited) {
  1648. var defaultColor = globalDashboardNode.theme.themeState['base-color'].value;
  1649. var colour;
  1650. //set colour
  1651. if (elementID === 'base-color') {
  1652. colour = defaultColor;
  1653. generateColours(colour);
  1654. }
  1655. else {
  1656. var underscore = elementID.split('-').join('_');
  1657. colour = colours['calculate_'+underscore](defaultColor);
  1658. }
  1659. setColourPickerColour(elementID, colour);
  1660. $("#"+elementID+"-reset").css({opacity:0.2});
  1661. globalDashboardNode.theme.themeState[elementID].edited = false;
  1662. globalDashboardNode.theme.themeState[elementID].value = colour;
  1663. RED.nodes.dirty(true);
  1664. }
  1665. }
  1666. function setColourPickerColour(id, val, ed) {
  1667. $("#"+id).val(val);
  1668. $("#"+id).css("background-color", val);
  1669. //call mostReadableGreyWhite to set text colour
  1670. var textColor = colours.whiteGreyMostReadable(val);
  1671. $("#"+id).css("color", textColor);
  1672. if (ed === true) { $("#"+id+"-reset").css({opacity:1}); }
  1673. else { $("#"+id+"-reset").css({opacity:0.2}); }
  1674. }
  1675. //Layout Tab
  1676. var divTabs = $('<div>',{class:"form-row",style:"position:relative"}).appendTo(layoutTab);
  1677. $('<label>').html('<b>'+c_("layout.tab-and-link")+'</b>').appendTo(divTabs);
  1678. var buttonGroup = $('<div>',{class:"nr-db-sb-list-button-group"}).appendTo(divTabs);
  1679. //Toggle expand buttons
  1680. $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-angle-double-up"></i></a>')
  1681. .click(function(evt) {
  1682. tabContainer.find(".nr-db-sb-group-list-container").slideUp().addClass('nr-db-sb-collapsed');
  1683. tabContainer.find(".nr-db-sb-tab-list-header>.nr-db-sb-list-chevron").css({"transform":"rotate(-90deg)"});
  1684. evt.preventDefault();
  1685. })
  1686. .appendTo(buttonGroup);
  1687. $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-angle-double-down"></i></a>')
  1688. .click(function(evt) {
  1689. tabContainer.find(".nr-db-sb-group-list-container").slideDown().removeClass('nr-db-sb-collapsed');
  1690. tabContainer.find(".nr-db-sb-tab-list-header>.nr-db-sb-list-chevron").css({"transform":""});
  1691. evt.preventDefault();
  1692. })
  1693. .appendTo(buttonGroup);
  1694. //Add item button
  1695. $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> '+c_("layout.tab")+'</a>')
  1696. .click(function(evt) {
  1697. tabContainer.editableList('addItem',{type: 'ui_tab'});
  1698. evt.preventDefault();
  1699. })
  1700. .appendTo(buttonGroup);
  1701. $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> '+c_("layout.link")+'</a>')
  1702. .click(function(evt) {
  1703. tabContainer.editableList('addItem',{type: 'ui_link'});
  1704. evt.preventDefault();
  1705. })
  1706. .appendTo(buttonGroup);
  1707. var tabLists = {};
  1708. var groupLists = {};
  1709. // toggle slide tab group content
  1710. var titleToggle = function (id,content,chevron) {
  1711. return function(evt) {
  1712. if (content.is(":visible")) {
  1713. content.slideUp();
  1714. chevron.css({"transform":"rotate(-90deg)"});
  1715. content.addClass('nr-db-sb-collapsed');
  1716. listStates[id] = false;
  1717. }
  1718. else {
  1719. content.slideDown();
  1720. chevron.css({"transform":""});
  1721. content.removeClass('nr-db-sb-collapsed');
  1722. listStates[id] = true;
  1723. }
  1724. };
  1725. }
  1726. var addTabOrLinkItem = function(container,i,item) {
  1727. ensureDashboardNode(true);
  1728. // create node if needed
  1729. if (!item.node) {
  1730. var defaultItem = {
  1731. 'ui_tab': {
  1732. _def: RED.nodes.getType('ui_tab'),
  1733. type: 'ui_tab',
  1734. users: [],
  1735. icon: 'dashboard',
  1736. name: 'Tab'
  1737. },
  1738. 'ui_link': {
  1739. _def: RED.nodes.getType('ui_link'),
  1740. type: 'ui_link',
  1741. users: [],
  1742. icon: 'open_in_browser',
  1743. name: 'Link',
  1744. target: 'newtab'
  1745. }
  1746. }
  1747. item.node = defaultItem[item.type]
  1748. item.node.id = RED.nodes.id()
  1749. item.node.order = i+1
  1750. item.node.name += ' '+item.node.order
  1751. listElements[item.node.id] = container;
  1752. if (item.type === 'ui_tab') {
  1753. item.groups = [];
  1754. }
  1755. RED.nodes.add(item.node);
  1756. RED.editor.validateNode(item.node);
  1757. RED.history.push({
  1758. t:'add',
  1759. nodes:[item.node.id],
  1760. dirty:RED.nodes.dirty()
  1761. });
  1762. RED.nodes.dirty(true);
  1763. }
  1764. else if (item.type === undefined) {
  1765. item.type = item.node.type
  1766. }
  1767. listElements[item.node.id] = container;
  1768. if (RED.nodes.hasOwnProperty('updateConfigNodeUsers')) {
  1769. RED.nodes.updateConfigNodeUsers(item.node);
  1770. }
  1771. // title
  1772. var titleRow = $('<div>',{class:"nr-db-sb-list-header nr-db-sb-tab-list-header"}).appendTo(container);
  1773. switch (item.type) {
  1774. case 'ui_tab': {
  1775. container.addClass("nr-db-sb-tab-list-item");
  1776. $('<i class="nr-db-sb-list-handle nr-db-sb-tab-list-handle fa fa-bars"></i>').appendTo(titleRow);
  1777. var chevron = $('<i class="fa fa-angle-down nr-db-sb-list-chevron">',{style:"width:10px;"}).appendTo(titleRow);
  1778. var tabicon = "fa-object-group";
  1779. //var tabicon = item.node.disabled ? "fa-window-close-o" : item.node.hidden ? "fa-eye-slash" : "fa-object-group";
  1780. $('<i>',{class:"nr-db-sb-icon nr-db-sb-tab-icon fa "+tabicon}).appendTo(titleRow);
  1781. var tabhide = item.node.hidden ? " nr-db-sb-title-hidden" : "";
  1782. var tabable = item.node.disabled ? " nr-db-sb-title-disabled" : "";
  1783. $('<span>',{class:"nr-db-sb-title"+tabhide+tabable}).text(item.node.name||"").appendTo(titleRow);
  1784. break;
  1785. }
  1786. case 'ui_link': {
  1787. $('<i class="nr-db-sb-list-handle fa fa-bars"></i>').appendTo(titleRow);
  1788. var title = $('<div class="nr-db-sb-link">').appendTo(titleRow);
  1789. var nameContainer = $('<div class="nr-db-sb-link-name-container">').appendTo(title);
  1790. $('<i class="fa fa-external-link"></i>').appendTo(nameContainer);
  1791. $('<span class="nr-db-sb-link-name">').text(item.node.name||"untitled").appendTo(nameContainer);
  1792. $('<div class="nr-db-sb-link-url">').text(item.node.link||"http://").appendTo(title);
  1793. break;
  1794. }
  1795. }
  1796. // buttons
  1797. var buttonGroup = $('<div>',{class:"nr-db-sb-list-header-button-group",id: item.node.id}).appendTo(titleRow);
  1798. if (item.type === 'ui_tab') {
  1799. var addGroupButton = $('<a href="#" class="nr-db-sb-tab-add-group-button editor-button editor-button-small nr-db-sb-list-header-button" ><i class="fa fa-plus"></i> '+c_("layout.group")+'</a>').appendTo(buttonGroup);
  1800. }
  1801. var editButton = $('<a href="#" class="nr-db-sb-tab-edit-button editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> '+c_("layout.edit")+'</a>').appendTo(buttonGroup);
  1802. editButton.on('click',function(evt) {
  1803. RED.editor.editConfig("", item.type, item.node.id);
  1804. evt.stopPropagation();
  1805. evt.preventDefault();
  1806. });
  1807. // Dashboard layout tool
  1808. if (item.type === 'ui_tab') {
  1809. var layoutButton = $('<a href="#" class="nr-db-sb-tab-edit-layout-button editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> '+c_("layout.layout")+'</a>').appendTo(buttonGroup);
  1810. layoutButton.on('click',function(evt) {
  1811. var editTabName = item.node.name ? item.node.name : item.node.id;
  1812. var trayOptions = {
  1813. title: c_("layout.layout-editor") + " : " + editTabName,
  1814. width: Infinity,
  1815. buttons: [
  1816. {
  1817. id: "node-dialog-cancel",
  1818. text: RED._("common.label.cancel"),
  1819. click: function() {
  1820. // clean editor
  1821. RED.tray.close();
  1822. }
  1823. },
  1824. {
  1825. id: "node-dialog-ok",
  1826. text: RED._("common.label.done"),
  1827. class: "primary",
  1828. click: function() {
  1829. // Save data after editing
  1830. saveGridDatas();
  1831. RED.tray.close();
  1832. }
  1833. }
  1834. ],
  1835. resize: function(dimensions) {},
  1836. open: function(tray) {
  1837. // Get widget of specified tab from node information
  1838. tabDatas = getTabDataFromNodes(item.node.id);
  1839. // The width that can be handled by Layout is up to MAX_GROUP_WIDTH
  1840. // Groups exceeding the maximum width are not supported.
  1841. var tmpGroups = tabDatas.groups;
  1842. tmpGroups.sort(compareOrder);
  1843. var groups = [];
  1844. for (var cnt = 0; cnt < tmpGroups.length; cnt++) {
  1845. if (tmpGroups[cnt].width <= MAX_GROUP_WIDTH) {
  1846. groups.push(tmpGroups[cnt]);
  1847. }
  1848. }
  1849. tabDatas.groups = groups;
  1850. var editor = $('<div></div>',{addClass: 'nr-dashboard-layout-container-fluid'});
  1851. var row = $('<div></div>',{addClass: 'nr-dashboard-layout-row'});
  1852. var span_num = Math.floor(12 / groups.length); // bootstrap grid 12 splits
  1853. span_num = span_num < 2 ? 2 : span_num; // max 6 groups per row
  1854. for (var cnt = 0; cnt < groups.length; cnt++) {
  1855. if (cnt !=0 && (cnt % 6) == 0) {
  1856. editor.append(row);
  1857. editor.append('<div><br></div>');
  1858. row = $('<div></div>',{addClass: 'nr-dashboard-layout-row'});
  1859. }
  1860. var span = $('<div></div>',{addClass: 'nr-dashboard-layout-span' + span_num});
  1861. var groupName = groups[cnt].name ? groups[cnt].name : groups[cnt].id;
  1862. var title = $('<div></div>', {
  1863. style: "margin-top:2px; margin-bottom:2px;"
  1864. });
  1865. var title_group = $('<div></div>', {
  1866. title: groupName,
  1867. style: "margin-left:4px; margin-right:8px; overflow:hidden;"
  1868. }).appendTo(title);
  1869. $("<b/>").text(groupName).appendTo(title_group);
  1870. var title_width = $('<div></div>', {
  1871. style: "text-align:right; margin-right:8px;"
  1872. }).appendTo(title);
  1873. $("<span/>", {
  1874. style: "margin_right: 8px;"
  1875. }).text(c_("layout.width")+': ').appendTo(title_width);
  1876. var changeWidth = $('<input>', {
  1877. id: 'change-width' + cnt,
  1878. value: groups[cnt].width,
  1879. style: 'width:30px;',
  1880. readonly: true,
  1881. 'node-id': groups[cnt].id,
  1882. });
  1883. title_width.append(changeWidth);
  1884. title.css('white-space','nowrap');
  1885. title.css('overflow','hidden');
  1886. var gridstack = $('<div></div>', {
  1887. id: 'grid'+cnt,
  1888. addClass: 'grid-stack'
  1889. });
  1890. span.append(title);
  1891. span.append(gridstack);
  1892. row.append(span);
  1893. }
  1894. if (groups.length != 0) {
  1895. editor.append(row);
  1896. }
  1897. // Show layout editor in tray
  1898. var trayBody = tray.find('.red-ui-tray-body, .editor-tray-body');
  1899. trayBody.css('overflow','auto');
  1900. trayBody.append(editor);
  1901. /////////////////////////////////////////
  1902. // Editor screen generation
  1903. /////////////////////////////////////////
  1904. oldSpacer = [];
  1905. widthChange = [];
  1906. widgetResize = [];
  1907. widgetDrag = [];
  1908. for (var cnt=0; cnt < groups.length; cnt++) {
  1909. // Gridstack.js option
  1910. var options = {
  1911. acceptWidgets: true,
  1912. alwaysShowResizeHandle: true,
  1913. cellHeight: 42,
  1914. disableOneColumnMode : true,
  1915. float: true,
  1916. verticalMargin: 1
  1917. };
  1918. var gridID='#grid' + cnt;
  1919. // gridstack generation
  1920. $(gridID).gridstack(options);
  1921. // Clear the contents of Grid
  1922. var grid = $(gridID+'.grid-stack').data('gridstack');
  1923. grid.removeAll();
  1924. $(gridID).on("dropped", handleMove(grid));
  1925. // Set the width of the display area of gridstack
  1926. var groupWidth = Number(groups[cnt].width);
  1927. $(gridID+'.grid-stack').css("width", groupWidth * 40);
  1928. $(gridID+'.grid-stack').css("background-size", 100/groupWidth+"% 43px");
  1929. $(gridID+'.grid-stack').attr("node-id", groups[cnt].id);
  1930. $(gridID+'.grid-stack').attr("grid-column", groups[cnt].width);
  1931. grid.setColumn(groupWidth, true);
  1932. // Determination of placement position of widget of Grid
  1933. var widgets = groups[cnt].widgets;
  1934. widgets.sort(compareOrder);
  1935. var tbl = {};
  1936. for (var cnt2 = 0; cnt2 < widgets.length; cnt2++) {
  1937. // Set default value when there is auto width
  1938. if (widgets[cnt2].auto == true) {
  1939. widgets[cnt2].width = groupWidth;
  1940. // Adjust to the group width
  1941. } else if (widgets[cnt2].width > groupWidth) {
  1942. widgets[cnt2].width = groupWidth;
  1943. }
  1944. // Auto support
  1945. if (widgets[cnt2].auto === true || widgets[cnt2].type === 'ui_form') {
  1946. widgets[cnt2].height = getDefaultHeight(widgets[cnt2].id, groupWidth);
  1947. }
  1948. // Calculate coordinates to be placed
  1949. var point = search_point(Number(widgets[cnt2].width), Number(widgets[cnt2].height), groupWidth, 256, tbl);
  1950. if (point) {
  1951. widgets[cnt2].x = point.x;
  1952. widgets[cnt2].y = point.y;
  1953. }
  1954. }
  1955. var items = GridStackUI.Utils.sort(widgets);
  1956. items.forEach(function (node) {
  1957. var minHeight = null;
  1958. var maxHeight = null;
  1959. // ui_form is fixed to height 2
  1960. if (node.type === 'ui_form') {
  1961. minHeight = node.height;
  1962. maxHeight = node.height;
  1963. }
  1964. if (node.type !== 'ui_spacer') {
  1965. var dispNode = RED.nodes.node(node.id);
  1966. var dispType = dispNode._def.paletteLabel;
  1967. var dispLabel = dispNode._def.label;
  1968. try {
  1969. dispLabel = (typeof dispLabel === "function" ? dispLabel.call(dispNode) : dispLabel)||"";
  1970. }
  1971. catch(err) {
  1972. console.log("Definition error: " + node.type + ".label",err);
  1973. dispLabel = dispType;
  1974. }
  1975. var item = $('<div></div>', {
  1976. 'data-noderedtype': node.type,
  1977. 'data-noderedid': node.id,
  1978. 'data-nodereddisptype': dispType,
  1979. 'data-nodereddisplabel': dispLabel,
  1980. 'data-noderedsizeauto': node.auto
  1981. });
  1982. var itemContent = $('<div></div>', {
  1983. addClass: 'grid-stack-item-content',
  1984. title: dispLabel + ':' + dispType
  1985. });
  1986. if (node.auto === true) {
  1987. itemContent.append('<i class="fa fa-unlock nr-dashboard-layout-resize-enable" title="'+c_("layout.auto")+'"></i>');
  1988. } else {
  1989. itemContent.append('<i class="fa fa-lock nr-dashboard-layout-resize-disable" title="'+c_("layout.manual")+'"></i>');
  1990. }
  1991. itemContent.append('<b>'+ dispLabel +'</b><br/>'+ dispType);
  1992. item.append(itemContent);
  1993. grid.addWidget(
  1994. item,
  1995. node.x, node.y, node.width, node.height, false, null, null,
  1996. minHeight, maxHeight, node.id);
  1997. } else {
  1998. // Record the spacer node ID to be deleted
  1999. oldSpacer.push(node.id);
  2000. }
  2001. });
  2002. $(gridID+'.grid-stack > .grid-stack-item:visible').each( function(idx, el) {
  2003. el = $(el);
  2004. var node = el.data('_gridstack_node');
  2005. var auto = (el[0].dataset.noderedsizeauto == 'true') ? true : false;
  2006. grid.resizable(el, !auto);
  2007. });
  2008. // Group width change
  2009. widthChange.push(new changeGroupWidth(cnt));
  2010. // Resize widget in group (start event)
  2011. widgetResize.push(new resizeGroupWidget(cnt));
  2012. // Dragging widgets in a group (start event)
  2013. widgetDrag.push(new dragGroupWidget(cnt));
  2014. }
  2015. $('.grid-stack>.grid-stack-item>.grid-stack-item-content>.nr-dashboard-layout-resize-disable').on('click',layoutResizeDisable);
  2016. $('.grid-stack>.grid-stack-item>.grid-stack-item-content>.nr-dashboard-layout-resize-enable').on('click',layoutResizeEnable);
  2017. },
  2018. close: function() {},
  2019. show: function() {}
  2020. }
  2021. RED.tray.show(trayOptions);
  2022. evt.stopPropagation();
  2023. evt.preventDefault();
  2024. });
  2025. }
  2026. if (item.type === 'ui_tab') {
  2027. var content = $('<div>',{class:"nr-db-sb-group-list-container"}).appendTo(container);
  2028. // ui_tab group chevron
  2029. if (listStates.hasOwnProperty(item.node.id) && !listStates[item.node.id]) {
  2030. content.hide();
  2031. chevron.css({"transform":"rotate(-90deg)"});
  2032. content.addClass('nr-db-sb-collapsed');
  2033. listStates[item.node.id] = false;
  2034. }
  2035. else {
  2036. listStates[item.node.id] = true;
  2037. }
  2038. titleRow.click(titleToggle(item.node.id,content,chevron));
  2039. // ui_tab group list
  2040. var ol = $('<ol>',{class:"nr-db-sb-group-list"}).appendTo(content).editableList({
  2041. sortable:".nr-db-sb-group-list-header",
  2042. addButton: false,
  2043. height: 'auto',
  2044. connectWith: ".nr-db-sb-group-list",
  2045. addItem: function(container,i,group) {
  2046. if (!group.node) {
  2047. group.node = {
  2048. id: RED.nodes.id(),
  2049. _def: RED.nodes.getType("ui_group"),
  2050. type: "ui_group",
  2051. users: [],
  2052. tab: item.node.id,
  2053. order: i+1,
  2054. name: "Group "+(i+1),
  2055. width: 6,
  2056. disp: true
  2057. };
  2058. listElements[group.node.id] = container;
  2059. RED.nodes.add(group.node);
  2060. RED.editor.validateNode(group.node);
  2061. group.widgets = [];
  2062. RED.history.push({
  2063. t:'add',
  2064. nodes:[group.node.id],
  2065. dirty:RED.nodes.dirty()
  2066. });
  2067. RED.nodes.dirty(true);
  2068. if (RED.nodes.hasOwnProperty('updateConfigNodeUsers')) {
  2069. RED.nodes.updateConfigNodeUsers(group.node);
  2070. }
  2071. }
  2072. else {
  2073. if (group.node.order === undefined) {
  2074. group.node.order = i+1;
  2075. }
  2076. }
  2077. var groupNode = group.node;
  2078. elementParents[groupNode] = item.node.id;
  2079. var titleRow = $('<div>',{class:"nr-db-sb-list-header nr-db-sb-group-list-header"}).appendTo(container);
  2080. $('<i class="nr-db-sb-list-handle nr-db-sb-group-list-handle fa fa-bars"></i>').appendTo(titleRow);
  2081. var chevron = $('<i class="fa fa-angle-down nr-db-sb-list-chevron">',{style:"width:10px;"}).appendTo(titleRow);
  2082. $('<i class="nr-db-sb-icon nr-db-sb-group-icon fa fa-table"></i>').appendTo(titleRow);
  2083. var title = $('<span class="nr-db-sb-title">').text(groupNode.name||groupNode.id||"").appendTo(titleRow);
  2084. listElements[groupNode.id] = container;
  2085. var buttonGroup = $('<div>',{class:"nr-db-sb-list-header-button-group",id:groupNode.id}).appendTo(titleRow);
  2086. var spacerButton = $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> '+c_("layout.spacer")+'</a>').appendTo(buttonGroup);
  2087. spacerButton.on('click',function(evt) {
  2088. var spaceNode = {
  2089. _def: RED.nodes.getType("ui_spacer"),
  2090. type: "ui_spacer",
  2091. hasUsers: false,
  2092. users: [],
  2093. id: RED.nodes.id(),
  2094. tab: item.node.name,
  2095. group: group.node.id,
  2096. order: i+1,
  2097. name: "spacer",
  2098. width: 1,
  2099. height:1,
  2100. z: RED.workspaces.active(),
  2101. label: function() { return "spacer " + this.width + "x" + this.height; }
  2102. };
  2103. RED.nodes.add(spaceNode);
  2104. RED.editor.validateNode(spaceNode);
  2105. RED.history.push({
  2106. t:'add',
  2107. nodes:[spaceNode.id],
  2108. dirty:RED.nodes.dirty()
  2109. });
  2110. RED.nodes.dirty(true);
  2111. RED.view.redraw();
  2112. evt.stopPropagation();
  2113. evt.preventDefault();
  2114. });
  2115. var editButton = $('<a href="#" class="nr-db-sb-edit-group-button editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> '+c_("layout.edit")+'</a>').appendTo(buttonGroup);
  2116. var content = $('<div>',{class:"nr-db-sb-widget-list-container"}).appendTo(container);
  2117. if (!listStates.hasOwnProperty(groupNode.id) || !listStates[groupNode.id]) {
  2118. content.hide();
  2119. chevron.css({"transform":"rotate(-90deg)"});
  2120. content.addClass('nr-db-sb-collapsed');
  2121. listStates[groupNode.id] = false;
  2122. }
  2123. else {
  2124. listStates[groupNode.id] = true;
  2125. }
  2126. var ol = $('<ol>',{class:"nr-db-sb-widget-list"}).appendTo(content).editableList({
  2127. sortable:".nr-db-sb-widget-list-header",
  2128. addButton: false,
  2129. height: 'auto',
  2130. connectWith: ".nr-db-sb-widget-list",
  2131. addItem: function(container,i,widgetNode) {
  2132. elementParents[widgetNode.id] = groupNode.id;
  2133. var titleRow = $('<div>',{class:"nr-db-sb-list-header nr-db-sb-widget-list-header"}).appendTo(container);
  2134. $('<i class="nr-db-sb-list-handle nr-db-sb-widget-list-handle fa fa-bars"></i>').appendTo(titleRow);
  2135. $('<i class="nr-db-sb-icon nr-db-sb-widget-icon fa fa-picture-o"></i>').click(function(e) { e.preventDefault(); RED.search.show(widgetNode.id); }).appendTo(titleRow);
  2136. var l = widgetNode._def.label;
  2137. try {
  2138. l = (typeof l === "function" ? l.call(widgetNode) : l)||"";
  2139. }
  2140. catch(err) {
  2141. console.log("Definition error: "+d.type+".label",err);
  2142. l = d.type;
  2143. }
  2144. var title = $('<span class="nr-db-sb-title">').text(l).appendTo(titleRow);
  2145. listElements[widgetNode.id] = container;
  2146. var buttonGroup = $('<div>',{class:"nr-db-sb-list-header-button-group"}).appendTo(titleRow);
  2147. var editButton = $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-pencil"></i> '+c_("layout.edit")+'</a>').appendTo(buttonGroup);
  2148. container.on('mouseover',function() {
  2149. widgetNode.highlighted = true;
  2150. widgetNode.dirty = true;
  2151. RED.view.redraw();
  2152. });
  2153. container.on('mouseout',function() {
  2154. widgetNode.highlighted = false;
  2155. widgetNode.dirty = true;
  2156. RED.view.redraw();
  2157. });
  2158. editButton.on('click',function(evt) {
  2159. RED.editor.edit(widgetNode);
  2160. evt.stopPropagation();
  2161. evt.preventDefault();
  2162. });
  2163. },
  2164. sortItems: function(items) {
  2165. var historyEvents = [];
  2166. items.each(function(i,el) {
  2167. var node = el.data('data');
  2168. var hev = {
  2169. t:'edit',
  2170. node:node,
  2171. changes:{
  2172. order:node.order,
  2173. group:node.group
  2174. },
  2175. dirty:node.dirty,
  2176. changed:node.changed
  2177. };
  2178. historyEvents.push(hev);
  2179. var changed = false;
  2180. if (node.order !== i+1) {
  2181. node.order = i+1;
  2182. changed = true;
  2183. }
  2184. if (node.group !== group.node.id) {
  2185. var oldGroupNode = RED.nodes.node(node.group);
  2186. if (oldGroupNode) {
  2187. var index = oldGroupNode.users.indexOf(node);
  2188. oldGroupNode.users.splice(index,1);
  2189. }
  2190. node.group = group.node.id;
  2191. group.node.users.push(node);
  2192. changed = true;
  2193. }
  2194. if (changed) {
  2195. node.dirty = true;
  2196. node.changed = true;
  2197. }
  2198. })
  2199. RED.history.push({
  2200. t:'multi',
  2201. events: historyEvents
  2202. });
  2203. RED.nodes.dirty(true);
  2204. RED.view.redraw();
  2205. }
  2206. });
  2207. ol.css("min-height","5px");
  2208. if (groupNode.id) {
  2209. groupLists[groupNode.id] = ol;
  2210. }
  2211. titleRow.click(titleToggle(groupNode.id,content,chevron));
  2212. editButton.on('click',function(evt) {
  2213. RED.editor.editConfig("", groupNode.type, groupNode.id);
  2214. evt.stopPropagation();
  2215. evt.preventDefault();
  2216. });
  2217. group.widgets.forEach(function(widget) {
  2218. ol.editableList('addItem',widget);
  2219. })
  2220. },
  2221. sortItems: function(items) {
  2222. var historyEvents = [];
  2223. items.each(function(i,el) {
  2224. var groupData = el.data('data');
  2225. var node = groupData.node;
  2226. var hev = {
  2227. t:'edit',
  2228. node:node,
  2229. changes:{
  2230. order:node.order,
  2231. tab:node.tab
  2232. },
  2233. dirty:node.dirty,
  2234. changed:node.changed
  2235. };
  2236. historyEvents.push(hev);
  2237. var changed = false;
  2238. if (node.order !== i+1) {
  2239. node.order = i+1;
  2240. changed = true;
  2241. }
  2242. if (changed) {
  2243. node.dirty = true;
  2244. node.changed = true;
  2245. }
  2246. if (node.tab !== item.node.id) {
  2247. var oldTabNode = RED.nodes.node(node.tab);
  2248. if (oldTabNode) {
  2249. var index = oldTabNode.users.indexOf(node);
  2250. oldTabNode.users.splice(index,1);
  2251. }
  2252. node.tab = item.node.id;
  2253. item.node.users.push(node);
  2254. changed = true;
  2255. }
  2256. })
  2257. RED.history.push({
  2258. t:'multi',
  2259. events: historyEvents
  2260. });
  2261. RED.nodes.dirty(true);
  2262. RED.view.redraw();
  2263. }
  2264. })
  2265. tabLists[item.node.id] = ol;
  2266. addGroupButton.click(function(evt) {
  2267. ol.editableList('addItem',{});
  2268. evt.stopPropagation();
  2269. evt.preventDefault();
  2270. });
  2271. item.groups.forEach(function(group) {
  2272. ol.editableList('addItem',group);
  2273. });
  2274. }
  2275. }
  2276. var tabContainer = $('<ol>',{class:"nr-db-sb-tab-list"}).appendTo(divTabs).editableList({
  2277. sortable:".nr-db-sb-tab-list-header",
  2278. addButton: false,
  2279. addItem: addTabOrLinkItem,
  2280. sortItems: function(items) {
  2281. var historyEvents = [];
  2282. items.each(function(i,el) {
  2283. var itemData = el.data('data');
  2284. var node = itemData.node;
  2285. var hev = {
  2286. t:'edit',
  2287. node:node,
  2288. changes:{
  2289. order:node.order
  2290. },
  2291. dirty:node.dirty,
  2292. changed:node.changed
  2293. }
  2294. historyEvents.push(hev);
  2295. var changed = false;
  2296. if (node.order !== i+1) {
  2297. node.order = i+1;
  2298. changed = true;
  2299. }
  2300. if (changed) {
  2301. node.dirty = true;
  2302. node.changed = true;
  2303. }
  2304. })
  2305. RED.history.push({
  2306. t:'multi',
  2307. events: historyEvents
  2308. });
  2309. RED.nodes.dirty(true);
  2310. RED.view.redraw();
  2311. }
  2312. });
  2313. var orphanedWidgets = $('<div>',{class:"form-row"}).appendTo(layoutTab);
  2314. $('<span><i class="fa fa-info-circle"></i> There <span id="nr-db-missing-group-count"></span> not in a group. Click <a id="nr-db-add-missing-groups" href="#">here</a> to create the missing groups</span>').appendTo(orphanedWidgets);
  2315. orphanedWidgets.find('a').click(function(event) {
  2316. var unknownGroups = {};
  2317. RED.nodes.eachNode(function(node) {
  2318. if (/^ui_/.test(node.type) && node.type !== 'ui_link' && node.type !== 'ui_toast' && node.type !== 'ui_ui_control') {
  2319. if (!RED.nodes.node(node.group)) {
  2320. var g = node.group || "_BLANK_";
  2321. unknownGroups[g] = unknownGroups[g] || [];
  2322. unknownGroups[g].push(node);
  2323. }
  2324. }
  2325. });
  2326. var tab = null;
  2327. var tabs = tabContainer.editableList('items');
  2328. tabs.first().each(function(i,el) {
  2329. var tabData = el.data('data');
  2330. tab = tabData.node;
  2331. });
  2332. var hev = [];
  2333. if (tab === null) {
  2334. tab = {
  2335. id: RED.nodes.id(),
  2336. _def: RED.nodes.getType("ui_tab"),
  2337. type: "ui_tab",
  2338. users: [],
  2339. order: 0,
  2340. name: "Tab",
  2341. icon: "dashboard"
  2342. };
  2343. RED.nodes.add(tab);
  2344. RED.editor.validateNode(tab);
  2345. hev.push(tab.id);
  2346. }
  2347. for (var groupId in unknownGroups) {
  2348. if (unknownGroups.hasOwnProperty(groupId)) {
  2349. var groupNode = {
  2350. id: RED.nodes.id(),
  2351. _def: RED.nodes.getType("ui_group"),
  2352. type: "ui_group",
  2353. users: [],
  2354. tab: tab.id,
  2355. order: i+1,
  2356. name: (groupId==="_BLANK_"?"Group":groupId),
  2357. width: 6,
  2358. disp: true
  2359. };
  2360. hev.push(groupNode.id);
  2361. RED.nodes.add(groupNode);
  2362. RED.editor.validateNode(groupNode);
  2363. if (RED.nodes.hasOwnProperty('updateConfigNodeUsers')) {
  2364. RED.nodes.updateConfigNodeUsers(groupNode);
  2365. }
  2366. var widgets = unknownGroups[groupId];
  2367. for (var i=0; i<widgets.length; i++) {
  2368. widgets[i].group = groupNode.id;
  2369. widgets[i].changed = true;
  2370. widgets[i].dirty = true;
  2371. if (RED.nodes.hasOwnProperty('updateConfigNodeUsers')) {
  2372. RED.nodes.updateConfigNodeUsers(widgets[i]);
  2373. }
  2374. RED.editor.validateNode(widgets[i]);
  2375. }
  2376. }
  2377. }
  2378. RED.history.push({
  2379. t:'add',
  2380. nodes: hev,
  2381. dirty:RED.nodes.dirty()
  2382. });
  2383. RED.nodes.dirty(true);
  2384. refresh();
  2385. refreshOrphanedWidgets();
  2386. RED.view.redraw();
  2387. event.preventDefault();
  2388. });
  2389. var listElements = {};
  2390. var dashboard = [];
  2391. var listStates = {};
  2392. var elementParents = {};
  2393. var awaitingGroups = {};
  2394. var awaitingTabs = {};
  2395. function getCurrentList() {
  2396. var currentList = [];
  2397. var tabs = tabContainer.editableList('items');
  2398. var open = false;
  2399. tabs.each(function(i,el) {
  2400. var tabData = el.data('data');
  2401. var tab = [];
  2402. var groups = el.find('.nr-db-sb-group-list').editableList('items');
  2403. groups.each(function(j,el) {
  2404. var group = [];
  2405. var groupData = el.data('data');
  2406. var widgets = el.find('.nr-db-sb-widget-list').editableList('items');
  2407. widgets.each(function(k,el) {
  2408. var widgetData = el.data('data');
  2409. group.push(widgetData.id);
  2410. })
  2411. tab.push({id:groupData.node.id, widgets:group});
  2412. });
  2413. currentList.push({id:tabData.node.id,groups:tab});
  2414. });
  2415. return currentList;
  2416. }
  2417. function refreshOrphanedWidgets() {
  2418. var unknownGroups = {};
  2419. var count = 0;
  2420. RED.nodes.eachNode(function(node) {
  2421. if (/^ui_/.test(node.type) && node.type !== 'ui_link' && node.type !== 'ui_toast' && node.type !== 'ui_ui_control' && (node.type === 'ui_template' && node.templateScope !== 'global')) {
  2422. if (!RED.nodes.node(node.group)) {
  2423. var g = node.group || "_BLANK_";
  2424. unknownGroups[g] = unknownGroups[g] || [];
  2425. unknownGroups[g].push(node);
  2426. count++;
  2427. }
  2428. }
  2429. });
  2430. if (count > 0) {
  2431. orphanedWidgets.show();
  2432. $("#nr-db-missing-group-count").text((count===1?"is ":"are ")+count+" widget"+(count === 1?"":"s"))
  2433. }
  2434. else {
  2435. orphanedWidgets.hide();
  2436. }
  2437. }
  2438. function refresh() {
  2439. var currentList = getCurrentList();
  2440. dashboard = [];
  2441. var tabs = {};
  2442. var groups = {};
  2443. var elements = [];
  2444. var groupElements = {};
  2445. var tabGroups = {};
  2446. var groupId;
  2447. var group;
  2448. var tabId;
  2449. var tab;
  2450. var unknownGroups = 0;
  2451. // Find all the tabs and groups
  2452. RED.nodes.eachConfig(function(node) {
  2453. switch (node.type) {
  2454. case 'ui_tab':
  2455. case 'ui_link': {
  2456. tabs[node.id] = node;
  2457. //tabContainer.editableList('addItem',node);
  2458. break;
  2459. }
  2460. case 'ui_group': {
  2461. groups[node.id] = node;
  2462. break;
  2463. }
  2464. case 'ui_spacer': {
  2465. if (groups.hasOwnProperty(node.group)) {
  2466. groupElements[node.group] = groupElements[node.group]||[];
  2467. groupElements[node.group].push(node);
  2468. }
  2469. break;
  2470. }
  2471. }
  2472. });
  2473. for (groupId in groups) {
  2474. if (groups.hasOwnProperty(groupId)) {
  2475. group = groups[groupId];
  2476. if (tabs.hasOwnProperty(group.tab)) {
  2477. // This group belongs to a tab
  2478. tabGroups[group.tab] = tabGroups[group.tab]||[];
  2479. tabGroups[group.tab].push(group);
  2480. }
  2481. else {
  2482. unknownGroups++;
  2483. }
  2484. }
  2485. }
  2486. // Find all ui widgets - list them by their group id
  2487. RED.nodes.eachNode(function(node) {
  2488. if (/^ui_/.test(node.type)) {
  2489. if (groups.hasOwnProperty(node.group)) {
  2490. groupElements[node.group] = groupElements[node.group]||[];
  2491. groupElements[node.group].push(node);
  2492. }
  2493. else if ((node.type !== 'ui_toast')&&(node.type !== 'ui_ui_control')&&(node.type === 'ui_template' && node.templateScope !== 'global')) {
  2494. unknownGroups++;
  2495. }
  2496. }
  2497. });
  2498. if (unknownGroups > 0) {
  2499. $("#nr-db-missing-group-count").text((unknownGroups===1?"is ":"are ")+unknownGroups+" widget"+(unknownGroups === 1?"":"s"))
  2500. orphanedWidgets.show();
  2501. }
  2502. else {
  2503. orphanedWidgets.hide();
  2504. }
  2505. // Sort each group's array of widgets
  2506. for (groupId in groupElements) {
  2507. if (groupElements.hasOwnProperty(groupId)) {
  2508. group = groupElements[groupId];
  2509. groupElements[groupId] = group.map(function(v,i) { return {n:v,i:i} }).sort(function(A,B) {
  2510. if (A.n.order < B.n.order) { return A.n.order!==0?-1:1;}
  2511. if (A.n.order > B.n.order) { return B.n.order!==0?1:-1;}
  2512. return A.i - B.i;
  2513. }).map(function(v) { return v.n})
  2514. }
  2515. }
  2516. // Sort each tabs's array of groups
  2517. for (tabId in tabGroups) {
  2518. if (tabGroups.hasOwnProperty(tabId)) {
  2519. tab = tabGroups[tabId];
  2520. tabGroups[tabId] = tab.map(function(v,i) { return {n:v,i:i} }).sort(function(A,B) {
  2521. if (A.n.order < B.n.order) { return -1;}
  2522. if (A.n.order > B.n.order) { return 1;}
  2523. return A.i - B.i;
  2524. }).map(function(v) { return v.n})
  2525. }
  2526. }
  2527. var tabIds = Object.keys(tabs).map(function(v,i) { return {n:tabs[v],i:i} }).sort(function(A,B) {
  2528. if (A.n.order < B.n.order) { return -1;}
  2529. if (A.n.order > B.n.order) { return 1;}
  2530. return A.i - B.i;
  2531. }).map(function(v) { return v.n.id});
  2532. tabIds.forEach(function(tabId) {
  2533. var tab = {node:tabs[tabId],groups:[]};
  2534. if (tabGroups[tabId]) {
  2535. tabGroups[tabId].forEach(function(groupNode) {
  2536. var group = {node:groupNode,widgets:[]};
  2537. if (groupElements[groupNode.id]) {
  2538. group.widgets = groupElements[groupNode.id];
  2539. }
  2540. tab.groups.push(group);
  2541. });
  2542. }
  2543. dashboard.push(tab);
  2544. });
  2545. var newList = dashboard.map(function(t) {
  2546. return {
  2547. id: t.node.id,
  2548. groups: t.groups.map(function(g) {
  2549. return {
  2550. id: g.node.id,
  2551. widgets: g.widgets.map(function(w) {
  2552. return w.id;
  2553. })
  2554. }
  2555. })
  2556. }
  2557. });
  2558. if (JSON.stringify(newList)!=JSON.stringify(currentList)) {
  2559. listElements = {};
  2560. groupLists = {};
  2561. tabLists = {};
  2562. tabs = {};
  2563. groups = {};
  2564. elementParents = {};
  2565. tabContainer.empty();
  2566. dashboard.forEach(function(tab) {
  2567. tabContainer.editableList('addItem',tab);
  2568. });
  2569. }
  2570. //ensureDashboardNode(true);
  2571. if (globalDashboardNode) {
  2572. $("#nr-db-field-title").val(globalDashboardNode.site.name);
  2573. $("#nr-db-field-allowSwipe").val(globalDashboardNode.site.allowSwipe || "false");
  2574. $("#nr-db-field-allowTempTheme").val(globalDashboardNode.site.allowTempTheme || "true");
  2575. $("#nr-db-field-hideToolbar").val(globalDashboardNode.site.hideToolbar || "false");
  2576. $("#nr-db-field-dateFormat").val(globalDashboardNode.site.dateFormat);
  2577. if (typeof globalDashboardNode.site.sizes !== "object") {
  2578. globalDashboardNode.site.sizes = sizes;
  2579. }
  2580. $("#nr-db-field-sx").val(globalDashboardNode.site.sizes.sx);
  2581. $("#nr-db-field-sy").val(globalDashboardNode.site.sizes.sy);
  2582. $("#nr-db-field-px").val(globalDashboardNode.site.sizes.px);
  2583. $("#nr-db-field-py").val(globalDashboardNode.site.sizes.py);
  2584. $("#nr-db-field-cx").val(globalDashboardNode.site.sizes.cx);
  2585. $("#nr-db-field-cy").val(globalDashboardNode.site.sizes.cy);
  2586. $("#nr-db-field-gx").val(globalDashboardNode.site.sizes.gx);
  2587. $("#nr-db-field-gy").val(globalDashboardNode.site.sizes.gy);
  2588. if (typeof globalDashboardNode.theme.angularTheme !== "object") {
  2589. globalDashboardNode.theme.angularTheme = aTheme;
  2590. }
  2591. $("#nr-db-field-angPrimary").val(globalDashboardNode.theme.angularTheme.primary || "indigo");
  2592. $("#nr-db-field-angAccents").val(globalDashboardNode.theme.angularTheme.accents || "blue");
  2593. $("#nr-db-field-angWarn").val(globalDashboardNode.theme.angularTheme.warn || "red");
  2594. $("#nr-db-field-angBackground").val(globalDashboardNode.theme.angularTheme.background || "grey");
  2595. $("#nr-db-field-angLook").val(globalDashboardNode.theme.angularTheme.palette || "light");
  2596. $("#nr-db-field-theme").val(globalDashboardNode.theme.name);
  2597. $("#ui-sidebar-name").val(globalDashboardNode.theme.customTheme.name);
  2598. if (globalDashboardNode.theme.name === 'theme-custom') {
  2599. $("#custom-theme-library-container").show();
  2600. $("#custom-theme-settings").show();
  2601. }
  2602. else {
  2603. $("#custom-theme-library-container").hide();
  2604. $("#custom-theme-settings").hide();
  2605. }
  2606. if ($('#nr-db-field-allowTempTheme').val() === "none") {
  2607. ulDashboardTabs.children().eq(2).addClass("hidden");
  2608. ulDashboardTabs.children().eq(3).removeClass("hidden");
  2609. }
  2610. else {
  2611. ulDashboardTabs.children().eq(2).removeClass("hidden");
  2612. ulDashboardTabs.children().eq(3).addClass("hidden");
  2613. }
  2614. //set colour start
  2615. if (typeof globalDashboardNode.theme.name !== "string") {
  2616. globalDashboardNode.theme.name = "theme-light";
  2617. }
  2618. var currentTheme = globalDashboardNode.theme.name.split("-")[1];
  2619. var startingValue = globalDashboardNode.theme[currentTheme+"Theme"].baseColor;
  2620. setColourPickerColour("base-color", startingValue);
  2621. $("#nr-db-field-font").val(globalDashboardNode.theme[currentTheme+"Theme"].baseFont);
  2622. generateColours(startingValue);
  2623. if (globalDashboardNode.theme.name === 'theme-light' || globalDashboardNode.theme.name === 'theme-dark') {
  2624. addLightAndDarkResetButton('base-color', $('#base-settings-ul').children().first());
  2625. }
  2626. if (editor === undefined) {
  2627. editor = RED.editor.createEditor({
  2628. id: 'nr-db-field-format-editor',
  2629. mode: 'ace/mode/javascript',
  2630. value: JSON.stringify({theme:globalDashboardNode.theme.themeState, site:globalDashboardNode.site})
  2631. });
  2632. RED.library.create({
  2633. url:"themes", // where to get the data from
  2634. type:"theme", // the type of object the library is for
  2635. editor: editor, // the field name the main text body goes to
  2636. mode:"ace/mode/javascript",
  2637. fields:['name'],
  2638. elementPrefix:"ui-sidebar-"
  2639. });
  2640. }
  2641. editor.on('input', function() {
  2642. // Check for any changes on the editor object
  2643. // i.e. has the theme been customised compared
  2644. // to what is stored
  2645. var editorObject = JSON.parse(editor.getValue());
  2646. //Update theme object if necessary
  2647. if (JSON.stringify(editorObject.theme) !== JSON.stringify(globalDashboardNode.theme.themeState)) {
  2648. globalDashboardNode.theme.themeState = editorObject.theme;
  2649. if ($("#ui-sidebar-name").val() !== globalDashboardNode.theme.customTheme.name) {
  2650. globalDashboardNode.theme.customTheme.name = $("#ui-sidebar-name").val();
  2651. globalDashboardNode.theme.customTheme.baseColor = globalDashboardNode.theme.themeState["base-color"].value;
  2652. setColourPickerColour("base-color", globalDashboardNode.theme.customTheme.baseColor);
  2653. generateColours(globalDashboardNode.theme.themeState["base-color"].value);
  2654. RED.nodes.dirty(true);
  2655. }
  2656. }
  2657. if (JSON.stringify(aTheme) !== JSON.stringify(globalDashboardNode.theme.angularTheme)) {
  2658. globalDashboardNode.theme.angularTheme = aTheme;
  2659. }
  2660. //Update site object if necessary
  2661. if (JSON.stringify(editorObject.site) !== JSON.stringify(globalDashboardNode.site)) {
  2662. globalDashboardNode.site = editorObject.site;
  2663. $("#nr-db-field-title").val(globalDashboardNode.site.name);
  2664. $("#nr-db-field-hideToolbar").val(globalDashboardNode.site.hideToolbar);
  2665. $("#nr-db-field-allowSwipe").val(globalDashboardNode.site.allowSwipe);
  2666. $("#nr-db-field-allowTempTheme").val(globalDashboardNode.site.allowTempTheme);
  2667. $("#nr-db-field-dateFormat").val(globalDashboardNode.site.dateFormat);
  2668. $("#nr-db-field-sx").val(globalDashboardNode.site.sizes.sx);
  2669. $("#nr-db-field-sy").val(globalDashboardNode.site.sizes.sy);
  2670. $("#nr-db-field-px").val(globalDashboardNode.site.sizes.px);
  2671. $("#nr-db-field-py").val(globalDashboardNode.site.sizes.py);
  2672. $("#nr-db-field-gx").val(globalDashboardNode.site.sizes.gx);
  2673. $("#nr-db-field-gy").val(globalDashboardNode.site.sizes.gy);
  2674. $("#nr-db-field-cx").val(globalDashboardNode.site.sizes.cx);
  2675. $("#nr-db-field-cy").val(globalDashboardNode.site.sizes.cy);
  2676. RED.nodes.dirty(true);
  2677. }
  2678. });
  2679. }
  2680. awaitingGroups = {};
  2681. awaitingTabs = {};
  2682. }
  2683. RED.sidebar.addTab({
  2684. id: "dashboard",
  2685. label: c_("label.dashboard"),
  2686. name: "Dashboard",
  2687. content: content,
  2688. closeable: true,
  2689. pinned: true,
  2690. iconClass: "fa fa-bar-chart",
  2691. disableOnEdit: true,
  2692. onchange: function() { refresh(); }
  2693. });
  2694. editSaveEventHandler = function(node) {
  2695. if (/^ui_/.test(node.type)) {
  2696. if (node.type === "ui_tab" || node.type === "ui_group") {
  2697. if (listElements[node.id]) {
  2698. // Existing element
  2699. listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").text(node.name||node.id);
  2700. if (node.type === "ui_group") {
  2701. refresh();
  2702. }
  2703. else {
  2704. if (node.hidden === true) { listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").addClass('nr-db-sb-title-hidden'); }
  2705. else { listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").removeClass('nr-db-sb-title-hidden'); }
  2706. if (node.disabled === true) { listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").addClass('nr-db-sb-title-disabled'); }
  2707. else { listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").removeClass('nr-db-sb-title-disabled'); }
  2708. }
  2709. }
  2710. else if (node.type === "ui_tab") {
  2711. // Adding a tab
  2712. tabContainer.editableList('addItem',{node:node,groups:[]})
  2713. }
  2714. else {
  2715. // Adding a group
  2716. if (tabLists[node.tab]) {
  2717. tabLists[node.tab].editableList('addItem',{node:node,widgets:[]})
  2718. }
  2719. }
  2720. }
  2721. else if (node.type === "ui_link") {
  2722. if (listElements[node.id]) {
  2723. var container = listElements[node.id];
  2724. container.find(".nr-db-sb-link-name").text(node.name||"untitled");
  2725. container.find(".nr-db-sb-link-url").text(node.link);
  2726. }
  2727. }
  2728. else {
  2729. refreshOrphanedWidgets();
  2730. if (listElements[node.id]) {
  2731. if (node.group != elementParents[node.id]) {
  2732. // Moved to a different group
  2733. if (groupLists[elementParents[node.id]]) {
  2734. groupLists[elementParents[node.id]].editableList('removeItem',listElements[node.id].data('data'))
  2735. }
  2736. if (groupLists[node.group]) {
  2737. groupLists[node.group].editableList('removeItem',node)
  2738. groupLists[node.group].editableList('addItem',node);
  2739. }
  2740. }
  2741. else {
  2742. var l = node._def.label;
  2743. try {
  2744. l = (typeof l === "function" ? l.call(node) : l)||"";
  2745. }
  2746. catch(err) {
  2747. console.log("Definition error: "+d.type+".label",err);
  2748. l = d.type;
  2749. }
  2750. listElements[node.id].children(".nr-db-sb-list-header").find(".nr-db-sb-title").text(l);
  2751. }
  2752. }
  2753. else {
  2754. if (groupLists[node.group]) {
  2755. if (node.order === 0) { node.order = groupLists[node.group].editableList('length'); }
  2756. groupLists[node.group].editableList('addItem',node);
  2757. }
  2758. }
  2759. }
  2760. }
  2761. };
  2762. RED.events.on("editor:save",editSaveEventHandler);
  2763. // Dashboard layout tool
  2764. layoutUpdateEventHandler = function(node) {
  2765. if (/^ui_/.test(node.type) && node.type !== 'ui_link' && node.type !== 'ui_toast' && node.type !== 'ui_ui_control' && node.type !== 'ui_audio' && node.type !== 'ui_base' && node.type !== 'ui_group' && node.type !== 'ui_tab') {
  2766. if (listElements[node.id]) {
  2767. if (node.group != elementParents[node.id]) {
  2768. // Moved to a different group
  2769. if (groupLists[elementParents[node.id]]) {
  2770. groupLists[elementParents[node.id]].editableList('removeItem',listElements[node.id].data('data'))
  2771. }
  2772. if (groupLists[node.group]) {
  2773. groupLists[node.group].editableList('removeItem',node)
  2774. groupLists[node.group].editableList('addItem',node);
  2775. groupLists[node.group].editableList('sort',function(a,b) {return a.order-b.order;});
  2776. }
  2777. }
  2778. else {
  2779. groupLists[node.group].editableList('sort',function(a,b) {return a.order-b.order;});
  2780. }
  2781. }
  2782. }
  2783. };
  2784. RED.events.on("layout:update",layoutUpdateEventHandler);
  2785. var pendingAdd = [];
  2786. var pendingAddTimer = null;
  2787. function handlePendingAdds() {
  2788. var hasTabs = false;
  2789. var hasGroups = false;
  2790. pendingAdd.sort(function(A,B) {
  2791. hasTabs = hasTabs || A.type === "ui_tab" || B.type === "ui_tab";
  2792. hasGroups = hasGroups || A.type === "ui_group" || B.type === "ui_group";
  2793. if (A.type === B.type) {
  2794. return 0;
  2795. }
  2796. if (A.type === "ui_tab") {
  2797. return -1;
  2798. }
  2799. else if (B.type === "ui_tab") {
  2800. return 1;
  2801. }
  2802. else if (A.type === "ui_group") {
  2803. return -1;
  2804. }
  2805. else if (B.type === "ui_group") {
  2806. return 1;
  2807. }
  2808. return 0
  2809. });
  2810. var updateList = {};
  2811. for (var i=0; i<pendingAdd.length; i++) {
  2812. var node = pendingAdd[i];
  2813. if (listElements[node.id]) {
  2814. continue;
  2815. }
  2816. if (node.type === "ui_tab") {
  2817. tabContainer.editableList('addItem',{node:node,groups:[]});
  2818. }
  2819. else {
  2820. if (hasTabs) {
  2821. // We've added some tabs, need to give jquery time to add the lists
  2822. pendingAdd = pendingAdd.slice(i);
  2823. pendingAddTimer = setTimeout(handlePendingAdds,50);
  2824. return;
  2825. }
  2826. if (node.type === "ui_group") {
  2827. if (tabLists[node.tab]) {
  2828. tabLists[node.tab].editableList('addItem',{node:node,widgets:[]});
  2829. }
  2830. }
  2831. else {
  2832. if (hasGroups) {
  2833. // We've added some tabs, need to give jquery time to add the lists
  2834. pendingAdd = pendingAdd.slice(i);
  2835. pendingAddTimer = setTimeout(handlePendingAdds,50);
  2836. return;
  2837. }
  2838. if (groupLists[node.group]) {
  2839. groupLists[node.group].editableList('addItem',node)
  2840. if (node.order >= 0) {
  2841. updateList[node.group] = true;
  2842. }
  2843. }
  2844. else {
  2845. refreshOrphanedWidgets();
  2846. }
  2847. }
  2848. }
  2849. }
  2850. Object.keys(updateList).forEach(function (group) {
  2851. var list = groupLists[group];
  2852. if (list) {
  2853. list.editableList("sort", function(a,b) {return a.order-b.order;});
  2854. }
  2855. });
  2856. pendingAdd = [];
  2857. }
  2858. nodesAddEventHandler = function(node) {
  2859. if (/^ui_/.test(node.type) && !listElements[node.id]) {
  2860. pendingAdd.push(node);
  2861. clearTimeout(pendingAddTimer);
  2862. pendingAddTimer = setTimeout(handlePendingAdds,100);
  2863. }
  2864. };
  2865. RED.events.on("nodes:add", nodesAddEventHandler);
  2866. nodesRemoveEventHandler = function(node) {
  2867. if (/^ui_/.test(node.type)) {
  2868. if (node.type === "ui_tab" || node.type === "ui_link") {
  2869. if (listElements[node.id]) {
  2870. tabContainer.editableList('removeItem',listElements[node.id].data('data'));
  2871. delete tabLists[node.id];
  2872. }
  2873. }
  2874. else if (node.type === "ui_group") {
  2875. if (tabLists[node.tab] && listElements[node.id]) {
  2876. tabLists[node.tab].editableList('removeItem',listElements[node.id].data('data'));
  2877. }
  2878. delete groupLists[node.id];
  2879. }
  2880. else {
  2881. if (groupLists[node.group]) {
  2882. groupLists[node.group].editableList('removeItem',node)
  2883. }
  2884. }
  2885. refreshOrphanedWidgets();
  2886. delete listElements[node.id];
  2887. }
  2888. };
  2889. RED.events.on("nodes:remove", nodesRemoveEventHandler);
  2890. }
  2891. });
  2892. $.widget("nodereddashboard.elementSizerByNum", {
  2893. _create: function() {
  2894. var that = this;
  2895. var has_height = this.options.has_height;
  2896. var pos = this.options.pos;
  2897. var c_width = has_height ? '15%' : '6%';
  2898. var container = $('<div>').css({
  2899. position: 'absolute',
  2900. background: 'white',
  2901. padding: '10px 10px 10px 10px',
  2902. border: '1px solid grey',
  2903. zIndex: '20',
  2904. borderRadius: "4px",
  2905. display:"none",
  2906. width: c_width
  2907. }).appendTo(document.body);
  2908. var box0 = $("<div>").css({
  2909. fontSize: '13px',
  2910. color: '#aaa',
  2911. float: 'left',
  2912. paddingTop: '1px'
  2913. }).appendTo(container);
  2914. var width = $(this.options.width).val();
  2915. var height = has_height ? $(this.options.height).val() : undefined;
  2916. var max_w = '';
  2917. var groupNode = this.options.groupNode;
  2918. if(groupNode) {
  2919. max_w = 'max="'+groupNode.width+'"';
  2920. }
  2921. width = (width > 0) ? width : 1;
  2922. height = (height > 0) ? height : 1;
  2923. var in0 = $('<input type="number" min="1" '+max_w+'>')
  2924. .css("width", has_height ? "40%" : "100%")
  2925. .val(width)
  2926. .appendTo(box0);
  2927. if(has_height) {
  2928. var pad = $('<span>')
  2929. .text(" x ")
  2930. .appendTo(box0);
  2931. var in1 = $('<input type="number" min="1">')
  2932. .css("width", "40%")
  2933. .val(height)
  2934. .appendTo(box0);
  2935. }
  2936. var closeTimer;
  2937. var closeFunc = function() {
  2938. var w = in0.val();
  2939. var h = has_height ? in1.val() : undefined;
  2940. var label = that.options.label;
  2941. label.text(w+(has_height ? (' x '+h) : ''));
  2942. $(that.options.width).val(w).change();
  2943. if(has_height) {
  2944. $(that.options.height).val(h).change();
  2945. }
  2946. that.destroy();
  2947. };
  2948. container.keypress(function(e) {
  2949. if(e.which === 13) { // pressed ENTER
  2950. container.fadeOut(100, closeFunc);
  2951. }
  2952. });
  2953. container.on('mouseleave', function(e) {
  2954. closeTimer = setTimeout(function() {
  2955. container.fadeOut(200, closeFunc);
  2956. }, 100);
  2957. });
  2958. container.on('mouseenter', function(e) {
  2959. clearTimeout(closeTimer);
  2960. });
  2961. container.css({
  2962. top: (pos.top -10)+"px",
  2963. left: (pos.left +10)+"px"
  2964. });
  2965. container.fadeIn(200);
  2966. }
  2967. });
  2968. $.widget( "nodereddashboard.elementSizer", {
  2969. _create: function() {
  2970. var that = this;
  2971. var gridWidth = 6;
  2972. var width = parseInt($(this.options.width).val()||0);
  2973. var height = parseInt(this.options.hasOwnProperty('height')?$(this.options.height).val():"1")||0;
  2974. var hasAuto = (!this.options.hasOwnProperty('auto') || this.options.auto);
  2975. this.element.css({
  2976. minWidth: this.element.height()+4
  2977. });
  2978. var auto_text = c_("auto");
  2979. var sizeLabel = (width === 0 && height === 0)?auto_text:width+(this.options.hasOwnProperty('height')?" x "+height:"");
  2980. this.element.text(sizeLabel).on('mousedown',function(evt) {
  2981. evt.stopPropagation();
  2982. evt.preventDefault();
  2983. var width = parseInt($(that.options.width).val()||0);
  2984. var height = parseInt(that.options.hasOwnProperty('height')?$(that.options.height).val():"1")||0;
  2985. var maxWidth = 0;
  2986. var maxHeight;
  2987. var fixedWidth = false;
  2988. var fixedHeight = false;
  2989. var group = $(that.options.group).val();
  2990. if (group) {
  2991. var groupNode = RED.nodes.node(group);
  2992. if (groupNode) {
  2993. gridWidth = Math.max(6,groupNode.width,+width);
  2994. maxWidth = groupNode.width || gridWidth;
  2995. fixedWidth = true;
  2996. }
  2997. maxHeight = Math.max(6,+height+1);
  2998. }
  2999. else {
  3000. gridWidth = Math.max(12,+width);
  3001. maxWidth = gridWidth;
  3002. maxHeight = 1;
  3003. fixedHeight = true;
  3004. }
  3005. var pos = $(this).offset();
  3006. var container = $('<div>').css({
  3007. position: 'absolute',
  3008. background: 'white',
  3009. padding: '5px 10px 10px 10px',
  3010. border: '1px solid grey',
  3011. zIndex: '20',
  3012. borderRadius: "4px",
  3013. display:"none"
  3014. }).appendTo(document.body);
  3015. var closeTimer;
  3016. container.on('mouseleave',function(evt) {
  3017. closeTimer = setTimeout(function() {
  3018. container.fadeOut(200, function() { $(this).remove(); });
  3019. },100)
  3020. });
  3021. container.on('mouseenter',function() {
  3022. clearTimeout(closeTimer);
  3023. })
  3024. var label = $("<div>").css({
  3025. fontSize: '13px',
  3026. color: '#aaa',
  3027. float: 'left',
  3028. paddingTop: '1px'
  3029. }).appendTo(container).text((width === 0 && height === 0)?auto_text:(width+(that.options.hasOwnProperty('height')?" x "+height:"")));
  3030. label.hover(function() {
  3031. $(this).css('text-decoration', 'underline');
  3032. }, function() {
  3033. $(this).css('text-decoration', 'none');
  3034. });
  3035. label.click(function(e) {
  3036. var group = $(that.options.group).val();
  3037. var groupNode = null;
  3038. if(group) {
  3039. groupNode = RED.nodes.node(group);
  3040. if(groupNode === null) {
  3041. return;
  3042. }
  3043. }
  3044. $(that).elementSizerByNum({
  3045. width: that.options.width,
  3046. height: that.options.height,
  3047. groupNode: groupNode,
  3048. pos: pos,
  3049. label: that.element,
  3050. has_height: that.options.hasOwnProperty('height')
  3051. });
  3052. closeTimer = setTimeout(function() {
  3053. container.fadeOut(200, function() {
  3054. $(this).remove();
  3055. });
  3056. },100)
  3057. });
  3058. var buttonRow = $('<div>',{style:"text-align:right; height:25px;"}).appendTo(container);
  3059. if (hasAuto) {
  3060. var button = $('<a>',{href:"#",class:"editor-button editor-button-small",style:"margin-bottom:5px"})
  3061. .text(auto_text)
  3062. .appendTo(buttonRow)
  3063. .on('mouseup',function(evt) {
  3064. that.element.text(auto_text)
  3065. $(that.options.width).val(0).change();
  3066. $(that.options.height).val(0).change();
  3067. evt.preventDefault();
  3068. container.fadeOut(200, function() { $(this).remove(); });
  3069. });
  3070. }
  3071. var cellBorder = "1px dashed lightGray";
  3072. var cellBorderExisting = "1px solid gray";
  3073. var cellBorderHighlight = "1px dashed black";
  3074. var rows = [];
  3075. function addRow(i) {
  3076. var row = $('<div>').css({padding:0,margin:0,height:"25px","box-sizing":"border-box"}).appendTo(container);
  3077. rows.push(row);
  3078. cells.push([])
  3079. for (var j=0; j<gridWidth; j++) {
  3080. addCell(i,j);
  3081. }
  3082. }
  3083. function addCell(i,j) {
  3084. var row = rows[i];
  3085. var cell = $('<div>').css({
  3086. display:"inline-block",
  3087. width: "25px",
  3088. height: "25px",
  3089. borderRight: (j===(width-1)&&i<height)?cellBorderExisting:cellBorder,
  3090. borderBottom: (i===(height-1)&&j<width)?cellBorderExisting:cellBorder,
  3091. boxSizing: "border-box",
  3092. cursor:"pointer",
  3093. background: (j<maxWidth)?"#fff":"#eee"
  3094. }).appendTo(row);
  3095. cells[i].push(cell);
  3096. if (j===0) {
  3097. cell.css({borderLeft:((i<=height-1)?cellBorderExisting:cellBorder)});
  3098. }
  3099. if (i===0) {
  3100. cell.css({borderTop:((j<=width-1)?cellBorderExisting:cellBorder)});
  3101. }
  3102. if (j<maxWidth) {
  3103. cell.data("w",j);
  3104. cell.data("h",i);
  3105. cell.on("mouseup",function() {
  3106. that.element.text(($(this).data("w")+1)+(that.options.hasOwnProperty('height')?" x "+($(this).data("h")+1):""))
  3107. $(that.options.width).val($(this).data("w")+1).change();
  3108. $(that.options.height).val($(this).data("h")+1).change();
  3109. container.fadeOut(200, function() { $(this).remove(); });
  3110. });
  3111. cell.on("mouseover",function() {
  3112. var w = $(this).data("w");
  3113. var h = $(this).data("h");
  3114. label.text((w+1)+(that.options.hasOwnProperty('height')?" x "+(h+1):""));
  3115. for (var y = 0; y<maxHeight; y++) {
  3116. for (var x = 0; x<maxWidth; x++) {
  3117. cells[y][x].css({
  3118. background: (y<=h && x<=w)?'#ddd':'#fff',
  3119. borderLeft: (x===0&&y<=h)?cellBorderHighlight:(x===0)?((y<=height-1)?cellBorderExisting:cellBorder):'',
  3120. borderTop: (y===0&&x<=w)?cellBorderHighlight:(y===0)?((x<=width-1)?cellBorderExisting:cellBorder):'',
  3121. borderRight: (x===w&&y<=h)?cellBorderHighlight:((x===width-1&&y<=height-1)?cellBorderExisting:cellBorder),
  3122. borderBottom: (y===h&&x<=w)?cellBorderHighlight:((y===height-1&&x<=width-1)?cellBorderExisting:cellBorder)
  3123. })
  3124. }
  3125. }
  3126. if (!fixedHeight && h === maxHeight-1) {
  3127. addRow(maxHeight++)
  3128. }
  3129. if (!fixedWidth && w === maxWidth-1) {
  3130. maxWidth++;
  3131. gridWidth++;
  3132. for (var r=0; r<maxHeight; r++) {
  3133. addCell(r,maxWidth-1);
  3134. }
  3135. }
  3136. })
  3137. }
  3138. }
  3139. var cells = [];
  3140. for (var i=0; i<maxHeight; i++) {
  3141. addRow(i);
  3142. }
  3143. container.css({
  3144. top:(pos.top)+"px",
  3145. left:(pos.left)+"px"
  3146. });
  3147. container.fadeIn(200);
  3148. })
  3149. }
  3150. });
  3151. })(jQuery);
  3152. </script>
  3153. <script type="text/html" data-template-name="ui_base">
  3154. <div class='form-row'>
  3155. This <i>ui_base</i> node is the main node that all<br/>other dashboard widget nodes communicate to.<br/>
  3156. <br/>One instance is required to support the dashboard.<br/>
  3157. <br/>If you have no dashboard you can delete this node.<br/>
  3158. It will be re-created automatically if required.<br/>
  3159. </div>
  3160. </script>