From 1085c400a3612d95f0876c351b8b885c99db7297 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 22 Mar 2022 19:54:31 +0800 Subject: [PATCH 01/46] Match-id-f9eaf1cfccee2ac7b3b2f5b071385d1356d11320 --- libs/extension/src/components/VTree.tsx | 103 ++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 libs/extension/src/components/VTree.tsx diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx new file mode 100644 index 00000000..040ea892 --- /dev/null +++ b/libs/extension/src/components/VTree.tsx @@ -0,0 +1,103 @@ +import { useState } from 'horizon'; + +export interface IData { + id: string; + name: string; + indentation: number; + userKey: string; +} + +type IItem = { + style: any, + hasChild: boolean, + onExpand: (id: string) => void, +} & IData + +// TODO: 计算可以展示的最多数量,并且监听显示器高度变化修改数值 +const showNum = 50; +const divHeight = 21; + +function Item({ name, style, userKey, hasChild, onExpand, id, indentation }: IItem) { + const key = userKey === '' ? '' : ` key = '${userKey}'`; + const showIcon = hasChild ? '△' : ''; + const onClickExpand = () => { + onExpand(id); + } + return ( +
+
{showIcon}
+ {name + key} +
+ ) +} + +function VTree({ data }: { data: IData[] }) { + const [scrollTop, setScrollTop] = useState(0); + const [collapseNode, setCollapseNode] = useState(new Set()); + const changeExpandNode = (id: string) => { + const nodes = new Set(); + collapseNode.forEach(value => { + nodes.add(value); + }); + if (nodes.has(id)) { + nodes.delete(id); + } else { + nodes.add(id); + } + setCollapseNode(nodes); + }; + const showList: any[] = []; + + let totalHeight = 0; + let currentCollapseIndentation: null| number = null; + data.forEach((item, index) => { + // 存在未处理完的收起节点 + if (currentCollapseIndentation !== null) { + const indentation = item.indentation; + // 缩进更大,不显示 + if (indentation > currentCollapseIndentation) { + return; + } else { + // 缩进小,说明完成了该收起节点的子节点处理。 + currentCollapseIndentation = null; + } + } + if (totalHeight >= scrollTop && showList.length <= showNum) { + const nextItem = data[index + 1]; + const hasChild = nextItem ? nextItem.indentation > item.indentation : false; + showList.push( + + ) + } + totalHeight = totalHeight + divHeight; + let id = item.id; + if (collapseNode.has(id)) { + // 该节点需要收起子节点 + currentCollapseIndentation = item.indentation; + } + }); + + const scroll = (event: any) => { + const scrollTop = event.target.scrollTop; + setScrollTop(Math.max(scrollTop - 100, 0)); + } + + return ( +
+ {showList} + {/* 确保有足够的高度 */} +
+
+ ) +} + +export default VTree; From 8a86d83faf85eaef64397e5a97d63cf3948ce337 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 22 Mar 2022 19:54:51 +0800 Subject: [PATCH 02/46] Match-id-87622cf11c2fc67c3916642abf1f1eb08b10dae6 --- libs/extension/src/panel/App.tsx | 35 ++++++++++++++++++ libs/extension/src/panel/index.tsx | 7 ++++ libs/extension/src/panel/panel.html | 22 ++++++++++++ libs/extension/webpack.dev.js | 55 +++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 libs/extension/src/panel/App.tsx create mode 100644 libs/extension/src/panel/index.tsx create mode 100644 libs/extension/src/panel/panel.html create mode 100644 libs/extension/webpack.dev.js diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx new file mode 100644 index 00000000..2c9014c5 --- /dev/null +++ b/libs/extension/src/panel/App.tsx @@ -0,0 +1,35 @@ +import VTree, { IData } from '../components/VTree'; + +// 临时开发用数据 +const arr = [9,"Main","","",10,"Jumbotron",9,"",11,"Button",10,"",12,"Button",10,"",13,"Button",10,"",14,"Button",10,"",15,"Button",10,"",16,"Button",10,"",17,"Row",9,"1",18,"Row",9,"2",19,"Row",9,"3",20,"Row",9,"4",21,"Row",9,"5",22,"Row",9,"6",23,"Row",9,"7",24,"Row",9,"8",25,"Row",9,"9",26,"Row",9,"10",27,"Row",9,"11",28,"Row",9,"12",29,"Row",9,"13",30,"Row",9,"14",31,"Row",9,"15",32,"Row",9,"16",33,"Row",9,"17",34,"Row",9,"18",35,"Row",9,"19",36,"Row",9,"20",37,"Row",9,"21",38,"Row",9,"22",39,"Row",9,"23",40,"Row",9,"24",41,"Row",9,"25",42,"Row",9,"26",43,"Row",9,"27",44,"Row",9,"28",45,"Row",9,"29",46,"Row",9,"30",47,"Row",9,"31",48,"Row",9,"32",49,"Row",9,"33",50,"Row",9,"34",51,"Row",9,"35",52,"Row",9,"36",53,"Row",9,"37",54,"Row",9,"38",55,"Row",9,"39",56,"Row",9,"40",57,"Row",9,"41",58,"Row",9,"42",59,"Row",9,"43",60,"Row",9,"44",61,"Row",9,"45",62,"Row",9,"46",63,"Row",9,"47",64,"Row",9,"48",65,"Row",9,"49",66,"Row",9,"50",67,"Row",9,"51",68,"Row",9,"52",69,"Row",9,"53",70,"Row",9,"54",71,"Row",9,"55",72,"Row",9,"56",73,"Row",9,"57",74,"Row",9,"58",75,"Row",9,"59",76,"Row",9,"60",77,"Row",9,"61",78,"Row",9,"62",79,"Row",9,"63",80,"Row",9,"64",81,"Row",9,"65",82,"Row",9,"66",83,"Row",9,"67",84,"Row",9,"68",85,"Row",9,"69",86,"Row",9,"70",87,"Row",9,"71",88,"Row",9,"72",89,"Row",9,"73",90,"Row",9,"74",91,"Row",9,"75",92,"Row",9,"76",93,"Row",9,"77",94,"Row",9,"78",95,"Row",9,"79",96,"Row",9,"80",97,"Row",9,"81",98,"Row",9,"82",99,"Row",9,"83",100,"Row",9,"84",101,"Row",9,"85",102,"Row",9,"86",103,"Row",9,"87",104,"Row",9,"88",105,"Row",9,"89",106,"Row",9,"90",107,"Row",9,"91",108,"Row",9,"92",109,"Row",9,"93",110,"Row",9,"94",111,"Row",9,"95",112,"Row",9,"96",113,"Row",9,"97",114,"Row",9,"98",115,"Row",9,"99",116,"Row",9,"100",117,"Row",9,"101",118,"Row",9,"102",119,"Row",9,"103",120,"Row",9,"104",121,"Row",9,"105",122,"Row",9,"106",123,"Row",9,"107",124,"Row",9,"108",125,"Row",9,"109",126,"Row",9,"110",127,"Row",9,"111",128,"Row",9,"112",129,"Row",9,"113",130,"Row",9,"114",131,"Row",9,"115",132,"Row",9,"116",133,"Row",9,"117",134,"Row",9,"118",135,"Row",9,"119",136,"Row",9,"120",137,"Row",9,"121",138,"Row",9,"122",139,"Row",9,"123",140,"Row",9,"124",141,"Row",9,"125",142,"Row",9,"126",143,"Row",9,"127",144,"Row",9,"128",145,"Row",9,"129",146,"Row",9,"130",147,"Row",9,"131",148,"Row",9,"132",149,"Row",9,"133",150,"Row",9,"134",151,"Row",9,"135",152,"Row",9,"136",153,"Row",9,"137",154,"Row",9,"138",155,"Row",9,"139",156,"Row",9,"140",157,"Row",9,"141",158,"Row",9,"142",159,"Row",9,"143",160,"Row",9,"144",161,"Row",9,"145",162,"Row",9,"146",163,"Row",9,"147",164,"Row",9,"148",165,"Row",9,"149",166,"Row",9,"150",167,"Row",9,"151",168,"Row",9,"152",169,"Row",9,"153",170,"Row",9,"154",171,"Row",9,"155",172,"Row",9,"156",173,"Row",9,"157",174,"Row",9,"158",175,"Row",9,"159",176,"Row",9,"160",177,"Row",9,"161",178,"Row",9,"162",179,"Row",9,"163",180,"Row",9,"164",181,"Row",9,"165",182,"Row",9,"166",183,"Row",9,"167",184,"Row",9,"168",185,"Row",9,"169",186,"Row",9,"170",187,"Row",9,"171",188,"Row",9,"172",189,"Row",9,"173",190,"Row",9,"174",191,"Row",9,"175",192,"Row",9,"176",193,"Row",9,"177",194,"Row",9,"178",195,"Row",9,"179",196,"Row",9,"180",197,"Row",9,"181",198,"Row",9,"182",199,"Row",9,"183",200,"Row",9,"184",201,"Row",9,"185",202,"Row",9,"186",203,"Row",9,"187",204,"Row",9,"188",205,"Row",9,"189",206,"Row",9,"190",207,"Row",9,"191",208,"Row",9,"192",209,"Row",9,"193",210,"Row",9,"194",211,"Row",9,"195",212,"Row",9,"196",213,"Row",9,"197",214,"Row",9,"198",215,"Row",9,"199",216,"Row",9,"200",217,"Row",9,"201",218,"Row",9,"202",219,"Row",9,"203",220,"Row",9,"204",221,"Row",9,"205",222,"Row",9,"206",223,"Row",9,"207",224,"Row",9,"208",225,"Row",9,"209",226,"Row",9,"210",227,"Row",9,"211",228,"Row",9,"212",229,"Row",9,"213",230,"Row",9,"214",231,"Row",9,"215",232,"Row",9,"216",233,"Row",9,"217",234,"Row",9,"218",235,"Row",9,"219",236,"Row",9,"220",237,"Row",9,"221",238,"Row",9,"222",239,"Row",9,"223",240,"Row",9,"224",241,"Row",9,"225",242,"Row",9,"226",243,"Row",9,"227",244,"Row",9,"228",245,"Row",9,"229",246,"Row",9,"230",247,"Row",9,"231",248,"Row",9,"232",249,"Row",9,"233",250,"Row",9,"234",251,"Row",9,"235",252,"Row",9,"236",253,"Row",9,"237",254,"Row",9,"238",255,"Row",9,"239",256,"Row",9,"240",257,"Row",9,"241",258,"Row",9,"242",259,"Row",9,"243",260,"Row",9,"244",261,"Row",9,"245",262,"Row",9,"246",263,"Row",9,"247",264,"Row",9,"248",265,"Row",9,"249",266,"Row",9,"250",267,"Row",9,"251",268,"Row",9,"252",269,"Row",9,"253",270,"Row",9,"254",271,"Row",9,"255",272,"Row",9,"256",273,"Row",9,"257",274,"Row",9,"258",275,"Row",9,"259",276,"Row",9,"260",277,"Row",9,"261",278,"Row",9,"262",279,"Row",9,"263",280,"Row",9,"264",281,"Row",9,"265",282,"Row",9,"266",283,"Row",9,"267",284,"Row",9,"268",285,"Row",9,"269",286,"Row",9,"270",287,"Row",9,"271",288,"Row",9,"272",289,"Row",9,"273",290,"Row",9,"274",291,"Row",9,"275",292,"Row",9,"276",293,"Row",9,"277",294,"Row",9,"278",295,"Row",9,"279",296,"Row",9,"280",297,"Row",9,"281",298,"Row",9,"282",299,"Row",9,"283",300,"Row",9,"284",301,"Row",9,"285",302,"Row",9,"286",303,"Row",9,"287",304,"Row",9,"288",305,"Row",9,"289",306,"Row",9,"290",307,"Row",9,"291",308,"Row",9,"292",309,"Row",9,"293",310,"Row",9,"294",311,"Row",9,"295",312,"Row",9,"296",313,"Row",9,"297",314,"Row",9,"298",315,"Row",9,"299",316,"Row",9,"300",317,"Row",9,"301",318,"Row",9,"302",319,"Row",9,"303",320,"Row",9,"304",321,"Row",9,"305",322,"Row",9,"306",323,"Row",9,"307",324,"Row",9,"308",325,"Row",9,"309",326,"Row",9,"310",327,"Row",9,"311",328,"Row",9,"312",329,"Row",9,"313",330,"Row",9,"314",331,"Row",9,"315",332,"Row",9,"316",333,"Row",9,"317",334,"Row",9,"318",335,"Row",9,"319",336,"Row",9,"320",337,"Row",9,"321",338,"Row",9,"322",339,"Row",9,"323",340,"Row",9,"324",341,"Row",9,"325",342,"Row",9,"326",343,"Row",9,"327",344,"Row",9,"328",345,"Row",9,"329",346,"Row",9,"330",347,"Row",9,"331",348,"Row",9,"332",349,"Row",9,"333",350,"Row",9,"334",351,"Row",9,"335",352,"Row",9,"336",353,"Row",9,"337",354,"Row",9,"338",355,"Row",9,"339",356,"Row",9,"340",357,"Row",9,"341",358,"Row",9,"342",359,"Row",9,"343",360,"Row",9,"344",361,"Row",9,"345",362,"Row",9,"346",363,"Row",9,"347",364,"Row",9,"348",365,"Row",9,"349",366,"Row",9,"350",367,"Row",9,"351",368,"Row",9,"352",369,"Row",9,"353",370,"Row",9,"354",371,"Row",9,"355",372,"Row",9,"356",373,"Row",9,"357",374,"Row",9,"358",375,"Row",9,"359",376,"Row",9,"360",377,"Row",9,"361",378,"Row",9,"362",379,"Row",9,"363",380,"Row",9,"364",381,"Row",9,"365",382,"Row",9,"366",383,"Row",9,"367",384,"Row",9,"368",385,"Row",9,"369",386,"Row",9,"370",387,"Row",9,"371",388,"Row",9,"372",389,"Row",9,"373",390,"Row",9,"374",391,"Row",9,"375",392,"Row",9,"376",393,"Row",9,"377",394,"Row",9,"378",395,"Row",9,"379",396,"Row",9,"380",397,"Row",9,"381",398,"Row",9,"382",399,"Row",9,"383",400,"Row",9,"384",401,"Row",9,"385",402,"Row",9,"386",403,"Row",9,"387",404,"Row",9,"388",405,"Row",9,"389",406,"Row",9,"390",407,"Row",9,"391",408,"Row",9,"392",409,"Row",9,"393",410,"Row",9,"394",411,"Row",9,"395",412,"Row",9,"396",413,"Row",9,"397",414,"Row",9,"398",415,"Row",9,"399",416,"Row",9,"400",417,"Row",9,"401",418,"Row",9,"402",419,"Row",9,"403",420,"Row",9,"404",421,"Row",9,"405",422,"Row",9,"406",423,"Row",9,"407",424,"Row",9,"408",425,"Row",9,"409",426,"Row",9,"410",427,"Row",9,"411",428,"Row",9,"412",429,"Row",9,"413",430,"Row",9,"414",431,"Row",9,"415",432,"Row",9,"416",433,"Row",9,"417",434,"Row",9,"418",435,"Row",9,"419",436,"Row",9,"420",437,"Row",9,"421",438,"Row",9,"422",439,"Row",9,"423",440,"Row",9,"424",441,"Row",9,"425",442,"Row",9,"426",443,"Row",9,"427",444,"Row",9,"428",445,"Row",9,"429",446,"Row",9,"430",447,"Row",9,"431",448,"Row",9,"432",449,"Row",9,"433",450,"Row",9,"434",451,"Row",9,"435",452,"Row",9,"436",453,"Row",9,"437",454,"Row",9,"438",455,"Row",9,"439",456,"Row",9,"440",457,"Row",9,"441",458,"Row",9,"442",459,"Row",9,"443",460,"Row",9,"444",461,"Row",9,"445",462,"Row",9,"446",463,"Row",9,"447",464,"Row",9,"448",465,"Row",9,"449",466,"Row",9,"450",467,"Row",9,"451",468,"Row",9,"452",469,"Row",9,"453",470,"Row",9,"454",471,"Row",9,"455",472,"Row",9,"456",473,"Row",9,"457",474,"Row",9,"458",475,"Row",9,"459",476,"Row",9,"460",477,"Row",9,"461",478,"Row",9,"462",479,"Row",9,"463",480,"Row",9,"464",481,"Row",9,"465",482,"Row",9,"466",483,"Row",9,"467",484,"Row",9,"468",485,"Row",9,"469",486,"Row",9,"470",487,"Row",9,"471",488,"Row",9,"472",489,"Row",9,"473",490,"Row",9,"474",491,"Row",9,"475",492,"Row",9,"476",493,"Row",9,"477",494,"Row",9,"478",495,"Row",9,"479",496,"Row",9,"480",497,"Row",9,"481",498,"Row",9,"482",499,"Row",9,"483",500,"Row",9,"484",501,"Row",9,"485",502,"Row",9,"486",503,"Row",9,"487",504,"Row",9,"488",505,"Row",9,"489",506,"Row",9,"490",507,"Row",9,"491",508,"Row",9,"492",509,"Row",9,"493",510,"Row",9,"494",511,"Row",9,"495",512,"Row",9,"496",513,"Row",9,"497",514,"Row",9,"498",515,"Row",9,"499",516,"Row",9,"500",517,"Row",9,"501",518,"Row",9,"502",519,"Row",9,"503",520,"Row",9,"504",521,"Row",9,"505",522,"Row",9,"506",523,"Row",9,"507",524,"Row",9,"508",525,"Row",9,"509",526,"Row",9,"510",527,"Row",9,"511",528,"Row",9,"512",529,"Row",9,"513",530,"Row",9,"514",531,"Row",9,"515",532,"Row",9,"516",533,"Row",9,"517",534,"Row",9,"518",535,"Row",9,"519",536,"Row",9,"520",537,"Row",9,"521",538,"Row",9,"522",539,"Row",9,"523",540,"Row",9,"524",541,"Row",9,"525",542,"Row",9,"526",543,"Row",9,"527",544,"Row",9,"528",545,"Row",9,"529",546,"Row",9,"530",547,"Row",9,"531",548,"Row",9,"532",549,"Row",9,"533",550,"Row",9,"534",551,"Row",9,"535",552,"Row",9,"536",553,"Row",9,"537",554,"Row",9,"538",555,"Row",9,"539",556,"Row",9,"540",557,"Row",9,"541",558,"Row",9,"542",559,"Row",9,"543",560,"Row",9,"544",561,"Row",9,"545",562,"Row",9,"546",563,"Row",9,"547",564,"Row",9,"548",565,"Row",9,"549",566,"Row",9,"550",567,"Row",9,"551",568,"Row",9,"552",569,"Row",9,"553",570,"Row",9,"554",571,"Row",9,"555",572,"Row",9,"556",573,"Row",9,"557",574,"Row",9,"558",575,"Row",9,"559",576,"Row",9,"560",577,"Row",9,"561",578,"Row",9,"562",579,"Row",9,"563",580,"Row",9,"564",581,"Row",9,"565",582,"Row",9,"566",583,"Row",9,"567",584,"Row",9,"568",585,"Row",9,"569",586,"Row",9,"570",587,"Row",9,"571",588,"Row",9,"572",589,"Row",9,"573",590,"Row",9,"574",591,"Row",9,"575",592,"Row",9,"576",593,"Row",9,"577",594,"Row",9,"578",595,"Row",9,"579",596,"Row",9,"580",597,"Row",9,"581",598,"Row",9,"582",599,"Row",9,"583",600,"Row",9,"584",601,"Row",9,"585",602,"Row",9,"586",603,"Row",9,"587",604,"Row",9,"588",605,"Row",9,"589",606,"Row",9,"590",607,"Row",9,"591",608,"Row",9,"592",609,"Row",9,"593",610,"Row",9,"594",611,"Row",9,"595",612,"Row",9,"596",613,"Row",9,"597",614,"Row",9,"598",615,"Row",9,"599",616,"Row",9,"600",617,"Row",9,"601",618,"Row",9,"602",619,"Row",9,"603",620,"Row",9,"604",621,"Row",9,"605",622,"Row",9,"606",623,"Row",9,"607",624,"Row",9,"608",625,"Row",9,"609",626,"Row",9,"610",627,"Row",9,"611",628,"Row",9,"612",629,"Row",9,"613",630,"Row",9,"614",631,"Row",9,"615",632,"Row",9,"616",633,"Row",9,"617",634,"Row",9,"618",635,"Row",9,"619",636,"Row",9,"620",637,"Row",9,"621",638,"Row",9,"622",639,"Row",9,"623",640,"Row",9,"624",641,"Row",9,"625",642,"Row",9,"626",643,"Row",9,"627",644,"Row",9,"628",645,"Row",9,"629",646,"Row",9,"630",647,"Row",9,"631",648,"Row",9,"632",649,"Row",9,"633",650,"Row",9,"634",651,"Row",9,"635",652,"Row",9,"636",653,"Row",9,"637",654,"Row",9,"638",655,"Row",9,"639",656,"Row",9,"640",657,"Row",9,"641",658,"Row",9,"642",659,"Row",9,"643",660,"Row",9,"644",661,"Row",9,"645",662,"Row",9,"646",663,"Row",9,"647",664,"Row",9,"648",665,"Row",9,"649",666,"Row",9,"650",667,"Row",9,"651",668,"Row",9,"652",669,"Row",9,"653",670,"Row",9,"654",671,"Row",9,"655",672,"Row",9,"656",673,"Row",9,"657",674,"Row",9,"658",675,"Row",9,"659",676,"Row",9,"660",677,"Row",9,"661",678,"Row",9,"662",679,"Row",9,"663",680,"Row",9,"664",681,"Row",9,"665",682,"Row",9,"666",683,"Row",9,"667",684,"Row",9,"668",685,"Row",9,"669",686,"Row",9,"670",687,"Row",9,"671",688,"Row",9,"672",689,"Row",9,"673",690,"Row",9,"674",691,"Row",9,"675",692,"Row",9,"676",693,"Row",9,"677",694,"Row",9,"678",695,"Row",9,"679",696,"Row",9,"680",697,"Row",9,"681",698,"Row",9,"682",699,"Row",9,"683",700,"Row",9,"684",701,"Row",9,"685",702,"Row",9,"686",703,"Row",9,"687",704,"Row",9,"688",705,"Row",9,"689",706,"Row",9,"690",707,"Row",9,"691",708,"Row",9,"692",709,"Row",9,"693",710,"Row",9,"694",711,"Row",9,"695",712,"Row",9,"696",713,"Row",9,"697",714,"Row",9,"698",715,"Row",9,"699",716,"Row",9,"700",717,"Row",9,"701",718,"Row",9,"702",719,"Row",9,"703",720,"Row",9,"704",721,"Row",9,"705",722,"Row",9,"706",723,"Row",9,"707",724,"Row",9,"708",725,"Row",9,"709",726,"Row",9,"710",727,"Row",9,"711",728,"Row",9,"712",729,"Row",9,"713",730,"Row",9,"714",731,"Row",9,"715",732,"Row",9,"716",733,"Row",9,"717",734,"Row",9,"718",735,"Row",9,"719",736,"Row",9,"720",737,"Row",9,"721",738,"Row",9,"722",739,"Row",9,"723",740,"Row",9,"724",741,"Row",9,"725",742,"Row",9,"726",743,"Row",9,"727",744,"Row",9,"728",745,"Row",9,"729",746,"Row",9,"730",747,"Row",9,"731",748,"Row",9,"732",749,"Row",9,"733",750,"Row",9,"734",751,"Row",9,"735",752,"Row",9,"736",753,"Row",9,"737",754,"Row",9,"738",755,"Row",9,"739",756,"Row",9,"740",757,"Row",9,"741",758,"Row",9,"742",759,"Row",9,"743",760,"Row",9,"744",761,"Row",9,"745",762,"Row",9,"746",763,"Row",9,"747",764,"Row",9,"748",765,"Row",9,"749",766,"Row",9,"750",767,"Row",9,"751",768,"Row",9,"752",769,"Row",9,"753",770,"Row",9,"754",771,"Row",9,"755",772,"Row",9,"756",773,"Row",9,"757",774,"Row",9,"758",775,"Row",9,"759",776,"Row",9,"760",777,"Row",9,"761",778,"Row",9,"762",779,"Row",9,"763",780,"Row",9,"764",781,"Row",9,"765",782,"Row",9,"766",783,"Row",9,"767",784,"Row",9,"768",785,"Row",9,"769",786,"Row",9,"770",787,"Row",9,"771",788,"Row",9,"772",789,"Row",9,"773",790,"Row",9,"774",791,"Row",9,"775",792,"Row",9,"776",793,"Row",9,"777",794,"Row",9,"778",795,"Row",9,"779",796,"Row",9,"780",797,"Row",9,"781",798,"Row",9,"782",799,"Row",9,"783",800,"Row",9,"784",801,"Row",9,"785",802,"Row",9,"786",803,"Row",9,"787",804,"Row",9,"788",805,"Row",9,"789",806,"Row",9,"790",807,"Row",9,"791",808,"Row",9,"792",809,"Row",9,"793",810,"Row",9,"794",811,"Row",9,"795",812,"Row",9,"796",813,"Row",9,"797",814,"Row",9,"798",815,"Row",9,"799",816,"Row",9,"800",817,"Row",9,"801",818,"Row",9,"802",819,"Row",9,"803",820,"Row",9,"804",821,"Row",9,"805",822,"Row",9,"806",823,"Row",9,"807",824,"Row",9,"808",825,"Row",9,"809",826,"Row",9,"810",827,"Row",9,"811",828,"Row",9,"812",829,"Row",9,"813",830,"Row",9,"814",831,"Row",9,"815",832,"Row",9,"816",833,"Row",9,"817",834,"Row",9,"818",835,"Row",9,"819",836,"Row",9,"820",837,"Row",9,"821",838,"Row",9,"822",839,"Row",9,"823",840,"Row",9,"824",841,"Row",9,"825",842,"Row",9,"826",843,"Row",9,"827",844,"Row",9,"828",845,"Row",9,"829",846,"Row",9,"830",847,"Row",9,"831",848,"Row",9,"832",849,"Row",9,"833",850,"Row",9,"834",851,"Row",9,"835",852,"Row",9,"836",853,"Row",9,"837",854,"Row",9,"838",855,"Row",9,"839",856,"Row",9,"840",857,"Row",9,"841",858,"Row",9,"842",859,"Row",9,"843",860,"Row",9,"844",861,"Row",9,"845",862,"Row",9,"846",863,"Row",9,"847",864,"Row",9,"848",865,"Row",9,"849",866,"Row",9,"850",867,"Row",9,"851",868,"Row",9,"852",869,"Row",9,"853",870,"Row",9,"854",871,"Row",9,"855",872,"Row",9,"856",873,"Row",9,"857",874,"Row",9,"858",875,"Row",9,"859",876,"Row",9,"860",877,"Row",9,"861",878,"Row",9,"862",879,"Row",9,"863",880,"Row",9,"864",881,"Row",9,"865",882,"Row",9,"866",883,"Row",9,"867",884,"Row",9,"868",885,"Row",9,"869",886,"Row",9,"870",887,"Row",9,"871",888,"Row",9,"872",889,"Row",9,"873",890,"Row",9,"874",891,"Row",9,"875",892,"Row",9,"876",893,"Row",9,"877",894,"Row",9,"878",895,"Row",9,"879",896,"Row",9,"880",897,"Row",9,"881",898,"Row",9,"882",899,"Row",9,"883",900,"Row",9,"884",901,"Row",9,"885",902,"Row",9,"886",903,"Row",9,"887",904,"Row",9,"888",905,"Row",9,"889",906,"Row",9,"890",907,"Row",9,"891",908,"Row",9,"892",909,"Row",9,"893",910,"Row",9,"894",911,"Row",9,"895",912,"Row",9,"896",913,"Row",9,"897",914,"Row",9,"898",915,"Row",9,"899",916,"Row",9,"900",917,"Row",9,"901",918,"Row",9,"902",919,"Row",9,"903",920,"Row",9,"904",921,"Row",9,"905",922,"Row",9,"906",923,"Row",9,"907",924,"Row",9,"908",925,"Row",9,"909",926,"Row",9,"910",927,"Row",9,"911",928,"Row",9,"912",929,"Row",9,"913",930,"Row",9,"914",931,"Row",9,"915",932,"Row",9,"916",933,"Row",9,"917",934,"Row",9,"918",935,"Row",9,"919",936,"Row",9,"920",937,"Row",9,"921",938,"Row",9,"922",939,"Row",9,"923",940,"Row",9,"924",941,"Row",9,"925",942,"Row",9,"926",943,"Row",9,"927",944,"Row",9,"928",945,"Row",9,"929",946,"Row",9,"930",947,"Row",9,"931",948,"Row",9,"932",949,"Row",9,"933",950,"Row",9,"934",951,"Row",9,"935",952,"Row",9,"936",953,"Row",9,"937",954,"Row",9,"938",955,"Row",9,"939",956,"Row",9,"940",957,"Row",9,"941",958,"Row",9,"942",959,"Row",9,"943",960,"Row",9,"944",961,"Row",9,"945",962,"Row",9,"946",963,"Row",9,"947",964,"Row",9,"948",965,"Row",9,"949",966,"Row",9,"950",967,"Row",9,"951",968,"Row",9,"952",969,"Row",9,"953",970,"Row",9,"954",971,"Row",9,"955",972,"Row",9,"956",973,"Row",9,"957",974,"Row",9,"958",975,"Row",9,"959",976,"Row",9,"960",977,"Row",9,"961",978,"Row",9,"962",979,"Row",9,"963",980,"Row",9,"964",981,"Row",9,"965",982,"Row",9,"966",983,"Row",9,"967",984,"Row",9,"968",985,"Row",9,"969",986,"Row",9,"970",987,"Row",9,"971",988,"Row",9,"972",989,"Row",9,"973",990,"Row",9,"974",991,"Row",9,"975",992,"Row",9,"976",993,"Row",9,"977",994,"Row",9,"978",995,"Row",9,"979",996,"Row",9,"980",997,"Row",9,"981",998,"Row",9,"982",999,"Row",9,"983",1000,"Row",9,"984",1001,"Row",9,"985",1002,"Row",9,"986",1003,"Row",9,"987",1004,"Row",9,"988",1005,"Row",9,"989",1006,"Row",9,"990",1007,"Row",9,"991",1008,"Row",9,"992",1009,"Row",9,"993",1010,"Row",9,"994",1011,"Row",9,"995",1012,"Row",9,"996",1013,"Row",9,"997",1014,"Row",9,"998",1015,"Row",9,"999",1016,"Row",9,"1000"]; + +function App() { + const idIndentationMap: { + [id: string]: number; + } = {}; + const data: IData[] = []; + let i = 0; + while(i < arr.length) { + const id = arr[i] as string; + i++; + const name = arr[i] as string; + i++; + const parentId = arr[i] as string; + i++; + const userKey = arr[i] as string; + i++; + const indentation = parentId === '' ? 0 : idIndentationMap[parentId] + 1; + idIndentationMap[id] = indentation; + const item = { + id, name, indentation, userKey + }; + data.push(item); + } + return ( +
+ +
+ ); +} + +export default App; diff --git a/libs/extension/src/panel/index.tsx b/libs/extension/src/panel/index.tsx new file mode 100644 index 00000000..9e50ba58 --- /dev/null +++ b/libs/extension/src/panel/index.tsx @@ -0,0 +1,7 @@ +import {render} from 'horizon'; +import App from './App'; + +render( + , + document.getElementById('root') +); \ No newline at end of file diff --git a/libs/extension/src/panel/panel.html b/libs/extension/src/panel/panel.html new file mode 100644 index 00000000..5aac97d6 --- /dev/null +++ b/libs/extension/src/panel/panel.html @@ -0,0 +1,22 @@ + + + + + + + + + +
+ + diff --git a/libs/extension/webpack.dev.js b/libs/extension/webpack.dev.js new file mode 100644 index 00000000..57f8730a --- /dev/null +++ b/libs/extension/webpack.dev.js @@ -0,0 +1,55 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +// 用于 panel 页面开发 + +module.exports = { + mode: 'development', + entry: { + panel: path.join(__dirname, './src/panel/index.tsx'), + }, + output: { + path: path.join(__dirname, 'dist'), + filename: '[name].js' + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'] + }, + module: { + rules: [{ + test: /\.tsx?$/, + exclude: /node_modules/, + use: [ + { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env', + '@babel/preset-typescript', + ['@babel/preset-react', { + runtime: 'classic', + "pragma": "Horizon.createElement", + "pragmaFrag": "Horizon.Fragment", + }]], + } + } + ] + }] + }, + externals: { + 'horizon': 'Horizon', + }, + devServer: { + static: { + directory: path.join(__dirname, 'dist'), + }, + open: 'panel.html', + port: 9000, + magicHtml: true, + }, + plugins: [ + new HtmlWebpackPlugin({ + filename: 'panel.html', + template: './src/panel/panel.html' + }), + ], +}; From dec773daef880378f6a92cf6c1415b0504421aa0 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 22 Mar 2022 19:55:15 +0800 Subject: [PATCH 03/46] Match-id-ccc378b1bded3de0c9f33bad2878073aa2bd86c1 --- libs/extension/readme.md | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 libs/extension/readme.md diff --git a/libs/extension/readme.md b/libs/extension/readme.md new file mode 100644 index 00000000..d7e98884 --- /dev/null +++ b/libs/extension/readme.md @@ -0,0 +1,57 @@ +## 文件清单说明: +devtools_page: devtool主页面 +default_popup: 拓展图标点击时弹窗页面 +content_scripts: 内容脚本,在项目中负责在页面初始化时调用注入全局变量代码和消息传递 +web_accessible_resources: 注入全局变量代码 + +## 打开 panel 页面调试面板的方式 + +1. Open the developer tools. +1. Undock the developer tools if not already done (via the button in the bottom-left corner). +1. Press Ctrl + Shift + J to open the developer tools of the developer tools. +Optional: Feel free to dock the developer tools again if you had undocked it at step 2. +1. Switch from "" to devtoolsBackground.html (or whatever name you have chosen for your devtools). (example) +1. Now you can use the Console tab to play with the chrome.devtools API. + +## 全局变量注入 +通过content_scripts在document初始化时给页面添加script脚本,在新添加的脚本中给window注入全局变量 + +## horizon页面判断 +在页面完成渲染后往全局变量中添加信息,并传递 tabId 给 background 告知这是 horizon 页面 + +## 通信方式: +```mermaid +sequenceDiagram + participant web_page + participant script_content + participant background + participant panel + + Note over web_page: window.postMessage + web_page ->> script_content : {} + Note over script_content: window.addEventListener + Note over script_content: chrome.runtime.sendMessage + script_content ->> background : {} + Note over background: chrome.runtime.onMessage + Note over background: port.postMessage + background ->> panel : {} + Note over panel: connection.onMessage.addListener + Note over panel: connection.postMessage + panel ->> background : {} + Note over background: port.onMessage.addListener + Note over background: chrome.tabs.sendMessage + background ->> script_content : {} + Note over script_content: chrome.runtime.onMessage + Note over script_content: window.postMessage + script_content ->> web_page : {} + Note over web_page: window.addEventListener +``` + +## 数据压缩 +渲染组件树需要知道组件名和层次信息,如果把整个vNode树传递过来,传递对象太大,最好将数据进行压缩然后传递。 +- 相同的组件名可以进行压缩 +- 每个vNode有唯一的 path 属性,可以作为标识使用 +- 通过解析 path 值可以分析出组件树的结构 + +## 滚动动态渲染 Tree +考虑到组件树可能很大,所以并不适合一次性全部渲染出来,可以通过滚动渲染的方式减少页面 dom 的数量。我们可以把树看成有不同缩进长度的列表,动态渲染滚动列表的实现可以参考谷歌的这篇文章:https://developers.google.com/web/updates/2016/07/infinite-scroller 这样,我们需要的组件树数据可以由树结构转变为数组,可以减少动态渲染时对树结构进行解析时的计算工作。 From 5f042d8fa87e901628f2308f9a746c89075185c3 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 23 Mar 2022 11:08:28 +0800 Subject: [PATCH 04/46] Match-id-af5f4471321b0cdce3f5343e1c2f451887b30c17 --- libs/extension/src/components/VTree.less | 10 ++++++ libs/extension/src/components/VTree.tsx | 6 ++-- libs/extension/webpack.dev.js | 39 +++++++++++++----------- 3 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 libs/extension/src/components/VTree.less diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less new file mode 100644 index 00000000..81b716be --- /dev/null +++ b/libs/extension/src/components/VTree.less @@ -0,0 +1,10 @@ +.tree_container { + position: absolute; + width: 100%; + height: 100%; + overflow-y: auto; +} + +.tree_item{ + width: 100%; +} \ No newline at end of file diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 040ea892..59c8ec0c 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -1,4 +1,5 @@ import { useState } from 'horizon'; +import styles from './VTree.less'; export interface IData { id: string; @@ -24,7 +25,7 @@ function Item({ name, style, userKey, hasChild, onExpand, id, indentation }: IIt onExpand(id); } return ( -
+
{showIcon}
{name + key}
@@ -72,7 +73,6 @@ function VTree({ data }: { data: IData[] }) { style={{ position: 'absolute', transform: `translateY(${totalHeight}px)`, - width: '100%' }} onExpand={changeExpandNode} {...item} /> @@ -92,7 +92,7 @@ function VTree({ data }: { data: IData[] }) { } return ( -
+
{showList} {/* 确保有足够的高度 */}
diff --git a/libs/extension/webpack.dev.js b/libs/extension/webpack.dev.js index 57f8730a..0ee87691 100644 --- a/libs/extension/webpack.dev.js +++ b/libs/extension/webpack.dev.js @@ -16,24 +16,29 @@ module.exports = { extensions: ['.ts', '.tsx', '.js'] }, module: { - rules: [{ - test: /\.tsx?$/, - exclude: /node_modules/, - use: [ - { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env', - '@babel/preset-typescript', - ['@babel/preset-react', { - runtime: 'classic', - "pragma": "Horizon.createElement", - "pragmaFrag": "Horizon.Fragment", - }]], + rules: [ + { + test: /\.tsx?$/, + exclude: /node_modules/, + use: [ + { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env', + '@babel/preset-typescript', + ['@babel/preset-react', { + runtime: 'classic', + "pragma": "Horizon.createElement", + "pragmaFrag": "Horizon.Fragment", + }]], + } } - } - ] - }] + ] + }, + { + test: /\.less/i, + use: ["style-loader", { loader: "css-loader", options: { modules: true } }, 'less-loader'], + }] }, externals: { 'horizon': 'Horizon', From de7d629d304377eb6d5af1d39d4f2438bf1ec9b9 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 23 Mar 2022 16:19:33 +0800 Subject: [PATCH 05/46] Match-id-ac2594b0aedd973bc420d51b1106d3bfc294541c --- libs/extension/src/components/VTree.less | 15 +++++++++--- libs/extension/src/components/VTree.tsx | 29 ++++++++++++++---------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index 81b716be..fe24a888 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -1,10 +1,19 @@ +@import 'assets.less'; + .tree_container { - position: absolute; + position: relative; width: 100%; height: 100%; overflow-y: auto; } -.tree_item{ +.tree_item { width: 100%; -} \ No newline at end of file + position: absolute; +} + +.tree_icon { + color: @gray; + display: inline-block; + width: 12px +} diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 59c8ec0c..51e8b828 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -1,5 +1,6 @@ import { useState } from 'horizon'; import styles from './VTree.less'; +import Arrow from '../svgs/Arrow'; export interface IData { id: string; @@ -11,22 +12,24 @@ export interface IData { type IItem = { style: any, hasChild: boolean, - onExpand: (id: string) => void, + onCollapse: (id: string) => void, + isCollapsed: boolean, } & IData // TODO: 计算可以展示的最多数量,并且监听显示器高度变化修改数值 const showNum = 50; const divHeight = 21; +const indentationLength = 20; -function Item({ name, style, userKey, hasChild, onExpand, id, indentation }: IItem) { +function Item({ name, style, userKey, hasChild, onCollapse, isCollapsed, id, indentation }: IItem) { const key = userKey === '' ? '' : ` key = '${userKey}'`; - const showIcon = hasChild ? '△' : ''; - const onClickExpand = () => { - onExpand(id); + const showIcon = hasChild ? : ''; + const onClickCollapse = () => { + onCollapse(id); } return (
-
{showIcon}
+
{showIcon}
{name + key}
) @@ -35,7 +38,7 @@ function Item({ name, style, userKey, hasChild, onExpand, id, indentation }: IIt function VTree({ data }: { data: IData[] }) { const [scrollTop, setScrollTop] = useState(0); const [collapseNode, setCollapseNode] = useState(new Set()); - const changeExpandNode = (id: string) => { + const changeCollapseNode = (id: string) => { const nodes = new Set(); collapseNode.forEach(value => { nodes.add(value); @@ -50,7 +53,7 @@ function VTree({ data }: { data: IData[] }) { const showList: any[] = []; let totalHeight = 0; - let currentCollapseIndentation: null| number = null; + let currentCollapseIndentation: null | number = null; data.forEach((item, index) => { // 存在未处理完的收起节点 if (currentCollapseIndentation !== null) { @@ -63,6 +66,8 @@ function VTree({ data }: { data: IData[] }) { currentCollapseIndentation = null; } } + let id = item.id; + const isCollapsed = collapseNode.has(id); if (totalHeight >= scrollTop && showList.length <= showNum) { const nextItem = data[index + 1]; const hasChild = nextItem ? nextItem.indentation > item.indentation : false; @@ -71,16 +76,15 @@ function VTree({ data }: { data: IData[] }) { key={item.id} hasChild={hasChild} style={{ - position: 'absolute', transform: `translateY(${totalHeight}px)`, }} - onExpand={changeExpandNode} + onCollapse={changeCollapseNode} + isCollapsed={isCollapsed} {...item} /> ) } totalHeight = totalHeight + divHeight; - let id = item.id; - if (collapseNode.has(id)) { + if (isCollapsed) { // 该节点需要收起子节点 currentCollapseIndentation = item.indentation; } @@ -88,6 +92,7 @@ function VTree({ data }: { data: IData[] }) { const scroll = (event: any) => { const scrollTop = event.target.scrollTop; + // 顶部留 100px 冗余高度 setScrollTop(Math.max(scrollTop - 100, 0)); } From a4f376b84b5edb70e937333fe376633822106558 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 24 Mar 2022 16:08:18 +0800 Subject: [PATCH 06/46] Match-id-a2ff0c3642c1fb4b11d6b634a3fba736bf98d405 --- .eslintrc.js | 3 ++- libs/horizon/src/renderer/vnode/VNodeCreator.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 1a425fc3..ab410fd1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,7 +34,8 @@ module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', - 'semi': ["error", "always"], + 'semi': ['warn', 'always'], + 'quotes': ['warn', 'single'], 'accessor-pairs': 'off', 'brace-style': ['error', '1tbs'], 'func-style': ['warn', 'declaration', { allowArrowFunctions: true }], diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index 457a1000..9a677f53 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -56,7 +56,7 @@ export function getLazyVNodeTag(lazyComp: any): string { } else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) { return typeLazyMap[lazyComp.vtype]; } - throw Error("Horizon can't resolve the content of lazy "); + throw Error('Horizon can\'t resolve the content of lazy'); } // 创建processing From 867cb48365e5fd88cbe488d8040b6e178b98e40d Mon Sep 17 00:00:00 2001 From: * <8> Date: Mon, 28 Mar 2022 20:10:21 +0800 Subject: [PATCH 07/46] Match-id-2a7374e4246145afb0681f473ee89cd06e492eb7 --- libs/horizon/src/renderer/ContextSaver.ts | 68 ++---------- .../components/context/CompatibleContext.ts | 101 ------------------ .../src/renderer/render/BaseComponent.ts | 7 -- .../src/renderer/render/ClassComponent.ts | 27 +---- .../src/renderer/render/FunctionComponent.ts | 8 +- .../render/IncompleteClassComponent.ts | 16 +-- .../src/renderer/render/SuspenseComponent.ts | 2 + libs/horizon/src/renderer/render/TreeRoot.ts | 2 - libs/horizon/src/renderer/vnode/VNodeFlags.ts | 1 + 9 files changed, 16 insertions(+), 216 deletions(-) delete mode 100644 libs/horizon/src/renderer/components/context/CompatibleContext.ts diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts index 62586895..ef243cce 100644 --- a/libs/horizon/src/renderer/ContextSaver.ts +++ b/libs/horizon/src/renderer/ContextSaver.ts @@ -3,11 +3,11 @@ * 在深度遍历过程中,begin阶段会修改一些全局的值,在complete阶段会恢复。 */ -import type {VNode, ContextType} from './Types'; -import type {Container} from '../dom/DOMOperator'; +import type { VNode, ContextType } from './Types'; +import type { Container } from '../dom/DOMOperator'; -import {getNSCtx} from '../dom/DOMOperator'; -import {ContextProvider} from './vnode/VNodeTags'; +import { getNSCtx } from '../dom/DOMOperator'; +import { ContextProvider } from './vnode/VNodeTags'; // 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”, // 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM @@ -18,16 +18,8 @@ const CTX_CONTEXT = 'CTX_CONTEXT'; // 旧版context API,是否更改。 const CTX_OLD_CHANGE = 'CTX_OLD_CHANGE'; -// 旧版context API,保存的是的当前组件提供给子组件使用的context。 -const CTX_OLD_CONTEXT = 'CTX_OLD_CONTEXT'; -// 旧版context API,保存的是的上一个提供者提供给后代组件使用的context。 -const CTX_OLD_PREVIOUS_CONTEXT = 'CTX_OLD_PREVIOUS_CONTEXT'; - -let ctxNamespace: string = ''; - -let ctxOldContext: Object = {}; -let ctxOldChange: Boolean = false; -let ctxOldPreviousContext: Object = {}; +let ctxOldChange = false; +let ctxNamespace = ''; function setContext(vNode: VNode, contextName, value) { if (vNode.contexts === null) { @@ -38,6 +30,7 @@ function setContext(vNode: VNode, contextName, value) { vNode.contexts[contextName] = value; } } + function getContext(vNode: VNode, contextName) { if (vNode.contexts !== null) { return vNode.contexts[contextName]; @@ -87,44 +80,6 @@ function recoverParentsContextCtx(vNode: VNode) { } } -// ctxOldContext是 旧context提供者的context -function setVNodeOldContext(providerVNode: VNode, context: Object) { - setContext(providerVNode, CTX_OLD_CONTEXT, context); -} - -function getVNodeOldContext(vNode: VNode) { - return getContext(vNode, CTX_OLD_CONTEXT); -} - -function setOldContextCtx(providerVNode: VNode, context: Object) { - setVNodeOldContext(providerVNode, context); - ctxOldContext = context; -} - -function getOldContextCtx() { - return ctxOldContext; -} - -function resetOldContextCtx(vNode: VNode) { - ctxOldContext = getVNodeOldContext(vNode); -} - -function setVNodeOldPreviousContext(providerVNode: VNode, context: Object) { - setContext(providerVNode, CTX_OLD_PREVIOUS_CONTEXT, context); -} - -function getVNodeOldPreviousContext(vNode: VNode) { - return getContext(vNode, CTX_OLD_PREVIOUS_CONTEXT); -} - -function setOldPreviousContextCtx(context: Object) { - ctxOldPreviousContext = context; -} - -function getOldPreviousContextCtx() { - return ctxOldPreviousContext; -} - function setContextChangeCtx(providerVNode: VNode, didChange: boolean) { setContext(providerVNode, CTX_OLD_CHANGE, didChange); ctxOldChange = didChange; @@ -145,18 +100,9 @@ export { setContextCtx, resetContextCtx, recoverParentsContextCtx, - setOldContextCtx, - getOldContextCtx, - resetOldContextCtx, setContextChangeCtx, getContextChangeCtx, resetContextChangeCtx, - setOldPreviousContextCtx, - getOldPreviousContextCtx, - setVNodeOldContext, - getVNodeOldContext, - setVNodeOldPreviousContext, - getVNodeOldPreviousContext, }; diff --git a/libs/horizon/src/renderer/components/context/CompatibleContext.ts b/libs/horizon/src/renderer/components/context/CompatibleContext.ts deleted file mode 100644 index 0d6a4bd8..00000000 --- a/libs/horizon/src/renderer/components/context/CompatibleContext.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type {VNode} from '../../Types'; - -import { - setOldContextCtx, - setContextChangeCtx, - getOldContextCtx, - resetOldContextCtx, - resetContextChangeCtx, - setOldPreviousContextCtx, - getOldPreviousContextCtx, - setVNodeOldContext, - getVNodeOldContext, - setVNodeOldPreviousContext, - getVNodeOldPreviousContext, -} from '../../ContextSaver'; - -const emptyObject = {}; - -// 判断是否是过时的context的提供者 -export function isOldProvider(comp: Function): boolean { - // @ts-ignore - const childContextTypes = comp.childContextTypes; - return childContextTypes !== null && childContextTypes !== undefined; -} - -// 判断是否是过时的context的消费者 -export function isOldConsumer(comp: Function): boolean { - // @ts-ignore - const contextTypes = comp.contextTypes; - return contextTypes !== null && contextTypes !== undefined; -} - -// 如果是旧版context提供者,则缓存两个全局变量,上一个提供者提供的context和当前提供者提供的context -export function cacheOldCtx(processing: VNode, hasOldContext: any): void { - // 每一个context提供者都会更新ctxOldContext - if (hasOldContext) { - setOldPreviousContextCtx(getOldContextCtx()); - - const vNodeContext = getVNodeOldContext(processing) || emptyObject; - setOldContextCtx(processing, vNodeContext); - } -} - -// 获取当前组件可以消费的context -export function getOldContext(processing: VNode, clazz: Function, ifProvider: boolean) { - const type = processing.type; - // 不是context消费者, 则直接返回空对象 - if (!isOldConsumer(type)) { - return emptyObject; - } - - // 当组件既是提供者,也是消费者时,取上一个context,不能直接取最新context,因为已经被更新为当前组件的context; - // 当组件只是消费者时,则取最新context - const parentContext = (ifProvider && isOldProvider(clazz)) ? - getOldPreviousContextCtx() : - getOldContextCtx(); - - // 除非父级context更改,否则不需要重新创建子context,直接取对应节点上存的。 - if (getVNodeOldPreviousContext(processing) === parentContext) { - return getVNodeOldContext(processing); - } - - // 从父的context中取出子定义的context - const context = {}; - for (const key in type.contextTypes) { - context[key] = parentContext[key]; - } - - // 缓存当前组件的context,最近祖先传递下来context,当前可消费的context - setVNodeOldPreviousContext(processing, parentContext); - setVNodeOldContext(processing, context); - - return context; -} - -// 重置context -export function resetOldCtx(vNode: VNode): void { - resetOldContextCtx(vNode); - resetContextChangeCtx(vNode); -} - -// 当前组件是提供者,则需要合并祖先context和当前组件提供的context -function handleContext(vNode: VNode, parentContext: Object): Object { - const instance = vNode.realNode; - - if (typeof instance.getChildContext !== 'function') { - return parentContext; - } - - // 合并祖先提供的context和当前组件提供的context - return {...parentContext, ...instance.getChildContext()}; -} - -// 当前组件是context提供者,更新时,需要合并祖先context和当前组件提供的context -export function updateOldContext(vNode: VNode): void { - const ctx = handleContext(vNode, getOldPreviousContextCtx()); - // 更新context,给子组件用的context - setOldContextCtx(vNode, ctx); - // 标记更改 - setContextChangeCtx(vNode, true); -} diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts index c94d815d..94e5ecf4 100644 --- a/libs/horizon/src/renderer/render/BaseComponent.ts +++ b/libs/horizon/src/renderer/render/BaseComponent.ts @@ -1,8 +1,6 @@ import type { VNode } from '../Types'; -import {cacheOldCtx, isOldProvider} from '../components/context/CompatibleContext'; import { - ClassComponent, ContextProvider, DomComponent, DomPortal, @@ -23,11 +21,6 @@ function handlerContext(processing: VNode) { case DomComponent: setNamespaceCtx(processing); break; - case ClassComponent: { - const isOldCxtExist = isOldProvider(processing.type); - cacheOldCtx(processing, isOldCxtExist); - break; - } case DomPortal: setNamespaceCtx(processing, processing.realNode); break; diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index 6db4dcf2..002e343d 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -2,13 +2,6 @@ import type { VNode } from '../Types'; import { mergeDefaultProps } from './LazyComponent'; import { getNewContext, resetDepContexts } from '../components/context/Context'; -import { - cacheOldCtx, - getOldContext, - isOldProvider, - resetOldCtx, - updateOldContext, -} from '../components/context/CompatibleContext'; import { callComponentWillMount, callComponentWillReceiveProps, @@ -25,17 +18,18 @@ import { markRef } from './BaseComponent'; import { processUpdates, } from '../UpdateHandler'; -import { getContextChangeCtx, setContextChangeCtx } from '../ContextSaver'; +import { getContextChangeCtx } from '../ContextSaver'; import { setProcessingClassVNode } from '../GlobalVar'; import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import { createChildrenByDiff } from '../diff/nodeDiffComparator'; +const emptyContextObj = {}; // 获取当前节点的context export function getCurrentContext(clazz, processing: VNode) { const context = clazz.contextType; return typeof context === 'object' && context !== null ? getNewContext(processing, context) - : getOldContext(processing, clazz, true); + : emptyContextObj; } // 挂载实例 @@ -112,8 +106,6 @@ export function captureRender(processing: VNode): VNode | null { clazz = clazz._load(clazz._content); } } - const isOldCxtExist = isOldProvider(clazz); - cacheOldCtx(processing, isOldCxtExist); resetDepContexts(processing); @@ -170,24 +162,13 @@ export function captureRender(processing: VNode): VNode | null { // 不复用 if (shouldUpdate) { - // 更新context - if (isOldCxtExist) { - updateOldContext(processing); - } return createChildren(clazz, processing); } else { - if (isOldCxtExist) { - setContextChangeCtx(processing, false); - } return onlyUpdateChildVNodes(processing); } } -export function bubbleRender(processing: VNode) { - if (isOldProvider(processing.type)) { - resetOldCtx(processing); - } -} +export function bubbleRender(processing: VNode) {} // 用于未完成的类组件 export function getIncompleteClassComponent(clazz, processing: VNode, nextProps: object): VNode | null { diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index 0575257a..af634fe6 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -1,7 +1,6 @@ import type {VNode} from '../Types'; import {mergeDefaultProps} from './LazyComponent'; -import {getOldContext} from '../components/context/CompatibleContext'; import {resetDepContexts} from '../components/context/Context'; import {exeFunctionHook} from '../hooks/HookMain'; import {ForwardRef} from '../vnode/VNodeTags'; @@ -54,11 +53,6 @@ export function captureFunctionComponent( nextProps: any, shouldUpdate?: boolean ) { - let context; - if (processing.tag !== ForwardRef) { - context = getOldContext(processing, funcComp, true); - } - resetDepContexts(processing); const isCanReuse = checkIfCanReuseChildren(processing, shouldUpdate); @@ -68,7 +62,7 @@ export function captureFunctionComponent( const newElements = exeFunctionHook( processing.tag === ForwardRef ? funcComp.render : funcComp, nextProps, - processing.tag === ForwardRef ? processing.ref : context, + processing.tag === ForwardRef ? processing.ref : undefined, processing, ); diff --git a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts b/libs/horizon/src/renderer/render/IncompleteClassComponent.ts index 2c50ed18..8e02df51 100644 --- a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts +++ b/libs/horizon/src/renderer/render/IncompleteClassComponent.ts @@ -4,18 +4,10 @@ import {mergeDefaultProps} from './LazyComponent'; import {ClassComponent} from '../vnode/VNodeTags'; import {resetDepContexts} from '../components/context/Context'; import {getIncompleteClassComponent} from './ClassComponent'; -import { - isOldProvider, - resetOldCtx, - cacheOldCtx, -} from '../components/context/CompatibleContext'; function captureIncompleteClassComponent(processing, Component, nextProps) { processing.tag = ClassComponent; - const hasOldContext = isOldProvider(Component); - cacheOldCtx(processing, hasOldContext); - resetDepContexts(processing); return getIncompleteClassComponent(Component, processing, nextProps); @@ -32,10 +24,4 @@ export function captureRender(processing: VNode): VNode | null { return captureIncompleteClassComponent(processing, Component, resolvedProps); } -export function bubbleRender(processing: VNode) { - // 处理与类组件相同。 - const Component = processing.type; - if (isOldProvider(Component)) { - resetOldCtx(processing); - } -} +export function bubbleRender(processing: VNode) {} diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index 3e7bdcb2..28b2c59b 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -92,9 +92,11 @@ export function captureSuspenseComponent(processing: VNode) { if (showFallback) { processing.suspenseDidCapture = false; const nextFallbackChildren = nextProps.fallback; + debugger return createFallback(processing, nextFallbackChildren); } else { const newChildren = nextProps.children; + debugger return createSuspenseChildren(processing, newChildren); } } diff --git a/libs/horizon/src/renderer/render/TreeRoot.ts b/libs/horizon/src/renderer/render/TreeRoot.ts index 33a28402..949dc89b 100644 --- a/libs/horizon/src/renderer/render/TreeRoot.ts +++ b/libs/horizon/src/renderer/render/TreeRoot.ts @@ -2,13 +2,11 @@ import type {VNode} from '../Types'; import {throwIfTrue} from '../utils/throwIfTrue'; import {processUpdates} from '../UpdateHandler'; import {resetNamespaceCtx, setNamespaceCtx} from '../ContextSaver'; -import {resetOldCtx} from '../components/context/CompatibleContext'; import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; import { createChildrenByDiff } from '../diff/nodeDiffComparator'; export function bubbleRender(processing: VNode) { resetNamespaceCtx(processing); - resetOldCtx(processing); } function updateTreeRoot(processing) { diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/horizon/src/renderer/vnode/VNodeFlags.ts index 91f59d35..5b72b354 100644 --- a/libs/horizon/src/renderer/vnode/VNodeFlags.ts +++ b/libs/horizon/src/renderer/vnode/VNodeFlags.ts @@ -41,6 +41,7 @@ export class FlagUtils { node.flags |= Addition; } static setAddition(node: VNode) { + console.log('set addition', node.flags); node.flags = Addition; } From a5f78bf5fb4b64b1cac368825c0f2fd8046a7712 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 10:53:51 +0800 Subject: [PATCH 08/46] Match-id-a9c8af2dec397a83d20a0c78a9d464f4ea450dd6 --- libs/extension/src/components/VTree.less | 17 ++++++++++++---- libs/extension/src/components/VTree.tsx | 25 ++++++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index fe24a888..1890647d 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -1,19 +1,28 @@ @import 'assets.less'; -.tree_container { +.treeContainer { position: relative; width: 100%; height: 100%; overflow-y: auto; } -.tree_item { +.treeItem { width: 100%; position: absolute; + .componentName { + color: @component-name-color; + } + .componentKeyName { + color: @component-key-color; + } + .componentKeyValue { + color: @componentKeyValue-color; + } } -.tree_icon { - color: @gray; +.treeIcon { + color: @arrow-color; display: inline-block; width: 12px } diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 51e8b828..246a1e08 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -23,14 +23,31 @@ const indentationLength = 20; function Item({ name, style, userKey, hasChild, onCollapse, isCollapsed, id, indentation }: IItem) { const key = userKey === '' ? '' : ` key = '${userKey}'`; + const isShowKey = userKey !== ''; const showIcon = hasChild ? : ''; const onClickCollapse = () => { onCollapse(id); } return ( -
-
{showIcon}
- {name + key} +
+
+ {showIcon} +
+ + {name} + + {isShowKey && ( + <> + + {' '}key + + {'="'} + + {userKey} + + {'"'} + + )}
) } @@ -97,7 +114,7 @@ function VTree({ data }: { data: IData[] }) { } return ( -
+
{showList} {/* 确保有足够的高度 */}
From 23212774898c4ebb06f8f2b0dca9090461dfd73c Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 12:02:43 +0800 Subject: [PATCH 09/46] Match-id-c6f3be2ae5ec4f9fe7002b321e94288f8698854f --- libs/extension/src/components/VTree.less | 12 +++++++----- libs/extension/src/components/VTree.tsx | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index 1890647d..8c86cf5b 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -10,6 +10,13 @@ .treeItem { width: 100%; position: absolute; + +.treeIcon { + color: @arrow-color; + display: inline-block; + width: 12px; + padding-left: 0.2rem; +} .componentName { color: @component-name-color; } @@ -21,8 +28,3 @@ } } -.treeIcon { - color: @arrow-color; - display: inline-block; - width: 12px -} diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 246a1e08..3a2c5360 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -22,7 +22,6 @@ const divHeight = 21; const indentationLength = 20; function Item({ name, style, userKey, hasChild, onCollapse, isCollapsed, id, indentation }: IItem) { - const key = userKey === '' ? '' : ` key = '${userKey}'`; const isShowKey = userKey !== ''; const showIcon = hasChild ? : ''; const onClickCollapse = () => { From a8a2b55de3d289e213ea2f8b8c01e3976ee653c1 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 12:03:27 +0800 Subject: [PATCH 10/46] Match-id-5ab3df85931eea4cab53417f188dce92b348df28 --- libs/extension/src/components/VTree.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index 8c86cf5b..bf2fb88c 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -15,7 +15,7 @@ color: @arrow-color; display: inline-block; width: 12px; - padding-left: 0.2rem; + padding-left: 0.5rem; } .componentName { color: @component-name-color; From 0b56fb410484204fd1aa4fdc05c363e5ccc6ebaa Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 15:49:54 +0800 Subject: [PATCH 11/46] Match-id-07aa005631c43e03458d2e692810427c7c1cfb59 --- .../src/renderer/render/FunctionComponent.ts | 29 ++++++++++++------- .../src/renderer/render/SuspenseComponent.ts | 9 +++--- libs/horizon/src/renderer/vnode/VNode.ts | 2 +- libs/horizon/src/renderer/vnode/VNodeFlags.ts | 3 +- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index af634fe6..a0fba76a 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -1,18 +1,19 @@ -import type {VNode} from '../Types'; +import type { VNode } from '../Types'; -import {mergeDefaultProps} from './LazyComponent'; -import {resetDepContexts} from '../components/context/Context'; -import {exeFunctionHook} from '../hooks/HookMain'; -import {ForwardRef} from '../vnode/VNodeTags'; -import {FlagUtils, Update} from '../vnode/VNodeFlags'; -import {getContextChangeCtx} from '../ContextSaver'; -import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; +import { mergeDefaultProps } from './LazyComponent'; +import { resetDepContexts } from '../components/context/Context'; +import { exeFunctionHook } from '../hooks/HookMain'; +import { ForwardRef } from '../vnode/VNodeTags'; +import { FlagUtils, Update } from '../vnode/VNodeFlags'; +import { getContextChangeCtx } from '../ContextSaver'; +import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import { createChildrenByDiff } from '../diff/nodeDiffComparator'; // 在useState, useReducer的时候,会触发state变化 let stateChange = false; -export function bubbleRender() {} +export function bubbleRender() { +} // 判断children是否可以复用 function checkIfCanReuseChildren(processing: VNode, shouldUpdate?: boolean) { @@ -51,8 +52,14 @@ export function captureFunctionComponent( processing: VNode, funcComp: any, nextProps: any, - shouldUpdate?: boolean + shouldUpdate?: boolean, ) { + if (processing.isSuspended) { + processing.isCreated = true; + processing.isSuspended = false; + + FlagUtils.markAddition(processing); + } resetDepContexts(processing); const isCanReuse = checkIfCanReuseChildren(processing, shouldUpdate); @@ -89,7 +96,7 @@ export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode processing, Component, resolvedProps, - shouldUpdate + shouldUpdate, ); } diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index 28b2c59b..0686dc3f 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -4,6 +4,7 @@ import {FlagUtils, Interrupted} from '../vnode/VNodeFlags'; import {onlyUpdateChildVNodes, updateVNode, createFragmentVNode} from '../vnode/VNodeCreator'; import { ClassComponent, + FunctionComponent, IncompleteClassComponent, SuspenseComponent, } from '../vnode/VNodeTags'; @@ -21,7 +22,7 @@ export enum SuspenseChildStatus { // 创建fallback子节点 function createFallback(processing: VNode, fallbackChildren) { - const childFragment: VNode = processing.child; + const childFragment: VNode = processing.child!; let fallbackFragment; childFragment.childShouldUpdate = false; @@ -92,11 +93,9 @@ export function captureSuspenseComponent(processing: VNode) { if (showFallback) { processing.suspenseDidCapture = false; const nextFallbackChildren = nextProps.fallback; - debugger return createFallback(processing, nextFallbackChildren); } else { const newChildren = nextProps.children; - debugger return createSuspenseChildren(processing, newChildren); } } @@ -187,6 +186,9 @@ export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, } } + if(processing.tag === FunctionComponent) { + processing.isSuspended = true; + } // 应该抛出promise未完成更新,标志待更新 processing.shouldUpdate = true; @@ -225,7 +227,6 @@ export function listenToPromise(suspenseVNode: VNode) { // 记录已经监听的 promise let promiseCache = suspenseVNode.realNode; if (promiseCache === null) { - // @ts-ignore promiseCache = new PossiblyWeakSet(); suspenseVNode.realNode = new PossiblyWeakSet(); } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 6386fd98..8c98e673 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -30,7 +30,7 @@ export class VNode { updates: any[] | null; // TreeRoot和ClassComponent使用的更新数组 stateCallbacks: any[] | null; // 存放存在setState的第二个参数和HorizonDOM.render的第三个参数所在的node数组 isForceUpdate: boolean; // 是否使用强制更新 - + isSuspended = false; // 是否被suspense打断更新 state: any; // ClassComponent和TreeRoot的状态 hooks: Array> | null; // 保存hook suspenseChildStatus = ''; // Suspense的Children是否显示 diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/horizon/src/renderer/vnode/VNodeFlags.ts index 5b72b354..bb811c82 100644 --- a/libs/horizon/src/renderer/vnode/VNodeFlags.ts +++ b/libs/horizon/src/renderer/vnode/VNodeFlags.ts @@ -2,7 +2,7 @@ * vNode结构的变化标志 */ -import type { VNode } from '../Types'; +import type { VNode } from './VNode'; export const InitFlag = /** */ 0; @@ -41,7 +41,6 @@ export class FlagUtils { node.flags |= Addition; } static setAddition(node: VNode) { - console.log('set addition', node.flags); node.flags = Addition; } From bc59541e0c692fe037a7b47e0d9c34f8e74b7163 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 16:07:21 +0800 Subject: [PATCH 12/46] Match-id-6e5a892ace868547d76377206bd2654e93dfc333 --- libs/horizon/src/renderer/render/FunctionComponent.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index a0fba76a..8191293b 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -54,11 +54,13 @@ export function captureFunctionComponent( nextProps: any, shouldUpdate?: boolean, ) { + // 函数组件内已完成异步动作 if (processing.isSuspended) { + // 由于首次被打断,应仍为首次渲染 processing.isCreated = true; - processing.isSuspended = false; - FlagUtils.markAddition(processing); + + processing.isSuspended = false; } resetDepContexts(processing); From e78feb8712f4882338aa94a884ef827828b20555 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 17:56:06 +0800 Subject: [PATCH 13/46] Match-id-56a1ac16ba7fc8bbbb7b37d9ff7b62600e952ae7 --- .../src/components/ComponentInfo.tsx | 108 ++++++++++++++++++ .../src/components/ComponentsInfo.less | 91 +++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 libs/extension/src/components/ComponentInfo.tsx create mode 100644 libs/extension/src/components/ComponentsInfo.less diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx new file mode 100644 index 00000000..7e13bb42 --- /dev/null +++ b/libs/extension/src/components/ComponentInfo.tsx @@ -0,0 +1,108 @@ +import styles from './ComponentsInfo.less'; +import Eye from '../svgs/Eye'; +import Debug from '../svgs/Debug'; +import Copy from '../svgs/Copy'; +import Arrow from '../svgs/Arrow'; +import { useState } from 'horizon'; + +type IComponentInfo = { + name: string; + attrs: { + props?: IAttr[]; + context?: IAttr[]; + state?: IAttr[]; + hooks?: IAttr[]; + } +}; + +type IAttr = { + name: string; + type: string; + value: string | boolean; + indentation: number; +} + +function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { + const [collapsedNode, setCollapsedNode] = useState(new Set()); + const handleCollapse = (index: number) => { + const newSet = new Set(); + collapsedNode.forEach(value => { + newSet.add(value); + }); + if (newSet.has(index)) { + newSet.delete(index); + } else { + newSet.add(index); + } + setCollapsedNode(newSet); + } + + const showAttr = []; + let currentIndentation = null; + attr.forEach((item, index) => { + const indentation = item.indentation; + if (currentIndentation !== null) { + if (indentation > currentIndentation) { + return; + } else { + currentIndentation = null; + } + } + const nextItem = attr[index + 1]; + const hasChild = nextItem ? nextItem.indentation - item.indentation > 0 : false; + const isCollapsed = collapsedNode.has(index); + showAttr.push( +
(handleCollapse(index))}> + {hasChild && } + {`${item.name}`} + {' :'} + {item.value} +
+ ); + if (isCollapsed) { + currentIndentation = indentation; + } + }); + + return ( +
+
+ {name} + + + +
+
+ {showAttr} +
+
+ ) +} + +export default function ComponentInfo({ name, attrs }: IComponentInfo) { + const { state, props, context, hooks } = attrs; + return ( +
+
+ + {name} + + + + + + + +
+
+ {context && } + {props && } + {state && } + {hooks && } +
+ rendered by +
+
+
+ ) +} \ No newline at end of file diff --git a/libs/extension/src/components/ComponentsInfo.less b/libs/extension/src/components/ComponentsInfo.less new file mode 100644 index 00000000..173bec22 --- /dev/null +++ b/libs/extension/src/components/ComponentsInfo.less @@ -0,0 +1,91 @@ +@import 'assets.less'; + +.infoContainer { + display: flex; + flex-direction: column; + height: 100%; + + + .componentInfoHead { + flex: 0 0 @top-height; + display: flex; + align-items: center; + border-bottom: @divider-style; + + .name { + flex: 1 1 0; + padding: 0 1rem 0 1rem; + } + + .eye { + flex: 0 0 1rem; + padding-right: 1rem; + } + + .debug { + flex: 0 0 1rem; + padding-right: 1rem; + } + } + + + .componentInfoMain { + overflow-y: auto; + + :last-child { + border-bottom: unset; + } + + :first-child { + padding: unset; + } + + >div { + border-bottom: @divider-style; + padding: 0.5rem + } + + .attrContainer { + flex: 0 0; + + .attrHead { + display: flex; + flex-direction: row; + align-items: center; + padding: 0.5rem 0.5rem 0 0.5rem; + + .attrType { + flex: 1 1 0; + } + + + .attrCopy { + flex: 0 0 1rem; + padding-right: 1rem; + } + } + + + .attrDetail { + padding-bottom: 0.5rem; + .attrArrow { + color: @arrow-color; + width: 12px; + display: inline-block; + } + + .attrName { + color: @attr-name-color; + } + + .attrValue { + margin-left: 4px; + } + } + } + + .renderInfo { + flex: 1 1 0; + } + } +} From 4afe0f75cd78e8f668f79df978a0acc4281024d6 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 17:58:24 +0800 Subject: [PATCH 14/46] Match-id-71142987658f65616d22affbe9fbf5d524112007 --- libs/extension/src/parser/parseAttr.ts | 76 ++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 libs/extension/src/parser/parseAttr.ts diff --git a/libs/extension/src/parser/parseAttr.ts b/libs/extension/src/parser/parseAttr.ts new file mode 100644 index 00000000..0c5335d1 --- /dev/null +++ b/libs/extension/src/parser/parseAttr.ts @@ -0,0 +1,76 @@ +export function parseAttr(rootAttr: any) { + const result = []; + let indentation = 0; + const parseSubAttr = (attr: any, parentIndentation: number, attrName: string) => { + const stateType = typeof attr; + let value: any; + let showType; + let addSubState; + if (stateType === 'boolean' || + stateType === 'number' || + stateType === 'string' || + stateType === 'undefined') { + value = attr; + showType = stateType; + } else if (stateType === 'function') { + const funName = attr.name; + value = `f() ${funName}{}`; + } else if (stateType === 'symbol') { + value = attr.description; + } else if (stateType === 'object') { + if (attr === null) { + showType = 'null'; + }else if (attr instanceof Map) { + showType = 'map'; + const size = attr.size; + value = `Map(${size})`; + addSubState = () => { + attr.forEach((value, key) => { + parseSubAttr(value, parentIndentation + 2, key); + }); + } + } else if (attr instanceof Set) { + showType = 'set'; + const size = attr.size; + value = `Set(${size})`; + addSubState = () => { + let i = 0; + attr.forEach((value) => { + parseSubAttr(value, parentIndentation + 2, String(i)); + }); + i++; + }; + } else if (Array.isArray(attr)) { + showType = 'array'; + value = `Array(${attr.length})`; + addSubState = () => { + attr.forEach((value, index) => { + parseSubAttr(value, parentIndentation + 2, String(index)); + }) + } + } else { + showType = stateType; + value = '{...}'; + addSubState = () => { + Object.keys(attr).forEach((key) => { + parseSubAttr(attr[key], parentIndentation + 2, key); + }); + }; + } + } + + result.push({ + name: attrName, + type: showType, + value, + indentation: parentIndentation + 1, + }); + if (addSubState) { + addSubState(); + } + }; + Object.keys(rootAttr).forEach(key => { + parseSubAttr(rootAttr[key], indentation, key); + }); + return result; +} From f8fb9c5aef227aab13374ffba39f123b4cf82a68 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 17:58:41 +0800 Subject: [PATCH 15/46] Match-id-14bd1b592a5b4fba4420cfbf2a18d92b33b5c237 --- libs/extension/src/parser/parseVNode.ts | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 libs/extension/src/parser/parseVNode.ts diff --git a/libs/extension/src/parser/parseVNode.ts b/libs/extension/src/parser/parseVNode.ts new file mode 100644 index 00000000..bb8ad91d --- /dev/null +++ b/libs/extension/src/parser/parseVNode.ts @@ -0,0 +1,59 @@ +import { travelVNodeTree } from "../../../../libs/horizon/src/renderer/vnode/VNodeUtils"; +import { VNode } from "../../../../libs/horizon/src/renderer/Types"; +import { ClassComponent, FunctionComponent } from "../../../../libs/horizon/src/renderer/vnode/VNodeTags"; + +const VNodeToIdMap = new Map(); +const IdToVNodeMap = new Map(); + +let uid = 0; +function generateUid () { + uid++; + return uid; +} + +function isUserComponent(tag: string) { + // TODO: 添加其他组件 + return tag === ClassComponent || tag === FunctionComponent; +} + +function getParentUserComponent(node: VNode) { + let parent = node.parent; + while(parent) { + if (isUserComponent(parent.tag)) { + break; + } + parent = parent.parent; + } + return parent; +} + +function parseTreeRoot(treeRoot: VNode) { + const result: any[] = []; + travelVNodeTree(treeRoot, (node: VNode) => { + const tag = node.tag; + if (isUserComponent(tag)) { + const id = generateUid(); + result.push(id); + const name = (node.type as Function).name; + result.push(name); + const parent = getParentUserComponent(node); + if (parent) { + const parentId = VNodeToIdMap.get(parent); + result.push(parentId); + } else { + result.push(''); + } + const key = node.key; + if (key !== null) { + result.push(key); + } else { + result.push(''); + } + VNodeToIdMap.set(node, id); + IdToVNodeMap.set(id, node); + } + }, null, treeRoot, null); + return result; +} + +export default parseTreeRoot; From ecc99bf97a78210932f28a77a599f68277f8b83b Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 17:59:13 +0800 Subject: [PATCH 16/46] Match-id-8594911a1d814fe13ec8b977d4c31972e295f3cf --- libs/extension/src/components/Search.less | 3 +++ libs/extension/src/components/Search.tsx | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 libs/extension/src/components/Search.less create mode 100644 libs/extension/src/components/Search.tsx diff --git a/libs/extension/src/components/Search.less b/libs/extension/src/components/Search.less new file mode 100644 index 00000000..617a9d26 --- /dev/null +++ b/libs/extension/src/components/Search.less @@ -0,0 +1,3 @@ +.search { + width: 100%; +} \ No newline at end of file diff --git a/libs/extension/src/components/Search.tsx b/libs/extension/src/components/Search.tsx new file mode 100644 index 00000000..0686a79b --- /dev/null +++ b/libs/extension/src/components/Search.tsx @@ -0,0 +1,7 @@ +import styles from './Search.less'; + +export default function Search() { + return ( + + ) +} \ No newline at end of file From 3aeaf4969309dc9a99dfff8f3ce017d5bec90539 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 17:59:47 +0800 Subject: [PATCH 17/46] Match-id-7d9916e11a4b9ee889fdb62be87fe24b4d0e5f1c --- libs/extension/src/svgs/Arrow.tsx | 17 +++++++++++++++++ libs/extension/src/svgs/Copy.tsx | 8 ++++++++ libs/extension/src/svgs/Debug.tsx | 8 ++++++++ libs/extension/src/svgs/Eye.tsx | 10 ++++++++++ libs/extension/src/svgs/Select.tsx | 9 +++++++++ 5 files changed, 52 insertions(+) create mode 100644 libs/extension/src/svgs/Arrow.tsx create mode 100644 libs/extension/src/svgs/Copy.tsx create mode 100644 libs/extension/src/svgs/Debug.tsx create mode 100644 libs/extension/src/svgs/Eye.tsx create mode 100644 libs/extension/src/svgs/Select.tsx diff --git a/libs/extension/src/svgs/Arrow.tsx b/libs/extension/src/svgs/Arrow.tsx new file mode 100644 index 00000000..c315bf19 --- /dev/null +++ b/libs/extension/src/svgs/Arrow.tsx @@ -0,0 +1,17 @@ +interface IArrow { + director: 'right' | 'down' +} + +export default function Arrow({director}: IArrow) { + let d: string; + if (director === 'right') { + d = 'm2 0l12 8l-12 8 z' + } else if (director === 'down') { + d = 'm0 2h16 l-8 12 z'; + } + return ( + + + + ) +} \ No newline at end of file diff --git a/libs/extension/src/svgs/Copy.tsx b/libs/extension/src/svgs/Copy.tsx new file mode 100644 index 00000000..865222fe --- /dev/null +++ b/libs/extension/src/svgs/Copy.tsx @@ -0,0 +1,8 @@ + +export default function Copy() { + return ( + + + + ) +} diff --git a/libs/extension/src/svgs/Debug.tsx b/libs/extension/src/svgs/Debug.tsx new file mode 100644 index 00000000..6ac25393 --- /dev/null +++ b/libs/extension/src/svgs/Debug.tsx @@ -0,0 +1,8 @@ + +export default function Debug() { + return ( + + + + ) +} diff --git a/libs/extension/src/svgs/Eye.tsx b/libs/extension/src/svgs/Eye.tsx new file mode 100644 index 00000000..f17f1fd2 --- /dev/null +++ b/libs/extension/src/svgs/Eye.tsx @@ -0,0 +1,10 @@ + +export default function Eye() { + return ( + + + + + + ) +} diff --git a/libs/extension/src/svgs/Select.tsx b/libs/extension/src/svgs/Select.tsx new file mode 100644 index 00000000..08aef9bb --- /dev/null +++ b/libs/extension/src/svgs/Select.tsx @@ -0,0 +1,9 @@ + + +export default function Select() { + return ( + + + + ) +} \ No newline at end of file From 713c9d2bb80899fa8d1c679f32dc4091a37d0d58 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 18:00:03 +0800 Subject: [PATCH 18/46] Match-id-bb93b31ea0be8b9131ced01cdca40dfd0ec15dcf --- libs/extension/src/devtools/mock.ts | 87 +++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 libs/extension/src/devtools/mock.ts diff --git a/libs/extension/src/devtools/mock.ts b/libs/extension/src/devtools/mock.ts new file mode 100644 index 00000000..0c40d06b --- /dev/null +++ b/libs/extension/src/devtools/mock.ts @@ -0,0 +1,87 @@ + +import { parseAttr } from '../parser/parseAttr'; +import parseTreeRoot from '../parser/parseVNode'; +import { VNode } from './../../../horizon/src/renderer/vnode/VNode'; +import { FunctionComponent, ClassComponent } from './../../../horizon/src/renderer/vnode/VNodeTags'; + +const mockComponentNames = ['Apple', 'Pear', 'Banana', 'Orange', 'Jenny', 'Kiwi', 'Coconut']; + +function MockVNode(tag: string, props = {}, key = null, realNode = {}) { + const vNode = new VNode(tag, props, key, realNode); + const name = mockComponentNames.shift() || 'MockComponent'; + vNode.type = {name}; + return vNode; +} + +interface IMockTree { + tag: string, + children?: IMockTree[], +} + +const tree: IMockTree = { + tag: ClassComponent, + children: [ + {tag: FunctionComponent}, + {tag: ClassComponent}, + {tag: FunctionComponent}, + { + tag: FunctionComponent, + children: [ + {tag: ClassComponent} + ] + } + ] +} + +function addOneThousandNode(node: IMockTree) { + const nodes = []; + for(let i = 0; i < 1000; i++) { + nodes.push({tag: FunctionComponent}); + } + node?.children.push({tag: ClassComponent,children: nodes}); +}; + +addOneThousandNode(tree); + +function getMockVNodeTree(tree: IMockTree, vNode: VNode) { + const children = tree.children; + if (children && children.length !== 0) { + const childNode = children[0]; + let childVNode = MockVNode(childNode.tag); + childVNode.key = '0'; + getMockVNodeTree(childNode, childVNode); + vNode.child = childVNode; + childVNode.parent = vNode; + for(let i = 1; i < children.length; i++) { + const nextNode = children[i]; + const nextVNode = MockVNode(nextNode.tag); + nextVNode.key = String(i); + nextVNode.parent = vNode; + getMockVNodeTree(nextNode, nextVNode); + childVNode.next = nextVNode; + childVNode = nextVNode; + } + } +} +const rootVNode = MockVNode(tree.tag); +getMockVNodeTree(tree, rootVNode); + +export const mockParsedVNodeData = parseTreeRoot(rootVNode); + + +const mockState = { + str: 'jenny', + num: 3, + boolean: true, + und: undefined, + fun: () => {}, + symbol: Symbol('sym'), + map: new Map([['a', 'a']]), + set: new Set(['a', 1, 2, Symbol('bambi')]), + arr: [1,2,3,4], + obj: { + niko: {jenny: 'jenny'} + } +}; + +export const parsedMockState = parseAttr(mockState); \ No newline at end of file From 9ddccc81fa3b459fcbb0ee6a4d53791ad8279f2b Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 18:00:31 +0800 Subject: [PATCH 19/46] Match-id-a14c72ab119784f339027ab315a7f4d9b801fb80 --- libs/extension/src/components/assets.less | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 libs/extension/src/components/assets.less diff --git a/libs/extension/src/components/assets.less b/libs/extension/src/components/assets.less new file mode 100644 index 00000000..d649d367 --- /dev/null +++ b/libs/extension/src/components/assets.less @@ -0,0 +1,13 @@ +@arrow-color:rgb(95, 99, 104); +@divider-color:rgb(202, 205, 209); +@attr-name-color: rgb(200, 0, 0); +@component-name-color: rgb(136, 18, 128); +@component-key-color: rgb(153, 69, 0); +@componentKeyValue-color: rgb(26, 26, 166); +@component-attr-color: rgb(200, 0, 0); + +@top-height: 2.625rem; +@divider-width: 0.2px; +@common-font-size: 12px; + +@divider-style: @divider-color solid @divider-width; \ No newline at end of file From e6a83346124487d6315679720bc3c2f6706d85e0 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 18:01:12 +0800 Subject: [PATCH 20/46] Match-id-56773f8ccb7db62e98eae918795260754182b697 --- libs/extension/webpack.dev.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libs/extension/webpack.dev.js b/libs/extension/webpack.dev.js index 0ee87691..37cbf13c 100644 --- a/libs/extension/webpack.dev.js +++ b/libs/extension/webpack.dev.js @@ -1,5 +1,6 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const webpack = require('webpack'); // 用于 panel 页面开发 @@ -12,6 +13,7 @@ module.exports = { path: path.join(__dirname, 'dist'), filename: '[name].js' }, + devtool: 'source-map', resolve: { extensions: ['.ts', '.tsx', '.js'] }, @@ -31,13 +33,23 @@ module.exports = { "pragma": "Horizon.createElement", "pragmaFrag": "Horizon.Fragment", }]], + plugins: ['@babel/plugin-proposal-class-properties'], } } ] }, { test: /\.less/i, - use: ["style-loader", { loader: "css-loader", options: { modules: true } }, 'less-loader'], + use: [ + "style-loader", + { + loader: "css-loader", + options: { + modules: true, + + } + }, + 'less-loader'], }] }, externals: { @@ -56,5 +68,9 @@ module.exports = { filename: 'panel.html', template: './src/panel/panel.html' }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': '"development"', + isDev: 'true', + }), ], }; From cb3a9a069220556d3448d9b53eb1aaed2e7c2ab4 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 18:01:36 +0800 Subject: [PATCH 21/46] Match-id-a0dd71dd3bbb10c710c5f95972e5dde0948b11f2 --- libs/extension/src/panel/panel.html | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/libs/extension/src/panel/panel.html b/libs/extension/src/panel/panel.html index 5aac97d6..70f60f3b 100644 --- a/libs/extension/src/panel/panel.html +++ b/libs/extension/src/panel/panel.html @@ -5,13 +5,18 @@ From 1cef14404241ff668974489de4349b4ef19a6e7d Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 29 Mar 2022 18:02:13 +0800 Subject: [PATCH 22/46] Match-id-87c94fc4d1a59f0d468ba391f244eb3e6783516b --- libs/extension/src/panel/App.less | 48 ++++++++++++++++++++++++++++ libs/extension/src/panel/App.tsx | 53 +++++++++++++++++++++++++------ 2 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 libs/extension/src/panel/App.less diff --git a/libs/extension/src/panel/App.less b/libs/extension/src/panel/App.less new file mode 100644 index 00000000..0186eec1 --- /dev/null +++ b/libs/extension/src/panel/App.less @@ -0,0 +1,48 @@ +@import '../components/assets.less'; + +.app{ + display: flex; + flex-direction: row; + height: 100%; + font-size: @common-font-size; +} + +.left{ + flex: 7; + display: flex; + flex-direction: column; + .left_top { + border-bottom: @divider-style; + flex: 0 0 @top-height; + display: flex; + align-items: center; + .select { + padding: 0 0.25rem 0 0.25rem; + flex: 0 0; + } + .divider { + flex: 0 0 1px; + margin: 0 0.25rem 0 0.25rem; + border-left: @divider-style; + height: calc(100% - 1rem); + } + .search { + flex: 1 1 0; + } + } + .left_bottom { + flex: 1; + height: 0; + } +} + +.right{ + flex: 3; + border-left: @divider-style; +} + +input{ + outline: none; + border-width: 0; + padding: 0; +} \ No newline at end of file diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index 2c9014c5..73b409ac 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -1,22 +1,39 @@ +import {useState, useEffect} from 'horizon'; import VTree, { IData } from '../components/VTree'; - -// 临时开发用数据 -const arr = [9,"Main","","",10,"Jumbotron",9,"",11,"Button",10,"",12,"Button",10,"",13,"Button",10,"",14,"Button",10,"",15,"Button",10,"",16,"Button",10,"",17,"Row",9,"1",18,"Row",9,"2",19,"Row",9,"3",20,"Row",9,"4",21,"Row",9,"5",22,"Row",9,"6",23,"Row",9,"7",24,"Row",9,"8",25,"Row",9,"9",26,"Row",9,"10",27,"Row",9,"11",28,"Row",9,"12",29,"Row",9,"13",30,"Row",9,"14",31,"Row",9,"15",32,"Row",9,"16",33,"Row",9,"17",34,"Row",9,"18",35,"Row",9,"19",36,"Row",9,"20",37,"Row",9,"21",38,"Row",9,"22",39,"Row",9,"23",40,"Row",9,"24",41,"Row",9,"25",42,"Row",9,"26",43,"Row",9,"27",44,"Row",9,"28",45,"Row",9,"29",46,"Row",9,"30",47,"Row",9,"31",48,"Row",9,"32",49,"Row",9,"33",50,"Row",9,"34",51,"Row",9,"35",52,"Row",9,"36",53,"Row",9,"37",54,"Row",9,"38",55,"Row",9,"39",56,"Row",9,"40",57,"Row",9,"41",58,"Row",9,"42",59,"Row",9,"43",60,"Row",9,"44",61,"Row",9,"45",62,"Row",9,"46",63,"Row",9,"47",64,"Row",9,"48",65,"Row",9,"49",66,"Row",9,"50",67,"Row",9,"51",68,"Row",9,"52",69,"Row",9,"53",70,"Row",9,"54",71,"Row",9,"55",72,"Row",9,"56",73,"Row",9,"57",74,"Row",9,"58",75,"Row",9,"59",76,"Row",9,"60",77,"Row",9,"61",78,"Row",9,"62",79,"Row",9,"63",80,"Row",9,"64",81,"Row",9,"65",82,"Row",9,"66",83,"Row",9,"67",84,"Row",9,"68",85,"Row",9,"69",86,"Row",9,"70",87,"Row",9,"71",88,"Row",9,"72",89,"Row",9,"73",90,"Row",9,"74",91,"Row",9,"75",92,"Row",9,"76",93,"Row",9,"77",94,"Row",9,"78",95,"Row",9,"79",96,"Row",9,"80",97,"Row",9,"81",98,"Row",9,"82",99,"Row",9,"83",100,"Row",9,"84",101,"Row",9,"85",102,"Row",9,"86",103,"Row",9,"87",104,"Row",9,"88",105,"Row",9,"89",106,"Row",9,"90",107,"Row",9,"91",108,"Row",9,"92",109,"Row",9,"93",110,"Row",9,"94",111,"Row",9,"95",112,"Row",9,"96",113,"Row",9,"97",114,"Row",9,"98",115,"Row",9,"99",116,"Row",9,"100",117,"Row",9,"101",118,"Row",9,"102",119,"Row",9,"103",120,"Row",9,"104",121,"Row",9,"105",122,"Row",9,"106",123,"Row",9,"107",124,"Row",9,"108",125,"Row",9,"109",126,"Row",9,"110",127,"Row",9,"111",128,"Row",9,"112",129,"Row",9,"113",130,"Row",9,"114",131,"Row",9,"115",132,"Row",9,"116",133,"Row",9,"117",134,"Row",9,"118",135,"Row",9,"119",136,"Row",9,"120",137,"Row",9,"121",138,"Row",9,"122",139,"Row",9,"123",140,"Row",9,"124",141,"Row",9,"125",142,"Row",9,"126",143,"Row",9,"127",144,"Row",9,"128",145,"Row",9,"129",146,"Row",9,"130",147,"Row",9,"131",148,"Row",9,"132",149,"Row",9,"133",150,"Row",9,"134",151,"Row",9,"135",152,"Row",9,"136",153,"Row",9,"137",154,"Row",9,"138",155,"Row",9,"139",156,"Row",9,"140",157,"Row",9,"141",158,"Row",9,"142",159,"Row",9,"143",160,"Row",9,"144",161,"Row",9,"145",162,"Row",9,"146",163,"Row",9,"147",164,"Row",9,"148",165,"Row",9,"149",166,"Row",9,"150",167,"Row",9,"151",168,"Row",9,"152",169,"Row",9,"153",170,"Row",9,"154",171,"Row",9,"155",172,"Row",9,"156",173,"Row",9,"157",174,"Row",9,"158",175,"Row",9,"159",176,"Row",9,"160",177,"Row",9,"161",178,"Row",9,"162",179,"Row",9,"163",180,"Row",9,"164",181,"Row",9,"165",182,"Row",9,"166",183,"Row",9,"167",184,"Row",9,"168",185,"Row",9,"169",186,"Row",9,"170",187,"Row",9,"171",188,"Row",9,"172",189,"Row",9,"173",190,"Row",9,"174",191,"Row",9,"175",192,"Row",9,"176",193,"Row",9,"177",194,"Row",9,"178",195,"Row",9,"179",196,"Row",9,"180",197,"Row",9,"181",198,"Row",9,"182",199,"Row",9,"183",200,"Row",9,"184",201,"Row",9,"185",202,"Row",9,"186",203,"Row",9,"187",204,"Row",9,"188",205,"Row",9,"189",206,"Row",9,"190",207,"Row",9,"191",208,"Row",9,"192",209,"Row",9,"193",210,"Row",9,"194",211,"Row",9,"195",212,"Row",9,"196",213,"Row",9,"197",214,"Row",9,"198",215,"Row",9,"199",216,"Row",9,"200",217,"Row",9,"201",218,"Row",9,"202",219,"Row",9,"203",220,"Row",9,"204",221,"Row",9,"205",222,"Row",9,"206",223,"Row",9,"207",224,"Row",9,"208",225,"Row",9,"209",226,"Row",9,"210",227,"Row",9,"211",228,"Row",9,"212",229,"Row",9,"213",230,"Row",9,"214",231,"Row",9,"215",232,"Row",9,"216",233,"Row",9,"217",234,"Row",9,"218",235,"Row",9,"219",236,"Row",9,"220",237,"Row",9,"221",238,"Row",9,"222",239,"Row",9,"223",240,"Row",9,"224",241,"Row",9,"225",242,"Row",9,"226",243,"Row",9,"227",244,"Row",9,"228",245,"Row",9,"229",246,"Row",9,"230",247,"Row",9,"231",248,"Row",9,"232",249,"Row",9,"233",250,"Row",9,"234",251,"Row",9,"235",252,"Row",9,"236",253,"Row",9,"237",254,"Row",9,"238",255,"Row",9,"239",256,"Row",9,"240",257,"Row",9,"241",258,"Row",9,"242",259,"Row",9,"243",260,"Row",9,"244",261,"Row",9,"245",262,"Row",9,"246",263,"Row",9,"247",264,"Row",9,"248",265,"Row",9,"249",266,"Row",9,"250",267,"Row",9,"251",268,"Row",9,"252",269,"Row",9,"253",270,"Row",9,"254",271,"Row",9,"255",272,"Row",9,"256",273,"Row",9,"257",274,"Row",9,"258",275,"Row",9,"259",276,"Row",9,"260",277,"Row",9,"261",278,"Row",9,"262",279,"Row",9,"263",280,"Row",9,"264",281,"Row",9,"265",282,"Row",9,"266",283,"Row",9,"267",284,"Row",9,"268",285,"Row",9,"269",286,"Row",9,"270",287,"Row",9,"271",288,"Row",9,"272",289,"Row",9,"273",290,"Row",9,"274",291,"Row",9,"275",292,"Row",9,"276",293,"Row",9,"277",294,"Row",9,"278",295,"Row",9,"279",296,"Row",9,"280",297,"Row",9,"281",298,"Row",9,"282",299,"Row",9,"283",300,"Row",9,"284",301,"Row",9,"285",302,"Row",9,"286",303,"Row",9,"287",304,"Row",9,"288",305,"Row",9,"289",306,"Row",9,"290",307,"Row",9,"291",308,"Row",9,"292",309,"Row",9,"293",310,"Row",9,"294",311,"Row",9,"295",312,"Row",9,"296",313,"Row",9,"297",314,"Row",9,"298",315,"Row",9,"299",316,"Row",9,"300",317,"Row",9,"301",318,"Row",9,"302",319,"Row",9,"303",320,"Row",9,"304",321,"Row",9,"305",322,"Row",9,"306",323,"Row",9,"307",324,"Row",9,"308",325,"Row",9,"309",326,"Row",9,"310",327,"Row",9,"311",328,"Row",9,"312",329,"Row",9,"313",330,"Row",9,"314",331,"Row",9,"315",332,"Row",9,"316",333,"Row",9,"317",334,"Row",9,"318",335,"Row",9,"319",336,"Row",9,"320",337,"Row",9,"321",338,"Row",9,"322",339,"Row",9,"323",340,"Row",9,"324",341,"Row",9,"325",342,"Row",9,"326",343,"Row",9,"327",344,"Row",9,"328",345,"Row",9,"329",346,"Row",9,"330",347,"Row",9,"331",348,"Row",9,"332",349,"Row",9,"333",350,"Row",9,"334",351,"Row",9,"335",352,"Row",9,"336",353,"Row",9,"337",354,"Row",9,"338",355,"Row",9,"339",356,"Row",9,"340",357,"Row",9,"341",358,"Row",9,"342",359,"Row",9,"343",360,"Row",9,"344",361,"Row",9,"345",362,"Row",9,"346",363,"Row",9,"347",364,"Row",9,"348",365,"Row",9,"349",366,"Row",9,"350",367,"Row",9,"351",368,"Row",9,"352",369,"Row",9,"353",370,"Row",9,"354",371,"Row",9,"355",372,"Row",9,"356",373,"Row",9,"357",374,"Row",9,"358",375,"Row",9,"359",376,"Row",9,"360",377,"Row",9,"361",378,"Row",9,"362",379,"Row",9,"363",380,"Row",9,"364",381,"Row",9,"365",382,"Row",9,"366",383,"Row",9,"367",384,"Row",9,"368",385,"Row",9,"369",386,"Row",9,"370",387,"Row",9,"371",388,"Row",9,"372",389,"Row",9,"373",390,"Row",9,"374",391,"Row",9,"375",392,"Row",9,"376",393,"Row",9,"377",394,"Row",9,"378",395,"Row",9,"379",396,"Row",9,"380",397,"Row",9,"381",398,"Row",9,"382",399,"Row",9,"383",400,"Row",9,"384",401,"Row",9,"385",402,"Row",9,"386",403,"Row",9,"387",404,"Row",9,"388",405,"Row",9,"389",406,"Row",9,"390",407,"Row",9,"391",408,"Row",9,"392",409,"Row",9,"393",410,"Row",9,"394",411,"Row",9,"395",412,"Row",9,"396",413,"Row",9,"397",414,"Row",9,"398",415,"Row",9,"399",416,"Row",9,"400",417,"Row",9,"401",418,"Row",9,"402",419,"Row",9,"403",420,"Row",9,"404",421,"Row",9,"405",422,"Row",9,"406",423,"Row",9,"407",424,"Row",9,"408",425,"Row",9,"409",426,"Row",9,"410",427,"Row",9,"411",428,"Row",9,"412",429,"Row",9,"413",430,"Row",9,"414",431,"Row",9,"415",432,"Row",9,"416",433,"Row",9,"417",434,"Row",9,"418",435,"Row",9,"419",436,"Row",9,"420",437,"Row",9,"421",438,"Row",9,"422",439,"Row",9,"423",440,"Row",9,"424",441,"Row",9,"425",442,"Row",9,"426",443,"Row",9,"427",444,"Row",9,"428",445,"Row",9,"429",446,"Row",9,"430",447,"Row",9,"431",448,"Row",9,"432",449,"Row",9,"433",450,"Row",9,"434",451,"Row",9,"435",452,"Row",9,"436",453,"Row",9,"437",454,"Row",9,"438",455,"Row",9,"439",456,"Row",9,"440",457,"Row",9,"441",458,"Row",9,"442",459,"Row",9,"443",460,"Row",9,"444",461,"Row",9,"445",462,"Row",9,"446",463,"Row",9,"447",464,"Row",9,"448",465,"Row",9,"449",466,"Row",9,"450",467,"Row",9,"451",468,"Row",9,"452",469,"Row",9,"453",470,"Row",9,"454",471,"Row",9,"455",472,"Row",9,"456",473,"Row",9,"457",474,"Row",9,"458",475,"Row",9,"459",476,"Row",9,"460",477,"Row",9,"461",478,"Row",9,"462",479,"Row",9,"463",480,"Row",9,"464",481,"Row",9,"465",482,"Row",9,"466",483,"Row",9,"467",484,"Row",9,"468",485,"Row",9,"469",486,"Row",9,"470",487,"Row",9,"471",488,"Row",9,"472",489,"Row",9,"473",490,"Row",9,"474",491,"Row",9,"475",492,"Row",9,"476",493,"Row",9,"477",494,"Row",9,"478",495,"Row",9,"479",496,"Row",9,"480",497,"Row",9,"481",498,"Row",9,"482",499,"Row",9,"483",500,"Row",9,"484",501,"Row",9,"485",502,"Row",9,"486",503,"Row",9,"487",504,"Row",9,"488",505,"Row",9,"489",506,"Row",9,"490",507,"Row",9,"491",508,"Row",9,"492",509,"Row",9,"493",510,"Row",9,"494",511,"Row",9,"495",512,"Row",9,"496",513,"Row",9,"497",514,"Row",9,"498",515,"Row",9,"499",516,"Row",9,"500",517,"Row",9,"501",518,"Row",9,"502",519,"Row",9,"503",520,"Row",9,"504",521,"Row",9,"505",522,"Row",9,"506",523,"Row",9,"507",524,"Row",9,"508",525,"Row",9,"509",526,"Row",9,"510",527,"Row",9,"511",528,"Row",9,"512",529,"Row",9,"513",530,"Row",9,"514",531,"Row",9,"515",532,"Row",9,"516",533,"Row",9,"517",534,"Row",9,"518",535,"Row",9,"519",536,"Row",9,"520",537,"Row",9,"521",538,"Row",9,"522",539,"Row",9,"523",540,"Row",9,"524",541,"Row",9,"525",542,"Row",9,"526",543,"Row",9,"527",544,"Row",9,"528",545,"Row",9,"529",546,"Row",9,"530",547,"Row",9,"531",548,"Row",9,"532",549,"Row",9,"533",550,"Row",9,"534",551,"Row",9,"535",552,"Row",9,"536",553,"Row",9,"537",554,"Row",9,"538",555,"Row",9,"539",556,"Row",9,"540",557,"Row",9,"541",558,"Row",9,"542",559,"Row",9,"543",560,"Row",9,"544",561,"Row",9,"545",562,"Row",9,"546",563,"Row",9,"547",564,"Row",9,"548",565,"Row",9,"549",566,"Row",9,"550",567,"Row",9,"551",568,"Row",9,"552",569,"Row",9,"553",570,"Row",9,"554",571,"Row",9,"555",572,"Row",9,"556",573,"Row",9,"557",574,"Row",9,"558",575,"Row",9,"559",576,"Row",9,"560",577,"Row",9,"561",578,"Row",9,"562",579,"Row",9,"563",580,"Row",9,"564",581,"Row",9,"565",582,"Row",9,"566",583,"Row",9,"567",584,"Row",9,"568",585,"Row",9,"569",586,"Row",9,"570",587,"Row",9,"571",588,"Row",9,"572",589,"Row",9,"573",590,"Row",9,"574",591,"Row",9,"575",592,"Row",9,"576",593,"Row",9,"577",594,"Row",9,"578",595,"Row",9,"579",596,"Row",9,"580",597,"Row",9,"581",598,"Row",9,"582",599,"Row",9,"583",600,"Row",9,"584",601,"Row",9,"585",602,"Row",9,"586",603,"Row",9,"587",604,"Row",9,"588",605,"Row",9,"589",606,"Row",9,"590",607,"Row",9,"591",608,"Row",9,"592",609,"Row",9,"593",610,"Row",9,"594",611,"Row",9,"595",612,"Row",9,"596",613,"Row",9,"597",614,"Row",9,"598",615,"Row",9,"599",616,"Row",9,"600",617,"Row",9,"601",618,"Row",9,"602",619,"Row",9,"603",620,"Row",9,"604",621,"Row",9,"605",622,"Row",9,"606",623,"Row",9,"607",624,"Row",9,"608",625,"Row",9,"609",626,"Row",9,"610",627,"Row",9,"611",628,"Row",9,"612",629,"Row",9,"613",630,"Row",9,"614",631,"Row",9,"615",632,"Row",9,"616",633,"Row",9,"617",634,"Row",9,"618",635,"Row",9,"619",636,"Row",9,"620",637,"Row",9,"621",638,"Row",9,"622",639,"Row",9,"623",640,"Row",9,"624",641,"Row",9,"625",642,"Row",9,"626",643,"Row",9,"627",644,"Row",9,"628",645,"Row",9,"629",646,"Row",9,"630",647,"Row",9,"631",648,"Row",9,"632",649,"Row",9,"633",650,"Row",9,"634",651,"Row",9,"635",652,"Row",9,"636",653,"Row",9,"637",654,"Row",9,"638",655,"Row",9,"639",656,"Row",9,"640",657,"Row",9,"641",658,"Row",9,"642",659,"Row",9,"643",660,"Row",9,"644",661,"Row",9,"645",662,"Row",9,"646",663,"Row",9,"647",664,"Row",9,"648",665,"Row",9,"649",666,"Row",9,"650",667,"Row",9,"651",668,"Row",9,"652",669,"Row",9,"653",670,"Row",9,"654",671,"Row",9,"655",672,"Row",9,"656",673,"Row",9,"657",674,"Row",9,"658",675,"Row",9,"659",676,"Row",9,"660",677,"Row",9,"661",678,"Row",9,"662",679,"Row",9,"663",680,"Row",9,"664",681,"Row",9,"665",682,"Row",9,"666",683,"Row",9,"667",684,"Row",9,"668",685,"Row",9,"669",686,"Row",9,"670",687,"Row",9,"671",688,"Row",9,"672",689,"Row",9,"673",690,"Row",9,"674",691,"Row",9,"675",692,"Row",9,"676",693,"Row",9,"677",694,"Row",9,"678",695,"Row",9,"679",696,"Row",9,"680",697,"Row",9,"681",698,"Row",9,"682",699,"Row",9,"683",700,"Row",9,"684",701,"Row",9,"685",702,"Row",9,"686",703,"Row",9,"687",704,"Row",9,"688",705,"Row",9,"689",706,"Row",9,"690",707,"Row",9,"691",708,"Row",9,"692",709,"Row",9,"693",710,"Row",9,"694",711,"Row",9,"695",712,"Row",9,"696",713,"Row",9,"697",714,"Row",9,"698",715,"Row",9,"699",716,"Row",9,"700",717,"Row",9,"701",718,"Row",9,"702",719,"Row",9,"703",720,"Row",9,"704",721,"Row",9,"705",722,"Row",9,"706",723,"Row",9,"707",724,"Row",9,"708",725,"Row",9,"709",726,"Row",9,"710",727,"Row",9,"711",728,"Row",9,"712",729,"Row",9,"713",730,"Row",9,"714",731,"Row",9,"715",732,"Row",9,"716",733,"Row",9,"717",734,"Row",9,"718",735,"Row",9,"719",736,"Row",9,"720",737,"Row",9,"721",738,"Row",9,"722",739,"Row",9,"723",740,"Row",9,"724",741,"Row",9,"725",742,"Row",9,"726",743,"Row",9,"727",744,"Row",9,"728",745,"Row",9,"729",746,"Row",9,"730",747,"Row",9,"731",748,"Row",9,"732",749,"Row",9,"733",750,"Row",9,"734",751,"Row",9,"735",752,"Row",9,"736",753,"Row",9,"737",754,"Row",9,"738",755,"Row",9,"739",756,"Row",9,"740",757,"Row",9,"741",758,"Row",9,"742",759,"Row",9,"743",760,"Row",9,"744",761,"Row",9,"745",762,"Row",9,"746",763,"Row",9,"747",764,"Row",9,"748",765,"Row",9,"749",766,"Row",9,"750",767,"Row",9,"751",768,"Row",9,"752",769,"Row",9,"753",770,"Row",9,"754",771,"Row",9,"755",772,"Row",9,"756",773,"Row",9,"757",774,"Row",9,"758",775,"Row",9,"759",776,"Row",9,"760",777,"Row",9,"761",778,"Row",9,"762",779,"Row",9,"763",780,"Row",9,"764",781,"Row",9,"765",782,"Row",9,"766",783,"Row",9,"767",784,"Row",9,"768",785,"Row",9,"769",786,"Row",9,"770",787,"Row",9,"771",788,"Row",9,"772",789,"Row",9,"773",790,"Row",9,"774",791,"Row",9,"775",792,"Row",9,"776",793,"Row",9,"777",794,"Row",9,"778",795,"Row",9,"779",796,"Row",9,"780",797,"Row",9,"781",798,"Row",9,"782",799,"Row",9,"783",800,"Row",9,"784",801,"Row",9,"785",802,"Row",9,"786",803,"Row",9,"787",804,"Row",9,"788",805,"Row",9,"789",806,"Row",9,"790",807,"Row",9,"791",808,"Row",9,"792",809,"Row",9,"793",810,"Row",9,"794",811,"Row",9,"795",812,"Row",9,"796",813,"Row",9,"797",814,"Row",9,"798",815,"Row",9,"799",816,"Row",9,"800",817,"Row",9,"801",818,"Row",9,"802",819,"Row",9,"803",820,"Row",9,"804",821,"Row",9,"805",822,"Row",9,"806",823,"Row",9,"807",824,"Row",9,"808",825,"Row",9,"809",826,"Row",9,"810",827,"Row",9,"811",828,"Row",9,"812",829,"Row",9,"813",830,"Row",9,"814",831,"Row",9,"815",832,"Row",9,"816",833,"Row",9,"817",834,"Row",9,"818",835,"Row",9,"819",836,"Row",9,"820",837,"Row",9,"821",838,"Row",9,"822",839,"Row",9,"823",840,"Row",9,"824",841,"Row",9,"825",842,"Row",9,"826",843,"Row",9,"827",844,"Row",9,"828",845,"Row",9,"829",846,"Row",9,"830",847,"Row",9,"831",848,"Row",9,"832",849,"Row",9,"833",850,"Row",9,"834",851,"Row",9,"835",852,"Row",9,"836",853,"Row",9,"837",854,"Row",9,"838",855,"Row",9,"839",856,"Row",9,"840",857,"Row",9,"841",858,"Row",9,"842",859,"Row",9,"843",860,"Row",9,"844",861,"Row",9,"845",862,"Row",9,"846",863,"Row",9,"847",864,"Row",9,"848",865,"Row",9,"849",866,"Row",9,"850",867,"Row",9,"851",868,"Row",9,"852",869,"Row",9,"853",870,"Row",9,"854",871,"Row",9,"855",872,"Row",9,"856",873,"Row",9,"857",874,"Row",9,"858",875,"Row",9,"859",876,"Row",9,"860",877,"Row",9,"861",878,"Row",9,"862",879,"Row",9,"863",880,"Row",9,"864",881,"Row",9,"865",882,"Row",9,"866",883,"Row",9,"867",884,"Row",9,"868",885,"Row",9,"869",886,"Row",9,"870",887,"Row",9,"871",888,"Row",9,"872",889,"Row",9,"873",890,"Row",9,"874",891,"Row",9,"875",892,"Row",9,"876",893,"Row",9,"877",894,"Row",9,"878",895,"Row",9,"879",896,"Row",9,"880",897,"Row",9,"881",898,"Row",9,"882",899,"Row",9,"883",900,"Row",9,"884",901,"Row",9,"885",902,"Row",9,"886",903,"Row",9,"887",904,"Row",9,"888",905,"Row",9,"889",906,"Row",9,"890",907,"Row",9,"891",908,"Row",9,"892",909,"Row",9,"893",910,"Row",9,"894",911,"Row",9,"895",912,"Row",9,"896",913,"Row",9,"897",914,"Row",9,"898",915,"Row",9,"899",916,"Row",9,"900",917,"Row",9,"901",918,"Row",9,"902",919,"Row",9,"903",920,"Row",9,"904",921,"Row",9,"905",922,"Row",9,"906",923,"Row",9,"907",924,"Row",9,"908",925,"Row",9,"909",926,"Row",9,"910",927,"Row",9,"911",928,"Row",9,"912",929,"Row",9,"913",930,"Row",9,"914",931,"Row",9,"915",932,"Row",9,"916",933,"Row",9,"917",934,"Row",9,"918",935,"Row",9,"919",936,"Row",9,"920",937,"Row",9,"921",938,"Row",9,"922",939,"Row",9,"923",940,"Row",9,"924",941,"Row",9,"925",942,"Row",9,"926",943,"Row",9,"927",944,"Row",9,"928",945,"Row",9,"929",946,"Row",9,"930",947,"Row",9,"931",948,"Row",9,"932",949,"Row",9,"933",950,"Row",9,"934",951,"Row",9,"935",952,"Row",9,"936",953,"Row",9,"937",954,"Row",9,"938",955,"Row",9,"939",956,"Row",9,"940",957,"Row",9,"941",958,"Row",9,"942",959,"Row",9,"943",960,"Row",9,"944",961,"Row",9,"945",962,"Row",9,"946",963,"Row",9,"947",964,"Row",9,"948",965,"Row",9,"949",966,"Row",9,"950",967,"Row",9,"951",968,"Row",9,"952",969,"Row",9,"953",970,"Row",9,"954",971,"Row",9,"955",972,"Row",9,"956",973,"Row",9,"957",974,"Row",9,"958",975,"Row",9,"959",976,"Row",9,"960",977,"Row",9,"961",978,"Row",9,"962",979,"Row",9,"963",980,"Row",9,"964",981,"Row",9,"965",982,"Row",9,"966",983,"Row",9,"967",984,"Row",9,"968",985,"Row",9,"969",986,"Row",9,"970",987,"Row",9,"971",988,"Row",9,"972",989,"Row",9,"973",990,"Row",9,"974",991,"Row",9,"975",992,"Row",9,"976",993,"Row",9,"977",994,"Row",9,"978",995,"Row",9,"979",996,"Row",9,"980",997,"Row",9,"981",998,"Row",9,"982",999,"Row",9,"983",1000,"Row",9,"984",1001,"Row",9,"985",1002,"Row",9,"986",1003,"Row",9,"987",1004,"Row",9,"988",1005,"Row",9,"989",1006,"Row",9,"990",1007,"Row",9,"991",1008,"Row",9,"992",1009,"Row",9,"993",1010,"Row",9,"994",1011,"Row",9,"995",1012,"Row",9,"996",1013,"Row",9,"997",1014,"Row",9,"998",1015,"Row",9,"999",1016,"Row",9,"1000"]; +import Search from '../components/Search'; +import ComponentInfo from '../components/ComponentInfo'; +import styles from './App.less'; +import Select from '../svgs/Select'; +import { mockParsedVNodeData, parsedMockState } from '../devtools/mock'; function App() { + const [parsedVNodeData, setParsedVNodeData] = useState([]); + const [componentInfo, setComponentInfo] = useState({name: null, attrs: {}}); + useEffect(() => { + if (isDev) { + setParsedVNodeData(mockParsedVNodeData); + setComponentInfo({ + name: 'Demo', + attrs: { + state: parsedMockState, + props: parsedMockState, + }, + }) + } + }, []); const idIndentationMap: { [id: string]: number; } = {}; const data: IData[] = []; let i = 0; - while(i < arr.length) { - const id = arr[i] as string; + while(i < parsedVNodeData.length) { + const id = parsedVNodeData[i] as string; i++; - const name = arr[i] as string; + const name = parsedVNodeData[i] as string; i++; - const parentId = arr[i] as string; + const parentId = parsedVNodeData[i] as string; i++; - const userKey = arr[i] as string; + const userKey = parsedVNodeData[i] as string; i++; const indentation = parentId === '' ? 0 : idIndentationMap[parentId] + 1; idIndentationMap[id] = indentation; @@ -26,8 +43,24 @@ function App() { data.push(item); } return ( -
- +
+
+
+
+ + ) } \ No newline at end of file diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 6e594539..91a7c42e 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -1,6 +1,7 @@ import { useState } from 'horizon'; import styles from './VTree.less'; import Arrow from '../svgs/Arrow'; +import { createRegExp } from './../utils'; export interface IData { id: string; @@ -16,6 +17,7 @@ type IItem = { onClick: (id: string) => void, isCollapsed: boolean, isSelect: boolean, + highlightValue: string, } & IData // TODO: 计算可以展示的最多数量,并且监听显示器高度变化修改数值 @@ -35,6 +37,7 @@ function Item(props: IItem) { indentation, onClick, isSelect, + highlightValue, } = props; const isShowKey = userKey !== ''; const showIcon = hasChild ? : ''; @@ -49,13 +52,30 @@ function Item(props: IItem) { itemAttr.tabIndex = 0; itemAttr.className = styles.treeItem + ' ' + styles.select } + const reg = createRegExp(highlightValue); + const heightCharacters = name.match(reg); + let showName; + if (heightCharacters) { + let cutName = name; + showName = []; + // 高亮第一次匹配即可 + const char = heightCharacters[0]; + let index = name.search(char); + const notHighlightStr = cutName.slice(0, index); + showName.push(notHighlightStr); + showName.push({char}); + cutName = cutName.slice(index + char.length); + showName.push(cutName); + } else { + showName = name; + } return (
{showIcon}
- {name} + {showName} {isShowKey && ( <> @@ -73,7 +93,7 @@ function Item(props: IItem) { ) } -function VTree({ data }: { data: IData[] }) { +function VTree({ data, highlightValue }: { data: IData[], highlightValue: string }) { const [scrollTop, setScrollTop] = useState(0); const [collapseNode, setCollapseNode] = useState(new Set()); const [selectItem, setSelectItem] = useState(); @@ -124,6 +144,7 @@ function VTree({ data }: { data: IData[] }) { onClick={handleClickItem} isCollapsed={isCollapsed} isSelect={id === selectItem} + highlightValue={highlightValue} {...item} /> ) } diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index 73b409ac..871ad0ad 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -9,6 +9,7 @@ import { mockParsedVNodeData, parsedMockState } from '../devtools/mock'; function App() { const [parsedVNodeData, setParsedVNodeData] = useState([]); const [componentInfo, setComponentInfo] = useState({name: null, attrs: {}}); + const [filterValue, setFilterValue] = useState(''); useEffect(() => { if (isDev) { setParsedVNodeData(mockParsedVNodeData); @@ -42,6 +43,11 @@ function App() { }; data.push(item); } + + const handleSearchChange = (str: string) => { + setFilterValue(str); + } + return (
@@ -51,11 +57,11 @@ function App() {
- +
- +
diff --git a/libs/extension/src/utils.ts b/libs/extension/src/utils.ts new file mode 100644 index 00000000..727c672d --- /dev/null +++ b/libs/extension/src/utils.ts @@ -0,0 +1,15 @@ + +export function createRegExp(expression: string){ + let str = expression; + if (str[0] === '/') { + str = str.slice(1); + } + if (str[str.length - 1] === '/') { + str = str.slice(0, str.length - 1); + } + try { + return new RegExp(str, 'i'); + }catch(err) { + return null; + } +} \ No newline at end of file From da66b70642f17b759de01e2e34ce6e3a604db947 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 30 Mar 2022 17:37:46 +0800 Subject: [PATCH 26/46] Match-id-1e7778df481951bd1082406e28f7713d3ca5e6b6 --- .../src/renderer/render/ClassComponent.ts | 13 ++++----- .../render/IncompleteClassComponent.ts | 27 ------------------- .../src/renderer/render/SuspenseComponent.ts | 3 +-- libs/horizon/src/renderer/render/index.ts | 3 --- .../src/renderer/submit/LifeCycleHandler.ts | 19 ++++++------- libs/horizon/src/renderer/vnode/VNode.ts | 3 --- libs/horizon/src/renderer/vnode/VNodeUtils.ts | 1 - 7 files changed, 18 insertions(+), 51 deletions(-) delete mode 100644 libs/horizon/src/renderer/render/IncompleteClassComponent.ts diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index 228ae62a..f0056f04 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -106,6 +106,13 @@ export function captureRender(processing: VNode): VNode | null { resetDepContexts(processing); + // suspense打断后,再次render只需初次渲染 + if (processing.isSuspended) { + mountInstance(ctor, processing, nextProps); + processing.isSuspended = false; + return createChildren(ctor, processing); + } + // 通过 shouldUpdate 判断是否要复用 children,该值和props,state,context的变化,shouldComponentUpdate,forceUpdate api的调用结果有关 let shouldUpdate; const inst = processing.realNode; @@ -166,9 +173,3 @@ export function captureRender(processing: VNode): VNode | null { } export function bubbleRender(processing: VNode) {} - -// 用于未完成的类组件 -export function getIncompleteClassComponent(clazz, processing: VNode, nextProps: object): VNode | null { - mountInstance(clazz, processing, nextProps); - return createChildren(clazz, processing); -} diff --git a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts b/libs/horizon/src/renderer/render/IncompleteClassComponent.ts deleted file mode 100644 index 8e02df51..00000000 --- a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type {VNode} from '../Types'; - -import {mergeDefaultProps} from './LazyComponent'; -import {ClassComponent} from '../vnode/VNodeTags'; -import {resetDepContexts} from '../components/context/Context'; -import {getIncompleteClassComponent} from './ClassComponent'; - -function captureIncompleteClassComponent(processing, Component, nextProps) { - processing.tag = ClassComponent; - - resetDepContexts(processing); - - return getIncompleteClassComponent(Component, processing, nextProps); -} - -export function captureRender(processing: VNode): VNode | null { - const Component = processing.type; - const unresolvedProps = processing.props; - const resolvedProps = - processing.isLazyComponent - ? mergeDefaultProps(Component, unresolvedProps) - : unresolvedProps; - - return captureIncompleteClassComponent(processing, Component, resolvedProps); -} - -export function bubbleRender(processing: VNode) {} diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index 38a84fbc..225312aa 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -6,7 +6,6 @@ import { ClassComponent, ForwardRef, FunctionComponent, - IncompleteClassComponent, SuspenseComponent, } from '../vnode/VNodeTags'; import {pushForceUpdate} from '../UpdateHandler'; @@ -179,7 +178,7 @@ export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, if (processing.tag === ClassComponent) { if (processing.isCreated) { // 渲染类组件场景,要标志未完成(否则会触发componentWillUnmount) - processing.tag = IncompleteClassComponent; + processing.isSuspended = true ; } else { // 类组件更新,标记强制更新,否则被memo等优化跳过 pushForceUpdate(processing); diff --git a/libs/horizon/src/renderer/render/index.ts b/libs/horizon/src/renderer/render/index.ts index 2b6f418d..4e300c93 100644 --- a/libs/horizon/src/renderer/render/index.ts +++ b/libs/horizon/src/renderer/render/index.ts @@ -9,7 +9,6 @@ import * as DomComponentRender from './DomComponent'; import * as DomPortalRender from './DomPortal'; import * as TreeRootRender from './TreeRoot'; import * as DomTextRender from './DomText'; -import * as IncompleteClassComponentRender from './IncompleteClassComponent'; import * as LazyComponentRender from './LazyComponent'; import * as MemoComponentRender from './MemoComponent'; import * as SuspenseComponentRender from './SuspenseComponent'; @@ -25,7 +24,6 @@ import { DomPortal, TreeRoot, DomText, - IncompleteClassComponent, LazyComponent, MemoComponent, SuspenseComponent, @@ -46,7 +44,6 @@ export default { [DomPortal]: DomPortalRender, [TreeRoot]: TreeRootRender, [DomText]: DomTextRender, - [IncompleteClassComponent]: IncompleteClassComponentRender, [LazyComponent]: LazyComponentRender, [MemoComponent]: MemoComponentRender, [SuspenseComponent]: SuspenseComponentRender, diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index f4aaaddf..7201613d 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -32,7 +32,7 @@ import { callEffectRemove, callUseEffects, callUseLayoutEffectCreate, - callUseLayoutEffectRemove + callUseLayoutEffectRemove, } from './HookEffectHandler'; import { handleSubmitError } from '../ErrorHandler'; import { @@ -192,7 +192,8 @@ function unmountVNode(vNode: VNode): void { const instance = vNode.realNode; // 当constructor中抛出异常时,instance会是null,这里判断一下instance是否为空 - if (instance && typeof instance.componentWillUnmount === 'function') { + // suspense打断时不需要触发WillUnmount + if (instance && typeof instance.componentWillUnmount === 'function' && !vNode.isSuspended) { callComponentWillUnmount(vNode, instance); } break; @@ -212,11 +213,11 @@ function unmountVNode(vNode: VNode): void { // 卸载vNode,递归遍历子vNode function unmountNestedVNodes(vNode: VNode): void { travelVNodeTree(vNode, (node) => { - unmountVNode(node); - }, node => - // 如果是DomPortal,不需要遍历child - node.tag === DomPortal - , vNode, null); + unmountVNode(node); + }, node => + // 如果是DomPortal,不需要遍历child + node.tag === DomPortal + , vNode, null); } function submitAddition(vNode: VNode): void { @@ -329,7 +330,7 @@ function submitClear(vNode: VNode): void { // 但考虑到用户可能自定义其他属性,所以采用遍历赋值的方式 const customizeKeys = Object.keys(realNode); const keyLength = customizeKeys.length; - for(let i = 0; i < keyLength; i++) { + for (let i = 0; i < keyLength; i++) { const key = customizeKeys[i]; // 测试代码 mock 实例的全部可遍历属性都会被Object.keys方法读取到 // children 属性被复制意味着复制了子节点,因此要排除 @@ -351,7 +352,7 @@ function submitClear(vNode: VNode): void { } let clearChild = vNode.clearChild as VNode; // 上次渲染的child保存在clearChild属性中 // 卸载 clearChild 和 它的兄弟节点 - while(clearChild) { + while (clearChild) { // 卸载子vNode,递归遍历子vNode unmountNestedVNodes(clearChild); clearVNode(clearChild); diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 166c8c61..7189db06 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -16,7 +16,6 @@ import { ContextProvider, Profiler, MemoComponent, - IncompleteClassComponent, } from './VNodeTags'; import type { VNodeTag } from './VNodeTags'; import type { RefType, ContextType, SuspenseState } from '../Types'; @@ -158,8 +157,6 @@ export class VNode { break; case Profiler: break; - case IncompleteClassComponent: - break; } } } diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index d064b38c..2875a125 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -82,7 +82,6 @@ export function clearVNode(vNode: VNode) { vNode.hooks = null; vNode.props = null; vNode.parent = null; - // mark vNode.suspenseState = null; vNode.changeList = null; vNode.effectList = null; From e13f3245bd2258b6e58989e28d5e93c8bf356a3f Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 31 Mar 2022 10:25:52 +0800 Subject: [PATCH 27/46] Match-id-7133f2734eacafd1263023fbe3921097f915d497 --- .../src/components/ComponentInfo.tsx | 6 +-- .../src/components/ComponentsInfo.less | 1 + libs/extension/src/components/Search.less | 2 +- libs/extension/src/components/Search.tsx | 4 +- libs/extension/src/components/VTree.less | 9 ++-- libs/extension/src/components/VTree.tsx | 39 +++++++------- libs/extension/src/components/assets.less | 6 +-- libs/extension/src/devtools/mock.ts | 47 +++++++++------- libs/extension/src/panel/App.less | 17 +++--- libs/extension/src/panel/App.tsx | 16 +++--- libs/extension/src/panel/index.tsx | 4 +- libs/extension/src/panel/panel.html | 53 +++++++++++-------- libs/extension/src/parser/parseAttr.ts | 19 ++++--- libs/extension/src/parser/parseVNode.ts | 9 ++-- libs/extension/src/svgs/Arrow.tsx | 8 +-- libs/extension/src/svgs/Copy.tsx | 4 +- libs/extension/src/svgs/Debug.tsx | 2 +- libs/extension/src/svgs/Eye.tsx | 6 +-- libs/extension/src/svgs/Select.tsx | 3 +- libs/extension/src/utils.ts | 4 +- libs/extension/webpack.dev.js | 18 +++---- 21 files changed, 155 insertions(+), 122 deletions(-) diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx index 7e13bb42..9c76b503 100644 --- a/libs/extension/src/components/ComponentInfo.tsx +++ b/libs/extension/src/components/ComponentInfo.tsx @@ -35,7 +35,7 @@ function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { newSet.add(index); } setCollapsedNode(newSet); - } + }; const showAttr = []; let currentIndentation = null; @@ -76,7 +76,7 @@ function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { {showAttr}
- ) + ); } export default function ComponentInfo({ name, attrs }: IComponentInfo) { @@ -104,5 +104,5 @@ export default function ComponentInfo({ name, attrs }: IComponentInfo) {
- ) + ); } \ No newline at end of file diff --git a/libs/extension/src/components/ComponentsInfo.less b/libs/extension/src/components/ComponentsInfo.less index 173bec22..3622363e 100644 --- a/libs/extension/src/components/ComponentsInfo.less +++ b/libs/extension/src/components/ComponentsInfo.less @@ -68,6 +68,7 @@ .attrDetail { padding-bottom: 0.5rem; + .attrArrow { color: @arrow-color; width: 12px; diff --git a/libs/extension/src/components/Search.less b/libs/extension/src/components/Search.less index 617a9d26..003f8577 100644 --- a/libs/extension/src/components/Search.less +++ b/libs/extension/src/components/Search.less @@ -1,3 +1,3 @@ .search { width: 100%; -} \ No newline at end of file +} diff --git a/libs/extension/src/components/Search.tsx b/libs/extension/src/components/Search.tsx index 7418bc71..ce644325 100644 --- a/libs/extension/src/components/Search.tsx +++ b/libs/extension/src/components/Search.tsx @@ -8,12 +8,12 @@ export default function Search(props: SearchProps) { const { onChange } = props; const handleChange = (event) => { onChange(event.target.value); - } + }; return ( - ) + ); } \ No newline at end of file diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index e83125e3..1460ce0f 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -5,10 +5,12 @@ width: 100%; height: 100%; overflow-y: auto; + .treeItem { width: 100%; position: absolute; line-height: 18px; + &:hover { background-color: @select-color; } @@ -19,15 +21,15 @@ width: 12px; padding-left: 0.5rem; } - + .componentName { color: @component-name-color; } - + .componentKeyName { color: @component-key-color; } - + .componentKeyValue { color: @componentKeyValue-color; } @@ -37,4 +39,3 @@ background-color: rgb(141 199 248 / 60%); } } - diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 91a7c42e..86f1daf7 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -26,14 +26,14 @@ const lineHeight = 18; const indentationLength = 20; function Item(props: IItem) { - const { - name, - style, + const { + name, + style, userKey, - hasChild, - onCollapse, - isCollapsed, - id, + hasChild, + onCollapse, + isCollapsed, + id, indentation, onClick, isSelect, @@ -43,14 +43,14 @@ function Item(props: IItem) { const showIcon = hasChild ? : ''; const handleClickCollapse = () => { onCollapse(id); - } + }; const handleClick = () => { onClick(id); - } - const itemAttr: any = {style, className: styles.treeItem, onClick: handleClick}; + }; + const itemAttr: any = { style, className: styles.treeItem, onClick: handleClick }; if (isSelect) { itemAttr.tabIndex = 0; - itemAttr.className = styles.treeItem + ' ' + styles.select + itemAttr.className = styles.treeItem + ' ' + styles.select; } const reg = createRegExp(highlightValue); const heightCharacters = name.match(reg); @@ -60,7 +60,7 @@ function Item(props: IItem) { showName = []; // 高亮第一次匹配即可 const char = heightCharacters[0]; - let index = name.search(char); + const index = name.search(char); const notHighlightStr = cutName.slice(0, index); showName.push(notHighlightStr); showName.push({char}); @@ -90,7 +90,7 @@ function Item(props: IItem) { )}
- ) + ); } function VTree({ data, highlightValue }: { data: IData[], highlightValue: string }) { @@ -128,10 +128,11 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string currentCollapseIndentation = null; } } - let id = item.id; + const id = item.id; const isCollapsed = collapseNode.has(id); if (totalHeight >= scrollTop && showList.length <= showNum) { const nextItem = data[index + 1]; + // 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标 const hasChild = nextItem ? nextItem.indentation > item.indentation : false; showList.push( - ) + ); } totalHeight = totalHeight + lineHeight; if (isCollapsed) { @@ -155,19 +156,19 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string } }); - const scroll = (event: any) => { + const handleScroll = (event: any) => { const scrollTop = event.target.scrollTop; // 顶部留 100px 冗余高度 setScrollTop(Math.max(scrollTop - 100, 0)); - } + }; return ( -
+
{showList} {/* 确保有足够的高度 */}
- ) + ); } export default VTree; diff --git a/libs/extension/src/components/assets.less b/libs/extension/src/components/assets.less index a00d8ade..7e74bacf 100644 --- a/libs/extension/src/components/assets.less +++ b/libs/extension/src/components/assets.less @@ -1,5 +1,5 @@ -@arrow-color:rgb(95, 99, 104); -@divider-color:rgb(202, 205, 209); +@arrow-color: rgb(95, 99, 104); +@divider-color: rgb(202, 205, 209); @attr-name-color: rgb(200, 0, 0); @component-name-color: rgb(136, 18, 128); @component-key-color: rgb(153, 69, 0); @@ -11,4 +11,4 @@ @divider-width: 0.2px; @common-font-size: 12px; -@divider-style: @divider-color solid @divider-width; \ No newline at end of file +@divider-style: @divider-color solid @divider-width; diff --git a/libs/extension/src/devtools/mock.ts b/libs/extension/src/devtools/mock.ts index 0c40d06b..3a3eef88 100644 --- a/libs/extension/src/devtools/mock.ts +++ b/libs/extension/src/devtools/mock.ts @@ -1,3 +1,7 @@ +/** + * 用一个纯数据类型的对象 tree 去表示树的结构是非常清晰的,但是它不能准确的模拟 VNode 中存在的引用 + * 关系,需要进行转换 getMockVNodeTree + */ import { parseAttr } from '../parser/parseAttr'; import parseTreeRoot from '../parser/parseVNode'; @@ -9,7 +13,7 @@ const mockComponentNames = ['Apple', 'Pear', 'Banana', 'Orange', 'Jenny', 'Kiwi' function MockVNode(tag: string, props = {}, key = null, realNode = {}) { const vNode = new VNode(tag, props, key, realNode); const name = mockComponentNames.shift() || 'MockComponent'; - vNode.type = {name}; + vNode.type = { name }; return vNode; } @@ -18,41 +22,49 @@ interface IMockTree { children?: IMockTree[], } +// 模拟树 const tree: IMockTree = { tag: ClassComponent, children: [ - {tag: FunctionComponent}, - {tag: ClassComponent}, - {tag: FunctionComponent}, + { tag: FunctionComponent }, + { tag: ClassComponent }, + { tag: FunctionComponent }, { tag: FunctionComponent, children: [ - {tag: ClassComponent} + { tag: ClassComponent } ] } ] -} +}; function addOneThousandNode(node: IMockTree) { const nodes = []; - for(let i = 0; i < 1000; i++) { - nodes.push({tag: FunctionComponent}); + for (let i = 0; i < 1000; i++) { + nodes.push({ tag: FunctionComponent }); } - node?.children.push({tag: ClassComponent,children: nodes}); -}; + node?.children.push({ tag: ClassComponent, children: nodes }); +} addOneThousandNode(tree); -function getMockVNodeTree(tree: IMockTree, vNode: VNode) { - const children = tree.children; +/** + * 将mock数据转变为 VNode 树 + * + * @param node 树节点 + * @param vNode VNode节点 + */ +function getMockVNodeTree(node: IMockTree, vNode: VNode) { + const children = node.children; if (children && children.length !== 0) { const childNode = children[0]; let childVNode = MockVNode(childNode.tag); childVNode.key = '0'; getMockVNodeTree(childNode, childVNode); + // 需要建立双链 vNode.child = childVNode; childVNode.parent = vNode; - for(let i = 1; i < children.length; i++) { + for (let i = 1; i < children.length; i++) { const nextNode = children[i]; const nextVNode = MockVNode(nextNode.tag); nextVNode.key = String(i); @@ -68,20 +80,19 @@ getMockVNodeTree(tree, rootVNode); export const mockParsedVNodeData = parseTreeRoot(rootVNode); - const mockState = { str: 'jenny', num: 3, boolean: true, und: undefined, - fun: () => {}, + fun: () => ({}), symbol: Symbol('sym'), map: new Map([['a', 'a']]), set: new Set(['a', 1, 2, Symbol('bambi')]), - arr: [1,2,3,4], + arr: [1, 2, 3, 4], obj: { - niko: {jenny: 'jenny'} + niko: { jenny: 'jenny' } } }; -export const parsedMockState = parseAttr(mockState); \ No newline at end of file +export const parsedMockState = parseAttr(mockState); diff --git a/libs/extension/src/panel/App.less b/libs/extension/src/panel/App.less index 0186eec1..f48e225a 100644 --- a/libs/extension/src/panel/App.less +++ b/libs/extension/src/panel/App.less @@ -1,48 +1,53 @@ @import '../components/assets.less'; -.app{ +.app { display: flex; flex-direction: row; height: 100%; font-size: @common-font-size; } -.left{ +.left { flex: 7; display: flex; flex-direction: column; + .left_top { border-bottom: @divider-style; flex: 0 0 @top-height; display: flex; align-items: center; + .select { padding: 0 0.25rem 0 0.25rem; flex: 0 0; } + .divider { flex: 0 0 1px; margin: 0 0.25rem 0 0.25rem; border-left: @divider-style; height: calc(100% - 1rem); } + .search { flex: 1 1 0; } } + .left_bottom { - flex: 1; + flex: 1; height: 0; } } -.right{ +.right { flex: 3; border-left: @divider-style; } -input{ +input { outline: none; border-width: 0; padding: 0; -} \ No newline at end of file +} diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index 871ad0ad..2b9ef6bc 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -1,4 +1,4 @@ -import {useState, useEffect} from 'horizon'; +import { useState, useEffect } from 'horizon'; import VTree, { IData } from '../components/VTree'; import Search from '../components/Search'; import ComponentInfo from '../components/ComponentInfo'; @@ -8,7 +8,7 @@ import { mockParsedVNodeData, parsedMockState } from '../devtools/mock'; function App() { const [parsedVNodeData, setParsedVNodeData] = useState([]); - const [componentInfo, setComponentInfo] = useState({name: null, attrs: {}}); + const [componentInfo, setComponentInfo] = useState({ name: null, attrs: {} }); const [filterValue, setFilterValue] = useState(''); useEffect(() => { if (isDev) { @@ -19,7 +19,7 @@ function App() { state: parsedMockState, props: parsedMockState, }, - }) + }); } }, []); const idIndentationMap: { @@ -27,7 +27,7 @@ function App() { } = {}; const data: IData[] = []; let i = 0; - while(i < parsedVNodeData.length) { + while (i < parsedVNodeData.length) { const id = parsedVNodeData[i] as string; i++; const name = parsedVNodeData[i] as string; @@ -46,7 +46,7 @@ function App() { const handleSearchChange = (str: string) => { setFilterValue(str); - } + }; return (
@@ -57,15 +57,15 @@ function App() {
- +
- +
- +
); diff --git a/libs/extension/src/panel/index.tsx b/libs/extension/src/panel/index.tsx index 9e50ba58..a6174e37 100644 --- a/libs/extension/src/panel/index.tsx +++ b/libs/extension/src/panel/index.tsx @@ -1,7 +1,7 @@ -import {render} from 'horizon'; +import { render } from 'horizon'; import App from './App'; render( - , + , document.getElementById('root') ); \ No newline at end of file diff --git a/libs/extension/src/panel/panel.html b/libs/extension/src/panel/panel.html index 70f60f3b..ae944454 100644 --- a/libs/extension/src/panel/panel.html +++ b/libs/extension/src/panel/panel.html @@ -1,27 +1,34 @@ - - - - - - -
- + + + Horizon + + + + + + +
+ + diff --git a/libs/extension/src/parser/parseAttr.ts b/libs/extension/src/parser/parseAttr.ts index 0c5335d1..c52b1891 100644 --- a/libs/extension/src/parser/parseAttr.ts +++ b/libs/extension/src/parser/parseAttr.ts @@ -1,6 +1,13 @@ + +// 将状态的值解析成固定格式 export function parseAttr(rootAttr: any) { - const result = []; - let indentation = 0; + const result: { + name: string, + type: string, + value: string, + indentation: number + }[] = []; + const indentation = 0; const parseSubAttr = (attr: any, parentIndentation: number, attrName: string) => { const stateType = typeof attr; let value: any; @@ -20,7 +27,7 @@ export function parseAttr(rootAttr: any) { } else if (stateType === 'object') { if (attr === null) { showType = 'null'; - }else if (attr instanceof Map) { + } else if (attr instanceof Map) { showType = 'map'; const size = attr.size; value = `Map(${size})`; @@ -28,7 +35,7 @@ export function parseAttr(rootAttr: any) { attr.forEach((value, key) => { parseSubAttr(value, parentIndentation + 2, key); }); - } + }; } else if (attr instanceof Set) { showType = 'set'; const size = attr.size; @@ -46,8 +53,8 @@ export function parseAttr(rootAttr: any) { addSubState = () => { attr.forEach((value, index) => { parseSubAttr(value, parentIndentation + 2, String(index)); - }) - } + }); + }; } else { showType = stateType; value = '{...}'; diff --git a/libs/extension/src/parser/parseVNode.ts b/libs/extension/src/parser/parseVNode.ts index bb8ad91d..91104058 100644 --- a/libs/extension/src/parser/parseVNode.ts +++ b/libs/extension/src/parser/parseVNode.ts @@ -1,7 +1,8 @@ -import { travelVNodeTree } from "../../../../libs/horizon/src/renderer/vnode/VNodeUtils"; -import { VNode } from "../../../../libs/horizon/src/renderer/Types"; -import { ClassComponent, FunctionComponent } from "../../../../libs/horizon/src/renderer/vnode/VNodeTags"; +import { travelVNodeTree } from '../../../../libs/horizon/src/renderer/vnode/VNodeUtils'; +import { VNode } from '../../../../libs/horizon/src/renderer/Types'; +import { ClassComponent, FunctionComponent } from '../../../../libs/horizon/src/renderer/vnode/VNodeTags'; +// 建立双向映射关系,当用户在修改属性值后,可以找到对应的 VNode const VNodeToIdMap = new Map(); const IdToVNodeMap = new Map(); @@ -34,7 +35,7 @@ function parseTreeRoot(treeRoot: VNode) { if (isUserComponent(tag)) { const id = generateUid(); result.push(id); - const name = (node.type as Function).name; + const name = node.type.name; result.push(name); const parent = getParentUserComponent(node); if (parent) { diff --git a/libs/extension/src/svgs/Arrow.tsx b/libs/extension/src/svgs/Arrow.tsx index c315bf19..0cb49d0d 100644 --- a/libs/extension/src/svgs/Arrow.tsx +++ b/libs/extension/src/svgs/Arrow.tsx @@ -2,16 +2,16 @@ interface IArrow { director: 'right' | 'down' } -export default function Arrow({director}: IArrow) { +export default function Arrow({ director }: IArrow) { let d: string; if (director === 'right') { - d = 'm2 0l12 8l-12 8 z' + d = 'm2 0l12 8l-12 8 z'; } else if (director === 'down') { d = 'm0 2h16 l-8 12 z'; } return ( - + - ) + ); } \ No newline at end of file diff --git a/libs/extension/src/svgs/Copy.tsx b/libs/extension/src/svgs/Copy.tsx index 865222fe..483d9ece 100644 --- a/libs/extension/src/svgs/Copy.tsx +++ b/libs/extension/src/svgs/Copy.tsx @@ -2,7 +2,7 @@ export default function Copy() { return ( - + - ) + ); } diff --git a/libs/extension/src/svgs/Debug.tsx b/libs/extension/src/svgs/Debug.tsx index 6ac25393..41e037fb 100644 --- a/libs/extension/src/svgs/Debug.tsx +++ b/libs/extension/src/svgs/Debug.tsx @@ -4,5 +4,5 @@ export default function Debug() { - ) + ); } diff --git a/libs/extension/src/svgs/Eye.tsx b/libs/extension/src/svgs/Eye.tsx index f17f1fd2..bd34c2ac 100644 --- a/libs/extension/src/svgs/Eye.tsx +++ b/libs/extension/src/svgs/Eye.tsx @@ -3,8 +3,8 @@ export default function Eye() { return ( - - + + - ) + ); } diff --git a/libs/extension/src/svgs/Select.tsx b/libs/extension/src/svgs/Select.tsx index 08aef9bb..463805e5 100644 --- a/libs/extension/src/svgs/Select.tsx +++ b/libs/extension/src/svgs/Select.tsx @@ -1,9 +1,8 @@ - export default function Select() { return ( - ) + ); } \ No newline at end of file diff --git a/libs/extension/src/utils.ts b/libs/extension/src/utils.ts index 727c672d..ae2ee2a0 100644 --- a/libs/extension/src/utils.ts +++ b/libs/extension/src/utils.ts @@ -1,5 +1,5 @@ -export function createRegExp(expression: string){ +export function createRegExp(expression: string) { let str = expression; if (str[0] === '/') { str = str.slice(1); @@ -9,7 +9,7 @@ export function createRegExp(expression: string){ } try { return new RegExp(str, 'i'); - }catch(err) { + } catch (err) { return null; } } \ No newline at end of file diff --git a/libs/extension/webpack.dev.js b/libs/extension/webpack.dev.js index 37cbf13c..1c468b0c 100644 --- a/libs/extension/webpack.dev.js +++ b/libs/extension/webpack.dev.js @@ -30,8 +30,8 @@ module.exports = { '@babel/preset-typescript', ['@babel/preset-react', { runtime: 'classic', - "pragma": "Horizon.createElement", - "pragmaFrag": "Horizon.Fragment", + 'pragma': 'Horizon.createElement', + 'pragmaFrag': 'Horizon.Fragment', }]], plugins: ['@babel/plugin-proposal-class-properties'], } @@ -41,14 +41,14 @@ module.exports = { { test: /\.less/i, use: [ - "style-loader", - { - loader: "css-loader", - options: { + 'style-loader', + { + loader: 'css-loader', + options: { modules: true, - - } - }, + + } + }, 'less-loader'], }] }, From 858f669bf7844e312937ef6137d9aff92f4ac98f Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 1 Apr 2022 14:45:57 +0800 Subject: [PATCH 28/46] Match-id-39ccbc96998f49df9e8a525398082b5413bf72fe --- libs/horizon/src/renderer/ContextSaver.ts | 76 ++++--------------- libs/horizon/src/renderer/TreeBuilder.ts | 4 +- .../src/renderer/render/BaseComponent.ts | 5 +- .../src/renderer/render/ClassComponent.ts | 6 +- .../src/renderer/render/ContextProvider.ts | 11 ++- .../horizon/src/renderer/render/ForwardRef.ts | 4 +- .../src/renderer/render/FunctionComponent.ts | 27 ++----- .../src/renderer/render/SuspenseComponent.ts | 20 +++-- 8 files changed, 42 insertions(+), 111 deletions(-) diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts index ef243cce..932a6ffa 100644 --- a/libs/horizon/src/renderer/ContextSaver.ts +++ b/libs/horizon/src/renderer/ContextSaver.ts @@ -1,6 +1,6 @@ /** * 保存与深度遍历相关的一些context。 - * 在深度遍历过程中,begin阶段会修改一些全局的值,在complete阶段会恢复。 + * 在深度遍历过程中,capture阶段会修改一些全局的值,在bubble阶段会恢复。 */ import type { VNode, ContextType } from './Types'; @@ -11,98 +11,50 @@ import { ContextProvider } from './vnode/VNodeTags'; // 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”, // 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM -const CTX_NAMESPACE = 'CTX_NAMESPACE'; - -// 保存的是Horizon.createContext()的值,或Provider重新设置的值 -const CTX_CONTEXT = 'CTX_CONTEXT'; - -// 旧版context API,是否更改。 -const CTX_OLD_CHANGE = 'CTX_OLD_CHANGE'; -let ctxOldChange = false; let ctxNamespace = ''; -function setContext(vNode: VNode, contextName, value) { - if (vNode.contexts === null) { - vNode.contexts = { - [contextName]: value, - }; - } else { - vNode.contexts[contextName] = value; - } -} - -function getContext(vNode: VNode, contextName) { - if (vNode.contexts !== null) { - return vNode.contexts[contextName]; - } -} - // capture阶段设置 -function setNamespaceCtx(vNode: VNode, dom?: Container) { +export function setNamespaceCtx(vNode: VNode, dom?: Container) { const nextContext = getNSCtx(ctxNamespace, vNode.type, dom); - setContext(vNode, CTX_NAMESPACE, ctxNamespace); + vNode.contexts = ctxNamespace; + ctxNamespace = nextContext; } // bubble阶段恢复 -function resetNamespaceCtx(vNode: VNode) { - ctxNamespace = getContext(vNode, CTX_NAMESPACE); +export function resetNamespaceCtx(vNode: VNode) { + ctxNamespace = vNode.contexts; } -function getNamespaceCtx(): string { +export function getNamespaceCtx(): string { return ctxNamespace; } -function setContextCtx(providerVNode: VNode, nextValue: T) { +export function setContext(providerVNode: VNode, nextValue: T) { const context: ContextType = providerVNode.type._context; - setContext(providerVNode, CTX_CONTEXT, context.value); + providerVNode.contexts = context.value; + context.value = nextValue; } -function resetContextCtx(providerVNode: VNode) { +export function resetContext(providerVNode: VNode) { const context: ContextType = providerVNode.type._context; - context.value = getContext(providerVNode, CTX_CONTEXT); + context.value = providerVNode.contexts; } // 在局部更新时,恢复父节点的context -function recoverParentsContextCtx(vNode: VNode) { +export function recoverParentContext(vNode: VNode) { let parent = vNode.parent; while (parent !== null) { if (parent.tag === ContextProvider) { - const newValue = parent.props.value; - setContextCtx(parent, newValue); + parent.contexts = parent.props.value; } parent = parent.parent; } } -function setContextChangeCtx(providerVNode: VNode, didChange: boolean) { - setContext(providerVNode, CTX_OLD_CHANGE, didChange); - ctxOldChange = didChange; -} - -function getContextChangeCtx() { - return ctxOldChange; -} - -function resetContextChangeCtx(vNode: VNode) { - ctxOldChange = getContext(vNode, CTX_OLD_CHANGE); -} - -export { - getNamespaceCtx, - resetNamespaceCtx, - setNamespaceCtx, - setContextCtx, - resetContextCtx, - recoverParentsContextCtx, - setContextChangeCtx, - getContextChangeCtx, - resetContextChangeCtx, -}; - diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 721bbff6..1680c949 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -29,7 +29,7 @@ import { isExecuting, setExecuteMode } from './ExecuteMode'; -import { recoverParentsContextCtx, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver'; +import { recoverParentContext, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver'; import { updateChildShouldUpdate, updateParentsChildShouldUpdate, @@ -231,7 +231,7 @@ function buildVNodeTree(treeRoot: VNode) { } // 恢复父节点的context - recoverParentsContextCtx(startVNode); + recoverParentContext(startVNode); } // 重置环境变量,为重新进行深度遍历做准备 diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts index 94e5ecf4..73749909 100644 --- a/libs/horizon/src/renderer/render/BaseComponent.ts +++ b/libs/horizon/src/renderer/render/BaseComponent.ts @@ -7,7 +7,7 @@ import { TreeRoot, SuspenseComponent, } from '../vnode/VNodeTags'; -import { getContextChangeCtx, setContextCtx, setNamespaceCtx } from '../ContextSaver'; +import { setContext, setNamespaceCtx } from '../ContextSaver'; import { FlagUtils } from '../vnode/VNodeFlags'; import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; import componentRenders from './index'; @@ -26,7 +26,7 @@ function handlerContext(processing: VNode) { break; case ContextProvider: { const newValue = processing.props.value; - setContextCtx(processing, newValue); + setContext(processing, newValue); break; } // No Default @@ -41,7 +41,6 @@ export function captureVNode(processing: VNode): VNode | null { if ( !processing.isCreated && processing.oldProps === processing.props && - !getContextChangeCtx() && !processing.shouldUpdate ) { // 复用还需对stack进行处理 diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index f0056f04..2856ecf9 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -18,7 +18,6 @@ import { markRef } from './BaseComponent'; import { processUpdates, } from '../UpdateHandler'; -import { getContextChangeCtx } from '../ContextSaver'; import { setProcessingClassVNode } from '../GlobalVar'; import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import { createChildrenByDiff } from '../diff/nodeDiffComparator'; @@ -87,7 +86,7 @@ function callUpdateLifeCycle(processing: VNode, nextProps: object, clazz) { } } -function markLifeCycle(processing: VNode, nextProps: object, shouldUpdate: Boolean) { +function markLifeCycle(processing: VNode, nextProps: object, shouldUpdate: boolean) { if (processing.isCreated) { markComponentDidMount(processing); } else if (processing.state !== processing.oldState || shouldUpdate) { @@ -136,7 +135,6 @@ export function captureRender(processing: VNode): VNode | null { // 如果 props, state, context 都没有变化且 isForceUpdate 为 false则不需要更新 shouldUpdate = oldProps !== processing.props || inst.state !== processing.state || - getContextChangeCtx() || processing.isForceUpdate; if (shouldUpdate) { @@ -172,4 +170,4 @@ export function captureRender(processing: VNode): VNode | null { } } -export function bubbleRender(processing: VNode) {} +export function bubbleRender() {} diff --git a/libs/horizon/src/renderer/render/ContextProvider.ts b/libs/horizon/src/renderer/render/ContextProvider.ts index 24aaa9f4..42058bb8 100644 --- a/libs/horizon/src/renderer/render/ContextProvider.ts +++ b/libs/horizon/src/renderer/render/ContextProvider.ts @@ -4,9 +4,8 @@ import { isSame } from '../utils/compare'; import { ClassComponent, ContextProvider } from '../vnode/VNodeTags'; import { pushForceUpdate } from '../UpdateHandler'; import { - getContextChangeCtx, - resetContextCtx, - setContextCtx, + resetContext, + setContext, } from '../ContextSaver'; import { travelVNodeTree } from '../vnode/VNodeUtils'; import { launchUpdateFromVNode } from '../TreeBuilder'; @@ -75,14 +74,14 @@ function captureContextProvider(processing: VNode): VNode | null { const newCtx = newProps.value; // 更新processing的context值为newProps.value - setContextCtx(processing, newCtx); + setContext(processing, newCtx); if (oldProps !== null) { const oldCtx = oldProps.value; const isSameContext = isSame(oldCtx, newCtx); if (isSameContext) { // context没有改变,复用 - if (oldProps.children === newProps.children && !getContextChangeCtx()) { + if (oldProps.children === newProps.children) { return onlyUpdateChildVNodes(processing); } } else { @@ -101,6 +100,6 @@ export function captureRender(processing: VNode): VNode | null { } export function bubbleRender(processing: VNode) { - resetContextCtx(processing); + resetContext(processing); } diff --git a/libs/horizon/src/renderer/render/ForwardRef.ts b/libs/horizon/src/renderer/render/ForwardRef.ts index 35680a3f..ead06587 100644 --- a/libs/horizon/src/renderer/render/ForwardRef.ts +++ b/libs/horizon/src/renderer/render/ForwardRef.ts @@ -1,8 +1,8 @@ import type {VNode} from '../Types'; import {captureRender as funCaptureRender} from './FunctionComponent'; -export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode | null { - return funCaptureRender(processing, shouldUpdate); +export function captureRender(processing: VNode): VNode | null { + return funCaptureRender(processing); } export function bubbleRender() {} diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index 563cda52..128f100e 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -5,7 +5,6 @@ import { resetDepContexts } from '../components/context/Context'; import { runFunctionWithHooks } from '../hooks/HookMain'; import { ForwardRef } from '../vnode/VNodeTags'; import { FlagUtils, Update } from '../vnode/VNodeFlags'; -import { getContextChangeCtx } from '../ContextSaver'; import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import { createChildrenByDiff } from '../diff/nodeDiffComparator'; @@ -16,22 +15,10 @@ export function bubbleRender() { } // 判断children是否可以复用 -function checkIfCanReuseChildren(processing: VNode, shouldUpdate?: boolean) { - let isCanReuse = true; - - if (!processing.isCreated) { - const oldProps = processing.oldProps; - const newProps = processing.props; - - // 如果props或者context改变了 - if (oldProps !== newProps || getContextChangeCtx() || processing.isDepContextChange) { - isCanReuse = false; - } - } else { - isCanReuse = false; - } - - return isCanReuse; +function checkIfCanReuseChildren(processing: VNode) { + return !processing.isCreated && + processing.oldProps === processing.props && + !processing.isDepContextChange; } export function setStateChange(isUpdate) { @@ -46,7 +33,6 @@ export function captureFunctionComponent( processing: VNode, funcComp: any, nextProps: any, - shouldUpdate?: boolean, ) { // 函数组件内已完成异步动作 if (processing.isSuspended) { @@ -58,7 +44,7 @@ export function captureFunctionComponent( } resetDepContexts(processing); - const isCanReuse = checkIfCanReuseChildren(processing, shouldUpdate); + const isCanReuse = checkIfCanReuseChildren(processing); // 在执行exeFunctionHook前先设置stateChange为false setStateChange(false); @@ -80,7 +66,7 @@ export function captureFunctionComponent( return processing.child; } -export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode | null { +export function captureRender(processing: VNode): VNode | null { const Component = processing.type; const unresolvedProps = processing.props; const resolvedProps = @@ -92,7 +78,6 @@ export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode processing, Component, resolvedProps, - shouldUpdate, ); } diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index 225312aa..b57bef99 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -1,17 +1,16 @@ -import type {VNode, PromiseType} from '../Types'; +import type { VNode, PromiseType } from '../Types'; -import {FlagUtils, Interrupted} from '../vnode/VNodeFlags'; -import {onlyUpdateChildVNodes, updateVNode, createFragmentVNode} from '../vnode/VNodeCreator'; +import { FlagUtils, Interrupted } from '../vnode/VNodeFlags'; +import { onlyUpdateChildVNodes, updateVNode, createFragmentVNode } from '../vnode/VNodeCreator'; import { ClassComponent, ForwardRef, FunctionComponent, SuspenseComponent, } from '../vnode/VNodeTags'; -import {pushForceUpdate} from '../UpdateHandler'; -import {launchUpdateFromVNode, tryRenderFromRoot} from '../TreeBuilder'; -import {updateShouldUpdateOfTree} from '../vnode/VNodeShouldUpdate'; -import {getContextChangeCtx} from '../ContextSaver'; +import { pushForceUpdate } from '../UpdateHandler'; +import { launchUpdateFromVNode, tryRenderFromRoot } from '../TreeBuilder'; +import { updateShouldUpdateOfTree } from '../vnode/VNodeShouldUpdate'; import { markVNodePath } from '../utils/vNodePath'; export enum SuspenseChildStatus { @@ -101,7 +100,7 @@ export function captureSuspenseComponent(processing: VNode) { } function updateFallback(processing: VNode): Array | VNode | null { - const childFragment: VNode | null= processing.child; + const childFragment: VNode | null = processing.child; if (childFragment?.childShouldUpdate) { if (processing.suspenseState.promiseResolved) { @@ -130,7 +129,6 @@ export function captureRender(processing: VNode, shouldUpdate: boolean): Array Date: Fri, 1 Apr 2022 14:48:03 +0800 Subject: [PATCH 29/46] Match-id-2d3ad46152443e0e465ba7f54dc734e3cf1694af --- libs/horizon/src/renderer/ContextSaver.ts | 10 +++++----- libs/horizon/src/renderer/vnode/VNode.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts index 932a6ffa..6fd6ef9a 100644 --- a/libs/horizon/src/renderer/ContextSaver.ts +++ b/libs/horizon/src/renderer/ContextSaver.ts @@ -17,14 +17,14 @@ let ctxNamespace = ''; export function setNamespaceCtx(vNode: VNode, dom?: Container) { const nextContext = getNSCtx(ctxNamespace, vNode.type, dom); - vNode.contexts = ctxNamespace; + vNode.context = ctxNamespace; ctxNamespace = nextContext; } // bubble阶段恢复 export function resetNamespaceCtx(vNode: VNode) { - ctxNamespace = vNode.contexts; + ctxNamespace = vNode.context; } export function getNamespaceCtx(): string { @@ -34,7 +34,7 @@ export function getNamespaceCtx(): string { export function setContext(providerVNode: VNode, nextValue: T) { const context: ContextType = providerVNode.type._context; - providerVNode.contexts = context.value; + providerVNode.context = context.value; context.value = nextValue; } @@ -42,7 +42,7 @@ export function setContext(providerVNode: VNode, nextValue: T) { export function resetContext(providerVNode: VNode) { const context: ContextType = providerVNode.type._context; - context.value = providerVNode.contexts; + context.value = providerVNode.context; } // 在局部更新时,恢复父节点的context @@ -51,7 +51,7 @@ export function recoverParentContext(vNode: VNode) { while (parent !== null) { if (parent.tag === ContextProvider) { - parent.contexts = parent.props.value; + parent.context = parent.props.value; } parent = parent.parent; } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 7189db06..bbde565f 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -55,7 +55,7 @@ export class VNode { task: any; // 使用这个变量来记录修改前的值,用于恢复。 - contexts: any; + context: any; // 因为LazyComponent会修改tag和type属性,为了能识别,增加一个属性 isLazyComponent: boolean; @@ -93,7 +93,7 @@ export class VNode { this.stateCallbacks = null; this.state = null; this.oldState = null; - this.contexts = null; + this.context = null; break; case FunctionComponent: this.realNode = null; @@ -112,16 +112,16 @@ export class VNode { this.depContexts = null; this.isDepContextChange = false; this.oldState = null; - this.contexts = null; + this.context = null; break; case DomPortal: this.realNode = null; - this.contexts = null; + this.context = null; break; case DomComponent: this.realNode = null; this.changeList = null; - this.contexts = null; + this.context = null; break; case DomText: this.realNode = null; @@ -137,7 +137,7 @@ export class VNode { }; break; case ContextProvider: - this.contexts = null; + this.context = null; break; case MemoComponent: this.effectList = null; From 422726a88f8992010fd9090004d3a059b86160cd Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 1 Apr 2022 16:19:42 +0800 Subject: [PATCH 30/46] Match-id-52f4df6a91e64babf85d2b4fe312dff944c9a1b2 --- libs/horizon/index.ts | 9 +++++ libs/horizon/src/external/Horizon.ts | 15 +++++++-- .../src/renderer/hooks/HookExternal.ts | 23 +++++++------ libs/horizon/src/renderer/hooks/HookMain.ts | 19 ----------- .../horizon/src/renderer/hooks/HookMapping.ts | 21 ------------ .../src/renderer/render/FunctionComponent.ts | 4 ++- libs/horizon/src/renderer/vnode/VNode.ts | 6 ++++ .../ComponentTest/HookTest/UseContext.test.js | 4 +-- .../ComponentTest/HookTest/UseEffect.test.js | 6 ++-- .../HookTest/UseImperativeHandle.test.js | 6 ++-- .../HookTest/UseLayoutEffect.test.js | 6 ++-- .../ComponentTest/HookTest/UseMemo.test.js | 2 +- .../ComponentTest/HookTest/UseRef.test.js | 2 +- .../ComponentTest/HookTest/UseState.test.js | 6 ++-- .../__tests__/EventTest/FocusEvent.test.js | 3 +- scripts/__tests__/jest/Text.js | 9 ----- scripts/__tests__/jest/commonComponents.js | 20 +++++++++++ scripts/__tests__/jest/customMatcher.js | 33 ------------------- scripts/__tests__/jest/jestSetting.js | 22 ++++++++++++- scripts/__tests__/jest/testUtils.js | 11 +++++-- 20 files changed, 108 insertions(+), 119 deletions(-) delete mode 100644 libs/horizon/src/renderer/hooks/HookMapping.ts delete mode 100644 scripts/__tests__/jest/Text.js create mode 100644 scripts/__tests__/jest/commonComponents.js delete mode 100644 scripts/__tests__/jest/customMatcher.js diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts index 64762f9e..cc37e0d0 100644 --- a/libs/horizon/index.ts +++ b/libs/horizon/index.ts @@ -23,6 +23,9 @@ import { createElement, cloneElement, isValidElement, + act, + launchUpdateFromVNode as _launchUpdateFromVNode, + getProcessingVNode as _getProcessingVNode, } from './src/external/Horizon'; import { @@ -63,6 +66,9 @@ const Horizon = { unstable_batchedUpdates, findDOMNode, unmountComponentAtNode, + act, + _launchUpdateFromVNode, + _getProcessingVNode, }; export { @@ -95,6 +101,9 @@ export { unstable_batchedUpdates, findDOMNode, unmountComponentAtNode, + act, + _launchUpdateFromVNode, + _getProcessingVNode, }; export default Horizon; diff --git a/libs/horizon/src/external/Horizon.ts b/libs/horizon/src/external/Horizon.ts index 623bfbb4..6644f2a0 100644 --- a/libs/horizon/src/external/Horizon.ts +++ b/libs/horizon/src/external/Horizon.ts @@ -17,7 +17,6 @@ import {createContext} from '../renderer/components/context/CreateContext'; import {lazy} from '../renderer/components/Lazy'; import {forwardRef} from '../renderer/components/ForwardRef'; import {memo} from '../renderer/components/Memo'; -import hookMapping from '../renderer/hooks/HookMapping'; import { useCallback, @@ -30,6 +29,16 @@ import { useRef, useState, } from '../renderer/hooks/HookExternal'; +import {launchUpdateFromVNode, asyncUpdates} from '../renderer/TreeBuilder'; +import {callRenderQueueImmediate} from '../renderer/taskExecutor/RenderQueue'; +import {runAsyncEffects} from '../renderer/submit/HookEffectHandler'; +import { getProcessingVNode } from '../renderer/hooks/BaseHook'; + +const act = (fun) => { + asyncUpdates(fun); + callRenderQueueImmediate(); + runAsyncEffects(); +} export { Children, @@ -56,5 +65,7 @@ export { createElement, cloneElement, isValidElement, - hookMapping, + act, + launchUpdateFromVNode, + getProcessingVNode, }; diff --git a/libs/horizon/src/renderer/hooks/HookExternal.ts b/libs/horizon/src/renderer/hooks/HookExternal.ts index fc2805ab..e4610f43 100644 --- a/libs/horizon/src/renderer/hooks/HookExternal.ts +++ b/libs/horizon/src/renderer/hooks/HookExternal.ts @@ -1,17 +1,15 @@ import type {ContextType} from '../Types'; -import hookMapping from './HookMapping'; import {useRefImpl} from './UseRefHook'; import {useEffectImpl, useLayoutEffectImpl} from './UseEffectHook'; import {useCallbackImpl} from './UseCallbackHook'; import {useMemoImpl} from './UseMemoHook'; import {useImperativeHandleImpl} from './UseImperativeHook'; - -const { - UseContextHookMapping, - UseReducerHookMapping, - UseStateHookMapping -} = hookMapping; +import {useReducerImpl} from './UseReducerHook'; +import {useStateImpl} from './UseStateHook'; +import {getNewContext} from '../components/context/Context'; +import {getProcessingVNode} from './BaseHook'; +import {Ref, Trigger} from './HookType'; type BasicStateAction = ((S) => S) | S; type Dispatch = (A) => void; @@ -20,22 +18,23 @@ type Dispatch = (A) => void; export function useContext( Context: ContextType, ): T { - return UseContextHookMapping.val.useContext(Context); + const processingVNode = getProcessingVNode(); + return getNewContext(processingVNode!, Context, true); } export function useState(initialState: (() => S) | S,): [S, Dispatch>] { - return UseStateHookMapping.val.useState(initialState); + return useStateImpl(initialState); } export function useReducer( reducer: (S, A) => S, initialArg: I, init?: (I) => S, -): [S, Dispatch] { - return UseReducerHookMapping.val.useReducer(reducer, initialArg, init); +): [S, Trigger] | void { + return useReducerImpl(reducer, initialArg, init); } -export function useRef(initialValue: T): {current: T} { +export function useRef(initialValue: T): Ref { return useRefImpl(initialValue); } diff --git a/libs/horizon/src/renderer/hooks/HookMain.ts b/libs/horizon/src/renderer/hooks/HookMain.ts index f4c568ea..675afdbb 100644 --- a/libs/horizon/src/renderer/hooks/HookMain.ts +++ b/libs/horizon/src/renderer/hooks/HookMain.ts @@ -1,22 +1,11 @@ import type {VNode} from '../Types'; -import hookMapping from './HookMapping'; -const { - UseStateHookMapping, - UseReducerHookMapping, - UseContextHookMapping, -} = hookMapping; - -import {getNewContext} from '../components/context/Context'; import { getLastTimeHook, - getProcessingVNode, setLastTimeHook, setProcessingVNode, setCurrentHook, getNextHook } from './BaseHook'; -import {useStateImpl} from './UseStateHook'; -import {useReducerImpl} from './UseReducerHook'; import {HookStage, setHookStage} from './HookStage'; // hook对外入口 @@ -29,9 +18,6 @@ export function exeFunctionHook, Arg>( // 重置全局变量 resetGlobalVariable(); - // 初始化hook实现函数 - initHookMapping(); - setProcessingVNode(processing); processing.oldHooks = processing.hooks; @@ -71,8 +57,3 @@ function resetGlobalVariable() { setCurrentHook(null); } -export function initHookMapping() { - UseContextHookMapping.val = {useContext: context => getNewContext(getProcessingVNode(), context, true)}; - UseReducerHookMapping.val = {useReducer: useReducerImpl}; - UseStateHookMapping.val = {useState: useStateImpl}; -} diff --git a/libs/horizon/src/renderer/hooks/HookMapping.ts b/libs/horizon/src/renderer/hooks/HookMapping.ts deleted file mode 100644 index 2205d029..00000000 --- a/libs/horizon/src/renderer/hooks/HookMapping.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 暂时用于解决测试代码无法运行问题,估计是:测试代码会循环或者重复依赖 - */ - -import type { - UseContextHookType, - UseReducerHookType, - UseStateHookType -} from '../Types'; - -const UseStateHookMapping: {val: (null | UseStateHookType)} = {val: null}; -const UseReducerHookMapping: {val: (null | UseReducerHookType)} = {val: null}; -const UseContextHookMapping: {val: (null | UseContextHookType)} = {val: null}; - -const hookMapping = { - UseStateHookMapping, - UseReducerHookMapping, - UseContextHookMapping, -} - -export default hookMapping; diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index 8191293b..d7397e32 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -76,12 +76,14 @@ export function captureFunctionComponent( ); // 这里需要判断是否可以复用,因为函数组件比起其他组价,多了context和stateChange两个因素 - if (isCanReuse && !isStateChange()) { + if (isCanReuse && !isStateChange() && !processing.isStoreChange) { FlagUtils.removeFlag(processing, Update); return onlyUpdateChildVNodes(processing); } + processing.isStoreChange = false; + processing.child = createChildrenByDiff(processing, processing.child, newElements, !processing.isCreated); return processing.child; } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 8c98e673..1bd90f75 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -66,6 +66,10 @@ export class VNode { belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 + // 状态管理器使用 + isStoreChange: boolean; + functionToObserver: FunctionToObserver | null; // 记录这个函数组件依赖哪些Observer + constructor(tag: VNodeTag, props: any, key: null | string, realNode) { this.tag = tag; // 对应组件的类型,比如ClassComponent等 this.key = key; @@ -90,6 +94,8 @@ export class VNode { this.depContexts = null; this.isDepContextChange = false; this.oldHooks = null; + this.isStoreChange = false; + this.functionToObserver = null; break; case ClassComponent: this.realNode = null; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js index d94443b7..7cb53f24 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js @@ -1,9 +1,7 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import { act } from '../../jest/customMatcher'; describe('useContext Hook Test', () => { - const { useState, useContext } = Horizon; - const { unmountComponentAtNode } = Horizon; + const { useState, useContext, act, unmountComponentAtNode } = Horizon; it('简单使用useContext', () => { const LanguageTypes = { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js index b1bc34fe..f5b66614 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js @@ -1,7 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useEffect Hook Test', () => { const { @@ -9,7 +8,8 @@ describe('useEffect Hook Test', () => { useLayoutEffect, useState, memo, - forwardRef + forwardRef, + act, } = Horizon; it('简单使用useEffect', () => { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js index b7f5d6e8..99a7af6a 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js @@ -1,13 +1,13 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useImperativeHandle Hook Test', () => { const { useState, useImperativeHandle, - forwardRef + forwardRef, + act, } = Horizon; const { unmountComponentAtNode } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js index 84226eac..759f6cde 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js @@ -1,13 +1,13 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useLayoutEffect Hook Test', () => { const { useState, useEffect, - useLayoutEffect + useLayoutEffect, + act, } = Horizon; it('简单使用useLayoutEffect', () => { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js index 41882449..0e144f70 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useMemo Hook Test', () => { const { useMemo, useState } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js index b2bab667..a9defa95 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useRef Hook Test', () => { const { useState, useRef } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js index b9e1d950..7e485671 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js @@ -1,14 +1,14 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useState Hook Test', () => { const { useState, forwardRef, useImperativeHandle, - memo + memo, + act, } = Horizon; it('简单使用useState', () => { diff --git a/scripts/__tests__/EventTest/FocusEvent.test.js b/scripts/__tests__/EventTest/FocusEvent.test.js index ee5e3c46..d796960e 100644 --- a/scripts/__tests__/EventTest/FocusEvent.test.js +++ b/scripts/__tests__/EventTest/FocusEvent.test.js @@ -1,6 +1,5 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../jest/logUtils'; -import { act } from '../jest/customMatcher'; describe('合成焦点事件', () => { @@ -43,4 +42,4 @@ describe('合成焦点事件', () => { 'onBlur: blur', ]); }) -}) \ No newline at end of file +}) diff --git a/scripts/__tests__/jest/Text.js b/scripts/__tests__/jest/Text.js deleted file mode 100644 index 781c6d5a..00000000 --- a/scripts/__tests__/jest/Text.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../jest/logUtils'; - -const Text = (props) => { - LogUtils.log(props.text); - return

{props.text}

; -}; - -export default Text; diff --git a/scripts/__tests__/jest/commonComponents.js b/scripts/__tests__/jest/commonComponents.js new file mode 100644 index 00000000..138e18e7 --- /dev/null +++ b/scripts/__tests__/jest/commonComponents.js @@ -0,0 +1,20 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from './logUtils'; + +export const App = (props) => { + const Parent = props.parent; + const Child = props.child; + + return ( +
+ + + +
+ ); +} + +export const Text = (props) => { + LogUtils.log(props.text); + return

{props.text}

; +} diff --git a/scripts/__tests__/jest/customMatcher.js b/scripts/__tests__/jest/customMatcher.js deleted file mode 100644 index 1f6a96fb..00000000 --- a/scripts/__tests__/jest/customMatcher.js +++ /dev/null @@ -1,33 +0,0 @@ -import { runAsyncEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler'; -import { callRenderQueueImmediate } from '../../../libs/horizon/src/renderer/taskExecutor/RenderQueue'; -import { asyncUpdates } from '../../../libs/horizon/src/renderer/TreeBuilder'; - -function runAssertion(fn) { - try { - fn(); - } catch (error) { - return { - pass: false, - message: () => error.message, - }; - } - return { pass: true }; -} - -function toMatchValue(LogUtils, expectedValues) { - return runAssertion(() => { - const actualValues = LogUtils.getAndClear(); - expect(actualValues).toEqual(expectedValues); - }); -} - -const act = (fun) => { - asyncUpdates(fun); - callRenderQueueImmediate(); - runAsyncEffects(); -} - -module.exports = { - toMatchValue, - act -}; diff --git a/scripts/__tests__/jest/jestSetting.js b/scripts/__tests__/jest/jestSetting.js index deda8dd9..78773f65 100644 --- a/scripts/__tests__/jest/jestSetting.js +++ b/scripts/__tests__/jest/jestSetting.js @@ -17,7 +17,27 @@ global.afterEach(() => { LogUtils.clear(); }); + +function runAssertion(fn) { + try { + fn(); + } catch (error) { + return { + pass: false, + message: () => error.message, + }; + } + return { pass: true }; +} + +function toMatchValue(LogUtils, expectedValues) { + return runAssertion(() => { + const actualValues = LogUtils.getAndClear(); + expect(actualValues).toEqual(expectedValues); + }); +} + // 使Jest感知自定义匹配器 expect.extend({ - ...require('./customMatcher'), + toMatchValue, }); diff --git a/scripts/__tests__/jest/testUtils.js b/scripts/__tests__/jest/testUtils.js index 8bab93d9..4afda9da 100644 --- a/scripts/__tests__/jest/testUtils.js +++ b/scripts/__tests__/jest/testUtils.js @@ -9,7 +9,7 @@ export const stopBubbleOrCapture = (e, value) => { export const getEventListeners = (dom) => { let ret = true let keyArray = []; - for (var key in dom) { + for (let key in dom) { keyArray.push(key); } try { @@ -23,4 +23,11 @@ export const getEventListeners = (dom) => { } return ret; -}; \ No newline at end of file +}; + +export function triggerClickEvent(container, id) { + const event = new MouseEvent('click', { + bubbles: true, + }); + container.querySelector(`#${id}`).dispatchEvent(event); +} From 238e438a749288931aac0c5f7d176596ecfad280 Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 1 Apr 2022 17:46:58 +0800 Subject: [PATCH 31/46] Match-id-ade86cdcab051f0fa0d646c659870f6823ac1a97 --- libs/horizon/index.ts | 49 ++++++++----- .../dom/valueHandler/OptionValueHandler.ts | 6 +- libs/horizon/src/external/Horizon.ts | 71 ------------------- 3 files changed, 33 insertions(+), 93 deletions(-) delete mode 100644 libs/horizon/src/external/Horizon.ts diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts index cc37e0d0..a7df0d8b 100644 --- a/libs/horizon/index.ts +++ b/libs/horizon/index.ts @@ -1,12 +1,20 @@ import { - Children, - createRef, - Component, - PureComponent, - createContext, - forwardRef, - lazy, - memo, + TYPE_FRAGMENT as Fragment, + TYPE_PROFILER as Profiler, + TYPE_STRICT_MODE as StrictMode, + TYPE_SUSPENSE as Suspense, +} from './src/external/JSXElementType'; + +import { Component, PureComponent } from './src/renderer/components/BaseClassComponent'; +import { createRef } from './src/renderer/components/CreateRef'; +import { Children } from './src/external/ChildrenUtil'; +import { createElement, cloneElement, isValidElement } from './src/external/JSXElement'; +import { createContext } from './src/renderer/components/context/CreateContext'; +import { lazy } from './src/renderer/components/Lazy'; +import { forwardRef } from './src/renderer/components/ForwardRef'; +import { memo } from './src/renderer/components/Memo'; + +import { useCallback, useContext, useEffect, @@ -16,17 +24,18 @@ import { useReducer, useRef, useState, - Fragment, - Profiler, - StrictMode, - Suspense, - createElement, - cloneElement, - isValidElement, - act, - launchUpdateFromVNode as _launchUpdateFromVNode, - getProcessingVNode as _getProcessingVNode, -} from './src/external/Horizon'; +} from './src/renderer/hooks/HookExternal'; +import { launchUpdateFromVNode as _launchUpdateFromVNode, asyncUpdates } from './src/renderer/TreeBuilder'; +import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue'; +import { runAsyncEffects } from './src/renderer/submit/HookEffectHandler'; +import { getProcessingVNode as _getProcessingVNode } from './src/renderer/hooks/BaseHook'; + +// act用于测试,作用是:如果fun触发了刷新(包含了异步刷新),可以保证在act后面的代码是在刷新完成后才执行。 +const act = fun => { + asyncUpdates(fun); + callRenderQueueImmediate(); + runAsyncEffects(); +}; import { render, @@ -102,6 +111,8 @@ export { findDOMNode, unmountComponentAtNode, act, + + // 暂时给HorizonX使用 _launchUpdateFromVNode, _getProcessingVNode, }; diff --git a/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts b/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts index bd487d4e..12cc6e9a 100644 --- a/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts +++ b/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts @@ -1,10 +1,10 @@ -import * as Horizon from '../../external/Horizon'; -import {IProperty} from '../utils/Interface'; +import { Children } from '../../external/ChildrenUtil'; +import { IProperty } from '../utils/Interface'; // 把 const a = 'a'; 转成 giraffe function concatChildren(children) { let content = ''; - Horizon.Children.forEach(children, function(child) { + Children.forEach(children, function(child) { content += child; }); diff --git a/libs/horizon/src/external/Horizon.ts b/libs/horizon/src/external/Horizon.ts deleted file mode 100644 index 6644f2a0..00000000 --- a/libs/horizon/src/external/Horizon.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - TYPE_FRAGMENT, - TYPE_PROFILER, - TYPE_STRICT_MODE, - TYPE_SUSPENSE, -} from './JSXElementType'; - -import {Component, PureComponent} from '../renderer/components/BaseClassComponent'; -import {createRef} from '../renderer/components/CreateRef'; -import {Children} from './ChildrenUtil'; -import { - createElement, - cloneElement, - isValidElement, -} from './JSXElement'; -import {createContext} from '../renderer/components/context/CreateContext'; -import {lazy} from '../renderer/components/Lazy'; -import {forwardRef} from '../renderer/components/ForwardRef'; -import {memo} from '../renderer/components/Memo'; - -import { - useCallback, - useContext, - useEffect, - useImperativeHandle, - useLayoutEffect, - useMemo, - useReducer, - useRef, - useState, -} from '../renderer/hooks/HookExternal'; -import {launchUpdateFromVNode, asyncUpdates} from '../renderer/TreeBuilder'; -import {callRenderQueueImmediate} from '../renderer/taskExecutor/RenderQueue'; -import {runAsyncEffects} from '../renderer/submit/HookEffectHandler'; -import { getProcessingVNode } from '../renderer/hooks/BaseHook'; - -const act = (fun) => { - asyncUpdates(fun); - callRenderQueueImmediate(); - runAsyncEffects(); -} - -export { - Children, - createRef, - Component, - PureComponent, - createContext, - forwardRef, - lazy, - memo, - useCallback, - useContext, - useEffect, - useImperativeHandle, - useLayoutEffect, - useMemo, - useReducer, - useRef, - useState, - TYPE_FRAGMENT as Fragment, - TYPE_PROFILER as Profiler, - TYPE_STRICT_MODE as StrictMode, - TYPE_SUSPENSE as Suspense, - createElement, - cloneElement, - isValidElement, - act, - launchUpdateFromVNode, - getProcessingVNode, -}; From 871fd3bd9b5bbd4b4ba0ebd1b50a01e9e033850e Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 11:49:21 +0800 Subject: [PATCH 32/46] Match-id-dc2d118d676e9ef910e7da00850f961c33cf80b4 --- libs/extension/src/svgs/Arrow.tsx | 12 ++++++------ libs/extension/src/svgs/Close.tsx | 8 ++++++++ libs/extension/src/svgs/Triangle.tsx | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 libs/extension/src/svgs/Close.tsx create mode 100644 libs/extension/src/svgs/Triangle.tsx diff --git a/libs/extension/src/svgs/Arrow.tsx b/libs/extension/src/svgs/Arrow.tsx index 0cb49d0d..4ee84159 100644 --- a/libs/extension/src/svgs/Arrow.tsx +++ b/libs/extension/src/svgs/Arrow.tsx @@ -1,16 +1,16 @@ interface IArrow { - director: 'right' | 'down' + direction: 'up' | 'down' } -export default function Arrow({ director }: IArrow) { +export default function Arrow({ direction: director }: IArrow) { let d: string; - if (director === 'right') { - d = 'm2 0l12 8l-12 8 z'; + if (director === 'up') { + d = 'M4 9.5 L5 10.5 L8 7.5 L11 10.5 L12 9.5 L8 5.5 z'; } else if (director === 'down') { - d = 'm0 2h16 l-8 12 z'; + d = 'M5 5.5 L4 6.5 L8 10.5 L12 6.5 L11 5.5 L8 8.5z'; } return ( - + ); diff --git a/libs/extension/src/svgs/Close.tsx b/libs/extension/src/svgs/Close.tsx new file mode 100644 index 00000000..621082fd --- /dev/null +++ b/libs/extension/src/svgs/Close.tsx @@ -0,0 +1,8 @@ + +export default function Close() { + return ( + + + + ); +} diff --git a/libs/extension/src/svgs/Triangle.tsx b/libs/extension/src/svgs/Triangle.tsx new file mode 100644 index 00000000..ecb6b3a0 --- /dev/null +++ b/libs/extension/src/svgs/Triangle.tsx @@ -0,0 +1,17 @@ +interface IArrow { + director: 'right' | 'down' +} + +export default function Triangle({ director }: IArrow) { + let d: string; + if (director === 'right') { + d = 'm2 0l12 8l-12 8 z'; + } else if (director === 'down') { + d = 'm0 2h16 l-8 12 z'; + } + return ( + + + + ); +} \ No newline at end of file From 5dabd922ba3e5d2a904ce29eca5b4bc5598a3b2b Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 11:49:39 +0800 Subject: [PATCH 33/46] Match-id-7c00d7de1207a0fdf7279258b7a41ca480e7b798 --- libs/extension/src/components/assets.less | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/extension/src/components/assets.less b/libs/extension/src/components/assets.less index 7e74bacf..45e1b0c4 100644 --- a/libs/extension/src/components/assets.less +++ b/libs/extension/src/components/assets.less @@ -6,6 +6,7 @@ @componentKeyValue-color: rgb(26, 26, 166); @component-attr-color: rgb(200, 0, 0); @select-color: rgb(141 199 248 / 60%); +@hover-color: black; @top-height: 2.625rem; @divider-width: 0.2px; From e851c5a37ea2d375ccc72e2e66cf71c4b4345cfb Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 11:50:27 +0800 Subject: [PATCH 34/46] Match-id-0f71aa2d32776927b1e526631295e8268fb0b82f --- libs/extension/src/components/ComponentInfo.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx index 9c76b503..f9ab06c6 100644 --- a/libs/extension/src/components/ComponentInfo.tsx +++ b/libs/extension/src/components/ComponentInfo.tsx @@ -2,7 +2,7 @@ import styles from './ComponentsInfo.less'; import Eye from '../svgs/Eye'; import Debug from '../svgs/Debug'; import Copy from '../svgs/Copy'; -import Arrow from '../svgs/Arrow'; +import Triangle from '../svgs/Triangle'; import { useState } from 'horizon'; type IComponentInfo = { @@ -53,7 +53,7 @@ function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { const isCollapsed = collapsedNode.has(index); showAttr.push(
(handleCollapse(index))}> - {hasChild && } + {hasChild && } {`${item.name}`} {' :'} {item.value} From f5600dd58c5ceeaa957f33bf6099037cf31155d8 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 11:50:51 +0800 Subject: [PATCH 35/46] Match-id-165f210a5dda1d08fe348888121abc112590ceac --- libs/extension/src/components/ResizeEvent.ts | 78 +++++++++++++++++++ .../extension/src/components/SizeObserver.tsx | 33 ++++++++ 2 files changed, 111 insertions(+) create mode 100644 libs/extension/src/components/ResizeEvent.ts create mode 100644 libs/extension/src/components/SizeObserver.tsx diff --git a/libs/extension/src/components/ResizeEvent.ts b/libs/extension/src/components/ResizeEvent.ts new file mode 100644 index 00000000..0150d0ad --- /dev/null +++ b/libs/extension/src/components/ResizeEvent.ts @@ -0,0 +1,78 @@ +/** + * + * 由于 ResizeObserver 对 IE 和低版本主流浏览器不兼容,需要我们自己解决这个问题。 + * 这是一个不依赖任何框架的监听 dom 元素尺寸变化的解决方案。 + * 浏览器出于性能的考虑,只有 window 的 resize 事件会触发。我们通过 object 标签可以得到 + * 一个 window 对象,让 object dom 元素成为待观测 dom 的子元素,并且和待观测 dom 大小一致。 + * 这样一旦待观测 dom 的大小发生变化, window 的大小也会发生变化,我们就可以通过监听 window + * 大小变化的方式监听待观测 dom 的大小变化。 + * + *
+ * --> 和父 div 保持大小一致 + * --> 添加 resize 事件监听 + * + *
+ * + */ + +function timeout(fn) { + return setTimeout(fn, 20); +} + +function requestFrame(fn) { + const raf = requestAnimationFrame || timeout; + return raf(fn); +} + +function cancelFrame(id) { + const cancel = cancelAnimationFrame || clearTimeout; + cancel(id); +} + +// 在闲置帧触发回调事件,如果在本次触发前存在未处理回调事件, +// 需要取消未处理的回调事件 +function resizeListener(event) { + const win = event.target; + if (win.__resizeRAF__) { + cancelFrame(win.__resizeRAF__); + } + win.__resizeRAF__ = requestFrame(function () { + const observeElement = win.__observeElement__; + observeElement.__resizeCallbacks__.forEach(function (fn) { + fn.call(observeElement, observeElement, event); + }); + }); +} + +function loadObserver() { + // 将待观测元素传递给 object 标签的 window 对象,这样在触发 resize 事件时可以拿到待观测元素 + this.contentDocument.defaultView.__observeElement__ = this.__observeElement__; + // 给 html 的 window 对象添加 resize 事件 + this.contentDocument.defaultView.addEventListener('resize', resizeListener); +} + +export function addResizeListener(element: any, fn: any) { + if (!element.__resizeCallbacks__) { + element.__resizeCallbacks__ = [fn]; + element.style.position = 'relative'; + const observer = document.createElement('object'); + observer.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;'); + observer.data = 'about:blank'; + observer.onload = loadObserver; + observer.type = 'text/html'; + observer.__observeElement__ = element; + element.__observer__ = observer; + element.appendChild(observer); + } else { + element.__resizeCallbacks__.push(fn); + } +} + +export function removeResizeListener(element, fn) { + element.__resizeCallbacks__.splice(element.__resizeCallbacks__.indexOf(fn), 1); + if (!element.__resizeCallbacks__.length) { + element.__observer__.contentDocument.defaultView.removeEventListener('resize', resizeListener); + element.removeChild(element.__observer__); + element.__observer__ = null; + } +} diff --git a/libs/extension/src/components/SizeObserver.tsx b/libs/extension/src/components/SizeObserver.tsx new file mode 100644 index 00000000..3d430093 --- /dev/null +++ b/libs/extension/src/components/SizeObserver.tsx @@ -0,0 +1,33 @@ +import { useEffect, useState, useRef } from 'horizon'; +import { addResizeListener, removeResizeListener } from './ResizeEvent'; + + +export function SizeObserver(props) { + const { children, ...rest } = props; + const containerRef = useRef(); + const [size, setSize] = useState(); + const notifyChild = (element) => { + setSize({ + width: element.offsetWidth, + height: element.offsetHeight, + }); + }; + useEffect(() => { + const element = containerRef.current; + setSize({ + width: element.offsetWidth, + height: element.offsetHeight, + }); + addResizeListener(element, notifyChild); + return () => { + removeResizeListener(element, notifyChild); + }; + }, []); + const myChild = size ? children(size.width, size.height) : null; + + return ( +
+ {myChild} +
+ ); +} \ No newline at end of file From d490707e78ca11f01f1876d0e1fe83e4be04c4fc Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 11:51:43 +0800 Subject: [PATCH 36/46] Match-id-d9d1111f6e1761d1de56351a998c5c7fff5cb483 --- libs/extension/src/components/VList.less | 11 +++ libs/extension/src/components/VList.tsx | 78 +++++++++++++++++ libs/extension/src/components/VTree.less | 3 - libs/extension/src/components/VTree.tsx | 104 ++++++++++++----------- 4 files changed, 142 insertions(+), 54 deletions(-) create mode 100644 libs/extension/src/components/VList.less create mode 100644 libs/extension/src/components/VList.tsx diff --git a/libs/extension/src/components/VList.less b/libs/extension/src/components/VList.less new file mode 100644 index 00000000..8c14e471 --- /dev/null +++ b/libs/extension/src/components/VList.less @@ -0,0 +1,11 @@ +.container { + position: relative; + overflow-y: auto; + height: 100%; + width: 100%; +} + +.item { + position: absolute; + width: 100%; +} \ No newline at end of file diff --git a/libs/extension/src/components/VList.tsx b/libs/extension/src/components/VList.tsx new file mode 100644 index 00000000..84b71943 --- /dev/null +++ b/libs/extension/src/components/VList.tsx @@ -0,0 +1,78 @@ + +import { useState, useRef, useEffect } from 'horizon'; +import styles from './VList.less'; + +interface IProps { + data: T[], + width: number, // 暂时未用到,当需要支持横向滚动时使用 + height: number, // VList 的高度 + children: any, // horizon 组件,组件类型是 T + itemHeight: number, + scrollIndex?: number, + onRendered:(renderInfo: renderInfoType) => void; + filter?(data: T): boolean, // false 表示该行不显示 +} + +const defaultRenderInfo = { + visibleItems: ([] as string[]) +}; + +export type renderInfoType = typeof defaultRenderInfo; + +export function VList(props: IProps) { + const { + data, + height, + children, + itemHeight, + scrollIndex = 0, + filter, + onRendered, + } = props; + const [scrollTop, setScrollTop] = useState(scrollIndex * itemHeight); + const renderInfo = useRef({visibleItems: []}); + useEffect(() => { + onRendered(renderInfo.current); + }); + + const handleScroll = (event: any) => { + const scrollTop = event.target.scrollTop; + setScrollTop(scrollTop); + }; + const showList: any[] = []; + let totalHeight = 0; + // 顶部冗余 + const startShowTopValue = Math.max(scrollTop - itemHeight * 4, 0); + // 底部冗余 + const showNum = Math.floor(height / itemHeight) + 4; + // 如果最后一个显示不全,不统计在显示 ids 内 + const maxTop = scrollTop + height - itemHeight; + // 清空记录的上次渲染的数据 + renderInfo.current.visibleItems.length = 0; + data.forEach((item, i) => { + if (filter && !filter(item)) { + return; + } + if (totalHeight >= startShowTopValue && showList.length <= showNum) { + showList.push( +
+ {children(i, item)} +
+ ); + if (totalHeight >= scrollTop && totalHeight < maxTop) { + renderInfo.current.visibleItems.push(item); + } + } + totalHeight += itemHeight; + }); + + return ( +
+ {showList} +
+
+ ); +} diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index 1460ce0f..0f34f9cd 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -1,10 +1,7 @@ @import 'assets.less'; .treeContainer { - position: relative; - width: 100%; height: 100%; - overflow-y: auto; .treeItem { width: 100%; diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 86f1daf7..5b04385e 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -1,7 +1,9 @@ -import { useState } from 'horizon'; +import { useState, useEffect } from 'horizon'; import styles from './VTree.less'; -import Arrow from '../svgs/Arrow'; +import Triangle from '../svgs/Triangle'; import { createRegExp } from './../utils'; +import { SizeObserver } from './SizeObserver'; +import { renderInfoType, VList } from './VList'; export interface IData { id: string; @@ -11,7 +13,6 @@ export interface IData { } type IItem = { - style: any, hasChild: boolean, onCollapse: (id: string) => void, onClick: (id: string) => void, @@ -20,15 +21,11 @@ type IItem = { highlightValue: string, } & IData -// TODO: 计算可以展示的最多数量,并且监听显示器高度变化修改数值 -const showNum = 70; -const lineHeight = 18; const indentationLength = 20; function Item(props: IItem) { const { name, - style, userKey, hasChild, onCollapse, @@ -37,17 +34,17 @@ function Item(props: IItem) { indentation, onClick, isSelect, - highlightValue, + highlightValue = '', } = props; const isShowKey = userKey !== ''; - const showIcon = hasChild ? : ''; + const showIcon = hasChild ? : ''; const handleClickCollapse = () => { onCollapse(id); }; const handleClick = () => { onClick(id); }; - const itemAttr: any = { style, className: styles.treeItem, onClick: handleClick }; + const itemAttr: any = { className: styles.treeItem, onClick: handleClick }; if (isSelect) { itemAttr.tabIndex = 0; itemAttr.className = styles.treeItem + ' ' + styles.select; @@ -93,10 +90,17 @@ function Item(props: IItem) { ); } -function VTree({ data, highlightValue }: { data: IData[], highlightValue: string }) { - const [scrollTop, setScrollTop] = useState(0); +function VTree({ data, highlightValue, selectedId, onRendered }: { + data: IData[], + highlightValue: string, + selectedId: number, + onRendered: (renderInfo: renderInfoType) => void +}) { const [collapseNode, setCollapseNode] = useState(new Set()); - const [selectItem, setSelectItem] = useState(); + const [selectItem, setSelectItem] = useState(selectedId); + useEffect(() => { + setSelectItem(selectedId); + }, [selectedId]); const changeCollapseNode = (id: string) => { const nodes = new Set(); collapseNode.forEach(value => { @@ -112,17 +116,14 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string const handleClickItem = (id: string) => { setSelectItem(id); }; - const showList: any[] = []; - let totalHeight = 0; let currentCollapseIndentation: null | number = null; - data.forEach((item, index) => { - // 存在未处理完的收起节点 + // 过滤掉折叠的 item,不展示在 VList 中 + const filter = (item: IData) => { if (currentCollapseIndentation !== null) { - const indentation = item.indentation; // 缩进更大,不显示 - if (indentation > currentCollapseIndentation) { - return; + if (item.indentation > currentCollapseIndentation) { + return false; } else { // 缩进小,说明完成了该收起节点的子节点处理。 currentCollapseIndentation = null; @@ -130,44 +131,45 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string } const id = item.id; const isCollapsed = collapseNode.has(id); - if (totalHeight >= scrollTop && showList.length <= showNum) { - const nextItem = data[index + 1]; - // 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标 - const hasChild = nextItem ? nextItem.indentation > item.indentation : false; - showList.push( - - ); - } - totalHeight = totalHeight + lineHeight; if (isCollapsed) { // 该节点需要收起子节点 currentCollapseIndentation = item.indentation; } - }); - - const handleScroll = (event: any) => { - const scrollTop = event.target.scrollTop; - // 顶部留 100px 冗余高度 - setScrollTop(Math.max(scrollTop - 100, 0)); + return true; }; return ( -
- {showList} - {/* 确保有足够的高度 */} -
-
+ + {(width, height) => { + return ( + + {(index: number, item: IData) => { + // 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标 + const nextItem = data[index + 1]; + const hasChild = nextItem && nextItem.indentation > item.indentation; + return ( + + ); + }} + + ); + }} + ); } From baf13b7c5972333da1683e9d47b4cfe458354d1a Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 11:52:07 +0800 Subject: [PATCH 37/46] Match-id-1ded90506bca2ba868af23776da7f666859a3b06 --- libs/extension/src/components/FilterTree.ts | 77 +++++++++++++++++++++ libs/extension/src/components/Search.tsx | 4 +- libs/extension/src/panel/App.less | 18 +++++ libs/extension/src/panel/App.tsx | 34 ++++++++- 4 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 libs/extension/src/components/FilterTree.ts diff --git a/libs/extension/src/components/FilterTree.ts b/libs/extension/src/components/FilterTree.ts new file mode 100644 index 00000000..4660c1b0 --- /dev/null +++ b/libs/extension/src/components/FilterTree.ts @@ -0,0 +1,77 @@ +// 过滤树的抽象逻辑实现 +// 需要知道渲染了哪些数据,搜索的字符串 +// 控制Tree组件位置跳转,告知搜索文本 +// 清空搜索框,告知搜索框当前是第几个结果,跳转搜索结果接口 + +import { useState, useRef } from 'horizon'; +import { createRegExp } from '../utils'; + +export function FilterTree(props: { data: T[] }) { + const { data } = props; + const [filterValue, setFilterValue] = useState(''); + const [selectId, setSelectId] = useState(null); + const showItems = useRef([]); + const matchItemsRef = useRef([]); + const matchItems = matchItemsRef.current; + const onChangeSearchValue = (search: string) => { + const reg = createRegExp(search); + let matchShowId = null; + let newMatchItems = []; + if (search !== '') { + newMatchItems = data.reduce((pre, current) => { + const { id, name } = current; + if (reg && name.match(reg)) { + pre.push(id); + if (matchShowId === null) { + matchShowId = id; + } + } + return pre; + }, []); + if (newMatchItems.length === 0) { + setSelectId(null); + } else { + if (matchShowId === null) { + setSelectId(newMatchItems[0]); + } else { + setSelectId(matchShowId); + } + } + } + matchItemsRef.current = newMatchItems; + setFilterValue(search); + }; + const onSelectNext = () => { + const index = matchItems.indexOf(selectId); + const nextIndex = index + 1; + if (nextIndex < matchItemsRef.current.length) { + setSelectId(matchItems[nextIndex]); + } + }; + const onSelectLast = () => { + const index = matchItems.indexOf(selectId); + const last = index - 1; + if (last >= 0) { + setSelectId(matchItems[last]); + } + }; + const setShowItems = (items) => { + showItems.current = [...items]; + }; + const onClear = () => { + onChangeSearchValue(''); + }; + return { + filterValue, + setFilterValue: onChangeSearchValue, + onClear, + selectId, + matchItems, + onSelectNext, + onSelectLast, + setShowItems, + }; +} diff --git a/libs/extension/src/components/Search.tsx b/libs/extension/src/components/Search.tsx index ce644325..1ea264d3 100644 --- a/libs/extension/src/components/Search.tsx +++ b/libs/extension/src/components/Search.tsx @@ -2,10 +2,11 @@ import styles from './Search.less'; interface SearchProps { onChange: (event: any) => void, + value: string, } export default function Search(props: SearchProps) { - const { onChange } = props; + const { onChange, value } = props; const handleChange = (event) => { onChange(event.target.value); }; @@ -13,6 +14,7 @@ export default function Search(props: SearchProps) { ); diff --git a/libs/extension/src/panel/App.less b/libs/extension/src/panel/App.less index f48e225a..dc0da330 100644 --- a/libs/extension/src/panel/App.less +++ b/libs/extension/src/panel/App.less @@ -17,6 +17,7 @@ flex: 0 0 @top-height; display: flex; align-items: center; + padding-right: 0.4rem; .select { padding: 0 0.25rem 0 0.25rem; @@ -33,6 +34,23 @@ .search { flex: 1 1 0; } + + .searchResult{ + flex: 0 0 ; + padding: 0 0.4rem; + } + + .searchAction { + padding: 0; + flex: 0 0 1rem; + border: none; + background: none; + height: 1rem; + color: @arrow-color; + &:hover{ + color: @hover-color; + } + } } .left_bottom { diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index 2b9ef6bc..1e9a9ae0 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -5,11 +5,14 @@ import ComponentInfo from '../components/ComponentInfo'; import styles from './App.less'; import Select from '../svgs/Select'; import { mockParsedVNodeData, parsedMockState } from '../devtools/mock'; +import { FilterTree } from '../components/FilterTree'; +import Close from '../svgs/Close'; +import Arrow from './../svgs/Arrow'; function App() { const [parsedVNodeData, setParsedVNodeData] = useState([]); const [componentInfo, setComponentInfo] = useState({ name: null, attrs: {} }); - const [filterValue, setFilterValue] = useState(''); + useEffect(() => { if (isDev) { setParsedVNodeData(mockParsedVNodeData); @@ -43,11 +46,25 @@ function App() { }; data.push(item); } + const { + filterValue, + setFilterValue, + onClear, + selectId, + matchItems, + onSelectNext, + onSelectLast, + setShowItems, + } = FilterTree({ data }); const handleSearchChange = (str: string) => { setFilterValue(str); }; + const onRendered = (info) => { + setShowItems(info.visibleItems); + }; + return (
@@ -57,11 +74,22 @@ function App() {
- +
+ {filterValue !== '' && <> + {`${matchItems.indexOf(selectId) + 1}/${matchItems.length}`} +
+ + + + }
- +
From cc11ffd17fb92f6e8bdc3eee76e72b49a6dc00fe Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 11:56:48 +0800 Subject: [PATCH 38/46] Match-id-e266428ff041c68ffd2851a12262c612f5a354c3 --- libs/extension/src/components/VTree.tsx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 5b04385e..8835aede 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -12,30 +12,36 @@ export interface IData { userKey: string; } -type IItem = { +interface IItem { hasChild: boolean, onCollapse: (id: string) => void, onClick: (id: string) => void, isCollapsed: boolean, isSelect: boolean, highlightValue: string, -} & IData + data: IData, +} const indentationLength = 20; function Item(props: IItem) { const { - name, - userKey, hasChild, onCollapse, isCollapsed, - id, - indentation, + data, onClick, isSelect, highlightValue = '', } = props; + + const { + name, + userKey, + id, + indentation, + } = data; + const isShowKey = userKey !== ''; const showIcon = hasChild ? : ''; const handleClickCollapse = () => { @@ -163,7 +169,7 @@ function VTree({ data, highlightValue, selectedId, onRendered }: { onCollapse={changeCollapseNode} onClick={handleClickItem} highlightValue={highlightValue} - {...item} /> + data={item} /> ); }} From b3a46f31c51d3f45259c809bb667c4a277e48657 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 19:15:31 +0800 Subject: [PATCH 39/46] Match-id-afbd9aba70098fe6a0446b81b44bcd1220e61ee5 --- libs/extension/src/components/FilterTree.ts | 126 ++++++++++++++++---- libs/extension/src/components/VList.tsx | 46 +++++-- libs/extension/src/components/VTree.tsx | 66 +++++----- libs/extension/src/panel/App.tsx | 72 ++++++----- 4 files changed, 210 insertions(+), 100 deletions(-) diff --git a/libs/extension/src/components/FilterTree.ts b/libs/extension/src/components/FilterTree.ts index 4660c1b0..66dd101a 100644 --- a/libs/extension/src/components/FilterTree.ts +++ b/libs/extension/src/components/FilterTree.ts @@ -1,77 +1,151 @@ -// 过滤树的抽象逻辑实现 -// 需要知道渲染了哪些数据,搜索的字符串 -// 控制Tree组件位置跳转,告知搜索文本 -// 清空搜索框,告知搜索框当前是第几个结果,跳转搜索结果接口 +// 过滤树的抽象逻辑 +// 需要知道渲染了哪些数据,过滤的字符串/正则表达式 +// 控制Tree组件位置跳转,告知匹配结果 +// 清空搜索框,告知搜索框当前是第几个结果,跳转搜索结果 +// +// 跳转搜索结果的交互逻辑: +// 如果当前页面存在匹配项,页面不动 +// 如果当前页面不存在匹配项,页面跳转到第一个匹配项位置 +// 如果匹配项被折叠,需要展开其父节点。注意只展开当前匹配项的父节点,其他匹配项的父节点不展开 +// 跳转到上一个匹配项或下一个匹配项时,如果匹配项被折叠,需要展开其父节点 +// +// 寻找父节点: +// 找到该节点的缩进值,和index值,在data中向上遍历,通过缩进值判断父节点 import { useState, useRef } from 'horizon'; import { createRegExp } from '../utils'; -export function FilterTree= 0; i--) { + const lastData = data[i]; + const lastIndentation = lastData.indentation; + // 缩进更小,找到了父节点 + if (lastIndentation < currentIndentation) { + // 更新缩进值,只找父节点的父节点,避免修改父节点的兄弟节点的展开状态 + currentIndentation = lastIndentation; + const cIndex = newcollapsedNodes.indexOf(lastData); + if (cIndex !== -1) { + newcollapsedNodes.splice(cIndex, 1); + } + } + } + return newcollapsedNodes; +} + +type BaseType = { id: string, - name: string -}>(props: { data: T[] }) { + name: string, + indentation: number, +} + +export function FilterTree(props: { data: T[] }) { const { data } = props; const [filterValue, setFilterValue] = useState(''); - const [selectId, setSelectId] = useState(null); - const showItems = useRef([]); - const matchItemsRef = useRef([]); + const [currentItem, setCurrentItem] = useState(null); // 当前选中的匹配项 + const showItemsRef = useRef([]); // 页面展示的 items + const matchItemsRef = useRef([]); // 匹配过滤条件的 items + const collapsedNodesRef = useRef([]); // 折叠节点,如果匹配 item 被折叠了,需要展开 + const matchItems = matchItemsRef.current; + const collapsedNodes = collapsedNodesRef.current; + + const updatecollapsedNodes = (item: BaseType) => { + const newcollapsedNodes = expandItemParent(item, data, collapsedNodes); + // 如果新旧收起节点数组长度不一样,说明存在收起节点 + if (newcollapsedNodes.length !== collapsedNodes.length) { + // 更新引用,确保 VTree 拿到新的 collapsedNodes + collapsedNodesRef.current = newcollapsedNodes; + } + }; + const onChangeSearchValue = (search: string) => { const reg = createRegExp(search); - let matchShowId = null; + let newCurrentItem = null; let newMatchItems = []; if (search !== '') { + const showItems: T[] = showItemsRef.current; newMatchItems = data.reduce((pre, current) => { - const { id, name } = current; + const { name } = current; if (reg && name.match(reg)) { - pre.push(id); - if (matchShowId === null) { - matchShowId = id; + pre.push(current); + // 如果当前页面显示的 item 存在匹配项,则把它设置为 currentItem + if (newCurrentItem === null && showItems.includes(current)) { + newCurrentItem = current; } } return pre; }, []); if (newMatchItems.length === 0) { - setSelectId(null); + setCurrentItem(null); } else { - if (matchShowId === null) { - setSelectId(newMatchItems[0]); + if (newCurrentItem === null) { + const item = newMatchItems[0]; + // 不处于当前展示页面,需要展开父节点 + updatecollapsedNodes(item); + setCurrentItem(item); } else { - setSelectId(matchShowId); + setCurrentItem(newCurrentItem); } } + } else { + setCurrentItem(null); } matchItemsRef.current = newMatchItems; setFilterValue(search); }; const onSelectNext = () => { - const index = matchItems.indexOf(selectId); + const index = matchItems.indexOf(currentItem); const nextIndex = index + 1; if (nextIndex < matchItemsRef.current.length) { - setSelectId(matchItems[nextIndex]); + const item = matchItems[nextIndex]; + // 不处于当前展示页面,需要展开父节点 + updatecollapsedNodes(item); + setCurrentItem(item); } }; const onSelectLast = () => { - const index = matchItems.indexOf(selectId); + const index = matchItems.indexOf(currentItem); const last = index - 1; if (last >= 0) { - setSelectId(matchItems[last]); + const item = matchItems[last]; + // 不处于当前展示页面,需要展开父节点 + updatecollapsedNodes(item); + setCurrentItem(matchItems[last]); } }; const setShowItems = (items) => { - showItems.current = [...items]; + showItemsRef.current = [...items]; }; const onClear = () => { onChangeSearchValue(''); }; + const setcollapsedNodes = (items) => { + // 不更新引用,避免子组件的重复渲染 + collapsedNodesRef.current.length = 0; + collapsedNodesRef.current.push(...items); + }; return { filterValue, - setFilterValue: onChangeSearchValue, + onChangeSearchValue, onClear, - selectId, + currentItem, matchItems, onSelectNext, onSelectLast, setShowItems, + collapsedNodes, + setcollapsedNodes, }; } diff --git a/libs/extension/src/components/VList.tsx b/libs/extension/src/components/VList.tsx index 84b71943..c932a492 100644 --- a/libs/extension/src/components/VList.tsx +++ b/libs/extension/src/components/VList.tsx @@ -8,33 +8,48 @@ interface IProps { height: number, // VList 的高度 children: any, // horizon 组件,组件类型是 T itemHeight: number, - scrollIndex?: number, - onRendered:(renderInfo: renderInfoType) => void; + scrollToItem?: T, // 滚动到指定项位置,如果该项在可见区域内,不滚动,如果不在,则滚动到中间位置 + onRendered: (renderInfo: renderInfoType) => void; filter?(data: T): boolean, // false 表示该行不显示 } -const defaultRenderInfo = { - visibleItems: ([] as string[]) +export type renderInfoType = { + visibleItems: T[], + skipItemCountBeforeScrollItem: number, }; -export type renderInfoType = typeof defaultRenderInfo; - export function VList(props: IProps) { const { data, height, children, itemHeight, - scrollIndex = 0, + scrollToItem, filter, onRendered, } = props; - const [scrollTop, setScrollTop] = useState(scrollIndex * itemHeight); - const renderInfo = useRef({visibleItems: []}); + const [scrollTop, setScrollTop] = useState(data.indexOf(scrollToItem) * itemHeight); + const renderInfoRef: { current: renderInfoType } = useRef({ visibleItems: [], skipItemCountBeforeScrollItem: 0 }); + const containerRef = useRef(); useEffect(() => { - onRendered(renderInfo.current); + onRendered(renderInfoRef.current); }); + useEffect(() => { + if (scrollToItem) { + const renderInfo = renderInfoRef.current; + // 在滚动区域,不滚动 + if (!renderInfo.visibleItems.includes(scrollToItem)) { + const index = data.indexOf(scrollToItem); + // top值计算需要减掉filter条件判定不显示项 + const totalCount = index - renderInfoRef.current.skipItemCountBeforeScrollItem; + // 显示在页面中间 + const top = totalCount * itemHeight - height / 2; + containerRef.current.scrollTo({ top: top }); + } + } + }, [scrollToItem]); + const handleScroll = (event: any) => { const scrollTop = event.target.scrollTop; setScrollTop(scrollTop); @@ -48,9 +63,14 @@ export function VList(props: IProps) { // 如果最后一个显示不全,不统计在显示 ids 内 const maxTop = scrollTop + height - itemHeight; // 清空记录的上次渲染的数据 - renderInfo.current.visibleItems.length = 0; + renderInfoRef.current.visibleItems.length = 0; + const scrollItemIndex = data.indexOf(scrollToItem); + renderInfoRef.current.skipItemCountBeforeScrollItem = 0; data.forEach((item, i) => { if (filter && !filter(item)) { + if (scrollItemIndex > i) { + renderInfoRef.current.skipItemCountBeforeScrollItem++; + } return; } if (totalHeight >= startShowTopValue && showList.length <= showNum) { @@ -63,14 +83,14 @@ export function VList(props: IProps) {
); if (totalHeight >= scrollTop && totalHeight < maxTop) { - renderInfo.current.visibleItems.push(item); + renderInfoRef.current.visibleItems.push(item); } } totalHeight += itemHeight; }); return ( -
+
{showList}
diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 8835aede..044e64dc 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -14,8 +14,8 @@ export interface IData { interface IItem { hasChild: boolean, - onCollapse: (id: string) => void, - onClick: (id: string) => void, + onCollapse: (data: IData) => void, + onClick: (id: IData) => void, isCollapsed: boolean, isSelect: boolean, highlightValue: string, @@ -34,21 +34,20 @@ function Item(props: IItem) { isSelect, highlightValue = '', } = props; - + const { name, userKey, - id, - indentation, + indentation, } = data; const isShowKey = userKey !== ''; const showIcon = hasChild ? : ''; const handleClickCollapse = () => { - onCollapse(id); + onCollapse(data); }; const handleClick = () => { - onClick(id); + onClick(data); }; const itemAttr: any = { className: styles.treeItem, onClick: handleClick }; if (isSelect) { @@ -96,31 +95,39 @@ function Item(props: IItem) { ); } -function VTree({ data, highlightValue, selectedId, onRendered }: { +function VTree(props: { data: IData[], highlightValue: string, - selectedId: number, - onRendered: (renderInfo: renderInfoType) => void + scrollToItem: IData, + onRendered: (renderInfo: renderInfoType) => void, + collapsedNodes?: IData[], + onCollapseNode?: (item: IData[]) => void, }) { - const [collapseNode, setCollapseNode] = useState(new Set()); - const [selectItem, setSelectItem] = useState(selectedId); + const { data, highlightValue, scrollToItem, onRendered, onCollapseNode } = props; + const [collapseNode, setCollapseNode] = useState(props.collapsedNodes || []); + const [selectItem, setSelectItem] = useState(scrollToItem); useEffect(() => { - setSelectItem(selectedId); - }, [selectedId]); - const changeCollapseNode = (id: string) => { - const nodes = new Set(); - collapseNode.forEach(value => { - nodes.add(value); - }); - if (nodes.has(id)) { - nodes.delete(id); + setSelectItem(scrollToItem); + }, [scrollToItem]); + useEffect(() => { + setCollapseNode(props.collapsedNodes || []); + }, [props.collapsedNodes]); + + const changeCollapseNode = (item: IData) => { + const nodes: IData[] = [...collapseNode]; + const index = nodes.indexOf(item); + if (index === -1) { + nodes.push(item); } else { - nodes.add(id); + nodes.splice(index, 1); } setCollapseNode(nodes); + if (onCollapseNode) { + onCollapseNode(nodes); + } }; - const handleClickItem = (id: string) => { - setSelectItem(id); + const handleClickItem = (item: IData) => { + setSelectItem(item); }; let currentCollapseIndentation: null | number = null; @@ -135,8 +142,7 @@ function VTree({ data, highlightValue, selectedId, onRendered }: { currentCollapseIndentation = null; } } - const id = item.id; - const isCollapsed = collapseNode.has(id); + const isCollapsed = collapseNode.includes(item); if (isCollapsed) { // 该节点需要收起子节点 currentCollapseIndentation = item.indentation; @@ -146,14 +152,14 @@ function VTree({ data, highlightValue, selectedId, onRendered }: { return ( - {(width, height) => { + {(width: number, height: number) => { return ( @@ -164,8 +170,8 @@ function VTree({ data, highlightValue, selectedId, onRendered }: { return ( { + const idIndentationMap: { + [id: string]: number; + } = {}; + const data: IData[] = []; + let i = 0; + while (i < rawData.length) { + const id = rawData[i] as string; + i++; + const name = rawData[i] as string; + i++; + const parentId = rawData[i] as string; + i++; + const userKey = rawData[i] as string; + i++; + const indentation = parentId === '' ? 0 : idIndentationMap[parentId] + 1; + idIndentationMap[id] = indentation; + const item = { + id, name, indentation, userKey + }; + data.push(item); + } + return data; +}; + function App() { const [parsedVNodeData, setParsedVNodeData] = useState([]); const [componentInfo, setComponentInfo] = useState({ name: null, attrs: {} }); useEffect(() => { if (isDev) { - setParsedVNodeData(mockParsedVNodeData); + const parsedData = parseVNodeData(mockParsedVNodeData); + setParsedVNodeData(parsedData); setComponentInfo({ name: 'Demo', attrs: { @@ -25,37 +51,19 @@ function App() { }); } }, []); - const idIndentationMap: { - [id: string]: number; - } = {}; - const data: IData[] = []; - let i = 0; - while (i < parsedVNodeData.length) { - const id = parsedVNodeData[i] as string; - i++; - const name = parsedVNodeData[i] as string; - i++; - const parentId = parsedVNodeData[i] as string; - i++; - const userKey = parsedVNodeData[i] as string; - i++; - const indentation = parentId === '' ? 0 : idIndentationMap[parentId] + 1; - idIndentationMap[id] = indentation; - const item = { - id, name, indentation, userKey - }; - data.push(item); - } + const { filterValue, - setFilterValue, + onChangeSearchValue: setFilterValue, onClear, - selectId, + currentItem, matchItems, onSelectNext, onSelectLast, setShowItems, - } = FilterTree({ data }); + collapsedNodes, + setcollapsedNodes, + } = FilterTree({ data: parsedVNodeData }); const handleSearchChange = (str: string) => { setFilterValue(str); @@ -77,19 +85,21 @@ function App() {
{filterValue !== '' && <> - {`${matchItems.indexOf(selectId) + 1}/${matchItems.length}`} + {`${matchItems.indexOf(currentItem) + 1}/${matchItems.length}`}
- - - + + + }
+ collapsedNodes={collapsedNodes} + onCollapseNode={setcollapsedNodes} + scrollToItem={currentItem} />
From e0ac32cda970fc1d29d6287651e1811aedc06ba0 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 19:15:39 +0800 Subject: [PATCH 40/46] Match-id-30ecadc80ad5388949a0fc159b3355ae65b8f9b3 --- .eslintrc.js | 25 +++++++------------ libs/horizon/src/dom/utils/Common.ts | 2 +- .../src/renderer/vnode/VNodeCreator.ts | 6 ++--- .../src/renderer/vnode/VNodeShouldUpdate.ts | 2 +- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ab410fd1..7aedabc1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,19 +1,13 @@ module.exports = { extends: [ 'eslint:recommended', - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', 'prettier', ], root: true, - plugins: [ - 'jest', - 'no-for-of-loops', - 'no-function-declare-after-return', - 'react', - '@typescript-eslint', - ], + plugins: ['jest', 'no-for-of-loops', 'no-function-declare-after-return', 'react', '@typescript-eslint'], parser: '@typescript-eslint/parser', parserOptions: { @@ -34,8 +28,8 @@ module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', - 'semi': ['warn', 'always'], - 'quotes': ['warn', 'single'], + semi: ['warn', 'always'], + quotes: ['warn', 'single'], 'accessor-pairs': 'off', 'brace-style': ['error', '1tbs'], 'func-style': ['warn', 'declaration', { allowArrowFunctions: true }], @@ -44,19 +38,18 @@ module.exports = { // 尾随逗号 'comma-dangle': ['error', 'only-multiline'], + 'no-constant-condition': 'off', 'no-for-of-loops/no-for-of-loops': 'error', 'no-function-declare-after-return/no-function-declare-after-return': 'error', }, globals: { - isDev: true + isDev: true, }, overrides: [ { - files: [ - 'scripts/__tests__/**/*.js' - ], + files: ['scripts/__tests__/**/*.js'], globals: { - container: true + container: true, }, }, ], diff --git a/libs/horizon/src/dom/utils/Common.ts b/libs/horizon/src/dom/utils/Common.ts index 09602c82..dae06d2b 100644 --- a/libs/horizon/src/dom/utils/Common.ts +++ b/libs/horizon/src/dom/utils/Common.ts @@ -6,7 +6,7 @@ import {Props} from '../DOMOperator'; * @param doc 指定 document */ export function getFocusedDom(doc?: Document): HorizonDom | null { - let currentDocument = doc ?? document; + const currentDocument = doc ?? document; return currentDocument.activeElement ?? currentDocument.body; } diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index 932c7fa7..8b7ad630 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -38,7 +38,7 @@ const typeMap = { [TYPE_LAZY]: LazyComponent, }; -function newVirtualNode (tag: VNodeTag, key?: null | string, vNodeProps?: any, realNode?: any): VNode { +function newVirtualNode(tag: VNodeTag, key?: null | string, vNodeProps?: any, realNode?: any): VNode { return new VNode(tag, vNodeProps, key, realNode); } @@ -201,7 +201,7 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null { sibling = sibling.next; } } - } + }; putChildrenIntoQueue(processing.child); @@ -210,7 +210,7 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null { markVNodePath(vNode); - putChildrenIntoQueue(vNode) + putChildrenIntoQueue(vNode); } } // 子树无需工作 diff --git a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts index 72afb236..ec5dce84 100644 --- a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts +++ b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts @@ -53,7 +53,7 @@ export function setParentsChildShouldUpdate(parent: VNode | null) { // 设置节点的所有父节点的childShouldUpdate export function updateParentsChildShouldUpdate(vNode: VNode) { let node = vNode.parent; - let isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate; + const isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate; if (isShouldUpdate) { // 开始节点是shouldUpdate或childShouldUpdate // 更新从当前节点到根节点的childShouldUpdate为true From e7dbc1c1bf04d0dab2f61dfa62702cb96a342891 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 19:23:39 +0800 Subject: [PATCH 41/46] Match-id-5fb1eed20c25112c3ca480cb9f1f793fcfb1c046 --- libs/extension/src/components/FilterTree.ts | 28 +++++++++------------ libs/extension/src/panel/App.tsx | 4 +-- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/libs/extension/src/components/FilterTree.ts b/libs/extension/src/components/FilterTree.ts index 66dd101a..58706902 100644 --- a/libs/extension/src/components/FilterTree.ts +++ b/libs/extension/src/components/FilterTree.ts @@ -61,7 +61,7 @@ export function FilterTree(props: { data: T[] }) { const matchItems = matchItemsRef.current; const collapsedNodes = collapsedNodesRef.current; - const updatecollapsedNodes = (item: BaseType) => { + const updateCollapsedNodes = (item: BaseType) => { const newcollapsedNodes = expandItemParent(item, data, collapsedNodes); // 如果新旧收起节点数组长度不一样,说明存在收起节点 if (newcollapsedNodes.length !== collapsedNodes.length) { @@ -93,7 +93,7 @@ export function FilterTree(props: { data: T[] }) { if (newCurrentItem === null) { const item = newMatchItems[0]; // 不处于当前展示页面,需要展开父节点 - updatecollapsedNodes(item); + updateCollapsedNodes(item); setCurrentItem(item); } else { setCurrentItem(newCurrentItem); @@ -108,22 +108,18 @@ export function FilterTree(props: { data: T[] }) { const onSelectNext = () => { const index = matchItems.indexOf(currentItem); const nextIndex = index + 1; - if (nextIndex < matchItemsRef.current.length) { - const item = matchItems[nextIndex]; - // 不处于当前展示页面,需要展开父节点 - updatecollapsedNodes(item); - setCurrentItem(item); - } + const item = nextIndex < matchItemsRef.current.length ? matchItems[nextIndex] : matchItems[0]; + // 可能不处于当前展示页面,需要展开父节点 + updateCollapsedNodes(item); + setCurrentItem(item); }; const onSelectLast = () => { const index = matchItems.indexOf(currentItem); const last = index - 1; - if (last >= 0) { - const item = matchItems[last]; - // 不处于当前展示页面,需要展开父节点 - updatecollapsedNodes(item); - setCurrentItem(matchItems[last]); - } + const item = last >= 0 ? matchItems[last] : matchItems[matchItems.length - 1]; + // 可能不处于当前展示页面,需要展开父节点 + updateCollapsedNodes(item); + setCurrentItem(item); }; const setShowItems = (items) => { showItemsRef.current = [...items]; @@ -131,7 +127,7 @@ export function FilterTree(props: { data: T[] }) { const onClear = () => { onChangeSearchValue(''); }; - const setcollapsedNodes = (items) => { + const setCollapsedNodes = (items) => { // 不更新引用,避免子组件的重复渲染 collapsedNodesRef.current.length = 0; collapsedNodesRef.current.push(...items); @@ -146,6 +142,6 @@ export function FilterTree(props: { data: T[] }) { onSelectLast, setShowItems, collapsedNodes, - setcollapsedNodes, + setCollapsedNodes, }; } diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index af5a8990..dcc18a67 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -62,7 +62,7 @@ function App() { onSelectLast, setShowItems, collapsedNodes, - setcollapsedNodes, + setCollapsedNodes, } = FilterTree({ data: parsedVNodeData }); const handleSearchChange = (str: string) => { @@ -98,7 +98,7 @@ function App() { highlightValue={filterValue} onRendered={onRendered} collapsedNodes={collapsedNodes} - onCollapseNode={setcollapsedNodes} + onCollapseNode={setCollapsedNodes} scrollToItem={currentItem} />
From 3de86a411cc0651c9dc7b4ed9a892aa4c54c2112 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 23 Mar 2022 10:28:58 +0800 Subject: [PATCH 42/46] Match-id-a642a9a90565e0bc7897482ce61d1ac65f8120c5 --- libs/horizon/src/renderer/ContextSaver.ts | 2 +- .../ComponentTest/HookTest/UseContext.test.js | 48 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts index 6fd6ef9a..cd21da0b 100644 --- a/libs/horizon/src/renderer/ContextSaver.ts +++ b/libs/horizon/src/renderer/ContextSaver.ts @@ -51,7 +51,7 @@ export function recoverParentContext(vNode: VNode) { while (parent !== null) { if (parent.tag === ContextProvider) { - parent.context = parent.props.value; + setContext(parent, parent.props.value); } parent = parent.parent; } diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js index d94443b7..c0fc0a53 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js @@ -2,7 +2,7 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import { act } from '../../jest/customMatcher'; describe('useContext Hook Test', () => { - const { useState, useContext } = Horizon; + const { useState, useContext, createContext } = Horizon; const { unmountComponentAtNode } = Horizon; it('简单使用useContext', () => { @@ -14,16 +14,12 @@ describe('useContext Hook Test', () => { const SystemLanguageContext = Horizon.createContext(defaultValue); const SystemLanguageProvider = ({ type, children }) => { - return ( - - {children} - - ); + return {children}; }; const TestFunction = () => { const context = useContext(SystemLanguageContext); return

{context.type}

; - } + }; let setValue; const App = () => { const [value, _setValue] = useState(LanguageTypes.JAVA); @@ -34,8 +30,8 @@ describe('useContext Hook Test', () => {
- ) - } + ); + }; Horizon.render(, container); // 测试当Provider未提供时,获取到的默认值'JavaScript'。 expect(container.querySelector('p').innerHTML).toBe('JavaScript'); @@ -47,4 +43,38 @@ describe('useContext Hook Test', () => { act(() => setValue(LanguageTypes.JAVASCRIPT)); expect(container.querySelector('p').innerHTML).toBe('JavaScript'); }); + + it('更新后useContext仍能获取到context', () => { + const Context = createContext({}); + const ref = Horizon.createRef(); + + function App() { + return ( + + + + ); + } + + let update; + + function Child() { + const context = useContext(Context); + const [_, setState] = useState({}); + update = () => setState({}); + + return
{context.text}
; + } + + Horizon.render(, container); + expect(ref.current.innerHTML).toBe('context'); + + update(); + + expect(ref.current.innerHTML).toBe('context'); + }); }); From a7e601ad08ef1f54abfe84f89e7658a574ca0246 Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 8 Apr 2022 14:27:16 +0800 Subject: [PATCH 43/46] Match-id-55d57069bcbae4d9269d2060b72e9784ec558608 --- scripts/__tests__/ComponentTest/HookTest/UseContext.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js index dfa2aec9..bce485c6 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -describe('useContext Hook Test', () => {; +describe('useContext Hook Test', () => { const { useState, useContext, createContext, act, unmountComponentAtNode } = Horizon; it('简单使用useContext', () => { From 64a78f34ba05c89ecbd5e235bce1a214898e715b Mon Sep 17 00:00:00 2001 From: * <8> Date: Sat, 9 Apr 2022 15:55:40 +0800 Subject: [PATCH 44/46] Match-id-76e847694efa871d9b2bb281012e3ba581f963f8 --- .../src/components/ComponentInfo.tsx | 43 +++++++++++-------- libs/extension/src/components/FilterTree.ts | 8 ++-- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx index f9ab06c6..80127470 100644 --- a/libs/extension/src/components/ComponentInfo.tsx +++ b/libs/extension/src/components/ComponentInfo.tsx @@ -22,24 +22,29 @@ type IAttr = { indentation: number; } -function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { - const [collapsedNode, setCollapsedNode] = useState(new Set()); - const handleCollapse = (index: number) => { - const newSet = new Set(); - collapsedNode.forEach(value => { - newSet.add(value); - }); - if (newSet.has(index)) { - newSet.delete(index); +function collapseAllNodes(attrs: IAttr[]) { + return attrs.filter((item, index) => { + const nextItem = attrs[index + 1]; + return nextItem ? nextItem.indentation - item.indentation > 0 : false; + }); +} + +function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) { + const [collapsedNode, setCollapsedNode] = useState(collapseAllNodes(attrs)); + const handleCollapse = (item: IAttr) => { + const nodes = [...collapsedNode]; + const i = nodes.indexOf(item); + if (i === -1) { + nodes.push(item); } else { - newSet.add(index); + nodes.splice(i, 1); } - setCollapsedNode(newSet); + setCollapsedNode(nodes); }; const showAttr = []; let currentIndentation = null; - attr.forEach((item, index) => { + attrs.forEach((item, index) => { const indentation = item.indentation; if (currentIndentation !== null) { if (indentation > currentIndentation) { @@ -48,11 +53,11 @@ function ComponentAttr({ name, attr }: { name: string, attr: IAttr[] }) { currentIndentation = null; } } - const nextItem = attr[index + 1]; + const nextItem = attrs[index + 1]; const hasChild = nextItem ? nextItem.indentation - item.indentation > 0 : false; - const isCollapsed = collapsedNode.has(index); + const isCollapsed = collapsedNode.includes(item); showAttr.push( -
(handleCollapse(index))}> +
(handleCollapse(item))}> {hasChild && } {`${item.name}`} {' :'} @@ -95,10 +100,10 @@ export default function ComponentInfo({ name, attrs }: IComponentInfo) {
- {context && } - {props && } - {state && } - {hooks && } + {context && } + {props && } + {state && } + {hooks && }
rendered by
diff --git a/libs/extension/src/components/FilterTree.ts b/libs/extension/src/components/FilterTree.ts index 58706902..446bd05d 100644 --- a/libs/extension/src/components/FilterTree.ts +++ b/libs/extension/src/components/FilterTree.ts @@ -27,7 +27,7 @@ function expandItemParent(item: BaseType, data: BaseType[], collapsedNodes: Base const index = data.indexOf(item); let currentIndentation = item.indentation; // 不对原始数据进行修改 - const newcollapsedNodes = [...collapsedNodes]; + const newCollapsedNodes = [...collapsedNodes]; for (let i = index - 1; i >= 0; i--) { const lastData = data[i]; const lastIndentation = lastData.indentation; @@ -35,13 +35,13 @@ function expandItemParent(item: BaseType, data: BaseType[], collapsedNodes: Base if (lastIndentation < currentIndentation) { // 更新缩进值,只找父节点的父节点,避免修改父节点的兄弟节点的展开状态 currentIndentation = lastIndentation; - const cIndex = newcollapsedNodes.indexOf(lastData); + const cIndex = newCollapsedNodes.indexOf(lastData); if (cIndex !== -1) { - newcollapsedNodes.splice(cIndex, 1); + newCollapsedNodes.splice(cIndex, 1); } } } - return newcollapsedNodes; + return newCollapsedNodes; } type BaseType = { From a81e49833d6493431fee44c3541a13e7f45be5a5 Mon Sep 17 00:00:00 2001 From: * <8> Date: Mon, 11 Apr 2022 11:04:30 +0800 Subject: [PATCH 45/46] Match-id-059b810a64127ac4b754b8a924dd931f129dafa2 --- .../src/components/ComponentInfo.tsx | 39 +++++++---- .../src/components/ComponentsInfo.less | 22 +++--- libs/extension/src/components/VTree.tsx | 16 ++++- libs/extension/src/panel/App.less | 9 ++- libs/extension/src/panel/App.tsx | 68 ++++++++++++++----- 5 files changed, 110 insertions(+), 44 deletions(-) diff --git a/libs/extension/src/components/ComponentInfo.tsx b/libs/extension/src/components/ComponentInfo.tsx index 80127470..10a1f637 100644 --- a/libs/extension/src/components/ComponentInfo.tsx +++ b/libs/extension/src/components/ComponentInfo.tsx @@ -4,6 +4,7 @@ import Debug from '../svgs/Debug'; import Copy from '../svgs/Copy'; import Triangle from '../svgs/Triangle'; import { useState } from 'horizon'; +import { IData } from './VTree'; type IComponentInfo = { name: string; @@ -12,7 +13,9 @@ type IComponentInfo = { context?: IAttr[]; state?: IAttr[]; hooks?: IAttr[]; - } + }; + parents: IData[]; + onClickParent: (item: IData) => void; }; type IAttr = { @@ -84,28 +87,38 @@ function ComponentAttr({ name, attrs }: { name: string, attrs: IAttr[] }) { ); } -export default function ComponentInfo({ name, attrs }: IComponentInfo) { +export default function ComponentInfo({ name, attrs, parents, onClickParent }: IComponentInfo) { const { state, props, context, hooks } = attrs; return (
- - {name} - - - - - - - + {name && <> + + {name} + + + + + + + + }
{context && } {props && } {state && } {hooks && } -
- rendered by +
+ {name &&
+ parents: { + parents.map(item => ()) + } +
}
diff --git a/libs/extension/src/components/ComponentsInfo.less b/libs/extension/src/components/ComponentsInfo.less index 3622363e..9a52e2fb 100644 --- a/libs/extension/src/components/ComponentsInfo.less +++ b/libs/extension/src/components/ComponentsInfo.less @@ -32,11 +32,11 @@ .componentInfoMain { overflow-y: auto; - :last-child { + >:last-child { border-bottom: unset; } - :first-child { + >:first-child { padding: unset; } @@ -57,15 +57,11 @@ .attrType { flex: 1 1 0; } - - .attrCopy { flex: 0 0 1rem; padding-right: 1rem; } } - - .attrDetail { padding-bottom: 0.5rem; @@ -84,9 +80,19 @@ } } } + } - .renderInfo { - flex: 1 1 0; + .parentsInfo { + flex: 1 1 0; + .parent { + display: block; + cursor: pointer; + text-align: left; + color: @component-name-color; + width: 100%; + &:hover { + background-color: @select-color; + } } } } diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 044e64dc..a286e411 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -102,13 +102,20 @@ function VTree(props: { onRendered: (renderInfo: renderInfoType) => void, collapsedNodes?: IData[], onCollapseNode?: (item: IData[]) => void, + selectItem: IData[], + onSelectItem: (item: IData) => void, }) { - const { data, highlightValue, scrollToItem, onRendered, onCollapseNode } = props; + const { data, highlightValue, scrollToItem, onRendered, onCollapseNode, onSelectItem } = props; const [collapseNode, setCollapseNode] = useState(props.collapsedNodes || []); - const [selectItem, setSelectItem] = useState(scrollToItem); + const [selectItem, setSelectItem] = useState(props.selectItem); useEffect(() => { setSelectItem(scrollToItem); }, [scrollToItem]); + useEffect(() => { + if (props.selectItem !== selectItem) { + setSelectItem(props.selectItem); + } + }, [props.selectItem]); useEffect(() => { setCollapseNode(props.collapsedNodes || []); }, [props.collapsedNodes]); @@ -128,6 +135,9 @@ function VTree(props: { }; const handleClickItem = (item: IData) => { setSelectItem(item); + if (onSelectItem) { + onSelectItem(item); + } }; let currentCollapseIndentation: null | number = null; @@ -159,7 +169,7 @@ function VTree(props: { width={width} height={height} itemHeight={18} - scrollToItem={scrollToItem} + scrollToItem={selectItem} filter={filter} onRendered={onRendered} > diff --git a/libs/extension/src/panel/App.less b/libs/extension/src/panel/App.less index dc0da330..b974c557 100644 --- a/libs/extension/src/panel/App.less +++ b/libs/extension/src/panel/App.less @@ -7,6 +7,12 @@ font-size: @common-font-size; } +button { + border: none; + background: none; + padding: 0; +} + .left { flex: 7; display: flex; @@ -41,10 +47,7 @@ } .searchAction { - padding: 0; flex: 0 0 1rem; - border: none; - background: none; height: 1rem; color: @arrow-color; &:hover{ diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index dcc18a67..7cea62e9 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -34,23 +34,27 @@ const parseVNodeData = (rawData) => { return data; }; +const getParents = (item: IData | null, parsedVNodeData: IData[]) => { + const parents: IData[] = []; + if (item) { + const index = parsedVNodeData.indexOf(item); + let indentation = item.indentation; + for (let i = index; i >= 0; i--) { + const last = parsedVNodeData[i]; + const lastIndentation = last.indentation; + if (lastIndentation < indentation) { + parents.push(last); + indentation = lastIndentation; + } + } + } + return parents; +}; + function App() { const [parsedVNodeData, setParsedVNodeData] = useState([]); - const [componentInfo, setComponentInfo] = useState({ name: null, attrs: {} }); - - useEffect(() => { - if (isDev) { - const parsedData = parseVNodeData(mockParsedVNodeData); - setParsedVNodeData(parsedData); - setComponentInfo({ - name: 'Demo', - attrs: { - state: parsedMockState, - props: parsedMockState, - }, - }); - } - }, []); + const [componentAttrs, setComponentAttrs] = useState({}); + const [selectComp, setSelectComp] = useState(null); const { filterValue, @@ -65,13 +69,37 @@ function App() { setCollapsedNodes, } = FilterTree({ data: parsedVNodeData }); + useEffect(() => { + if (isDev) { + const parsedData = parseVNodeData(mockParsedVNodeData); + setParsedVNodeData(parsedData); + setComponentAttrs({ + state: parsedMockState, + props: parsedMockState, + }); + } + }, []); + const handleSearchChange = (str: string) => { setFilterValue(str); }; + const handleSelectComp = (item: IData) => { + setComponentAttrs({ + state: parsedMockState, + props: parsedMockState, + }); + setSelectComp(item); + }; + + const handleClickParent = (item: IData) => { + setSelectComp(item); + }; + const onRendered = (info) => { setShowItems(info.visibleItems); }; + const parents = getParents(selectComp, parsedVNodeData); return (
@@ -99,11 +127,17 @@ function App() { onRendered={onRendered} collapsedNodes={collapsedNodes} onCollapseNode={setCollapsedNodes} - scrollToItem={currentItem} /> + scrollToItem={currentItem} + selectItem={selectComp} + onSelectItem={handleSelectComp} />
- +
); From 626acff217936015d8fdb344060490eeb8ba4909 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 12 Apr 2022 10:51:36 +0800 Subject: [PATCH 46/46] Match-id-0bfccd0377bf0ca213fe7d07078950badd89e215 --- .eslintrc.js | 1 + libs/horizon/index.ts | 3 +++ libs/horizon/src/renderer/hooks/HookExternal.ts | 3 +++ 3 files changed, 7 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 7aedabc1..f0692e11 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -28,6 +28,7 @@ module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-empty-function': 'off', semi: ['warn', 'always'], quotes: ['warn', 'single'], 'accessor-pairs': 'off', diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts index a7df0d8b..cd194973 100644 --- a/libs/horizon/index.ts +++ b/libs/horizon/index.ts @@ -24,6 +24,7 @@ import { useReducer, useRef, useState, + useDebugValue } from './src/renderer/hooks/HookExternal'; import { launchUpdateFromVNode as _launchUpdateFromVNode, asyncUpdates } from './src/renderer/TreeBuilder'; import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue'; @@ -54,6 +55,7 @@ const Horizon = { forwardRef, lazy, memo, + useDebugValue, useCallback, useContext, useEffect, @@ -89,6 +91,7 @@ export { forwardRef, lazy, memo, + useDebugValue, useCallback, useContext, useEffect, diff --git a/libs/horizon/src/renderer/hooks/HookExternal.ts b/libs/horizon/src/renderer/hooks/HookExternal.ts index e4610f43..307a80cb 100644 --- a/libs/horizon/src/renderer/hooks/HookExternal.ts +++ b/libs/horizon/src/renderer/hooks/HookExternal.ts @@ -73,3 +73,6 @@ export function useImperativeHandle( ): void { return useImperativeHandleImpl(ref, create, deps); } + +// 兼容react-redux +export const useDebugValue = () => {};