From a06191125c877c9612597ed34301e9f8226d47cd Mon Sep 17 00:00:00 2001 From: Dave Freeman Date: Tue, 13 Aug 2019 11:41:16 -0500 Subject: [PATCH 1/6] PLAT-84218: Update Agate to Enact 3.0 (#110) * update dependencies for Enact 3.x * update Panels for arranger changes * Add embedded theme-specific HTML template and updated NPM scripts for latest CLI syntax. Integrated-By: Ryan Duffy (ryan.duffy@lge.com) --- Panels/Panels.js | 38 ++------------------------------------ html-template.ejs | 17 +++++++++++++++++ package.json | 28 ++++++++++++++++------------ 3 files changed, 35 insertions(+), 48 deletions(-) create mode 100644 html-template.ejs diff --git a/Panels/Panels.js b/Panels/Panels.js index 6866e8c58..697115626 100644 --- a/Panels/Panels.js +++ b/Panels/Panels.js @@ -1,52 +1,18 @@ import kind from '@enact/core/kind'; import PropTypes from 'prop-types'; import React from 'react'; -import ViewManager, {shape, SlideArranger} from '@enact/ui/ViewManager'; +import ViewManager, {shape, SlideBottomArranger as VerticalArranger, SlideRightArranger as HorizontalArranger} from '@enact/ui/ViewManager'; import Panel from './Panel'; import TabbedPanels from './TabbedPanels'; import componentCss from './Panels.module.less'; -import easing from 'eases/back-out'; -import * as arrange from '@enact/ui/ViewManager/arrange'; - -const slideIn = arrange.reverse(arrange.fadeIn); -const slideOut = arrange.reverse(arrange.fadeOut); - -const HorizontalArranger = SlideArranger({ - amount: 50, - enter: 'right', - leave: 'left' -}); -HorizontalArranger.enter = arrange.ease( - easing, - arrange.compose(slideIn, HorizontalArranger.enter) -); -HorizontalArranger.leave = arrange.ease( - easing, - arrange.compose(slideOut, HorizontalArranger.leave) -); - -const VerticalArranger = SlideArranger({ - amount: 50, - enter: 'bottom', - leave: 'top' -}); -VerticalArranger.enter = arrange.ease( - easing, - arrange.compose(slideIn, VerticalArranger.enter) -); -VerticalArranger.leave = arrange.ease( - easing, - arrange.compose(slideOut, VerticalArranger.leave) -); - const mapChildren = (childs) => React.Children.map(childs, (child, index) => { return child ? React.cloneElement(child, { 'data-index': index }) : null; -}) +}); const PanelsBase = kind({ name: 'Panels', diff --git a/html-template.ejs b/html-template.ejs new file mode 100644 index 000000000..a3a568ed9 --- /dev/null +++ b/html-template.ejs @@ -0,0 +1,17 @@ + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + +
+ + diff --git a/package.json b/package.json index 02486abd8..cee40cc76 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,9 @@ "main": "index.js", "scripts": { "clean": "enact clean", - "lint": "enact lint --framework", - "test": "enact test start --single-run --browsers PhantomJS", - "test-json": "enact test start --single-run --browsers PhantomJS --reporters json", - "test-watch": "enact test start --browsers PhantomJS", + "lint": "enact lint --strict", + "test": "enact test", + "test-watch": "enact test --watch", "transpile": "enact transpile" }, "license": "Apache-2.0", @@ -21,23 +20,28 @@ "webOS" ], "enact": { - "ri": { - "baseSize": 24 - } + "template": "html-template.ejs" }, "eslintConfig": { "extends": "enact/strict" }, "dependencies": { - "@enact/core": "^2.0.0", - "@enact/i18n": "^2.0.0", - "@enact/spotlight": "^2.0.0", - "@enact/ui": "^2.0.0", + "@enact/core": "^3.0.0-rc.1", + "@enact/i18n": "^3.0.0-rc.1", + "@enact/spotlight": "^3.0.0-rc.1", + "@enact/ui": "^3.0.0-rc.1", "classnames": "^2.2.6", "color-convert": "^1.9.3", "eases": "^1.0.8", "prop-types": "^15.6.2", "ramda": "^0.25.0", - "react": "^16.0.0" + "react": "^16.8.0", + "react-dom": "^16.8.0" + }, + "peerDependencies": { + "ilib": "^14.2.0 || ^14.2.0-webostv1" + }, + "devDependencies": { + "ilib": "^14.2.0" } } From 3dbcd51bfbc9d856bd2f816f6f7d583e4fdbf643 Mon Sep 17 00:00:00 2001 From: Teck Date: Thu, 15 Aug 2019 12:53:43 -0700 Subject: [PATCH 2/6] PLAT-84315: Add new Agate icons (#111) * update with new Agate-icons * update font assignment and center icons Integrated-By: Ryan Duffy (ryan.duffy@lge.com) --- Icon/IconList.js | 278 ++++++++++++++++++++++----------- Switch/Switch.module.less | 2 +- fonts/Agate-icons.ttf | Bin 0 -> 31680 bytes styles/internal/fonts.less | 8 +- styles/variables-carbon.less | 2 +- styles/variables-cobalt.less | 2 +- styles/variables-copper.less | 2 +- styles/variables-electro.less | 2 +- styles/variables-titanium.less | 2 +- 9 files changed, 193 insertions(+), 105 deletions(-) create mode 100644 fonts/Agate-icons.ttf diff --git a/Icon/IconList.js b/Icon/IconList.js index c8cde1c5c..5188fe429 100644 --- a/Icon/IconList.js +++ b/Icon/IconList.js @@ -4,105 +4,193 @@ export default { plus : 0x0002B, // plus minus : 0x0002D, // hyphen - arrowhookleft : 0x021A9, // LeftArrowHook - arrowhookright : 0x021AA, // RightArrowHook + arrowup : 0x02191, // arrowup + arrowdown : 0x02193, // arrowdown ellipsis : 0x022EF, // ellipsis check : 0x02713, // checkmark - circle : 0x0EFFDB, // record - stop : 0x0EFFDC, // stop - play : 0x0EFFDD, // play - pause : 0x0EFFDE, // pause - forward : 0x0EFFDF, // forward - backward : 0x0EFFE0, // rewind - skipforward : 0x0EFFE1, // skip_forward - skipbackward : 0x0EFFE2, // skip_backwards - pauseforward : 0x0EFFE3, // indicator_forward - pausebackward : 0x0EFFE4, // indicator_backward - pausejumpforward : 0x0EFFE5, // indicator_skip_forward - pausejumpbackward : 0x0EFFE6, // indicator_skip_backward - jumpforward : 0x0EFFE7, // indicator_end - jumpbackward : 0x0EFFE8, // indicator_begin - denselist : 0x0EFFE9, // list_big - bulletlist : 0x0EFFEA, // list_bullets - list : 0x0EFFEB, // list_simple - drawer : 0x0EFFEC, // list_actions arrowlargedown : 0x0EFFED, // caret_down_large arrowlargeup : 0x0EFFEE, // caret_up_large arrowlargeleft : 0x0EFFEF, // caret_left_large arrowlargeright : 0x0EFFF0, // caret_right_large - arrowsmallup : 0x0EFFF1, // caret_up_small - arrowsmalldown : 0x0EFFF2, // caret_down_small - arrowsmallleft : 0x0EFFF3, // caret_left_small - arrowsmallright : 0x0EFFF4, // caret_right_small - closex : 0x0EFFF5, // close_x - search : 0x0EFFF6, // magnify - rollforward : 0x0EFFF7, // redo - rollbackward : 0x0EFFF8, // undo - exitfullscreen : 0x0EFFF9, // minimize - fullscreen : 0x0EFFFA, // maximize - arrowshrinkleft : 0x0EFFFB, // shrink_panel_left - arrowshrinkright : 0x0EFFFC, // shrink_panel_right - arrowextend : 0x0F0021, // arrow_left - arrowshrink : 0x0F0022, // arrow_right - flag : 0x0F0023, // flag - funnel : 0x0F0024, // filter - trash : 0x0F0025, // trash - star : 0x0F0028, // star_full - hollowstar : 0x0F0029, // star_empty - halfstar : 0x0F002A, // star_half - gear : 0x0F002B, // gear - plug : 0x0F002C, // input - lock : 0x0F002D, // lock - forward15 : 0x0F0041, // forward15 - back15 : 0x0F0042, // back15 - continousplay : 0x0F0043, // continous_play - playlist : 0x0F0044, // playlist - resumeplay : 0x0F0045, // resume_play - image : 0x0F0046, // image - audio : 0x0F0061, // audio - music : 0x0F0062, // music - languages : 0x0F0081, // languages - cc : 0x0F0082, // cc - ccon : 0x0F0083, // cc_on - ccoff : 0x0F0084, // cc_off - sub : 0x0F0085, // sub - recordings : 0x0F00A1, // recordings - livezoom : 0x0F00A2, // live_zoom - liveplayback : 0x0F00A3, // live_playback - liveplaybackoff : 0x0F00A4, // live_playback_off - repeat : 0x0F00A5, // repeat - repeatoff : 0x0F00A6, // repeat_off - series : 0x0F00A7, // series - repeatdownload : 0x0F00A8, // repeat_download - view360 : 0x0F00A9, // view_360 - view360off : 0x0F00AA, // view_360_off - info : 0x0F00AB, // info - airdown : 0x0F00B5, // air_down - airright : 0x0F00B6, // air_right - airup : 0x0F00B7, // air_up - heatseatleft : 0x0F00B8, // seat_heat_left - heatseatright : 0x0F00B9, // seat_heat_right - aircirculation : 0x0F00BA, // air_circulation - fan : 0x0F00BB, // fan - defrosterfront : 0x0F00BC, // defroster_front - defrosterback : 0x0F00BD, // defroster_back - user : 0x0F00BE, // users - home : 0x0F00BF, // home - temperature : 0x0F00C0, // temperature_icon - compass : 0x0F00C1, // compass - phone : 0x0F00C2, // phone - fanoff : 0x0F00C9, // fan_off - bluetooth : 0x0F00CA, // bluetooth - datetime : 0x0F00CB, // date_time - display : 0x0F00CC, // display - seatbelt : 0x0F00CD, // seat_belt - wifi : 0x0F00CE, // wifi - apps : 0x0F00CF, // apps - climate : 0x0F00D0, // climate - dashboard : 0x0F00D1, // dashboard - edit : 0x0F00D2, // edit - expand : 0x0F00D3, // expand - radio : 0x0F00D4, // radio - rearscreen : 0x0F00D5, // rear_screen - weather : 0x0F00D6 // weather + closex : 0x0EFFF5, // close + search : 0x0EFFF6, // search + bright1 : 0x0F000C, // bright01 + bright2 : 0x0F000D, // bright02 + bluetooth : 0x0F000E, // bluetooth + usb : 0x0F000F, // usb + volume0 : 0x0F0010, // volume00 + volume1 : 0x0F0011, // volume01 + volume2 : 0x0F0012, // volume02 + wifi : 0x0F0013, // wifi + happyface : 0x0F0014, // happyface + notification : 0x0F0015, // notification + pairing : 0x0F0016, // pairing + user : 0x0F0017, // user + calendar : 0x0F0018, // calendar + edit : 0x0F0019, // edit + gallery : 0x0F001A, // gallery + internet : 0x0F001B, // internet + map : 0x0F001C, // map + music : 0x0F001D, // music + video : 0x0F001E, // video + circle : 0x0F001F, // circle + setting : 0x0F002B, // setting + accept : 0x0F0036, // accept + decline : 0x0F0037, // decline + stop : 0x0F0038, // stop + cancel : 0x0F0039, // cancel + carspeaker : 0x0F003A, // carspeaker + speaker : 0x0F003B, // speaker + earphone : 0x0F003C, // earphone + detail : 0x0F003D, // detail + install : 0x0F003E, // install + send : 0x0F003F, // send + netbook : 0x0F0040, // netbook + pad : 0x0F0041, // pad + mobile : 0x0F0042, // mobile + rain : 0x0F0060, // rain + profileA1 : 0x0F008A, // profileA1 + profileA2 : 0x0F008B, // profileA2 + profileA3 : 0x0F008C, // profileA3 + profileA4 : 0x0F008D, // profileA4 + profileB1 : 0x0F008E, // profileB1 + profileB2 : 0x0F008F, // profileB2 + profileB3 : 0x0F0090, // profileB3 + profileB4 : 0x0F0091, // profileB4 + profileC1 : 0x0F0092, // profileC1 + profileC2 : 0x0F0093, // profileC2 + profileC3 : 0x0F0094, // profileC3 + profileC4 : 0x0F0095, // profileC4 + airdown : 0x0F00B5, // air_down + airright : 0x0F00B6, // air_right + airup : 0x0F00B7, // air_up + heatseatleft : 0x0F00B8, // seat_heat_left + heatseatright : 0x0F00B9, // seat_heat_right + aircirculation : 0x0F00BA, // air_circulation + fan : 0x0F00BB, // fan + defrosterfront : 0x0F00BC, // defroster_front + defrosterback : 0x0F00BD, // defroster_back + home : 0x0F00BF, // home + temperature : 0x0F00C0, // temperature_icon + compass : 0x0F00C1, // compass + phone : 0x0F00C2, // phone + fanoff : 0x0F00C9, // fan_off + datetime : 0x0F00CB, // date_time + display : 0x0F00CC, // display + seatbelt : 0x0F00CD, // seat_belt + apps : 0x0F00CF, // apps + climate : 0x0F00D0, // climate + dashboard : 0x0F00D1, // dashboard + expand : 0x0F00D3, // expand + radio : 0x0F00D4, // radio + rearscreen : 0x0F00D5, // rear_screen + weather : 0x0F00D6 // weather }; + +// Deprecated icon list from `Moonstone-Agate.ttf` for reference + +// export default { +// plus : 0x0002B, // plus +// minus : 0x0002D, // hyphen +// arrowhookleft : 0x021A9, // LeftArrowHook +// arrowhookright : 0x021AA, // RightArrowHook +// ellipsis : 0x022EF, // ellipsis +// check : 0x02713, // checkmark +// circle : 0x0EFFDB, // record +// stop : 0x0EFFDC, // stop +// play : 0x0EFFDD, // play +// pause : 0x0EFFDE, // pause +// forward : 0x0EFFDF, // forward +// backward : 0x0EFFE0, // rewind +// skipforward : 0x0EFFE1, // skip_forward +// skipbackward : 0x0EFFE2, // skip_backwards +// pauseforward : 0x0EFFE3, // indicator_forward +// pausebackward : 0x0EFFE4, // indicator_backward +// pausejumpforward : 0x0EFFE5, // indicator_skip_forward +// pausejumpbackward : 0x0EFFE6, // indicator_skip_backward +// jumpforward : 0x0EFFE7, // indicator_end +// jumpbackward : 0x0EFFE8, // indicator_begin +// denselist : 0x0EFFE9, // list_big +// bulletlist : 0x0EFFEA, // list_bullets +// list : 0x0EFFEB, // list_simple +// drawer : 0x0EFFEC, // list_actions +// arrowlargedown : 0x0EFFED, // caret_down_large +// arrowlargeup : 0x0EFFEE, // caret_up_large +// arrowlargeleft : 0x0EFFEF, // caret_left_large +// arrowlargeright : 0x0EFFF0, // caret_right_large +// arrowsmallup : 0x0EFFF1, // caret_up_small +// arrowsmalldown : 0x0EFFF2, // caret_down_small +// arrowsmallleft : 0x0EFFF3, // caret_left_small +// arrowsmallright : 0x0EFFF4, // caret_right_small +// closex : 0x0EFFF5, // close_x +// search : 0x0EFFF6, // magnify +// rollforward : 0x0EFFF7, // redo +// rollbackward : 0x0EFFF8, // undo +// exitfullscreen : 0x0EFFF9, // minimize +// fullscreen : 0x0EFFFA, // maximize +// arrowshrinkleft : 0x0EFFFB, // shrink_panel_left +// arrowshrinkright : 0x0EFFFC, // shrink_panel_right +// arrowextend : 0x0F0021, // arrow_left +// arrowshrink : 0x0F0022, // arrow_right +// flag : 0x0F0023, // flag +// funnel : 0x0F0024, // filter +// trash : 0x0F0025, // trash +// star : 0x0F0028, // star_full +// hollowstar : 0x0F0029, // star_empty +// halfstar : 0x0F002A, // star_half +// gear : 0x0F002B, // gear +// plug : 0x0F002C, // input +// lock : 0x0F002D, // lock +// forward15 : 0x0F0041, // forward15 +// back15 : 0x0F0042, // back15 +// continousplay : 0x0F0043, // continous_play +// playlist : 0x0F0044, // playlist +// resumeplay : 0x0F0045, // resume_play +// image : 0x0F0046, // image +// audio : 0x0F0061, // audio +// music : 0x0F0062, // music +// languages : 0x0F0081, // languages +// cc : 0x0F0082, // cc +// ccon : 0x0F0083, // cc_on +// ccoff : 0x0F0084, // cc_off +// sub : 0x0F0085, // sub +// recordings : 0x0F00A1, // recordings +// livezoom : 0x0F00A2, // live_zoom +// liveplayback : 0x0F00A3, // live_playback +// liveplaybackoff : 0x0F00A4, // live_playback_off +// repeat : 0x0F00A5, // repeat +// repeatoff : 0x0F00A6, // repeat_off +// series : 0x0F00A7, // series +// repeatdownload : 0x0F00A8, // repeat_download +// view360 : 0x0F00A9, // view_360 +// view360off : 0x0F00AA, // view_360_off +// info : 0x0F00AB, // info +// airdown : 0x0F00B5, // air_down +// airright : 0x0F00B6, // air_right +// airup : 0x0F00B7, // air_up +// heatseatleft : 0x0F00B8, // seat_heat_left +// heatseatright : 0x0F00B9, // seat_heat_right +// aircirculation : 0x0F00BA, // air_circulation +// fan : 0x0F00BB, // fan +// defrosterfront : 0x0F00BC, // defroster_front +// defrosterback : 0x0F00BD, // defroster_back +// user : 0x0F00BE, // users +// home : 0x0F00BF, // home +// temperature : 0x0F00C0, // temperature_icon +// compass : 0x0F00C1, // compass +// phone : 0x0F00C2, // phone +// fanoff : 0x0F00C9, // fan_off +// bluetooth : 0x0F00CA, // bluetooth +// datetime : 0x0F00CB, // date_time +// display : 0x0F00CC, // display +// seatbelt : 0x0F00CD, // seat_belt +// wifi : 0x0F00CE, // wifi +// apps : 0x0F00CF, // apps +// climate : 0x0F00D0, // climate +// dashboard : 0x0F00D1, // dashboard +// edit : 0x0F00D2, // edit +// expand : 0x0F00D3, // expand +// radio : 0x0F00D4, // radio +// rearscreen : 0x0F00D5, // rear_screen +// weather : 0x0F00D6 // weather +// }; diff --git a/Switch/Switch.module.less b/Switch/Switch.module.less index a1c6d6d9c..498a2c0c1 100644 --- a/Switch/Switch.module.less +++ b/Switch/Switch.module.less @@ -11,7 +11,7 @@ width: @agate-switch-width; height: @agate-switch-height; line-height: @agate-switch-height; - font-family: "Moonstone Icons"; + font-family: "Agate Icons"; position: relative; display: inline-block; text-align: left; diff --git a/fonts/Agate-icons.ttf b/fonts/Agate-icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..70e0e9f14a9f782bf12212c6a3cf5ca7e32a7a26 GIT binary patch literal 31680 zcmdqKd7KFH&#{q{Bk`OMd0|88E-S4luN0x-)#y zUAl1VMD1TbG=clpvi3C|oqxaasWF5ooOcLet%2anu*fP2#)uH-lsbzJqpq5XUIJ^8MwwJ2-ie(Ml2 z#Lsiz#5KxrgNLp;ag+5HLBREQaQ*2cNB8cz_38)F7UdYOKXlEWn~w3f@I$zN2-n$b z_gu68r_WxuhU2b#3LX5nV@HplIQoIN|A^yme4OKiPanH(|FMT2whwTe{0WqQh@-I( zxH~I%zN`NVXZoj{A$$ip+^xk^C+Ylmx2%56>E};>hv(TZaj9}V<>G_q_#d2>k@yMj zKhN`FP2GFlqY>t8=gXWQX3(2BPRDa$0tbOT$L{AO4g3$N{5dVio^usG$9{q$U&rr?@Z3Opl9f4~^1{zP4&&1M z9OkxlQ>Rh$>CQs-S!WvW7`}AmxTTznBmFpToV)n+FQ|2R&g8P(D1A@U_;IIy&e_~@ z{47q!xc%bvzj76f|B2JTJpHrqnea`ge}y~0!1F&k{bSt4P~b=BG5ly$?(O!Dy#u*4 zu46cN^F?kquH#$;IW$~W`2Iz?x8&ZI|6w_H`ZV%hDOWiC)6?HZi~jC~8(z5n8>#1h z_Uq#pK1op8g;h#;bm)DkeOB*BQ=IMns1>8VAN6BV?+4h)y`}r(6rA7I{Q<_f{13Z7 z!kaQimwxDi%JN$869p}68 zdl2~zoc|oZk03pV^T+V}IMNs3{F(5_aT{>{9DZLy{zja?jNez0z8K?xK2W#m0{}e> zw8Jc9hD4@!fmuZP|MNZ|G-KLMK8bonst1pt_}$x4!&SW+a1OI3euYBP*t+Z~p|ok+=541= zu1uerTe&@*%Vk%cdUn&cQ_rqU=eBQ0DfvuG(c;?OP}Jl z-+edLxpiA1ck1Nbcc<@0KYG_sanGKY!MO7?=6V?z3Dkj+SoIV;xe1lwRLG?%qmV1) z(9Z2E@osHw-PUcZ(9+!YMI6R_lzSTRC2;&Gdm5-7CO(G5-Q24{yJkcDQ%64rsJn{0 z2v{(2CPxOn807juB@95LIts97K%XQs>|WN#K&AJd_n2`0V^0Hb8K;b*G2PKZZE@~H z++y(0A>bCc=&yaoxOwgv_bB&C?z>E7Lu@TO!rsZg&Cd&}FfY7Scu|}bAC*MuZt08i zdigy{O1VUNQsvc^>SOA^s6W?-^xO4s8IK#^H2>C`wqCNo>FjjA6Uj$zjXdktViAw` zBS9&#CV3+DZ2w?pefB50FBbL4qfEV^mYbK`eRytNXo zJXQU}>L1p2)id?m>rXev8xJ+tatxHxVBcW>#$`a@>ownt+ZbstF6T>2Ss`C)w5C|I z(ZY?Fz9z{`6#1_yx`L!EQG&^e#ET+(PE-`}IhpP#3dkQQ0{1O;1&a`^WK^wO$mL6@ zsm?CYEKBQro{lVH>AFQflxG-AgZ&}<9+$v{k0wkomm1P^6&xG0tW|FX>^-XMsVAI- zbFJ#R>O0klX|j3MkEpNeJZL(O$qpP*Bfk2uDuReJZW49A7yV0vRA!j$Fsc^astqu= zF~g|KZo5@uzbguY_^^rxHPnAZF;&w!qIzGJY)gJvMRjeBSw6(L^+wxc#i-PblM@zp(!tw}pM3y@!i(T$@@s!_dlh%MbiG zM*2@o{XUJw651`l(2|;}c3#vk*50h8T=mwMG^VLZt@9%BEt=EoFsFBbJ8~E_wCUwu z2S0d9OU1M==qddRS}dhK`xG7jM#CSn&wVzwFzjdr^{af|v#fFM!(kuxVZ3j`cvBzH z?(S&M^4(5+;91p8s?Vt^{~0~0>w4!|H5F06swGwavl>(N6owUZb^vwU(5<6bE;TSc z1AMm=xL~7C>bjEBKg;X-m-IwT?>wm`bWQs-uP3yxXc(W~7?A}`vxao@dr_ujjY zJt;|x&6Ks+T&AX@Om9cMMRlQta_x5W;l+zg_sHJ8Z`!-J^M1)e!KAsEa7RAVCW6Hb zHv*Yp6$I-ASnReV-X_TGXF$gMINx*^u7G0YE~*p!b`f=kp90?EiVa*jKY!)?JC=`+ zFCU*Wfo`g($O0uI=BHG{P=_?b(AWXZ5F}L`qMSSN!2FdNJTSieKYK;!X6Kz$G%WI| zZUtv5>q>kEdja#V19~!$FQ>Wnpz#N|6Wr^1eW}+nM4Zm=4$%mZmRd%j&f!WRs2Koq zDmI&7!8pzK0N=YTp3~QHqHn#vN&xcGESq7NK!>$$Rvu@weD9KF_pVx1%eJ%cST-@S zY+~AQP18kp1X&V!q#l%IGtw_JS#o9mS2B}iS7vlAM`XV9u!?~jq60f1Yf^6%I`2SX z+wPMi3JS9S#1$Uo7q0J|-@9s{ovUW>zKLbOJW~aYBgn4Y9c^~#*D3*3UZ~?iYNTu8 zofv!9d{B~1w~tyEQP_X>T8nFHHSyWQz`bihg9o@gWJ}PYG8lm(V^o^u5{9%H4lH3~ z;DfBRy|&=B|5;?(COxL_-HXE}O;v6Y#VdE}`Uz3px^=tBMD>q1>2YnJvrms{n?J1c zB1#zI-mu75-nW&Fp4tYfaU0(KI?TTcNjCzDU4j(LvAUac>&?1X_Ug?T(yX86&Y-`4!ZLREt%BztnMTt1IsvMIgL6B1Bk5?=5%#13nW{;?b)%m(*s27^rZK5g*7u6z;EZ1w6g~#zo zREb3BdC@RMmb7+GN?YZ{APv3t?+x2ugnaZ$?GFfFLOw$nfOO+GHTYRsmR+S-#6gym zxBce!B~b!gYw!>TRF0^LRjYa}5;2||+2`5g&||1Q%P`0E${MjbdVLG8FQPf;4syMH zW(eEA)>?LT*sK*Rx-V^aKDzJhVg7@KX6`$u_3U;Goh+~T=t@aaB5Lyyd82BWoxeY8 zlw(Vzi=@TxJ-;tBt|BzqRiIaY3W{3Yhu9A7fm6tsDTE&%2t8dxL)vH%7>msRg>35ezY&}xk zSz?Y35|ds&V7XD#jJiKJ9h6_ZSaL01s$?X^Hoa_2mSb5@b|Ab-EB?l?c~?)RP>>g6 zZ;V=IG-_I4)yg^iqaPyh6t|As@iGVD^KzWHnL-_E1wQ>089gKnpbHRQrIc4>oarud z%fY#K4PaiY;O`+z0kUJ3W*B81alNo=Hde?7 z){bdlF-+k=3F-~1Q&rU#h-RJs4tqcQG`P?RtWKB% zo5*dmQEfKH*(jsI_KDL|M##RsQcu`XYBM|)D&*1EjL6--% zPy?Qkarzn)AwE!@vThShaw7XE`w?_*12nu4Y8gu*Fg42nE=cT<4Iuji8z3M+@C9-} z4f*%C8;=b?X0Qt$(6k31O+S{)J=T|gEYEP7UMHJRZ~o*bH-FfYzrR)#*IxLM&2I7I zMR&7pZ+1(6Nr?~_V9)*rdlV{s9rS5MINBM8_W(AaT@q$a0p20Z1$Z-n#HO1*1~o+6 z5^^ROf1XUtcCLz~BlzpQGnvmPSLJ2>CSAS_r%f0=64%1hCSA^}TP7#BOx{{66lw)_ zWhCuBAL)w}lFug#bxj+nL&B1uS4}!!q?B6#Ss&_u9Nj-ZWWLFS$gmO>Sbh@ouUx z94uV_@-=FE@r|!h7q>_M>fU?*>fSFe-Qld?F}h>DvtwyuQ7P-oy{ki)tyyyyey>5H zd;h9Ct`^$$DYPg9d@BLl%8Cq_b3>iGtAheOF{XvQPA^c7`%%id>8A3&&R6&Ci>zBG@P^7}4EaKVU$<5UZ~Zu>*{bQz52snQ^P?p!vINH- zzJG&#k{ia%u7RO>fy&Y}(#*~a^BE2uos1u`?o?SLHcFvf3;b?nfda&76-*s z$_LRqYnqs2yRgg(V297kl1|8B5d!Q0$Z-bUZL(8k8ATzg6b$MA;f$qtI4*Arr z6`~A)poWF?v&!(i1k8=#nQoD93HhNe0bNX6Aq$i83Q% zGS|)}yM>dE=)`msCPkMDGb8AhPGxmlg+k^!{H4psy+J=WenmE6M2$#-P0_0r=qWEZ zFVg4alV{q;6jd!$&+Y+8w3sMRKcPudHyM*vern-uFSkupO=Y2VXLW^%PD)wmN!T_h z=%QvSys*&mGkpP0i%wc*pC8ZpgWmXZB@scp6It@$#JB831$c339 z#3=QYcd#KrVhH4)P_P@Q^e>Ngnl3|Ptn7y z^QAVZaQGDPGHBu{A}byez{gQmw1Y#hJrvl%vH6LuOC4wF)}0qkJKap4d4|93>TNjg zIhq<^UB2NV%fC>wqnZ^9Vu5S8BH%ZID?g?|>4{5k8a+5VS8NRqwu(_mnR+ah zDy1J-I0J(TrfGeskrRfk!8i=vJtv`Xky8diTdp9XY%#_**aa^2y3*a zS&C~paaj;;!%zVGQA>|#iey~tIM>dmi^Vj=^Rynd)IKlX*SGaHzu=EAwaf%~zyjQp z{S6Y)g5~Wj=nmlp1Q7x{SqNatzTnA|C!ajo`C9ff?kJaa;Otz!GQW#1%WQqQIQpgVN*BW`E-bD7#&WjOz5F`anC4`l38oFbM_}+p|Z{f`D?HlU5)$*-)--sU7 zi)lfu=U4YPhplS5=to9J6;sLhoyYx*V~k44TuMACCEZAJI6hZu*HvDYdF_!+V*hA= zzlTY5yhSm~idorM?74o=>wo$3xSMcYQ&Z-9XDErApg0aXy6)&}!{f!6>^Pdz7e`9~ zU-|L)V4{>tMSM#(m6Vsscq!d+;&J-z8yH-Xk{K`euPA>k?r5&&^{aTZ6^o6<>D9dp z=zuO>DOF=TDG?FZK-Fa)6L{fDyjgdu21I(uy(;t{gS{I%hTEg5i?}B8VBd}9$R6s_ z%;rG2pl<}wv(A@r7p7rhXj{FhTDV@%R*H%sC}QUsutu4!ykXny?6%prZfvzSwvM5n zrPO398cjuOXQkP91V!k~;GjzE!JaJ_u>-CP-N?CcO@$x z&=i`33;8nntlECy2Nl2C&^)jH8fEss6MkEPQF7=86R)emRMP?XV zC__98HTt;Eu8qaA!|92>k*VQq=Sdc0ao1>ox%XH5X2x8J56X;ZA}cl$!wH(aND!24 z7$LwU(>PdztLwo~Fj%_;^5pQ;h-$gM;Ut~xcrN$Gn3gbh`dN@42K4Q*&c5F_d7uV!6qx=0;V0%^FquG0ocbH`DQE7O4&5 zb+t9Q*g&jrc(~8-TuX7Br0r+3W8l!Rvx8>DK{LLIS+Lo~X z2ycxkz6Sj!M1Qb^Q2BL>GZ5gYXsG@|F!Ui-LY53jv70 zFzrZmcyP9*eHLa31`}No!n~>~Op`kwgMg&`OqSM)(X^8cY<>T%HtD+gzO*UvZ%~vs zKunzNOngWSip2gr`fcn;cCRGq!2j5Ts5BLQw)$C`z4|@G#6! z%C}YZ+hKBZQ4mG32xWrJwC6g1EI|~M<)Ur`vMer=HHdNVg^0dRRIb+gi(aWep%`pp zuB~+*FUzrL#)>*_MAKnXFr=N(>SsEa&zPf?5j0dzj3x%ED!i`33Q;JEct=s(={SGk zI4c|nb19#NN2A;l*izR5m!QAp6j0hY;2bm^9GtB7Q(*ffE`tt3&@N$ML5XuLhe`C} zpdU+F8|1!=Vxc5P!nQ=hIOH1w2=>%O=d%;H*g_#``VkoMjg!({JR6-EW&Eu?V~=*; z#->%iX>@iPP5x2aewQpShXS))>inIABR(r6`RRM^v3N0A&?13J#y!<$LS}4qKwyl$ zl})OTzTGh1{;2w(vQtrZ#$++9K@@uxER#dK3;8(_*~Fd&=T#7SRfgWx1|+Y5)$&5_ zGHw_2nr6N}U-{fXCjbC~s7znJvqm7v&&oSy3w!iEN;R z*&ilkUDnKu>#_^??XxrXFIiU3vVMVDtSE~B zZhB`8wNMVmg!H>D!0pE%g~Z@Nfg}PRagIfbI0Pm=?CV0`E%tF)QRL1H9YMzTvR5lg z=Pu>-ccJIh zNj9j{ujWm%Y9&mDRe;@-F##m8P|btdDX_oEIE6om3hfc*!8(Fm*i7@dk*9A#E>sFQ zSMxoNhWVf>Dg|(~Dt>gqCGP__-eDIkkg3BIeB0(RA`kIvR@)u31lCtsvH__Y`&ir zU%n#aXjr7qzMj+?+vav3vXUwL;71SIsm^+MOCZ?rfZ6UD2sYC{CX5SX{WB1A!n<&M zqX_Qv&hGc`9!t^#;a8xiAr;YwQYao48}{YN z!R{BN4fa3!(u0s~x^;G|A?iS+f@lWNq+X$4y;5tUCWWX3hz6Q+_A_T{@I7<{MCi<$ z2!p5*KjjR6SOZiP(?ksG9-0{o8`Fb*M2IMZ@8yqcFmM2Je@3)mrJCt>qK zk9+3|oq@o8%;xjpkPA2rtqk;`hsbz6`y$UD`L`p$I1@o$mSHpyxTea6tMWo;iNXxb zvA~O%XhG04NwQ5HG&=2o8^XH-2p}BdB|&kuh@sg4N=fA{-LM%mbc6j7JuF0Q$Fv;F z>K`!RnZqBV^Bej6NWudmqGv^t02?bA zu{^X3@tIc)6in#*(D_XWh&KG)q!0chqS-2tqSqoM_G<1p_iFA9+{4_vp%b3sJ`6ri zo&jPtz(HsOgoQz^4&O4tF>t+AFDrr9Cg`0A9nB6K;M>Rprh*5Noo9k4G>313@DllO zgF0~Q6SD=HEtn-rWI2KbX+iidcdud52O@#rg+2*lc;tjonx(Smw>@w}S&0X@qPpdP zaHs?XWY-@&`1OMj4F$d|A_7fyXi~ zLEfJXS;WEJyRYg?_&hVzWyK+kx*!RA@?;Zn{fJ>ijCUua(PVT$F$@P$aA3o$7cZSt z)a+;>KbF5CNce*(FL7T@)}xZl3m8ZS+oe<4hVobw-AY?u`fbS zPQx>|2)?;X5h3x12~2d zz!i|3Re%G>LzGAZ>>xC=VD^L1%nVhu4uFD1(i3EmdJ358+Su_>yDPfT(ToIIz^@!kz;7I|vkl8xS422~*EMEnTpem(;7O*caTfxh=D2@ydsjk3J zOlPk7?lqkPFK7s0tLA1&+9{S)h`N&pUN|s4XKJ!Ek{^{M&H7t_R@8N46d(|BKPbg`y`l0<6C3&bnAU)gD?S_%RX!ct zd-1Cd9C+0w`=W!IB|zv1cuPe&O^n7^D7ZyTYN7*b2~x&l+cigvrDL%i9GnpPVdcUV z@~m8_JZwcLG&`bc5gSe5#S-l0#9D~bXcTj+LPjvyGk_HZu~2Kd8@S&Alzf2u7Id0X z$k%nR9%NvG%Qa_2&-L~Fnn z!T=%M1D=QgK*}eckLqfC-sTk#RE8LCbDUQIQ?UHWJ+9vss!@{6P_QndBC;vsE#m^n z87{g!9elltw;|{P?7xaPRskPn@ONA62jaV`>TdD!zN3n)vky!nKGN@7CC&14yzC{1 z{7A-+TZX`Lo~;d8Lril5pp_^FDKH)2tYa%CPBD+T7ghs+H+wObsSKx0acWTDmGn|i zG}B*jv_xJb;lc#=gS(`8R@KlPy|6t(g1)kpg&P|K_unK%d`xD$4#^KvXgqR;s}bG=Es(%NFdQ!YN@6V z^9jSsXbrvdOHHtAiLxYPm&`T5AV0Zrl2{K zGzj$(KQJ(SyGPh6@l^N0R-+CWA(+4y+GNG|0^&0@;xbSJ!D8}I?;z|g9Lq?RE71BB zuU=}`P^@6ffK7rOgaLx>Nd#$SKybzL71*mogkTq@hoT0%)cmMMYY_sWlN~z(N2nKUGR+{+jr^1q=T$G}tB{B;%>GaIQ z#70+0h=F;vAd^`Dz)&4wfDeL@N(dWd41kpb#DoY+)$5VC2@oiRhXf1XCLktHq^nYG zlp7WDT@VEWxCSt9;5&)nmv)5=1TYGO&NCusWo6+Do!*P%Fn~xvfFh_Y#bqIl@RVAwoViNhdCkEPtW=8w?WdAMwNj?)dwW^xo(`M&C zf~5HL80yC?h?px(6iG>d6o4O~30IUwGCD!!dl+i`WFUC(1H}{ZgoD*f>Fd@ZvMScE zjx$7}8>SEjdZHGU zQJL@WhZc@_Jx6AS3^q5Ao)%-e&--JA^uR#6Fy^yB_xPDqpXM@EzGADHv&HyzUZMv{ zU3g+FaS!54ZQ17UNM=~xOC*x`B$9q%AkqH;*(NMQ15^Ci6i6Zvj6RUnlhc~sj8j?q z#G?g)dM=80=vH-Dwe{q|T3u4NEn2iqQWoip;*y;;w|A*=z$ug%x1W z9t8!gLmEHDJwwpRwy@jTDfSWeS@wmO`Dxj&=i3!T9e2-hrB)}31OQQ5O09|OYK87W z8_u`k(vJ}h&4nbiR9*`B4hS;*0MxP#mPvYVa~umMKtY3+Kf{BB0+bX)3V@ywd98RT z4`vwBMIR7Z12rSC#5cqi0C^!OgC!%QKP={>?vTB@gETOig;cef2hs$xg14Y53SbStpkmzwG<(>_n$@u8dWg6c7#XT~QMJ*o zHNr#-2Gr_;-GMO17D`IU{((9r1Z9_P()wZB!#n9r{VdxOkSSK0;7J$-Cr6b&79#~D zcse{fQI)H(1LUg;wR(gB!9nhcH284xLu6^AmH>_GDn-zT7)OBn8bA)@fGAE;Qn*7x zj0`BF$mgN!vvsLd>WTP(t+CE6ylM}`EnbkJWiw`}%7B6J2N>s26i65d@nUhv(nnDf zm;hy}D#ia0fFj&T;T2I)Eha|>S7P~tZs==DQHB44#5?_g6vSkUPxBs#x@kcj4UGwe zx`fanF2iJvGodyq@RStUj(9>$^tti(QsY@@B}Dv$%DjdLf~|&cKK}rwggRJ(cSg(Q zIAT7SYWK&Opu)wa_C-^wni549h5-a*sRFYD*xQ6PJP%)om56bf(8G@Jnnj0w?F;;@){ag-SuN;gKLJW48Q5FlBr0CH5@bJqYJ{{+MQOcX2 zsp(iwBMJuWGEA@y5LqN8wO73R5FNWI33IE++CaJ~U=1XK=R1EH=anc{G=K(L$gh7L>r~P{Ue8T26>_qg6)BsBK+2GNdjg1!5vOP(mtzmf)q- zG@t!F1ST+mB7|JiKrj~10yAqw7}lTSqZ@H^zziVX%WX*F05867E-|BiL9MJA8k(3qm@afm3bL4 z(t@W3YSiZ=LdroZCYbMwLsEnqp?Wy$#Dc-V*o`veAB}8mID@zHl8j##?qlYuyaWXm z6Cc$ylSmM>Qq2Xr3K_sK^un3n+2_a1Sb26ZzqDkVF@Ni$k(H|m^<|}1WiYC(T`6ul zEUmnY=P$DGLKi^3WbMXDfVvO!leM4cuU{q}Qbg?%m>KM~jNLYO{*cjX z;{uGSC;3%~RS-kLj~p)uUkC*+QW)?h2@D>m?$L+60!bLU$+tu)*qkeV9U@z3`)(r~ zy*vR47SZ4(&g%K*qw$wgbjwi2dv zbI?GrxbWA6RUO|3ir|P&H6%O-R6@VtXhN_aMuCPRPXkdQ4S3|;E>?1wJMZm>LK-?m_87Z?soB#B;pJl3Fr%6OUsPV z%b7456OK=a2N)H27H|)A`Ku&hG6!13>Z=d0W{BJ+bnTK$O5sDIs+mwmmIC(3R0A%B z6BJuqf`-BqLFi02Z5${wud*JvX#OWdbFlB7vEj*PzT;#vjxOP}!G z15?AEH@I>M%#*-y;;^CW2P}uJTfk~kSRDD#mM$S?292FfrBmj3rFCFS+OSXwdJJ#A zoIAwK5v=3a)&4CoWi!+3{CK=^$+8PJuU{1mCSdYW`W!k*wgW!E$4nTNgqUrrkJ+Fb zV#4Vo>vtjo=I5ITs{w5>?bws>dD|A>pJ1z3vUD!&6Gjq`Qh(?Wp3xVKDhU=eoTAL@ zkY#io6OhlSV+$;ZI(^m$Qp^x|tx_+C=2``m zHVpoUbPSG89JTAYE5Sy_P);tx6$gw2Aq`q>$n$;xR_+7sf#-y&Eka{Z0-!TixRip> ztVg+&UnXN7@b>MJ!baxubJr}}d(Zkg20fp>|3gpTwQfe_-_MIP>;5qXg@8<}i1ZxL z1*3YTYKr=REO$XjmV;C*3Yd~bQ;lki_Ks^&)e=oP8f&R)9PCgtV>t40G#E@wzKNzs ziJ2OXamb^n96t}Q9df7E4==fL$*tRF4=0Bt)np&Me(UtUfUj2hVBhrCFY5v%aX^A0 zCQ7E{`;u8ohz8~xFl`FD2@{13Q;Zq$UAPNOMNll|TFJCQXcWs5`6RiEL@~*WmZboW zY*RAB9F8+j|BS1%Z?V5b#7dLf3D3-%5YhYB+;_R30S#=1oibv1rCe7^;h18^K#p-N zgTm9dC*73EU4zL$K}-e=q=HZLfNxjl38^wT+$`pgW(AZtObUgd1%XRIUT9AX^D$50 za(I~DKtMnSdDzfxzyg+i5u*Y>Bnv#5P00&GKtT)|kAy|bq!h&!;(!&fJu+-`qsBv^ z=mHBss0%Xj8cX8jofyKo6KDyg=|QluO??O<_u~pEq@knq=uR^jK1o%6TmK9vK-ac}e+FIf#{pqy6z)`uY@k5awHJ{g`ECR%g5%B>p&7 z=ZhvpG2KWfHKs(GQv!ly?9Z9DWoF}ol*#A&Q7>%t#W~2|Q4EQyXNQM!nwl>RmJIVw zBU?x(RmXs^t;uj8g0_%Ef$Ypk7&t-;K*40m zBTD9`VS5)`#3qWc7mDgzA`scNv`a)T9zpY6tSeye<`YR_M|E=NL`qEJ9zG(3EH+kM zzy5kK$t3xAkn|evUT^{wRH~@#mNU~oFV6CL4 z&_@Ct>tCe9^NL!@DGELXR3I{l;&WiUh@iFT7=tpV0viJrYaT3%j_@U5fj)emBG#SQ zoWefGuyYo6k7*#f>pJ+Qx*@$#A!s3G=tcyXHA)JFw9>v109gPjz=u{L(QOgbxIlt#%P!%PL?uv|K=xss})>0agwdNwBxtDJSQf2>Z?jw(r{x7IQ)U zm{7~!^pKK@wLw+58BR~BvCj2Kv1D9rInpiQBZ2y6?qQ9)e=rqua4fd)Rp zN?l(x4Z5)u1Wi!b?T8=_a+(u|>w8Ch!1MdPYm}kj0nd}8+Z`A7;h6urXjG0|=^4gq z2NVVp_*EEKgfHI(W4|oK_b7Z!vhD55!79Rc#28*6FC9ZsjOTKU=R;?W=e7S$<5|ul z_%w9F&0;bT<{J(tWMXzX`k2EKkY685e`*M#h-sWZmJZflNqJSAJu{Xn2DLYqH!@ZQ zE-T8w#kzh;R<@L?@c-dB{)Dj!vbSeq8O2s6Achg&m0tla@A+>U$=?c@un7K!VgCOK zWZ&}NJN&N*#&3VcIl-zK%)s9wUiEQs(igZF8P-cew3%Yi2*dcK5FE*#K;CCWJxsum zi3Qs60QPi>1bW5$pfTb7g&M;3l3@iXJ^^;ut%3L`5gQN{C_IV~S3L0GQF1Hf;qk1L z3%yhx0rcfFx#TRBV8i$Ecrymp+aw-WD3@#s&nJQfrzJ{LBn7cUR7DlhC0eC)8-8}C zUxu5Mth9}=Pjo9UTWm1tKyrk+&k_(GBBX&aX+c&5EMY|S*tCE+lN5 zX88hJ3?heUAi?@3rW zj0o&zc=V}+HG;q)FhE_Oi@KekLeh&TnQh`h8{%7dg7J~gx*8Yqi{Ur5p(z-a$&0H( zM;*3sS%i?K_{Y=;cHywyNF?r=suk&78IefDI6DW@%sL$|T?s0!3ik|^O@K~guSND{ z_Fe8h*lX@N?t8GEa79|g0+^sL5(U6j8_)r!Mb~r%g4<1uGK6cu5+Y#ps;HF-VA{pK;69?KK$!<>0u|V995$kzzftu z;X)3rKK25%Fo<~+vfK92Txv$QWC+KQEYH5iyMVTBirb~z)M>;4wb%oxV!v<4B$DiP zRge%g3Grcy$6=E9auQoCVvh+$HtlG?s0LDL5bz6qAmk1N*L`5LSffCi*EA$^6-$0? zrH_Y`Q^9g40k>>nDV{m|4n%xau)7caYK5e!azSOoqATJtM~AEra8`s#CiwOiY=|^1 z!45yI?jF$ryQ;ht+cxZt_$(PssE7}M9{tfm5ah&|s__W7K!gd90UsM61(l(DVkwx= zp<=l1G9CRC#F#r`B|Z#o5c@ZnZW!W4P!FyS!<&lG4IN)7_%Z+q{4cx*EVmt)bi20@ z9)JF=?sLCmMj}qsv>o-?7);#b*nbLGC9V>A0}BFp!H%r*;t29{TsvmKA#16d1_WD~j%PFz}--|@V z{$UKjpke5*UbFpJGE%Bz5ssA?kS#s*5N}8PAY!qc<40PBNapC}F?BP)^rY|D{D6Po zeK8xK*zHsN1uR=ZOGdo@h!ey!nRq!Do46!VEzdfLyTCQY-$m?r(Oot)k$#uZC&v)^ z?+t*$+GQ*Xl%T?4r&p{45ZSlc58wgp$G*UItnZ_BEEI`%5;{6$S&~!3AbAAS0n`Ca zP@uz@r!v{u5LZR2JB2q8o-4jwZo*s`n!-J7k3-Bz7n1u4?e& z6emUTQb84Rb5~Js)3U4314wE)meI+Oo!JQ4Y1Ax&glcR2QmT~I`|{Hx{FYOCzLiZ? z@>u5}X0QVD4<9vCW+O8){Z+FzKVBk>nwkaOj`!JI*UNL{MC(2HHMPGO#}n_?taLe@j>pC$2>uM-b>>-BH|vGLXeK=_WcB_$=0nGh z#pCR!?88_iHw=AZ9&vAb5c&27?r&)MNGx3UOlzO9F&QyYB71N$P|P!UGTAv041zU@ zg*MqbiD+S|HADv}#sTee1!{@OYy&WRG_7l_#F}aRc?H_)th=@vU>h!XK*a#-a5j}t zCs&|jDs9YEmGl+*f|eE_2b4*T0THKDm>|_9OprAjAmG8>dDauOQ$x9{;UJo`PLEQ- zdMgP19D5m>6J%##6&5Zoy+KmYlRM{Gguo<1L3+b#K9nm-5z7u zJp&BdU!!V75%E#wIO3EwU?`X`QCeMAw(5hbKDf(o1)YC?Tx{I6mR)dHLp-V8RC~>; z`PWn*7+x|xq7Jndm-ireEw8}eWx5fF0Z0fsGow&$6b+e3Wa$NGVnnqUEulpsaJ4BVq@-jmKQ z&Js#Fg%uG|2Vpigi$>rahTRuux^x*fn@fScL?b3HBZ_HZNuCQoD)2x>M6l2PnEl^e z7B<7h+#KliA=nLH2yx&QAb*@E-$K4xpjH0>-5%(_j4xijT@MN%S?zimq9@^DzWi7>zK%<-XY#E};Da%M<_tb9Iiq0cD4zlZ)lkbiK)6q@{(p~!! zpFzZw3<%_>mIHqSb@_77OUDG2*TnH(e*yw3JpjQSPwfAZU5FxHz2t=Fe7~KK!*e@s zI}W&H;7H@+QY^@sdK60v5b=uKf`3jfM$K_FCm%$ptm|i~tjL~!-e5l_o`-$2u{ZwT zVIQd~xZN_WXWxQ#n^z%j6cQ|!WLB}k2^j>i(ZI_`FtKI>EQz*#3KwpA-J=k00))t> z5~7~4uC?AQ(x#svPDHJmy`4qEpkXP@0#O4kBuWkPNVhBOR$ZTYbWYb7V-ayn)^)ir z9*rX=K~)#4YGRvg>Z-WWZ*9Ex-p+I2Wum$pdJ-HJvbvjGt+-a%PL;BFx&sb~7=hgs z+ORwBy1#NaMip;B7*f?CKI42E3^CCU zNYHtR)Ha-Da)IY_2)Yw^C&x40ibEa4=B9}B4&sBUK#+dd!Y&D#YS;?AaVk7vD)wgs zgD~#2SV~BhdG@$2BBaDn0@rncat;iDgCOS?Kg=s`a?p*ZqP~eZEKx}%B0k(JIuGKn zL$lKDCwL2*U^;{05i}!3Ez*}Gev)AnK;nUp*q;V*3Nq~J--n##V;`|xxcg){|4@_& zR4@(*6siXbj7TLiV?#WO5xF5Cgj@u@X(@^FA=rV=02)1pJFu)6D@CSG@0u@4-i$yoJEgQ8mzPMc>d?R2x?fc7dVX zu>HWe$TJR5>8mLJk!8h)6t0MJ3+u+ zt0s&=`&|bSI-F3G$|$sLRhe6*;SC9ObSHGz9d}^KgDl*?A1Ed-*#d)f_zJp8 z)T&vUwnh1fxXXl1{T*WWR_L`|E1ob1XQ2NcL97@l05-o%jwkv?Ak~X`n3Va%VmNgZ z5!{pW;uEwGk-bAT<<24gm%mApf8sm|eM^6yWb}b)^4Lq#1@IK8*d4|k044c=Hk6a4 zYuTx{?@DDlPa{yP57BVoy4dL90{;G?yd)iglL00F1b*P?+jm)>qy9Pk_IZe_ikm3G z>*6L5Dj?;BFY++5JPad1=Mb#8w0@b^XnzNuQ;N-OV-?I+=mXtd25O#5OX;z|o%W{- z)g^GD@Jv!XM2e^K7h%Z$FL*JpMOBU za@QcXR?eSHUdYAhIqflY0&&#%??Kod6=7AOl{DAg@(%kg zV);&ELQp8q3QjtxnHUF9RzM_Jat$2>-Gnle>#cdN=gJ{&;Ylzj+Lfx-g5oKLLngw< z(kxKeG7YGQkz_BVqIuoV>f1Ws_51pKcB|hP7i&XT_05f3CPzg1*(2kfmyWbB7UN4wCNcf zZyp|w4G-TuH1xvI5O_B2y%GSHehJP~M2znY#&S1zExc9taPNbb-&L(bTUvJ?X&SFk zi~*#OmWlOrZ494MRbV9Rl5dPY&>f~UP)u-bO3(qLO(twC(gPoG_%^bGcCTb;n+~X+ z(Cmjj2uY@*$Rwp|lcA+iU)a%__#KZ}+D5w?t|gBbnB0oQeS0j9MInoMe*HXXggn1d zR&n8v+1J?#`<8c(Y+Qd*O_AU<)(4%w4>Tdu$Bu!DsN(z#uJ$2ur6|^T@k&*lx#ae3 zBRl0MW%&|hqn=A-b#Q)U{`Oti(?;Ft6IZm@XV|HfiPnmVpIlOwms$v!eUt2 zHq1klQ|C88T*H+&=EnRnE3hWsIkI8H8kMPw@m~{0(-~Rq=lM*!gEE3JKPw1(d9f-9 z`(VD?C#$pV+qWq@hsS00;%==MtLj~7$v}u^ARlP=w?&wTtD0qz;y=;CVYt=%cGox+L@aInN z(k@rk%QbiXhBNo(wHZyD5hZugH4CNizbbwKS?-^pKfDE)LmC5#w}AWt$j8f9p}#n5`E;ZyFPMITd`-)3Qb#g)m7_6X*LaY_H`Hw=_;&tw<19Sei$s0XY0^@ z9(x;z*xTS=@!x!~_e*H#gaU9jNYr^uBz6k{G$F%wm3ZDGX{i^zU zTYl(1U4IWGr04_eU$w^tS$p$?G_nsyJ6|>B2Q^sCMfP3|xp=RF|Lw)I@39{u7OMpN zO4q7_-C2udiJijgeiHjK0+|#cFk&<5GL|;NT1#7&#ry!P$Vz1eI~9RjV!!e8&LOzg z;w(EbI+j_C6{va7&ydEnxjdFJu_!S&o{J8zVJ5a^%ZXQaUeYuy6j0AQ-*cy?-Kp`T zOUKxSh!=(~toy|jEiR_r2Cd>mRgL>~F|uZQ6;k%)G-(UCnX>uTSo*L8k=)t0ON=T)1odcAJ9 zHnBMf?^?O?!wI+Zf4PZ-%Pw^j!?b_)X100rX4_c2xpOnN zz_vOsVw>O4UeSiV;)95vqkWrO&>h!-d!K}6KrRF^78{FI4p=fH#!_jsy0U-gPU_-U$GURtp+&+xKBsodtFe(XtCZshu~-L+q+-kkCAj&gfQ0$3_uDF8nX zjgF8uOW4i0`2HpV7bkqK}OHpZ3xU zw}`*ImsYt1|B+r==O%^qy>tZsA@be5w96^NU%*zyL<*%mfnPVxAP@g(FU=$Um0nsv z?94BEX%W`bD|%@O|A+C;URp-}H+pG>Lm+T34SOqpPcN-=C-|@S(h<%V-q1_CoGttj zu;?gc@S7ny9!4bRA+#%t|3NbUPk;sl;K3YqgT z(#L_5S-|lTe6wfObv(SarB1zlEsjZ zW8mpKg{v!Jm^qAL-FMcTiP%@+PU0@vf8FuJN3YFR$19b}RQAg24=p07MF!#_h<-8C(x!nc;Fm>$(FKPjvn8C;soye9|_f4Fas29eI%R( zZp;3I*B{w~_(XhQlB7?k2;h9B&vs-o4%!89-^PDGAZHqFb_pK3)c~)S9tcZ2gW&BUVVKxF)Z;UO1S#tvB z?tYy*56 z7qU%Ynp@abaOI5`uV1h1x$e57H(q~C3(x!hcT-oJ<0cG5m4;y0t&;7ZO1(vr?gg5t zy#^ZWA&g>d%QlI_NPMFPJWKVdxMuU|ZWaW4ZIOAZ^fmO`Rb>mK!42Gxs( zh`A9~!+|;B`sNtJ&AiuJ?DEa=?N&dnF*lFWmWNRDUM7U7`>LJlwz~(|*R35fd!PH| znA+;i+i^gaoVkPD1MG_nbyW8d!nFg9U!BkF(2#k$ssI$por;lHwMv6UXCM}6D611# zi}6~K%NQNzB}*u;hB1*|y^%p*YKq#Tl7Y^zC_!rsSsBz4m4fw)L#e@Ov`^f-6cElB z)xE5^SE2U~OM@B6Dsm}vgGJ&mP~2MU{dpO{l!I@{ViAE{9is2p__dqSenvlM^!h-D znRl3ZhnaVndB>S|oO#EYcYLilNK-Ov0aGGML1S2Rc5>6pLz21qFelbVSP~|l9un?l z2X14qi>&U9vEkC?^@klfC`IoFe9kxojF88OO8X%|N>5X`8rfGAzozkBR~z1GnoI*K zLn&Ce*3$>HXmdIK*=~=&3r>6Du|xw?nV9zVs+_ zdCBL)FRWT7>Wh3nV2}LI;yeFYQZ%C%tZlZKl!|4>ORd~;P36BIi@#!g)}#IpdVm#+ literal 0 HcmV?d00001 diff --git a/styles/internal/fonts.less b/styles/internal/fonts.less index d32630f00..1cc069390 100644 --- a/styles/internal/fonts.less +++ b/styles/internal/fonts.less @@ -1,5 +1,5 @@ // ----- SYSTEM FONT NAMES ------ -@font-system-src-moonstone-icons: local("Moonstone-Agate"), url("../../fonts/Moonstone-Agate.ttf") format("truetype"); +@font-system-src-agate-icons: local("Agate-icons"), url("../../fonts/Agate-icons.ttf") format("truetype"); // @import (css) url(https://fonts.googleapis.com/css?family=Orbitron); @import (css) url('https://fonts.googleapis.com/css?family=Work+Sans:300,400,700'); @import (css) url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,900'); @@ -58,11 +58,11 @@ font-style: normal; } -/* ----- Moonstone (Icons) ------ */ +/* ----- Agate (Icons) ------ */ @font-face { - font-family: "Moonstone Icons"; - src: @font-system-src-moonstone-icons; + font-family: "Agate Icons"; + src: @font-system-src-agate-icons; font-weight: normal; font-style: normal; } diff --git a/styles/variables-carbon.less b/styles/variables-carbon.less index 297fcdd8b..f37ee8e2d 100644 --- a/styles/variables-carbon.less +++ b/styles/variables-carbon.less @@ -10,7 +10,7 @@ @agate-base-font-size: 24px; @agate-base-font-weight: 300; @agate-base-font-ligatures: normal; -@agate-icon-font-family: "Moonstone Icons"; +@agate-icon-font-family: "Agate Icons"; @agate-component-spacing: 12px; // Button diff --git a/styles/variables-cobalt.less b/styles/variables-cobalt.less index 0fa704903..890b2732c 100644 --- a/styles/variables-cobalt.less +++ b/styles/variables-cobalt.less @@ -10,7 +10,7 @@ @agate-base-font-size: 24px; @agate-base-font-weight: 400; @agate-base-font-ligatures: none; -@agate-icon-font-family: "Moonstone Icons"; +@agate-icon-font-family: "Agate Icons"; @agate-component-spacing: 12px; // Button diff --git a/styles/variables-copper.less b/styles/variables-copper.less index b6c90c0fc..5c8cc9ab0 100644 --- a/styles/variables-copper.less +++ b/styles/variables-copper.less @@ -10,7 +10,7 @@ @agate-base-font-size: 24px; @agate-base-font-weight: 400; @agate-base-font-ligatures: none; -@agate-icon-font-family: "Moonstone Icons"; +@agate-icon-font-family: "Agate Icons"; @agate-component-spacing: 12px; // Button diff --git a/styles/variables-electro.less b/styles/variables-electro.less index 5920f9b91..457a72925 100644 --- a/styles/variables-electro.less +++ b/styles/variables-electro.less @@ -10,7 +10,7 @@ @agate-base-font-size: 24px; @agate-base-font-weight: 300; @agate-base-font-ligatures: normal; -@agate-icon-font-family: "Moonstone Icons"; +@agate-icon-font-family: "Agate Icons"; @agate-component-spacing: 12px; // local Electro Variables diff --git a/styles/variables-titanium.less b/styles/variables-titanium.less index 5e356d8e1..7659dfea9 100644 --- a/styles/variables-titanium.less +++ b/styles/variables-titanium.less @@ -10,7 +10,7 @@ @agate-base-font-size: 24px; @agate-base-font-weight: 400; @agate-base-font-ligatures: normal; -@agate-icon-font-family: "Moonstone Icons"; +@agate-icon-font-family: "Agate Icons"; @agate-component-spacing: 12px; // Button From b0761424ec9f62aea89e8f6578fcd133a1937021 Mon Sep 17 00:00:00 2001 From: Dave Freeman Date: Thu, 15 Aug 2019 17:10:43 -0500 Subject: [PATCH 3/6] add RoutablePanels --- Panels/CancelDecorator.js | 45 +++++++ Panels/Panels.js | 27 ++-- Panels/Routable.js | 102 +++++++++++++++ Panels/RoutablePanels.js | 52 ++++++++ Panels/Router.js | 259 ++++++++++++++++++++++++++++++++++++++ Panels/index.js | 30 +++++ Panels/package.json | 2 +- package.json | 4 +- 8 files changed, 509 insertions(+), 12 deletions(-) create mode 100644 Panels/CancelDecorator.js create mode 100644 Panels/Routable.js create mode 100644 Panels/RoutablePanels.js create mode 100644 Panels/Router.js create mode 100644 Panels/index.js diff --git a/Panels/CancelDecorator.js b/Panels/CancelDecorator.js new file mode 100644 index 000000000..8097e8b39 --- /dev/null +++ b/Panels/CancelDecorator.js @@ -0,0 +1,45 @@ +import hoc from '@enact/core/hoc'; +import Cancelable from '@enact/ui/Cancelable'; +import Spotlight from '@enact/spotlight'; + +const defaultConfig = { + cancel: null +}; + +const CancelDecorator = hoc(defaultConfig, (config, Wrapped) => { + const {cancel} = config; + + function handleCancel (ev, props) { + const {index, [cancel]: handler, path} = props; + const event = {}; + + if (index > 0 || (path && path.length > 1) && handler) { + // clear Spotlight focus + const current = Spotlight.getCurrent(); + if (current) { + current.blur(); + } + + if (path) { + // this is a RoutablePanels + path.pop(); + event.path = path; + } else { + // this is a Panels + event.index = index - 1; + } + + handler(event); + + ev.stopPropagation(); + } + } + + return Cancelable( + {modal: true, onCancel: handleCancel}, + Wrapped + ); +}); + +export default CancelDecorator; +export {CancelDecorator}; diff --git a/Panels/Panels.js b/Panels/Panels.js index 697115626..a8f144c95 100644 --- a/Panels/Panels.js +++ b/Panels/Panels.js @@ -1,13 +1,13 @@ +import compose from 'ramda/src/compose'; import kind from '@enact/core/kind'; import PropTypes from 'prop-types'; import React from 'react'; import ViewManager, {shape, SlideBottomArranger as VerticalArranger, SlideRightArranger as HorizontalArranger} from '@enact/ui/ViewManager'; -import Panel from './Panel'; -import TabbedPanels from './TabbedPanels'; - import componentCss from './Panels.module.less'; +import CancelDecorator from './CancelDecorator'; + const mapChildren = (childs) => React.Children.map(childs, (child, index) => { return child ? React.cloneElement(child, { 'data-index': index @@ -20,6 +20,7 @@ const PanelsBase = kind({ arranger: shape, duration: PropTypes.number, index: PropTypes.number, + onBack: PropTypes.func, orientation: PropTypes.string }, defaultProps: { @@ -40,12 +41,14 @@ const PanelsBase = kind({ }, enteringProp: ({noAnimation}) => noAnimation ? null : 'hideChildren' }, - render: ({children, ...props}) => { + render: ({children, ...rest}) => { + delete rest.onBack; + const mappedChildren = mapChildren(children); return ( {mappedChildren} @@ -53,10 +56,14 @@ const PanelsBase = kind({ } }); -export default PanelsBase; +const PanelsDecorator = compose( + CancelDecorator({cancel: 'onBack'}) +); + +const Panels = PanelsDecorator(PanelsBase); + +export default Panels; export { - Panel, - PanelsBase as Panels, - PanelsBase, - TabbedPanels + Panels, + PanelsBase }; diff --git a/Panels/Routable.js b/Panels/Routable.js new file mode 100644 index 000000000..41028ec2e --- /dev/null +++ b/Panels/Routable.js @@ -0,0 +1,102 @@ +import hoc from '@enact/core/hoc'; +import invariant from 'invariant'; +import kind from '@enact/core/kind'; +import React from 'react'; +import PropTypes from 'prop-types'; + +import {Router, propTypes, toSegments} from './Router'; + +/** + * Default config for [`Routable`]{@link agate/Panels.Routable}. + * + * @memberof agate/Panels.Routable + * @hocconfig + */ +const defaultConfig = { + /** + * The event to listen to for path changes. + * + * This defines the actual name of the [navigate]{@link agate/Panels.Routable#navigate} + * property. + * + * @type {String} + * @required + * @memberof agate/Panels.Routable.defaultConfig + */ + navigate: null +}; + +/** + * A higher-order component that provides support for Routes as children of Panels which are + * selected via `path` instead of the usual flat array of Panels. + * + * When using `Routable` you must specify the `navigate` config option. + * + * @class Routable + * @memberof agate/Panels + * @hoc + * @public + */ +const Routable = hoc(defaultConfig, (config, Wrapped) => { + const {navigate} = config; + + invariant(navigate, 'navigate must be specified with Routable'); + + return kind({ + name: 'Routable', + + propTypes: /** @lends agate/Panels.Routable.prototype */ { + /** + * Path to the active panel. + * + * May either be a URI-style path (`'/app/home/settings'`) or an array + * of strings (`['app', 'home', 'settings']`). + * + * @type {String|String[]} + * @required + * @public + */ + path: propTypes.path.isRequired, + + /** + * Called when navigating. + * + * The event object is decorated to add `path`. + * + * *NOTE*: The actual name of this property is configured in the HOC config. + * + * @type {Function} + */ + [navigate]: PropTypes.func + }, + + handlers: { + // Adds `path` to the payload of navigate handler in the same format (String, or String[]) + // as the current path prop. + [navigate]: ({index, ...rest}, {path, [navigate]: handler}) => { + if (handler) { + const p = toSegments(path).slice(0, index + 1); + handler({ + ...rest, + index, + path: Array.isArray(path) ? p : '/' + p.join('/') + }); + } + } + }, + + computed: { + // Determines the `index` as 1 less than the number of segments in the path + index: ({path}) => toSegments(path).length - 1 + }, + + render: ({children, index, path, ...rest}) => ( + + {children} + + ) + }); +}); + +export default Routable; +export {Routable}; diff --git a/Panels/RoutablePanels.js b/Panels/RoutablePanels.js new file mode 100644 index 000000000..1bf00a529 --- /dev/null +++ b/Panels/RoutablePanels.js @@ -0,0 +1,52 @@ +import {adaptEvent, forward, handle} from '@enact/core/handle'; +import compose from 'ramda/src/compose'; +import Changeable from '@enact/ui/Changeable'; +import kind from '@enact/core/kind'; +import PropTypes from 'prop-types'; +import React from 'react'; + +import Panels from './Panels'; +import Routable from './Routable'; + +const decorateRoutes = (routes, routeProps) => React.Children.map(routes, (route) => { + return React.cloneElement(route, {...routeProps}); +}); + +const navigateHandler = handle( + adaptEvent(({back}, {path}) => { + if (back && path.length > 1) { + path.pop(); + } + return {path: [...path]}; + }, forward('onChange')) +); + +const RoutablePanelsBase = kind({ + name: 'RoutablePanels', + propTypes: { + onNavigate: PropTypes.func, + path: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]) + }, + handlers: { + onBack: navigateHandler, + onNavigate: navigateHandler + }, + computed: { + children: ({children, onNavigate, path}) => decorateRoutes(children, {onNavigate, path}) + }, + render: (props) => { + delete props.onNavigate; + + return (); + } +}); + +const RoutableDecorator = compose( + Changeable({prop: 'path'}), + Routable({navigate: 'onNavigate'}) +); + +const RoutablePanels = RoutableDecorator(RoutablePanelsBase); + +export default RoutablePanels; +export {RoutablePanels, RoutablePanelsBase}; diff --git a/Panels/Router.js b/Panels/Router.js new file mode 100644 index 000000000..fd147c7c9 --- /dev/null +++ b/Panels/Router.js @@ -0,0 +1,259 @@ +import EnactPropTypes from '@enact/core/internal/prop-types'; +import ForwardRef from '@enact/ui/ForwardRef'; +import PropTypes from 'prop-types'; +import React from 'react'; +import warning from 'warning'; + +const toSegments = (path) => Array.isArray(path) ? path : path.split('/').slice(1) + +const getPaths = (routes, base) => { + let result = []; + Object.keys(routes).filter(s => s[0] !== '$').forEach(p => { + const path = base + '/' + p; + result.push(path); + result = result.concat(getPaths(routes[p], path)); + }); + + return result; +}; + +const stringifyRoutes = (routes) => { + const pad = '\n\t'; + const paths = getPaths(routes, ''); + return pad + paths.join(pad); +}; + +const propTypes = { + path: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), // array of path segments + PropTypes.string // URI-style path + ]) +}; + +/** + * A Router component for use with [`Panels`]{@link agate/Panels.Panels} + * + * @class Router + * @memberof agate/Panels + * @mixes ui/ForwardRef.ForwardRef + * @ui + * @private + */ +const RouterBase = class extends React.Component { + static displayName = 'Router'; + + static propTypes = /** @lends agate/Panels.Router.prototype */ { + /** + * List of views to render. + * + * Will be rendered as a flat array of views suitable for use in + * Panels and not a hierarchy of views as the path implies. + * + * May either be a URI-style path (`'/app/home/settings'`) or an array + * of strings (`['app', 'home', 'settings']`). + * + * @type {String|String[]} + * @required + * @public + */ + path: propTypes.path.isRequired, + + /** + * The component wrapping the rendered path. + * + * @type {String|Component} + * @default 'div' + * @public + */ + component: EnactPropTypes.renderable, + + /** + * Called with a reference to [component]{@link agate/Panels.Router#component}. + * + * @private + */ + componentRef: PropTypes.func, + + /** + * Routes defined as an object rather than via JSX. + * + * If specified, `routes` will take + * precendence over a JSX definition. + * + * ```JavaScript + * const routes = { + * 'first': { + * '$props': { + * 'title': 'About Routable Panels Pattern' + * }, + * '$component': AboutPanel, + * 'second': { + * '$props': { + * 'next': 'fourth', + * 'title': 'Second' + * }, + * '$component': MainPanel + * }, + * 'third': { + * '$props': { + * 'next': 'first', + * 'title': 'Third' + * }, + * '$component': MainPanel, + * 'fourth': { + * '$props': { + * 'next': 'third', + * 'title': 'Fourth' + * }, + * '$component': MainPanel + * } + * } + * } + * }; + * + * + * ``` + * + * @type {Object} + * @public + */ + routes: PropTypes.object + }; + + static defaultProps = { + component: 'div' + }; + + constructor (props) { + super(props); + } + + /** + * Generates a set of routes from `children` and appends them to `routes`. + * + * @param {React.element[]} children Array of children + * @param {Object} routes Route configuration object + * + * @returns {Object} Route configuration object + */ + createRoutes (children, routes) { + React.Children.forEach(children, child => { + const {path, children: grandchildren, component, ...rest} = child.props; + if (path && component) { + routes[path] = { + $component: component, + $props: rest + }; + + if (grandchildren) { + this.createRoutes(grandchildren, routes[path]); + } + } + }); + return routes; + } + + /** + * Creates an array of React.elements for the current path. + * + * @returns {React.element[]} Children to render + */ + createChildren () { + const segments = toSegments(this.props.path); + const {routes, children} = this.props; + const computedRoutes = routes || this.createRoutes(children, {}); + + let valid = true; + let route = computedRoutes; + const childrenElements = segments.map((segment, index) => { + const subPath = segments.slice(0, index + 1).join('/'); + route = route && route[segment]; + if (route && route.$component) { + return React.createElement(route.$component, { + ...route.$props, + key: 'view$/' + subPath, + spotlightId: `panel-${subPath.replace(/\//g, '-')}` + }); + } + + valid = false; + return null; + }); + + warning(valid, `${this.props.path} does not match the configured routes: ${stringifyRoutes(computedRoutes)}`); + + return valid ? childrenElements : []; + } + + render () { + const {component: Component, componentRef, ...rest} = this.props; + const children = this.createChildren(); + + // delete rest.path; + // delete rest.routes; + + return {children}; + } +}; + +const Router = ForwardRef({prop: 'componentRef'}, RouterBase); + +/** + * Used with {@link agate/Panels.Routable} to define the `path` segment and the + * `component` to render. + * + *`Route` elements can be nested to build multiple level paths. + * + * In the below example, `Panels` would render `SettingsPanel` with breadcrumbs to + * navigate `AppPanel` and `HomePanel`. + * + * ``` + * + * + * + * + * + * + * + * + * + * ``` + * + * @class Route + * @ui + * @memberof agate/Panels + * @public + */ +const Route = () => null; + +Route.propTypes = { + /** + * The component to render when the `path` for this Route matches the path of the + * {@link agate/Panels.Routable} container. + * + * @type {String|Component} + * @required + * @public + * @memberof agate/Panels.Route.prototype + */ + component: EnactPropTypes.renderable.isRequired, + + /** + * The name of the path segment. + * + * @type {String} + * @required + * @public + * @memberof agate/Panels.Route.prototype + */ + path: PropTypes.string.isRequired +}; + +export default Router; +export { + propTypes, + Route, + Router, + RouterBase, + toSegments +}; diff --git a/Panels/index.js b/Panels/index.js new file mode 100644 index 000000000..22fde61ca --- /dev/null +++ b/Panels/index.js @@ -0,0 +1,30 @@ +/** + * Panels provides a way to manage different screens of an app. + * + * @module agate/Panels + * @exports Panel + * @exports Panels + * @exports PanelsBase + * @exports Routable + * @exports RoutablePanels + * @exports Route + * @exports TabbedPanels + */ + +import Panel from './Panel'; +import {Panels, PanelsBase} from './Panels'; +import Routable from './Routable'; +import RoutablePanels from './RoutablePanels'; +import {Route} from './Router'; +import TabbedPanels from './TabbedPanels'; + +export default Panels; +export { + Panel, + Panels, + PanelsBase, + Routable, + RoutablePanels, + Route, + TabbedPanels +}; diff --git a/Panels/package.json b/Panels/package.json index abd95e2ea..14ab704d8 100644 --- a/Panels/package.json +++ b/Panels/package.json @@ -1,3 +1,3 @@ { - "main": "Panels.js" + "main": "index.js" } diff --git a/package.json b/package.json index cee40cc76..bc51b1bde 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,12 @@ "classnames": "^2.2.6", "color-convert": "^1.9.3", "eases": "^1.0.8", + "invariant": "^2.2.4", "prop-types": "^15.6.2", "ramda": "^0.25.0", "react": "^16.8.0", - "react-dom": "^16.8.0" + "react-dom": "^16.8.0", + "warning": "^3.0.0" }, "peerDependencies": { "ilib": "^14.2.0 || ^14.2.0-webostv1" From 7b79b34018eb0e349463094c51fbe4e7af52ffaa Mon Sep 17 00:00:00 2001 From: Dave Freeman Date: Thu, 15 Aug 2019 18:58:12 -0500 Subject: [PATCH 4/6] simplify navigateHandler --- Panels/RoutablePanels.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Panels/RoutablePanels.js b/Panels/RoutablePanels.js index 1bf00a529..252da694a 100644 --- a/Panels/RoutablePanels.js +++ b/Panels/RoutablePanels.js @@ -13,10 +13,7 @@ const decorateRoutes = (routes, routeProps) => React.Children.map(routes, (route }); const navigateHandler = handle( - adaptEvent(({back}, {path}) => { - if (back && path.length > 1) { - path.pop(); - } + adaptEvent(({path}) => { return {path: [...path]}; }, forward('onChange')) ); From 175cbd1e2873f7f995ad31606f2df42c7d702c5a Mon Sep 17 00:00:00 2001 From: Dave Freeman Date: Thu, 15 Aug 2019 19:25:58 -0500 Subject: [PATCH 5/6] handle string or array paths --- Panels/CancelDecorator.js | 6 +++++- Panels/RoutablePanels.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Panels/CancelDecorator.js b/Panels/CancelDecorator.js index 8097e8b39..a752158a8 100644 --- a/Panels/CancelDecorator.js +++ b/Panels/CancelDecorator.js @@ -22,8 +22,12 @@ const CancelDecorator = hoc(defaultConfig, (config, Wrapped) => { if (path) { // this is a RoutablePanels - path.pop(); event.path = path; + if (Array.isArray(event.path)) { + event.path.pop(); + } else { + event.path = event.path.split('/').slice(0, -1).join('/'); + } } else { // this is a Panels event.index = index - 1; diff --git a/Panels/RoutablePanels.js b/Panels/RoutablePanels.js index 252da694a..66126556d 100644 --- a/Panels/RoutablePanels.js +++ b/Panels/RoutablePanels.js @@ -14,7 +14,7 @@ const decorateRoutes = (routes, routeProps) => React.Children.map(routes, (route const navigateHandler = handle( adaptEvent(({path}) => { - return {path: [...path]}; + return {path: Array.isArray(path) ? [...path] : path}; }, forward('onChange')) ); From 99740ca05ffd1907de73ad2bb0c41a6e7eccda63 Mon Sep 17 00:00:00 2001 From: Dave Freeman Date: Fri, 16 Aug 2019 12:17:53 -0500 Subject: [PATCH 6/6] disambiguate path to currentRoute for children --- Panels/RoutablePanels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Panels/RoutablePanels.js b/Panels/RoutablePanels.js index 66126556d..60b418e2c 100644 --- a/Panels/RoutablePanels.js +++ b/Panels/RoutablePanels.js @@ -29,7 +29,7 @@ const RoutablePanelsBase = kind({ onNavigate: navigateHandler }, computed: { - children: ({children, onNavigate, path}) => decorateRoutes(children, {onNavigate, path}) + children: ({children, onNavigate: updateRoute, path: currentRoute}) => decorateRoutes(children, {currentRoute, updateRoute}) }, render: (props) => { delete props.onNavigate;