[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 100,\n  \"commit\": false,\n  \"contributors\": [\n    {\n      \"login\": \"manojVivek\",\n      \"name\": \"Manoj Vivek\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/1283424?v=4\",\n      \"profile\": \"http://x.com/vivek_jonam\",\n      \"contributions\": [\n        \"code\",\n        \"test\",\n        \"projectManagement\"\n      ]\n    },\n    {\n      \"login\": \"esprush\",\n      \"name\": \"Suresh P\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/26249498?v=4\",\n      \"profile\": \"https://github.com/esprush\",\n      \"contributions\": [\n        \"code\",\n        \"test\",\n        \"projectManagement\"\n      ]\n    },\n    {\n      \"login\": \"sprabowo\",\n      \"name\": \"Sigit Prabowo\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/11748183?v=4\",\n      \"profile\": \"https://github.com/sprabowo\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"leon0707\",\n      \"name\": \"Leon Feng\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/523684?v=4\",\n      \"profile\": \"https://github.com/leon0707\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"kishoreio\",\n      \"name\": \"Kishore S\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/30261988?v=4\",\n      \"profile\": \"https://github.com/kishoreio\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jjavierdguezas\",\n      \"name\": \"José Javier Rodríguez Zas\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/13673443?v=4\",\n      \"profile\": \"https://jjavierdguezas.github.io\",\n      \"contributions\": [\n        \"code\",\n        \"test\"\n      ]\n    },\n    {\n      \"login\": \"romanakash\",\n      \"name\": \"Roman Akash\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/40427975?v=4\",\n      \"profile\": \"https://github.com/romanakash\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"RomainFrancony\",\n      \"name\": \"Romain Francony\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/22396965?v=4\",\n      \"profile\": \"https://github.com/RomainFrancony\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"AARYAN-MAHENDRA\",\n      \"name\": \"AARYAN-MAHENDRA\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/64866670?v=4\",\n      \"profile\": \"https://github.com/AARYAN-MAHENDRA\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Nothing-Works\",\n      \"name\": \"Andy\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/18606648?v=4\",\n      \"profile\": \"https://github.com/Nothing-Works\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Kidcredo\",\n      \"name\": \"Ryan Pais\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/15522605?v=4\",\n      \"profile\": \"https://github.com/Kidcredo\",\n      \"contributions\": [\n        \"code\",\n        \"test\"\n      ]\n    },\n    {\n      \"login\": \"Grafikart\",\n      \"name\": \"Jonathan\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/395137?v=4\",\n      \"profile\": \"https://grafikart.fr\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"heygema\",\n      \"name\": \"Gema Anggada ✌︎\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/10743728?v=4\",\n      \"profile\": \"https://github.com/heygema\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jonathanurias96\",\n      \"name\": \"jonathanurias96\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/57416786?v=4\",\n      \"profile\": \"https://github.com/jonathanurias96\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"falecci\",\n      \"name\": \"Federico Alecci\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/17703824?v=4\",\n      \"profile\": \"https://falecci.dev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"MuminjonGuru\",\n      \"name\": \"Abduraimov Muminjon\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/24930020?v=4\",\n      \"profile\": \"https://linkedin.com/in/muminjon-abduraimov/\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"vlazaroes\",\n      \"name\": \"Víctor Lázaro\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/38981659?v=4\",\n      \"profile\": \"https://www.vlazaro.es/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kvnam\",\n      \"name\": \"Kavita Nambissan\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/3608742?v=4\",\n      \"profile\": \"https://github.com/kvnam\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"prashantpalikhe\",\n      \"name\": \"Prashant Palikhe\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/2657709?v=4\",\n      \"profile\": \"https://x.com/PrashantPalikhe\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jaunesarmiento\",\n      \"name\": \"Jaune Sarmiento\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/1166928?v=4\",\n      \"profile\": \"https://github.com/jaunesarmiento\",\n      \"contributions\": [\n        \"content\"\n      ]\n    },\n    {\n      \"login\": \"diego-vieira\",\n      \"name\": \"Diego Vieira\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/930792?v=4\",\n      \"profile\": \"https://github.com/diego-vieira\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"pajaydev\",\n      \"name\": \"Ajaykumar\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/21375014?v=4\",\n      \"profile\": \"https://github.com/pajaydev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kirubakarthikeyan\",\n      \"name\": \"Kiruba Karan\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/38885946?v=4\",\n      \"profile\": \"https://github.com/kirubakarthikeyan\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sebasrodriguez\",\n      \"name\": \"Sebastián Rodríguez\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/1605931?v=4\",\n      \"profile\": \"https://github.com/sebasrodriguez\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"karthick3018\",\n      \"name\": \"Karthick Raja\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/47154512?v=4\",\n      \"profile\": \"https://github.com/karthick3018\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jzabala\",\n      \"name\": \"Johnny Zabala\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/1315054?v=4\",\n      \"profile\": \"https://github.com/jzabala\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rossmoody\",\n      \"name\": \"Ross Moody\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/29072694?v=4\",\n      \"profile\": \"http://rossmoody.com\",\n      \"contributions\": [\n        \"design\"\n      ]\n    },\n    {\n      \"login\": \"mehrdad-shokri\",\n      \"name\": \"Mehrdad Shokri\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/13661520?v=4\",\n      \"profile\": \"https://shokri.me\",\n      \"contributions\": [\n        \"infra\"\n      ]\n    },\n    {\n      \"login\": \"abakermi\",\n      \"name\": \"Abdelhak Akermi\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/60294727?v=4\",\n      \"profile\": \"https://github.com/abakermi\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"crperezt\",\n      \"name\": \"Carlos Perez\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/20329014?v=4\",\n      \"profile\": \"https://github.com/crperezt\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"JayArya\",\n      \"name\": \"Jayant Arya\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/42388314?v=4\",\n      \"profile\": \"https://github.com/JayArya\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"JohnRawlins\",\n      \"name\": \"John Rawlins\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/42707277?v=4\",\n      \"profile\": \"https://github.com/JohnRawlins\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"lepasq\",\n      \"name\": \"lepasq\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/53230128?v=4\",\n      \"profile\": \"https://github.com/lepasq\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mrfelfel\",\n      \"name\": \"mrfelfel\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/19575588?v=4\",\n      \"profile\": \"https://github.com/mrfelfel\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gorogoroumaru\",\n      \"name\": \"gorogoroumaru\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/30716350?v=4\",\n      \"profile\": \"https://x.com/gorogoroumaru\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ruisaraiva19\",\n      \"name\": \"Rui Saraiva\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/7356098?v=4\",\n      \"profile\": \"http://ruisaraiva.dev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"MBakirci\",\n      \"name\": \"Mehmet Bakirci\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/9880089?v=4\",\n      \"profile\": \"http://www.bakirci.nl\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"JLambertazzo\",\n      \"name\": \"Julien Bertazzo Lambert\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/42924425?v=4\",\n      \"profile\": \"https://github.com/JLambertazzo\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sidthesloth92\",\n      \"name\": \"Dinesh Balaji\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/4656109?v=4\",\n      \"profile\": \"http://dbwriteups.wordpress.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"med1001\",\n      \"name\": \"MedBMoussa\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/26111211?v=4\",\n      \"profile\": \"https://github.com/med1001\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"lucievr\",\n      \"name\": \"Lucie Vrsovska\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/46979603?v=4\",\n      \"profile\": \"http://www.lucie.dev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jcabak\",\n      \"name\": \"Jakub Cabak\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1818155?v=4\",\n      \"profile\": \"https://github.com/jcabak\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"durgakiran\",\n      \"name\": \"Palakurthi Durga Kiran Kumar\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/17452039?v=4\",\n      \"profile\": \"https://github.com/durgakiran\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"karllabrador\",\n      \"name\": \"Karl Labrador\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/58193703?v=4\",\n      \"profile\": \"https://github.com/karllabrador\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rishichawda\",\n      \"name\": \"Rishi Kumar Chawda\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/26366288?v=4\",\n      \"profile\": \"http://rishikc.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"crocarneiro\",\n      \"name\": \"Carlos Rafael de Oliveira Carneiro\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10589421?v=4\",\n      \"profile\": \"https://github.com/crocarneiro\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"zachOS-tech\",\n      \"name\": \"Zach Hoskins\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/50255197?v=4\",\n      \"profile\": \"http://zachos.tech\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kiwan97\",\n      \"name\": \"KIWAN KIM\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/25267859?v=4\",\n      \"profile\": \"https://github.com/kiwan97\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"agaertner\",\n      \"name\": \"Andreas\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13819164?v=4\",\n      \"profile\": \"https://github.com/agaertner\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"p4nu\",\n      \"name\": \"Panu Valtanen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/28947061?v=4\",\n      \"profile\": \"https://www.linkedin.com/in/panu-valtanen-446ba9108/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"d19dotca\",\n      \"name\": \"Dustin Dauncey\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8153796?v=4\",\n      \"profile\": \"http://www.d19.ca\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"heagan01\",\n      \"name\": \"heagan01\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/54394590?v=4\",\n      \"profile\": \"https://github.com/heagan01\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Th0masCat\",\n      \"name\": \"Sahil Jangra\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/74812563?v=4\",\n      \"profile\": \"https://github.com/Th0masCat\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"nopdotcom\",\n      \"name\": \"Jay Carlson\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1357866?v=4\",\n      \"profile\": \"https://github.com/nopdotcom\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Mikadifo\",\n      \"name\": \"Michael Padilla\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/51935560?v=4\",\n      \"profile\": \"http://mikadifo.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"NarrowCode\",\n      \"name\": \"Andreas Steinkellner\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6213380?v=4\",\n      \"profile\": \"https://narrowcode.xyz\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"aniknia\",\n      \"name\": \"aniknia\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/40159649?v=4\",\n      \"profile\": \"http://niknia.dev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"WayneRocha\",\n      \"name\": \"Wayne Rocha\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/62760711?v=4\",\n      \"profile\": \"https://github.com/WayneRocha\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"crbon\",\n      \"name\": \"crbon\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2604330?v=4\",\n      \"profile\": \"https://github.com/crbon\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"themohammadsa\",\n      \"name\": \"Mohammad S\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/59393936?v=4\",\n      \"profile\": \"https://linktr.ee/themohammadsa\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ReshadSadik\",\n      \"name\": \"Reshad Sadik\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/66641469?v=4\",\n      \"profile\": \"https://github.com/ReshadSadik\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"PeterKwesiAnsah\",\n      \"name\": \"Kwesi Ansah\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/31078314?v=4\",\n      \"profile\": \"https://github.com/PeterKwesiAnsah\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"monalisamsteccentric\",\n      \"name\": \"Monalisa Sahoo\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/65477771?v=4\",\n      \"profile\": \"https://github.com/monalisamsteccentric\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"codewithmitesh\",\n      \"name\": \"Mitesh Tank\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/85953650?v=4\",\n      \"profile\": \"https://github.com/codewithmitesh\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Ryan0204\",\n      \"name\": \"Ryan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/82715592?v=4\",\n      \"profile\": \"https://github.com/Ryan0204\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jibranabsarulislam\",\n      \"name\": \"jayway\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/70596906?v=4\",\n      \"profile\": \"https://www.jibran.me\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"afermon\",\n      \"name\": \"Alex Fernandez\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3981106?v=4\",\n      \"profile\": \"https://www.xicre.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Danial-Gharib\",\n      \"name\": \"Danial Gharib\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/90343552?v=4\",\n      \"profile\": \"https://github.com/Danial-Gharib\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"amenk\",\n      \"name\": \"Alexander Menk\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1087128?v=4\",\n      \"profile\": \"http://www.x.com/s3lf\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"tunahangediz\",\n      \"name\": \"Tunahan Gediz\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/75015671?v=4\",\n      \"profile\": \"http://tunahangediz.com\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"jeffbowen\",\n      \"name\": \"Jeff Bowen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/272795?v=4\",\n      \"profile\": \"https://refer.codes/jeff\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ParamBirje\",\n      \"name\": \"Param Birje\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/87022870?v=4\",\n      \"profile\": \"https://github.com/ParamBirje\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"prajjwalyd\",\n      \"name\": \"Prajjwal Yadav\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/111794524?v=4\",\n      \"profile\": \"https://github.com/prajjwalyd\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"lyncasterc\",\n      \"name\": \"Steven Cabrera\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/49458912?v=4\",\n      \"profile\": \"https://github.com/lyncasterc\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"negar-75\",\n      \"name\": \"negar nasiri\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/113235504?v=4\",\n      \"profile\": \"https://github.com/negar-75\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gauravsingh94\",\n      \"name\": \"Gaurav Singh\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/99260988?v=4\",\n      \"profile\": \"https://github.com/gauravsingh94\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"NishidhJain\",\n      \"name\": \"Nishidh Jain\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/61869195?v=4\",\n      \"profile\": \"https://github.com/NishidhJain\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mishrasamiksha\",\n      \"name\": \"Samiksha Mishra\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/38784342?v=4\",\n      \"profile\": \"https://github.com/mishrasamiksha\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"astuanax\",\n      \"name\": \"Len Dierickx\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1730624?v=4\",\n      \"profile\": \"http://www.astuanax.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"surajbobade\",\n      \"name\": \"Suraj Bobade\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/102910293?v=4\",\n      \"profile\": \"https://github.com/surajbobade\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sagarhedaoo\",\n      \"name\": \"Sagar Hedaoo\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10000167?v=4\",\n      \"profile\": \"https://sagarhedaoo.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"violetadev\",\n      \"name\": \"V\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/36138541?v=4\",\n      \"profile\": \"http://www.violeta.dev\",\n      \"contributions\": [\n        \"code\",\n        \"test\"\n      ]\n    },\n    {\n      \"login\": \"minowau\",\n      \"name\": \"Prabhas Jupalli\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/139740712?v=4\",\n      \"profile\": \"https://github.com/minowau\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Pulkitxm\",\n      \"name\": \"Pulkit\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/65671483?v=4\",\n      \"profile\": \"http://devpulkit.in\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"brandonyee-cs\",\n      \"name\": \"Brandon Yee\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/139765638?v=4\",\n      \"profile\": \"https://github.com/brandonyee-cs\",\n      \"contributions\": [\n        \"content\"\n      ]\n    },\n    {\n      \"login\": \"wp043\",\n      \"name\": \"Wendy Pan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/110360465?v=4\",\n      \"profile\": \"https://github.com/wp043\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"pranavithape\",\n      \"name\": \"Pranav Ithape\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/170559714?v=4\",\n      \"profile\": \"https://github.com/pranavithape\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Sukrit-Prakash\",\n      \"name\": \"SUKRIT PRAKASH SINGH\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/136228545?v=4\",\n      \"profile\": \"https://github.com/Sukrit-Prakash\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"aryan262\",\n      \"name\": \"Aryan Panchal\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/97938438?v=4\",\n      \"profile\": \"https://github.com/aryan262\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"samranahm\",\n      \"name\": \"Samran Ahmed\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/149153498?v=4\",\n      \"profile\": \"https://github.com/samranahm\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"asatyam\",\n      \"name\": \"SatyamAgrawal\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/95954551?v=4\",\n      \"profile\": \"https://github.com/Asatyam\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"saiTharunDusa\",\n      \"name\": \"Sai Tharun Dusa\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/169873642?v=4\",\n      \"profile\": \"https://github.com/saiTharunDusa\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"happymvp\",\n      \"name\": \"Pavlo\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/179954458?v=4\",\n      \"profile\": \"http://happymvp.com\",\n      \"contributions\": [\n        \"code\",\n        \"test\"\n      ]\n    },\n    {\n      \"login\": \"AnalyticalDataArtisan\",\n      \"name\": \"AnalyticalDataArtisan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/183036785?v=4\",\n      \"profile\": \"https://github.com/AnalyticalDataArtisan\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Kieren-Foenander\",\n      \"name\": \"Kieren-Foenander\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/68266113?v=4\",\n      \"profile\": \"https://github.com/Kieren-Foenander\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Trishix\",\n      \"name\": \"Trishit Swarnakar\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/170200412?v=4\",\n      \"profile\": \"https://github.com/Trishix\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"cseas\",\n      \"name\": \"Abhijeet Singh\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/29686866?v=4\",\n      \"profile\": \"https://blog.absingh.com/\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"meezumi\",\n      \"name\": \"aaryan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/93996658?v=4\",\n      \"profile\": \"http://aaryancreates.framer.media\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mklikushin\",\n      \"name\": \"Michael Klikushin\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/135151016?v=4\",\n      \"profile\": \"https://github.com/mklikushin\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"bhnprksh222\",\n      \"name\": \"Bhanu Prakash\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/48930756?v=4\",\n      \"profile\": \"https://github.com/bhnprksh222\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sayagodev\",\n      \"name\": \"Saúl Sáyago\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/67727553?v=4\",\n      \"profile\": \"http://sayago.dev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    }\n  ],\n  \"contributorsPerLine\": 5,\n  \"projectName\": \"responsively-app\",\n  \"projectOwner\": \"responsively-org\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"skipCi\": true,\n  \"commitConvention\": \"none\",\n  \"commitType\": \"docs\"\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "/desktop-app-legacy/* export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: responsively-org # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: responsively\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/01-bug-report.md",
    "content": "---\nname: \"\\U0001F41E Bug report\"\nabout: Report a bug in Responsively\n---\n\n<!-- 🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅\nHi there! 😄\n\nTo expedite issue processing please search open and closed issues before submitting a new one. Existing issues often contain information about workarounds, resolution, or progress updates.\n🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅 -->\n\n# 🐞 bug report\n\n### ✍️ Description\n\n<!-- A clear and concise description of the problem. -->\n\n### 🕵🏼‍♂️ Is this a regression?\n\n<!-- Did this behavior use to work in the previous version? -->\n\n### 🔬 Minimal Reproduction\n\n<!-- Clear steps to re-produce the issue. -->\n\n### 🌍 Your Environment\n\n<!-- Press `Ctrl/Cmd + F1` and paste it here. -->\n<pre><code>\n\n</code></pre>\n\n### 🔥 Exception or Error or Screenshot\n\n<pre><code>\n\n</code></pre>\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/02-feature-request.md",
    "content": "---\nname: \"\\U0001F680 Feature request\"\nabout: Suggest a feature for Responsively.\n---\n\n# 🚀 Feature Request\n\n### 📝 Description\n\n<!-- A clear and concise description of the problem or missing capability. -->\n\n### ✨ Describe the solution you'd like\n\n<!-- If you have a solution in mind, please describe it. -->\n\n### ✍️ Describe alternatives you've considered\n\n<!-- Have you considered any alternative solutions or workarounds? -->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "# ✨ Pull Request\n\n### 📓 Referenced Issue\n\n<!-- Please link the related issue. Use # before the issue number and use the verbs 'fixes', 'resolves' to auto-link it, for eg, Fixes: #&lt;issue-number&gt; -->\n\n### ℹ️ About the PR\n\n<!-- Please provide a description of your solution if it is not clear in the related issue or if the PR has a breaking change. If there is an interesting topic to discuss or you have questions or there is an issue with electron or another library that you have used. -->\n\n### 🖼️ Testing Scenarios / Screenshots\n\n<!-- Please include screenshots or gif to showcase the final output. Also, try to explain the testing you did to validate your change.  -->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"npm\" # See documentation for possible values\n    directory: \"/desktop-app\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/opencollective.yml",
    "content": "collective: responsively\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - discussion\n  - security\n# Label to use when marking an issue as stale\nstaleLabel: wontfix\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ \"main\" ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ \"main\" ]\n  schedule:\n    - cron: '44 16 * * 4'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n    defaults:\n      run:\n        working-directory: ./desktop-app\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'javascript' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v3\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        \n        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n        # queries: security-extended,security-and-quality\n\n        \n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v3\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n\n    #   If the Autobuild fails above, remove it and uncomment the following three lines. \n    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.\n\n    # - run: |\n    #   echo \"Run, Build Application using script\"\n    #   ./location_of_script_within_repo/buildscript.sh\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v3"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\n\non:\n  workflow_dispatch:\n\njobs:\n  publish:\n    runs-on: ${{ matrix.os }}\n    defaults:\n      run:\n        working-directory: ./desktop-app\n\n    strategy:\n      matrix:\n        os: [macos-13]\n\n    steps:\n      - name: Checkout git repo\n        uses: actions/checkout@v4\n\n      - name: Install Node and NPM\n        uses: actions/setup-node@v4\n        with:\n          node-version: 24\n          cache: ${{ !env.ACT && 'npm' || '' }} # Disable cache for nektos/act\n          cache-dependency-path: ./desktop-app/yarn.lock\n\n      - name: Install and build\n        run: |\n          yarn install\n          yarn run postinstall\n          yarn run build\n\n      - name: Bump version\n        run: |\n          cd release/app\n          yarn version --preid beta --prerelease\n          git push\n\n      - name: Publish releases\n        env:\n          # These values are used for auto updates signing\n          APPLE_ID: ${{ secrets.APPLEID }}\n          APPLE_ID_PASS: ${{ secrets.APPLEIDPASS }}\n          CSC_LINK: ${{ secrets.CSC_LINK }}\n          CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}\n          # This is used for uploading release assets to github\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          yarn exec electron-builder -- --publish always --win --mac --linux --arm64 --x64\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    defaults:\n      run:\n        working-directory: ./desktop-app\n\n    strategy:\n      matrix:\n        os: [macos-latest]\n      fail-fast: false\n\n    steps:\n      - name: Check out Git repository\n        uses: actions/checkout@v4\n\n      - name: Install Node.js and NPM\n        uses: actions/setup-node@v4\n        with:\n          node-version: 24\n          cache: ${{ !env.ACT && 'npm' || '' }} # Disable cache for nektos/act\n          cache-dependency-path: ./desktop-app/yarn.lock\n\n      - name: yarn install\n        run: |\n          yarn install --network-timeout 120000\n\n      - name: yarn test\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          yarn run package\n          yarn run lint\n          yarn exec tsc\n          yarn test\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.idea\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "\n## Contributing\n\nContributions are welcome and always appreciated!\n\nTo begin working on an issue, simply leave a comment indicating that you're taking it on. There's no need to be officially assigned to the issue before you start.\n\n### Before Starting\nDo keep in mind before you start working on an issue / posting a PR:\n- Search existing PRs related to that issue which might close them\n- Confirm if other contributors are working on the same issue\n\n### Tips & Things to Consider\n- We are active in Discord and can help out if you get stuck, [join us!](https://responsively.app/join-discord)\n- PRs with tests are highly appreciated\n- Avoid adding third party libraries, whenever possible\n- Unless you are helping out by updating dependencies, you should not be uploading your yarn.lock or updating any dependencies in your PR\n- If you are unsure where to start, contact us and we will point you to a first good issue\n\n## Run Locally\nEnsure you have the following dependencies installed:\n- Install `node` and `yarn`\n- Configure your IDE to support ESLint and Prettier extensions.\n\nAfter having above installed, proceed through the following steps to setup the codebase locally.\n\n- Fork the project & [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) it locally.\n\n![fork-project](https://github.com/responsively-org/responsively-app/assets/87022870/2cae8b2a-850c-4f80-8ede-32eba622a854)\n\n- Create a new separate branch.\n\n```bash\ngit checkout -b BRANCH_NAME\n```\n- Go to the desktop-app directory.\n\n```bash\ncd desktop-app\n```\n\n- Run the following command to install dependencies inside the desktop-app directory.\n\n```bash\nyarn\n```\n\n- This will start the app for local development with live reloading.\n\n```bash\nyarn dev\n```\n\n## Running Tests\n\nIt is crucial to test your code before submitting a pull request. Please ensure that you can make a complete production build before you submit your code for merging.\n\n- Build the project\n```bash\nyarn build\n```\n\n- Now test your code using the following command\n```bash\nyarn test\n```\n\nMake sure the tests have successfully passed. \n\n## Pull Request\n\n🎉 Now that you're ready to submit your code for merging, there are some points to keep in mind.\n\n- Fill your PR description template accordingly.\n    - Have an appropriate title and description.\n    - Include relevant screenshots/gifs.\n\n- If your PR fixes some issue, be sure to add this line with the issue **in the body** of the Pull Request description.\n```text\nFixes #00000\n```\n\n- If your PR is referencing an issue\n```text\nRefs #00000\n```\n\n- Ensure that \"Allow edits from maintainers\" option is checked.\n\n## Community\nNeed help on a solution from fellow contributors or want to discuss about a feature/issue? \n\nJoin our [Discord](https://responsively.app/join-discord)!"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "MAINTAINERS.md",
    "content": "# Project Maintainer\n\n## How to become a maintainer\nThe following are the criteria to get considered becoming a maintainer:\n- Implemented two or more features to the project.\n- Being active and sticking around on GitHub and Discord, engaging with other user's on their problems and providing useful solutions.\n\nContributors who match the above criteria would be evaluated by the existing maintainers and invited as maintainers if they see fit.\n\n## Responsibilities\nAs a project maintainer for Responsively App, your responsibilities include:\n\n#### Code/Tech:\n- Do issue triage on user reported bugs.\n- Review and merge contributions.\n- Advise new contributors on better practices in their code.\n- Automate any manual process in the project/CI workflow.\n- Constantly strive to improve the DX of the project\n\n#### Community\n- Be nice to everyone on Discord and GitHub.\n- Try and provide sensible solution to user reported problems.\n\nAlways feel free to reach out to `p.manoj.vivek@gmail.com` or `manojVivek` on Discord if you have any questions. \n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <img src=\"https://responsively.app/assets/img/logo.png\" alt=\"Responsively Logo\" width=\"150\">\n  <h1>Responsively App <a href=\"https://github.com/responsively-org/responsively-app/releases/latest\" target=\"_blank\"><img alt=\"GitHub release\" src=\"https://img.shields.io/github/v/release/responsively-org/responsively-app\"></a></h1>\n  <strong>A must-have dev tool for web developers for quicker responsive web development. 🚀</strong>\n  <h6>Save time by becoming 5x faster!</h6>\n</div>\n<br>\n\n<p align=\"center\">\n  <a href=\"https://x.com/ResponsivelyApp\" target=\"_blank\">\n    <img src=\"https://img.shields.io/twitter/follow/responsivelyApp?color=26A0ED&label=Follow&logo=twitter&logoColor=white&style=flat\" alt=\"Twitter\">\n  </a>\n\n  <a href=\"#contributors-\" target=\"_blank\">\n    <img src=\"https://img.shields.io/github/all-contributors/responsively-org/responsively-app?style=flat\" alt=\"contributors\">\n  </a>\n\n  <a href=\"https://responsively.app/join-discord\" target=\"_blank\">\n    <img src=\"https://img.shields.io/badge/Join%20-Discord-blue?logo=discord&logoColor=white\" alt=\"Discord\">\n  </a>\n\n  <a href=\"https://xscode.com/manojvivek/responsively-app\" target=\"_blank\">\n    <img src=\"https://img.shields.io/badge/Available%20on-xs%3Acode-blue?style=?style=flat&logo=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAAlUlEQVR42uzXSwqAMAwE0Mn9L+3Ggtgkk35QwcnSJo9S+yGwM9DCooCbgn4YrJ4CIPUcQF7/XSBbx2TEz4sAZ2q1RAECBAiYBlCtvwN+KiYAlG7UDGj59MViT9hOwEqAhYCtAsUZvL6I6W8c2wcbd+LIWSCHSTeSAAECngN4xxIDSK9f4B9t377Wd7H5Nt7/Xz8eAgwAvesLRjYYPuUAAAAASUVORK5CYII=\" alt=\"XS:Code\">\n  </a>\n\n  <a href=\"https://github.com/responsively-org/responsively-app/issues\" target=\"_blank\">\n    <img src=\"https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat\" alt=\"PRs Welcome\">\n  </a>\n\n   <a href=\"https://opencollective.com/responsively\" target=\"_blank\">\n    <img alt=\"Open Collective backers and sponsors\" src=\"https://img.shields.io/opencollective/all/responsively\">\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.producthunt.com/posts/responsively?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-responsively\" target=\"_blank\">\n    <img src=\"https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=200375&theme=light&period=daily\" alt=\"ProductHunt\">\n  </a>\n</p>\n\n<p align=\"center\">\n  Download Now (free!): <a href=\"https://responsively.app/download\" target=\"_blank\">\n    responsively.app\n  </a>\n</p>\n<br>\n\n## Responsively App\n\n> A modified browser built using [Electron](https://www.electronjs.org/) that helps in responsive web development.\n> <br>\n\n![Quick Demo](https://responsively.app/assets/img/responsively-app.gif)\n\n## Features\n\n1. Mirrored User-interactions across all devices.\n2. Customizable preview layout to suit all your needs.\n3. One handy elements inspector for all devices in preview.\n4. 30+ built-in device profiles with the option to add custom devices.\n5. One-click screenshots on all your devices.\n6. Hot reloading is supported for developers.\n\nPlease visit the website to learn more about the application - https://responsively.app\n\n## Download\n\nThe application is available for Mac, Windows and Linux platforms. Please <a href=\"https://responsively.app/download\" target=\"_blank\">download it from responsively.app</a>\n\nAlternatively, MacOS users can use [`brew`](https://formulae.brew.sh/cask/responsively) <a href=\"https://formulae.brew.sh/cask/responsively\" target=\"_blank\"> <img src=\"https://badgen.net/homebrew/cask/dy/responsively\" alt=\"Homebrew installs\"></a>\n\n```bash\nbrew install --cask responsively\n```\n\nAlso, Windows users can use [`chocolatey`](https://chocolatey.org/packages/responsively/) <a href=\"https://chocolatey.org/packages/responsively/\" target=\"_blank\"> <img src=\"https://img.shields.io/chocolatey/dt/responsively\" alt=\"Chocolatey installs\"></a>\n\n```bash\nchoco install responsively\n```\n\nor [`winget`](https://github.com/microsoft/winget-cli):\n\n```bash\nwinget install ResponsivelyApp\n```\n\nLinux users using an RPM Package Manager can use `rpm`\n\n```bash\nsudo rpm -i https://github.com/responsively-org/responsively-app/releases/download/v[VERSION]/Responsively-App-[VERSION].x86_64.rpm\n```\n\notherwise, download an AppImage from [the releases page](https://github.com/responsively-org/responsively-app/releases)\n\nFollow us on Twitter for future updates - [![Twitter Follow](https://img.shields.io/twitter/follow/ResponsivelyApp?style=social)](https://x.com/ResponsivelyApp)\n\n## Browser Extension\n\nInstall the handy browser extension to easily send links from your browser to the app and preview instantly.\n\n- [Download for Chrome](https://chrome.google.com/webstore/detail/responsively-helper/jhphiidjkooiaollfiknkokgodbaddcj) <a href=\"https://chrome.google.com/webstore/detail/responsively-helper/jhphiidjkooiaollfiknkokgodbaddcj\" target=\"_blank\"><img alt=\"Chrome Web Store\" src=\"https://img.shields.io/chrome-web-store/users/jhphiidjkooiaollfiknkokgodbaddcj?color=blue\"></a>\n- [Download for Firefox](https://addons.mozilla.org/en-US/firefox/addon/responsively-helper/) <a href=\"https://addons.mozilla.org/en-US/firefox/addon/responsively-helper/\" target=\"_blank\"><img alt=\"Mozilla Add-on\" src=\"https://img.shields.io/amo/users/responsively-helper\"></a>\n- [Download for Edge](https://microsoftedge.microsoft.com/addons/detail/responsively-helper/ooiejjgflcgkbbehheengalibfehaojn) <a href=\"https://microsoftedge.microsoft.com/addons/detail/responsively-helper/ooiejjgflcgkbbehheengalibfehaojn\" target=\"_blank\"><img alt=\"Edge Add-on\" src=\"https://img.shields.io/badge/dynamic/json?label=users&query=%24.activeInstallCount&url=https%3A%2F%2Fmicrosoftedge.microsoft.com%2Faddons%2Fgetproductdetailsbycrxid%2Fooiejjgflcgkbbehheengalibfehaojn\"></a>\n\n## Issues\n\nIf you face any problems while using the application, please open an issue here - https://github.com/responsively-org/responsively-app/issues\n\n## Roadmap\n\nHere is the roadmap of the desktop app - https://github.com/responsively-org/responsively-app/projects/\n\n## Gold sponsors 🥇\n<table style=\"width: 100%; border: none;\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" align=\"center\" id=\"sponsors\">\n  <tr>\n    <td style=\"border: none;\"><a href=\"https://www.testmuai.com/?utm_medium=sponsor&utm_source=responsively-app\" target=\"_blank\">\n            <img src=\"https://responsively.app/_next/static/media/testmu.a5a8be46.svg\" style=\"vertical-align: middle;\" width=\"280\" />\n        </a></td>\n    <td rowspan=\"3\" style=\"border: none;\"><a href=\"https://github.com/sponsors/responsively-org\" target=\"_blank\">\n    <img src=\"https://user-images.githubusercontent.com/1283424/142834528-4cd5b8eb-eeae-4437-b749-d09c96dde160.png\" height=\"120px\" alt=\"Sponsor to add your company logo here\">\n  </a></td>\n  </tr>\n</table>\n\n\n\n[Become a sponsor and have your company logo here](https://opencollective.com/responsively)\n\n## Contribute\n\nTo get started with contributing your awesome ideas to Responsively, follow the [comprehensive guide here](https://github.com/responsively-org/responsively-app/blob/main/CONTRIBUTING.md)!\n\n## Get in touch\n\nCome say hi to us on [Discord](https://responsively.app/join-discord)! :wave:\n\n## Contributors ✨\n\nThanks go to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://x.com/vivek_jonam\"><img src=\"https://avatars1.githubusercontent.com/u/1283424?v=4?s=100\" width=\"100px;\" alt=\"Manoj Vivek\"/><br /><sub><b>Manoj Vivek</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=manojVivek\" title=\"Code\">💻</a> <a href=\"https://github.com/responsively-org/responsively-app/commits?author=manojVivek\" title=\"Tests\">⚠️</a> <a href=\"#projectManagement-manojVivek\" title=\"Project Management\">📆</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/esprush\"><img src=\"https://avatars0.githubusercontent.com/u/26249498?v=4?s=100\" width=\"100px;\" alt=\"Suresh P\"/><br /><sub><b>Suresh P</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=esprush\" title=\"Code\">💻</a> <a href=\"https://github.com/responsively-org/responsively-app/commits?author=esprush\" title=\"Tests\">⚠️</a> <a href=\"#projectManagement-esprush\" title=\"Project Management\">📆</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/sprabowo\"><img src=\"https://avatars2.githubusercontent.com/u/11748183?v=4?s=100\" width=\"100px;\" alt=\"Sigit Prabowo\"/><br /><sub><b>Sigit Prabowo</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=sprabowo\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/leon0707\"><img src=\"https://avatars1.githubusercontent.com/u/523684?v=4?s=100\" width=\"100px;\" alt=\"Leon Feng\"/><br /><sub><b>Leon Feng</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=leon0707\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/kishoreio\"><img src=\"https://avatars2.githubusercontent.com/u/30261988?v=4?s=100\" width=\"100px;\" alt=\"Kishore S\"/><br /><sub><b>Kishore S</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=kishoreio\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://jjavierdguezas.github.io\"><img src=\"https://avatars2.githubusercontent.com/u/13673443?v=4?s=100\" width=\"100px;\" alt=\"José Javier Rodríguez Zas\"/><br /><sub><b>José Javier Rodríguez Zas</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=jjavierdguezas\" title=\"Code\">💻</a> <a href=\"https://github.com/responsively-org/responsively-app/commits?author=jjavierdguezas\" title=\"Tests\">⚠️</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/romanakash\"><img src=\"https://avatars1.githubusercontent.com/u/40427975?v=4?s=100\" width=\"100px;\" alt=\"Roman Akash\"/><br /><sub><b>Roman Akash</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=romanakash\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/RomainFrancony\"><img src=\"https://avatars3.githubusercontent.com/u/22396965?v=4?s=100\" width=\"100px;\" alt=\"Romain Francony\"/><br /><sub><b>Romain Francony</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=RomainFrancony\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/AARYAN-MAHENDRA\"><img src=\"https://avatars1.githubusercontent.com/u/64866670?v=4?s=100\" width=\"100px;\" alt=\"AARYAN-MAHENDRA\"/><br /><sub><b>AARYAN-MAHENDRA</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=AARYAN-MAHENDRA\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Nothing-Works\"><img src=\"https://avatars3.githubusercontent.com/u/18606648?v=4?s=100\" width=\"100px;\" alt=\"Andy\"/><br /><sub><b>Andy</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Nothing-Works\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Kidcredo\"><img src=\"https://avatars0.githubusercontent.com/u/15522605?v=4?s=100\" width=\"100px;\" alt=\"Ryan Pais\"/><br /><sub><b>Ryan Pais</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Kidcredo\" title=\"Code\">💻</a> <a href=\"https://github.com/responsively-org/responsively-app/commits?author=Kidcredo\" title=\"Tests\">⚠️</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://grafikart.fr\"><img src=\"https://avatars1.githubusercontent.com/u/395137?v=4?s=100\" width=\"100px;\" alt=\"Jonathan\"/><br /><sub><b>Jonathan</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Grafikart\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/heygema\"><img src=\"https://avatars1.githubusercontent.com/u/10743728?v=4?s=100\" width=\"100px;\" alt=\"Gema Anggada ✌︎\"/><br /><sub><b>Gema Anggada ✌︎</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=heygema\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/jonathanurias96\"><img src=\"https://avatars2.githubusercontent.com/u/57416786?v=4?s=100\" width=\"100px;\" alt=\"jonathanurias96\"/><br /><sub><b>jonathanurias96</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=jonathanurias96\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://falecci.dev\"><img src=\"https://avatars2.githubusercontent.com/u/17703824?v=4?s=100\" width=\"100px;\" alt=\"Federico Alecci\"/><br /><sub><b>Federico Alecci</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=falecci\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://linkedin.com/in/muminjon-abduraimov/\"><img src=\"https://avatars1.githubusercontent.com/u/24930020?v=4?s=100\" width=\"100px;\" alt=\"Abduraimov Muminjon\"/><br /><sub><b>Abduraimov Muminjon</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=MuminjonGuru\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://www.vlazaro.es/\"><img src=\"https://avatars1.githubusercontent.com/u/38981659?v=4?s=100\" width=\"100px;\" alt=\"Víctor Lázaro\"/><br /><sub><b>Víctor Lázaro</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=vlazaroes\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/kvnam\"><img src=\"https://avatars0.githubusercontent.com/u/3608742?v=4?s=100\" width=\"100px;\" alt=\"Kavita Nambissan\"/><br /><sub><b>Kavita Nambissan</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=kvnam\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://x.com/PrashantPalikhe\"><img src=\"https://avatars0.githubusercontent.com/u/2657709?v=4?s=100\" width=\"100px;\" alt=\"Prashant Palikhe\"/><br /><sub><b>Prashant Palikhe</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=prashantpalikhe\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/jaunesarmiento\"><img src=\"https://avatars1.githubusercontent.com/u/1166928?v=4?s=100\" width=\"100px;\" alt=\"Jaune Sarmiento\"/><br /><sub><b>Jaune Sarmiento</b></sub></a><br /><a href=\"#content-jaunesarmiento\" title=\"Content\">🖋</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/diego-vieira\"><img src=\"https://avatars2.githubusercontent.com/u/930792?v=4?s=100\" width=\"100px;\" alt=\"Diego Vieira\"/><br /><sub><b>Diego Vieira</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=diego-vieira\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/pajaydev\"><img src=\"https://avatars0.githubusercontent.com/u/21375014?v=4?s=100\" width=\"100px;\" alt=\"Ajaykumar\"/><br /><sub><b>Ajaykumar</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=pajaydev\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/kirubakarthikeyan\"><img src=\"https://avatars0.githubusercontent.com/u/38885946?v=4?s=100\" width=\"100px;\" alt=\"Kiruba Karan\"/><br /><sub><b>Kiruba Karan</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=kirubakarthikeyan\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/sebasrodriguez\"><img src=\"https://avatars1.githubusercontent.com/u/1605931?v=4?s=100\" width=\"100px;\" alt=\"Sebastián Rodríguez\"/><br /><sub><b>Sebastián Rodríguez</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=sebasrodriguez\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/karthick3018\"><img src=\"https://avatars1.githubusercontent.com/u/47154512?v=4?s=100\" width=\"100px;\" alt=\"Karthick Raja\"/><br /><sub><b>Karthick Raja</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=karthick3018\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/jzabala\"><img src=\"https://avatars0.githubusercontent.com/u/1315054?v=4?s=100\" width=\"100px;\" alt=\"Johnny Zabala\"/><br /><sub><b>Johnny Zabala</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=jzabala\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://rossmoody.com\"><img src=\"https://avatars0.githubusercontent.com/u/29072694?v=4?s=100\" width=\"100px;\" alt=\"Ross Moody\"/><br /><sub><b>Ross Moody</b></sub></a><br /><a href=\"#design-rossmoody\" title=\"Design\">🎨</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://shokri.me\"><img src=\"https://avatars1.githubusercontent.com/u/13661520?v=4?s=100\" width=\"100px;\" alt=\"Mehrdad Shokri\"/><br /><sub><b>Mehrdad Shokri</b></sub></a><br /><a href=\"#infra-mehrdad-shokri\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/abakermi\"><img src=\"https://avatars1.githubusercontent.com/u/60294727?v=4?s=100\" width=\"100px;\" alt=\"Abdelhak Akermi\"/><br /><sub><b>Abdelhak Akermi</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=abakermi\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/crperezt\"><img src=\"https://avatars0.githubusercontent.com/u/20329014?v=4?s=100\" width=\"100px;\" alt=\"Carlos Perez\"/><br /><sub><b>Carlos Perez</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=crperezt\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/JayArya\"><img src=\"https://avatars0.githubusercontent.com/u/42388314?v=4?s=100\" width=\"100px;\" alt=\"Jayant Arya\"/><br /><sub><b>Jayant Arya</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=JayArya\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/JohnRawlins\"><img src=\"https://avatars3.githubusercontent.com/u/42707277?v=4?s=100\" width=\"100px;\" alt=\"John Rawlins\"/><br /><sub><b>John Rawlins</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=JohnRawlins\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/lepasq\"><img src=\"https://avatars3.githubusercontent.com/u/53230128?v=4?s=100\" width=\"100px;\" alt=\"lepasq\"/><br /><sub><b>lepasq</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=lepasq\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/mrfelfel\"><img src=\"https://avatars0.githubusercontent.com/u/19575588?v=4?s=100\" width=\"100px;\" alt=\"mrfelfel\"/><br /><sub><b>mrfelfel</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=mrfelfel\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://x.com/gorogoroumaru\"><img src=\"https://avatars3.githubusercontent.com/u/30716350?v=4?s=100\" width=\"100px;\" alt=\"gorogoroumaru\"/><br /><sub><b>gorogoroumaru</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=gorogoroumaru\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://ruisaraiva.dev\"><img src=\"https://avatars2.githubusercontent.com/u/7356098?v=4?s=100\" width=\"100px;\" alt=\"Rui Saraiva\"/><br /><sub><b>Rui Saraiva</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=ruisaraiva19\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://www.bakirci.nl\"><img src=\"https://avatars2.githubusercontent.com/u/9880089?v=4?s=100\" width=\"100px;\" alt=\"Mehmet Bakirci\"/><br /><sub><b>Mehmet Bakirci</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=MBakirci\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/JLambertazzo\"><img src=\"https://avatars0.githubusercontent.com/u/42924425?v=4?s=100\" width=\"100px;\" alt=\"Julien Bertazzo Lambert\"/><br /><sub><b>Julien Bertazzo Lambert</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=JLambertazzo\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://dbwriteups.wordpress.com\"><img src=\"https://avatars3.githubusercontent.com/u/4656109?v=4?s=100\" width=\"100px;\" alt=\"Dinesh Balaji\"/><br /><sub><b>Dinesh Balaji</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=sidthesloth92\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/med1001\"><img src=\"https://avatars3.githubusercontent.com/u/26111211?v=4?s=100\" width=\"100px;\" alt=\"MedBMoussa\"/><br /><sub><b>MedBMoussa</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=med1001\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://www.lucie.dev\"><img src=\"https://avatars.githubusercontent.com/u/46979603?v=4?s=100\" width=\"100px;\" alt=\"Lucie Vrsovska\"/><br /><sub><b>Lucie Vrsovska</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=lucievr\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/jcabak\"><img src=\"https://avatars.githubusercontent.com/u/1818155?v=4?s=100\" width=\"100px;\" alt=\"Jakub Cabak\"/><br /><sub><b>Jakub Cabak</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=jcabak\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/durgakiran\"><img src=\"https://avatars.githubusercontent.com/u/17452039?v=4?s=100\" width=\"100px;\" alt=\"Palakurthi Durga Kiran Kumar\"/><br /><sub><b>Palakurthi Durga Kiran Kumar</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=durgakiran\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/karllabrador\"><img src=\"https://avatars.githubusercontent.com/u/58193703?v=4?s=100\" width=\"100px;\" alt=\"Karl Labrador\"/><br /><sub><b>Karl Labrador</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=karllabrador\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://rishikc.com\"><img src=\"https://avatars.githubusercontent.com/u/26366288?v=4?s=100\" width=\"100px;\" alt=\"Rishi Kumar Chawda\"/><br /><sub><b>Rishi Kumar Chawda</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=rishichawda\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/crocarneiro\"><img src=\"https://avatars.githubusercontent.com/u/10589421?v=4?s=100\" width=\"100px;\" alt=\"Carlos Rafael de Oliveira Carneiro\"/><br /><sub><b>Carlos Rafael de Oliveira Carneiro</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=crocarneiro\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://zachos.tech\"><img src=\"https://avatars.githubusercontent.com/u/50255197?v=4?s=100\" width=\"100px;\" alt=\"Zach Hoskins\"/><br /><sub><b>Zach Hoskins</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=zachOS-tech\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/kiwan97\"><img src=\"https://avatars.githubusercontent.com/u/25267859?v=4?s=100\" width=\"100px;\" alt=\"KIWAN KIM\"/><br /><sub><b>KIWAN KIM</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=kiwan97\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/agaertner\"><img src=\"https://avatars.githubusercontent.com/u/13819164?v=4?s=100\" width=\"100px;\" alt=\"Andreas\"/><br /><sub><b>Andreas</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=agaertner\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://www.linkedin.com/in/panu-valtanen-446ba9108/\"><img src=\"https://avatars.githubusercontent.com/u/28947061?v=4?s=100\" width=\"100px;\" alt=\"Panu Valtanen\"/><br /><sub><b>Panu Valtanen</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=p4nu\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://www.d19.ca\"><img src=\"https://avatars.githubusercontent.com/u/8153796?v=4?s=100\" width=\"100px;\" alt=\"Dustin Dauncey\"/><br /><sub><b>Dustin Dauncey</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=d19dotca\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/heagan01\"><img src=\"https://avatars.githubusercontent.com/u/54394590?v=4?s=100\" width=\"100px;\" alt=\"heagan01\"/><br /><sub><b>heagan01</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=heagan01\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Th0masCat\"><img src=\"https://avatars.githubusercontent.com/u/74812563?v=4?s=100\" width=\"100px;\" alt=\"Sahil Jangra\"/><br /><sub><b>Sahil Jangra</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Th0masCat\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/nopdotcom\"><img src=\"https://avatars.githubusercontent.com/u/1357866?v=4?s=100\" width=\"100px;\" alt=\"Jay Carlson\"/><br /><sub><b>Jay Carlson</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=nopdotcom\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://mikadifo.com\"><img src=\"https://avatars.githubusercontent.com/u/51935560?v=4?s=100\" width=\"100px;\" alt=\"Michael Padilla\"/><br /><sub><b>Michael Padilla</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Mikadifo\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://narrowcode.xyz\"><img src=\"https://avatars.githubusercontent.com/u/6213380?v=4?s=100\" width=\"100px;\" alt=\"Andreas Steinkellner\"/><br /><sub><b>Andreas Steinkellner</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=NarrowCode\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://niknia.dev\"><img src=\"https://avatars.githubusercontent.com/u/40159649?v=4?s=100\" width=\"100px;\" alt=\"aniknia\"/><br /><sub><b>aniknia</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=aniknia\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/WayneRocha\"><img src=\"https://avatars.githubusercontent.com/u/62760711?v=4?s=100\" width=\"100px;\" alt=\"Wayne Rocha\"/><br /><sub><b>Wayne Rocha</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=WayneRocha\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/crbon\"><img src=\"https://avatars.githubusercontent.com/u/2604330?v=4?s=100\" width=\"100px;\" alt=\"crbon\"/><br /><sub><b>crbon</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=crbon\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://linktr.ee/themohammadsa\"><img src=\"https://avatars.githubusercontent.com/u/59393936?v=4?s=100\" width=\"100px;\" alt=\"Mohammad S\"/><br /><sub><b>Mohammad S</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=themohammadsa\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/ReshadSadik\"><img src=\"https://avatars.githubusercontent.com/u/66641469?v=4?s=100\" width=\"100px;\" alt=\"Reshad Sadik\"/><br /><sub><b>Reshad Sadik</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=ReshadSadik\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/PeterKwesiAnsah\"><img src=\"https://avatars.githubusercontent.com/u/31078314?v=4?s=100\" width=\"100px;\" alt=\"Kwesi Ansah\"/><br /><sub><b>Kwesi Ansah</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=PeterKwesiAnsah\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/monalisamsteccentric\"><img src=\"https://avatars.githubusercontent.com/u/65477771?v=4?s=100\" width=\"100px;\" alt=\"Monalisa Sahoo\"/><br /><sub><b>Monalisa Sahoo</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=monalisamsteccentric\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/codewithmitesh\"><img src=\"https://avatars.githubusercontent.com/u/85953650?v=4?s=100\" width=\"100px;\" alt=\"Mitesh Tank\"/><br /><sub><b>Mitesh Tank</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=codewithmitesh\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Ryan0204\"><img src=\"https://avatars.githubusercontent.com/u/82715592?v=4?s=100\" width=\"100px;\" alt=\"Ryan\"/><br /><sub><b>Ryan</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Ryan0204\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://www.jibran.me\"><img src=\"https://avatars.githubusercontent.com/u/70596906?v=4?s=100\" width=\"100px;\" alt=\"jayway\"/><br /><sub><b>jayway</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=jibranabsarulislam\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://www.xicre.com\"><img src=\"https://avatars.githubusercontent.com/u/3981106?v=4?s=100\" width=\"100px;\" alt=\"Alex Fernandez\"/><br /><sub><b>Alex Fernandez</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=afermon\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Danial-Gharib\"><img src=\"https://avatars.githubusercontent.com/u/90343552?v=4?s=100\" width=\"100px;\" alt=\"Danial Gharib\"/><br /><sub><b>Danial Gharib</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Danial-Gharib\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://www.x.com/s3lf\"><img src=\"https://avatars.githubusercontent.com/u/1087128?v=4?s=100\" width=\"100px;\" alt=\"Alexander Menk\"/><br /><sub><b>Alexander Menk</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=amenk\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://tunahangediz.com\"><img src=\"https://avatars.githubusercontent.com/u/75015671?v=4?s=100\" width=\"100px;\" alt=\"Tunahan Gediz\"/><br /><sub><b>Tunahan Gediz</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=tunahangediz\" title=\"Documentation\">📖</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://refer.codes/jeff\"><img src=\"https://avatars.githubusercontent.com/u/272795?v=4?s=100\" width=\"100px;\" alt=\"Jeff Bowen\"/><br /><sub><b>Jeff Bowen</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=jeffbowen\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/ParamBirje\"><img src=\"https://avatars.githubusercontent.com/u/87022870?v=4?s=100\" width=\"100px;\" alt=\"Param Birje\"/><br /><sub><b>Param Birje</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=ParamBirje\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/prajjwalyd\"><img src=\"https://avatars.githubusercontent.com/u/111794524?v=4?s=100\" width=\"100px;\" alt=\"Prajjwal Yadav\"/><br /><sub><b>Prajjwal Yadav</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=prajjwalyd\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/lyncasterc\"><img src=\"https://avatars.githubusercontent.com/u/49458912?v=4?s=100\" width=\"100px;\" alt=\"Steven Cabrera\"/><br /><sub><b>Steven Cabrera</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=lyncasterc\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/negar-75\"><img src=\"https://avatars.githubusercontent.com/u/113235504?v=4?s=100\" width=\"100px;\" alt=\"negar nasiri\"/><br /><sub><b>negar nasiri</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=negar-75\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/gauravsingh94\"><img src=\"https://avatars.githubusercontent.com/u/99260988?v=4?s=100\" width=\"100px;\" alt=\"Gaurav Singh\"/><br /><sub><b>Gaurav Singh</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=gauravsingh94\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/NishidhJain\"><img src=\"https://avatars.githubusercontent.com/u/61869195?v=4?s=100\" width=\"100px;\" alt=\"Nishidh Jain\"/><br /><sub><b>Nishidh Jain</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=NishidhJain\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/mishrasamiksha\"><img src=\"https://avatars.githubusercontent.com/u/38784342?v=4?s=100\" width=\"100px;\" alt=\"Samiksha Mishra\"/><br /><sub><b>Samiksha Mishra</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=mishrasamiksha\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://www.astuanax.com\"><img src=\"https://avatars.githubusercontent.com/u/1730624?v=4?s=100\" width=\"100px;\" alt=\"Len Dierickx\"/><br /><sub><b>Len Dierickx</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=astuanax\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/surajbobade\"><img src=\"https://avatars.githubusercontent.com/u/102910293?v=4?s=100\" width=\"100px;\" alt=\"Suraj Bobade\"/><br /><sub><b>Suraj Bobade</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=surajbobade\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://sagarhedaoo.com/\"><img src=\"https://avatars.githubusercontent.com/u/10000167?v=4?s=100\" width=\"100px;\" alt=\"Sagar Hedaoo\"/><br /><sub><b>Sagar Hedaoo</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=sagarhedaoo\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://www.violeta.dev\"><img src=\"https://avatars.githubusercontent.com/u/36138541?v=4?s=100\" width=\"100px;\" alt=\"V\"/><br /><sub><b>V</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=violetadev\" title=\"Code\">💻</a> <a href=\"https://github.com/responsively-org/responsively-app/commits?author=violetadev\" title=\"Tests\">⚠️</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/minowau\"><img src=\"https://avatars.githubusercontent.com/u/139740712?v=4?s=100\" width=\"100px;\" alt=\"Prabhas Jupalli\"/><br /><sub><b>Prabhas Jupalli</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=minowau\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://devpulkit.in\"><img src=\"https://avatars.githubusercontent.com/u/65671483?v=4?s=100\" width=\"100px;\" alt=\"Pulkit\"/><br /><sub><b>Pulkit</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Pulkitxm\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/brandonyee-cs\"><img src=\"https://avatars.githubusercontent.com/u/139765638?v=4?s=100\" width=\"100px;\" alt=\"Brandon Yee\"/><br /><sub><b>Brandon Yee</b></sub></a><br /><a href=\"#content-brandonyee-cs\" title=\"Content\">🖋</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/wp043\"><img src=\"https://avatars.githubusercontent.com/u/110360465?v=4?s=100\" width=\"100px;\" alt=\"Wendy Pan\"/><br /><sub><b>Wendy Pan</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=wp043\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/pranavithape\"><img src=\"https://avatars.githubusercontent.com/u/170559714?v=4?s=100\" width=\"100px;\" alt=\"Pranav Ithape\"/><br /><sub><b>Pranav Ithape</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=pranavithape\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Sukrit-Prakash\"><img src=\"https://avatars.githubusercontent.com/u/136228545?v=4?s=100\" width=\"100px;\" alt=\"SUKRIT PRAKASH SINGH\"/><br /><sub><b>SUKRIT PRAKASH SINGH</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Sukrit-Prakash\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/aryan262\"><img src=\"https://avatars.githubusercontent.com/u/97938438?v=4?s=100\" width=\"100px;\" alt=\"Aryan Panchal\"/><br /><sub><b>Aryan Panchal</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=aryan262\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/samranahm\"><img src=\"https://avatars.githubusercontent.com/u/149153498?v=4?s=100\" width=\"100px;\" alt=\"Samran Ahmed\"/><br /><sub><b>Samran Ahmed</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=samranahm\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Asatyam\"><img src=\"https://avatars.githubusercontent.com/u/95954551?v=4?s=100\" width=\"100px;\" alt=\"SatyamAgrawal\"/><br /><sub><b>SatyamAgrawal</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=asatyam\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/saiTharunDusa\"><img src=\"https://avatars.githubusercontent.com/u/169873642?v=4?s=100\" width=\"100px;\" alt=\"Sai Tharun Dusa\"/><br /><sub><b>Sai Tharun Dusa</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=saiTharunDusa\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://happymvp.com\"><img src=\"https://avatars.githubusercontent.com/u/179954458?v=4?s=100\" width=\"100px;\" alt=\"Pavlo\"/><br /><sub><b>Pavlo</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=happymvp\" title=\"Code\">💻</a> <a href=\"https://github.com/responsively-org/responsively-app/commits?author=happymvp\" title=\"Tests\">⚠️</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/AnalyticalDataArtisan\"><img src=\"https://avatars.githubusercontent.com/u/183036785?v=4?s=100\" width=\"100px;\" alt=\"AnalyticalDataArtisan\"/><br /><sub><b>AnalyticalDataArtisan</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=AnalyticalDataArtisan\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Kieren-Foenander\"><img src=\"https://avatars.githubusercontent.com/u/68266113?v=4?s=100\" width=\"100px;\" alt=\"Kieren-Foenander\"/><br /><sub><b>Kieren-Foenander</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Kieren-Foenander\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/Trishix\"><img src=\"https://avatars.githubusercontent.com/u/170200412?v=4?s=100\" width=\"100px;\" alt=\"Trishit Swarnakar\"/><br /><sub><b>Trishit Swarnakar</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=Trishix\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://blog.absingh.com/\"><img src=\"https://avatars.githubusercontent.com/u/29686866?v=4?s=100\" width=\"100px;\" alt=\"Abhijeet Singh\"/><br /><sub><b>Abhijeet Singh</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=cseas\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://aaryancreates.framer.media\"><img src=\"https://avatars.githubusercontent.com/u/93996658?v=4?s=100\" width=\"100px;\" alt=\"aaryan\"/><br /><sub><b>aaryan</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=meezumi\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/mklikushin\"><img src=\"https://avatars.githubusercontent.com/u/135151016?v=4?s=100\" width=\"100px;\" alt=\"Michael Klikushin\"/><br /><sub><b>Michael Klikushin</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=mklikushin\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"https://github.com/bhnprksh222\"><img src=\"https://avatars.githubusercontent.com/u/48930756?v=4?s=100\" width=\"100px;\" alt=\"Bhanu Prakash\"/><br /><sub><b>Bhanu Prakash</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=bhnprksh222\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"20%\"><a href=\"http://sayago.dev\"><img src=\"https://avatars.githubusercontent.com/u/67727553?v=4?s=100\" width=\"100px;\" alt=\"Saúl Sáyago\"/><br /><sub><b>Saúl Sáyago</b></sub></a><br /><a href=\"https://github.com/responsively-org/responsively-app/commits?author=sayagodev\" title=\"Code\">💻</a></td>\n    </tr>\n  </tbody>\n</table>\n\n<!-- markdownlint-restore -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome!\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nBelow are the versions that are currently being supported with security updates.\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 1.x.x   | :white_check_mark: |\n| < 1.0   | :x:                |\n\n## Reporting a Vulnerability\n\nVulnerabilities can be reported in the following ways:\n1. Create an issue required details (here)[https://github.com/responsively-org/responsively-app/issues].\n2. Send an email to `p.manoj.vivek` at `gmail.com` with details.\n"
  },
  {
    "path": "browser-extension/.gitignore",
    "content": "node_modules\nyarn.lock\ndist\nsetCreds.sh\nweb-ext-artifacts\n"
  },
  {
    "path": "browser-extension/.vscode/settings.json",
    "content": "{\n  \"editor.formatOnSave\": true,\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n}\n"
  },
  {
    "path": "browser-extension/package.json",
    "content": "{\n\t\"name\": \"Responsively-Helper\",\n\t\"version\": \"0.0.1\",\n\t\"description\": \"An extension to open current browser page in Responsively app\",\n\t\"private\": true,\n\t\"scripts\": {\n\t\t\"lint\": \"run-p lint:*\",\n\t\t\"lint:js\": \"xo\",\n\t\t\"lint:css\": \"stylelint source/**/*.css\",\n\t\t\"lint-fix\": \"run-p 'lint:* -- --fix'\",\n\t\t\"test\": \"run-s lint:* build\",\n\t\t\"start\": \"webpack serve --mode=development --hot --open\",\n \t \t\"build\": \"webpack --mode=production\",\n\t\t\"release:cws\": \"webstore upload --source=dist --auto-publish\",\n\t\t\"release:amo\": \"web-ext-submit --source-dir dist\",\n\t\t\"release\": \"run-s build release:*\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@babel/core\": \"^7.10.1\",\n\t\t\"@babel/plugin-proposal-class-properties\": \"^7.10.1\",\n\t\t\"@babel/plugin-proposal-object-rest-spread\": \"^7.10.1\",\n\t\t\"@babel/plugin-transform-react-constant-elements\": \"^7.10.1\",\n\t\t\"@babel/plugin-transform-runtime\": \"^7.10.1\",\n\t\t\"@babel/preset-env\": \"^7.10.1\",\n\t\t\"@babel/preset-react\": \"^7.10.1\",\n\t\t\"@ianwalter/web-ext-webpack-plugin\": \"^0.1.0\",\n\t\t\"babel-loader\": \"^8.1.0\",\n\t\t\"chrome-webstore-upload-cli\": \"^1.2.0\",\n\t\t\"copy-webpack-plugin\": \"^5.0.3\",\n\t\t\"daily-version\": \"^0.12.0\",\n\t\t\"dot-json\": \"^1.1.0\",\n\t\t\"eslint\": \"^6.1.0\",\n\t\t\"eslint-config-xo\": \"^0.26.0\",\n\t\t\"npm-run-all\": \"^4.1.5\",\n\t\t\"size-plugin\": \"^1.2.0\",\n\t\t\"stylelint\": \"^10.1.0\",\n\t\t\"stylelint-config-xo\": \"^0.15.0\",\n\t\t\"terser-webpack-plugin\": \"^1.3.0\",\n\t\t\"web-ext\": \"^4.2.0\",\n\t\t\"web-ext-submit\": \"^4.2.0\",\n\t\t\"webpack\": \"^4.36.1\",\n\t\t\"webpack-cli\": \"^3.3.6\",\n\t\t\"xo\": \"^0.24.0\"\n\t},\n\t\"dependencies\": {\n\t\t\"custom-protocol-check\": \"^1.1.0\",\n\t\t\"react\": \"^16.13.1\",\n\t\t\"react-dom\": \"^16.13.1\",\n\t\t\"url-loader\": \"^4.1.0\",\n\t\t\"webextension-polyfill\": \"^0.4.0\"\n\t},\n\t\"xo\": {\n\t\t\"envs\": [\n\t\t\t\"browser\"\n\t\t],\n\t\t\"ignores\": [\n\t\t\t\"dist\"\n\t\t],\n\t\t\"globals\": [\n\t\t\t\"browser\"\n\t\t]\n\t},\n\t\"stylelint\": {\n\t\t\"extends\": \"stylelint-config-xo\"\n\t}\n}\n"
  },
  {
    "path": "browser-extension/public/manifest.json",
    "content": "{\n\t\"name\": \"Responsively Helper\",\n\t\"version\": \"0.0.2\",\n\t\"description\": \"An extension to open current browser page in Responsively app\",\n\t\"homepage_url\": \"https://responsively.app\",\n\t\"manifest_version\": 2,\n\t\"icons\": {\n\t\t\"128\": \"logo_128.png\",\n\t\t\"48\": \"logo_48.png\",\n\t\t\"16\": \"logo_16.png\"\n\t},\n\t\"background\": {\n\t\t\"persistent\": false,\n\t\t\"scripts\": [\n\t\t\t\"browser-polyfill.min.js\",\n\t\t\t\"background.js\"\n\t\t]\n\t},\n\t\"browser_action\": {\n    \"default_icon\": \"logo_128.png\",\n\t\t\"default_title\": \"Open page in Responsively app\",\n\t\t\"default_popup\": \"popup.html\"\n\t},\n\t\"permissions\": [\n\t\t\"activeTab\"\n ]\n}\n"
  },
  {
    "path": "browser-extension/public/popup.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"\n    />\n    <style>\n      body {\n        margin: 0;\n        overflow: hidden;\n        width: 340px;\n        background-color: #1d1b26;\n        box-sizing: border-box;\n      }\n\n      .header {\n        display: flex;\n        justify-content: center;\n        margin: 10px 0 20px;\n      }\n\n      .logo {\n        height: 48px;\n      }\n\n      .anchorButton {\n        color: #fd2d55;\n        border-color: #fd2d55;\n        display: flex;\n        justify-content: center;\n        border: 1px solid;\n        padding: 5px;\n        border-radius: 3px;\n        text-decoration: none;\n      }\n\n      .popup {\n        height: 200px;\n        color: #fff;\n        padding: 25px;\n      }\n\n      .flexCenter {\n        display: flex;\n        justify-content: center;\n        align-items: center;\n      }\n\n      .textJustify {\n        text-align: justify;\n      }\n\n      .loadingSpinner {\n        height: 50px;\n      }\n    </style>\n    <script src=\"browser-polyfill.min.js\"></script>\n  </head>\n  <body>\n    <div class=\"header\">\n      <img class=\"logo\" src=\"logo_128.png\" alt=\"Responsively App\" />\n    </div>\n    <div id=\"app\"></div>\n    <script src=\"popup.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "browser-extension/src/background.js",
    "content": "browser.browserAction.onClicked.addListener((tab) => {\n  browser.tabs.executeScript({\n    file: './openURL.js'\n  });\n});\n"
  },
  {
    "path": "browser-extension/src/popup.js",
    "content": "import openCustomProtocolURI from \"custom-protocol-check\";\nimport React, { useEffect, useState, useCallback } from \"react\";\nimport ReactDOM from \"react-dom\";\nimport spinner from \"./spinner.svg\";\n\nconst isChrome = () => {\n  // IE11 returns undefined for window.chrome\n  // and new Opera 30 outputs true for window.chrome\n  // but needs to check if window.opr is not undefined\n  // and new IE Edge outputs to true for window.chrome\n  // and if not iOS Chrome check\n  const isChromium = window.chrome;\n  const winNav = window.navigator;\n  const vendorName = winNav.vendor;\n  const isOpera = typeof window.opr !== \"undefined\";\n  const isIEedge = winNav.userAgent.indexOf(\"Edge\") > -1;\n  const isIOSChrome = winNav.userAgent.match(\"CriOS\");\n  return (\n    (isChromium !== null &&\n      typeof isChromium !== \"undefined\" &&\n      vendorName === \"Google Inc.\" &&\n      isOpera === false &&\n      isIEedge === false) ||\n    isIOSChrome\n  );\n};\n\nconst URLOpenerNonChrome = () => {\n  const [status, setStatus] = useState(\"loading\");\n  const checkProtocolAndUpdateStatus = useCallback(async () => {\n    const [tab] = await window.browser.tabs.query({ currentWindow: true, active: true });\n    if (!tab || !tab.url) {\n      setStatus(\"false\");\n      return;\n    }\n    openCustomProtocolURI(\n      `responsively://${tab.url}`,\n      () => setStatus(\"false\"),\n      () => setStatus(\"true\")\n    );\n  }, []);\n\n  useEffect(() => {\n    checkProtocolAndUpdateStatus();\n  }, [checkProtocolAndUpdateStatus]);\n\n  if (status === \"loading\") {\n    return (\n      <div className=\"popup\">\n        <div className=\"flexCenter\">\n          <img className=\"loadingSpinner\" src={spinner} />\n        </div>\n      </div>\n    );\n  }\n  if (status === \"false\") {\n    return (\n      <div className=\"popup\">\n        <div className=\"textJustify\">\n          It looks like you dont have Responsively App installed to preview the\n          page in responsive mode.\n        </div>\n        <br />\n        <br />\n        <br />\n        Please install the app and open it once to continue using the extension.\n        <br />\n        <div className=\"flexCenter\">\n          <a\n            className=\"anchorButton\"\n            onClick={() => setTimeout(() => window.close(), 200)}\n            href=\"https://responsively.app\"\n            target=\"_blank\"\n          >\n            Install Now\n          </a>\n        </div>\n      </div>\n    );\n  }\n  return null;\n};\n\nconst URLOpenerChrome = () => {\n  const updateTabURL = useCallback(async () => {\n    const [tab] = await window.browser.tabs.query({ currentWindow: true, active: true });\n    if (!tab || !tab.url) {\n      return;\n    }\n    window.browser.tabs.update({ url: `responsively://${tab.url}` });\n  }, []);\n\n  useEffect(() => {\n    updateTabURL();\n    setTimeout(() => {\n      window.close();\n    }, 5000);\n  }, [updateTabURL]);\n\n  return (\n    <div className=\"popup\">\n      <div className=\"textJustify\">\n        If you have any issues seeing the responsive preview, please make sure\n        Responsively app is installed.\n      </div>\n      <br />\n      <br />\n      <div className=\"flexCenter\">\n        <a\n          className=\"anchorButton\"\n          onClick={() => setTimeout(() => window.close(), 200)}\n          href=\"https://responsively.app\"\n          target=\"_blank\"\n        >\n          Install Now\n        </a>\n      </div>\n      <br />\n      <br />\n    </div>\n  );\n};\n\nReactDOM.render(\n  isChrome() ? <URLOpenerChrome /> : <URLOpenerNonChrome />,\n  document.getElementById(\"app\")\n);\n\n// HMR integration\nif (module.hot) {\n  module.hot.accept('./popup', () => {\n    const NextPopup = require('./popup').default;\n    ReactDOM.render(<NextPopup />, document.getElementById('app'));\n  });\n}\n"
  },
  {
    "path": "browser-extension/webpack.config.js",
    "content": "const webpack = require('webpack');\nconst path = require('path');\nconst SizePlugin = require('size-plugin');\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\nconst WebExtWebpackPlugin = require('@ianwalter/web-ext-webpack-plugin');\nconst TerserPlugin = require('terser-webpack-plugin');\n\nmodule.exports = {\n  devtool: 'source-map',\n  stats: 'errors-only',\n  entry: {\n    background: './src/background',\n    popup: './src/popup',\n    // Add HMR client\n    main: [\n      'webpack-hot-middleware/client?reload=true', // Use 'reload=true' for CSS\n      './src/index.js', // Adjust to your main file\n    ],\n  },\n  output: {\n    path: path.join(__dirname, 'dist'),\n    filename: '[name].js',\n    publicPath: '/', // Required for HMR\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        include: [\n          path.resolve(__dirname, \"src\")\n        ],\n        use: {\n          loader: 'babel-loader',\n          options: {\n            presets: [\n              [\"@babel/preset-env\", { modules: false }],\n              \"@babel/preset-react\"\n            ],\n            plugins: [\n              \"@babel/plugin-transform-react-constant-elements\",\n              \"@babel/plugin-proposal-object-rest-spread\",\n              \"@babel/plugin-proposal-class-properties\",\n              \"@babel/plugin-transform-runtime\",\n              'react-refresh/babel', // Add React Refresh for HMR\n            ],\n          },\n        },\n      },\n      {\n        test: /\\.(svg|gif|png|jpg)$/,\n        use: 'url-loader',\n      }\n    ],\n  },\n  plugins: [\n    new webpack.HotModuleReplacementPlugin(), // Enable HMR\n    new SizePlugin(),\n    new CopyWebpackPlugin({\n      patterns: [\n        {\n          from: '**/*',\n          context: 'public',\n        },\n        {\n          from: 'node_modules/webextension-polyfill/dist/browser-polyfill.min.js'\n        }\n      ],\n    }),\n    new WebExtWebpackPlugin({ sourceDir: path.join(__dirname, 'dist'), verbose: true }),\n  ],\n  optimization: {\n    minimizer: [\n      new TerserPlugin({\n        terserOptions: {\n          mangle: false,\n          compress: false,\n          output: {\n            beautify: true,\n            indent_level: 2\n          }\n        }\n      })\n    ]\n  }\n};\n"
  },
  {
    "path": "desktop-app/. prettierignore",
    "content": ".erb/*"
  },
  {
    "path": "desktop-app/.editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "desktop-app/.erb/configs/.eslintrc",
    "content": "{\n  \"rules\": {\n    \"no-console\": \"off\",\n    \"global-require\": \"off\",\n    \"import/no-dynamic-require\": \"off\"\n  }\n}\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.config.base.ts",
    "content": "/**\n * Base webpack config used across other specific configs\n */\n\nimport webpack from 'webpack';\nimport TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin';\nimport webpackPaths from './webpack.paths';\nimport { dependencies as externals } from '../../release/app/package.json';\n\nconst configuration: webpack.Configuration = {\n  externals: [...Object.keys(externals || {})],\n\n  stats: 'errors-only',\n\n  module: {\n    rules: [\n      {\n        test: /\\.[jt]sx?$/,\n        exclude: /node_modules/,\n        use: {\n          loader: 'ts-loader',\n          options: {\n            // Remove this line to enable type checking in webpack builds\n            transpileOnly: true,\n            compilerOptions: {\n              module: 'esnext',\n            },\n          },\n        },\n      },\n    ],\n  },\n\n  output: {\n    path: webpackPaths.srcPath,\n    // https://github.com/webpack/webpack/issues/1114\n    library: {\n      type: 'commonjs2',\n    },\n  },\n\n  /**\n   * Determine the array of extensions that should be used to resolve modules.\n   */\n  resolve: {\n    extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],\n    modules: [webpackPaths.srcPath, 'node_modules'],\n    // There is no need to add aliases here, the paths in tsconfig get mirrored\n    plugins: [new TsconfigPathsPlugins()],\n  },\n\n  plugins: [\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production',\n    }),\n  ],\n};\n\nexport default configuration;\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.config.eslint.ts",
    "content": "/* eslint import/no-unresolved: off, import/no-self-import: off */\n\nmodule.exports = require('./webpack.config.renderer.dev').default;\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.config.main.prod.ts",
    "content": "/**\n * Webpack config for production electron main process\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport { merge } from 'webpack-merge';\nimport TerserPlugin from 'terser-webpack-plugin';\nimport { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';\nimport baseConfig from './webpack.config.base';\nimport webpackPaths from './webpack.paths';\nimport checkNodeEnv from '../scripts/check-node-env';\nimport deleteSourceMaps from '../scripts/delete-source-maps';\n\ncheckNodeEnv('production');\ndeleteSourceMaps();\n\nconst devtoolsConfig =\n  process.env.DEBUG_PROD === 'true'\n    ? {\n        devtool: 'source-map',\n      }\n    : {};\n\nconst configuration: webpack.Configuration = {\n  ...devtoolsConfig,\n\n  mode: 'production',\n\n  target: 'electron-main',\n\n  entry: {\n    main: path.join(webpackPaths.srcMainPath, 'main.ts'),\n    preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),\n    'preload-webview': path.join(\n      webpackPaths.srcMainPath,\n      'preload-webview.ts'\n    ),\n  },\n\n  externals: ['fsevents'],\n\n  output: {\n    path: webpackPaths.distMainPath,\n    filename: '[name].js',\n    library: {\n      type: 'umd',\n    },\n  },\n\n  optimization: {\n    minimizer: [\n      new TerserPlugin({\n        parallel: true,\n      }),\n    ],\n  },\n\n  plugins: [\n    new BundleAnalyzerPlugin({\n      analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',\n    }),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production',\n      DEBUG_PROD: false,\n      START_MINIMIZED: false,\n    }),\n\n    new webpack.DefinePlugin({\n      'process.type': '\"browser\"',\n    }),\n  ],\n\n  /**\n   * Disables webpack processing of __dirname and __filename.\n   * If you run the bundle in node.js it falls back to these values of node.js.\n   * https://github.com/webpack/webpack/issues/2010\n   */\n  node: {\n    __dirname: false,\n    __filename: false,\n  },\n};\n\nexport default merge(baseConfig, configuration);\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.config.preload-webview.dev.ts",
    "content": "import path from 'path';\nimport webpack from 'webpack';\nimport { merge } from 'webpack-merge';\nimport { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';\nimport baseConfig from './webpack.config.base';\nimport webpackPaths from './webpack.paths';\nimport checkNodeEnv from '../scripts/check-node-env';\n\n// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's\n// at the dev webpack config is not accidentally run in a production environment\nif (process.env.NODE_ENV === 'production') {\n  checkNodeEnv('development');\n}\n\nconst configuration: webpack.Configuration = {\n  devtool: 'inline-source-map',\n\n  mode: 'development',\n\n  target: 'electron-preload',\n\n  entry: path.join(webpackPaths.srcMainPath, 'preload-webview.ts'),\n\n  output: {\n    path: webpackPaths.dllPath,\n    filename: 'preload-webview.js',\n    library: {\n      type: 'umd',\n    },\n  },\n\n  plugins: [\n    new BundleAnalyzerPlugin({\n      analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',\n    }),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     *\n     * By default, use 'development' as NODE_ENV. This can be overriden with\n     * 'staging', for example, by changing the ENV variables in the npm scripts\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'development',\n    }),\n\n    new webpack.LoaderOptionsPlugin({\n      debug: true,\n    }),\n  ],\n\n  /**\n   * Disables webpack processing of __dirname and __filename.\n   * If you run the bundle in node.js it falls back to these values of node.js.\n   * https://github.com/webpack/webpack/issues/2010\n   */\n  node: {\n    __dirname: false,\n    __filename: false,\n  },\n\n  watch: true,\n};\n\nexport default merge(baseConfig, configuration);\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.config.preload.dev.ts",
    "content": "import path from 'path';\nimport webpack from 'webpack';\nimport { merge } from 'webpack-merge';\nimport { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';\nimport baseConfig from './webpack.config.base';\nimport webpackPaths from './webpack.paths';\nimport checkNodeEnv from '../scripts/check-node-env';\n\n// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's\n// at the dev webpack config is not accidentally run in a production environment\nif (process.env.NODE_ENV === 'production') {\n  checkNodeEnv('development');\n}\n\nconst configuration: webpack.Configuration = {\n  devtool: 'inline-source-map',\n\n  mode: 'development',\n\n  target: 'electron-preload',\n\n  entry: path.join(webpackPaths.srcMainPath, 'preload.ts'),\n\n  output: {\n    path: webpackPaths.dllPath,\n    filename: 'preload.js',\n    library: {\n      type: 'umd',\n    },\n  },\n\n  plugins: [\n    new BundleAnalyzerPlugin({\n      analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',\n    }),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     *\n     * By default, use 'development' as NODE_ENV. This can be overriden with\n     * 'staging', for example, by changing the ENV variables in the npm scripts\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'development',\n    }),\n\n    new webpack.LoaderOptionsPlugin({\n      debug: true,\n    }),\n  ],\n\n  /**\n   * Disables webpack processing of __dirname and __filename.\n   * If you run the bundle in node.js it falls back to these values of node.js.\n   * https://github.com/webpack/webpack/issues/2010\n   */\n  node: {\n    __dirname: false,\n    __filename: false,\n  },\n\n  watch: true,\n};\n\nexport default merge(baseConfig, configuration);\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.config.renderer.dev.dll.ts",
    "content": "/**\n * Builds the DLL for development electron renderer process\n */\n\nimport webpack from 'webpack';\nimport path from 'path';\nimport { merge } from 'webpack-merge';\nimport baseConfig from './webpack.config.base';\nimport webpackPaths from './webpack.paths';\nimport { dependencies } from '../../package.json';\nimport checkNodeEnv from '../scripts/check-node-env';\n\ncheckNodeEnv('development');\n\nconst dist = webpackPaths.dllPath;\n\nconst configuration: webpack.Configuration = {\n  context: webpackPaths.rootPath,\n\n  devtool: 'eval',\n\n  mode: 'development',\n\n  target: 'electron-renderer',\n\n  externals: ['fsevents', 'crypto-browserify'],\n\n  /**\n   * Use `module` from `webpack.config.renderer.dev.js`\n   */\n  module: require('./webpack.config.renderer.dev').default.module,\n\n  entry: {\n    renderer: Object.keys(dependencies || {}),\n  },\n\n  output: {\n    path: dist,\n    filename: '[name].dev.dll.js',\n    library: {\n      name: 'renderer',\n      type: 'var',\n    },\n  },\n\n  plugins: [\n    new webpack.DllPlugin({\n      path: path.join(dist, '[name].json'),\n      name: '[name]',\n    }),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'development',\n    }),\n\n    new webpack.LoaderOptionsPlugin({\n      debug: true,\n      options: {\n        context: webpackPaths.srcPath,\n        output: {\n          path: webpackPaths.dllPath,\n        },\n      },\n    }),\n  ],\n};\n\nexport default merge(baseConfig, configuration);\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.config.renderer.dev.ts",
    "content": "import 'webpack-dev-server';\nimport path from 'path';\nimport fs from 'fs';\nimport webpack from 'webpack';\nimport HtmlWebpackPlugin from 'html-webpack-plugin';\nimport chalk from 'chalk';\nimport { merge } from 'webpack-merge';\nimport { execSync, spawn } from 'child_process';\nimport ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';\nimport baseConfig from './webpack.config.base';\nimport webpackPaths from './webpack.paths';\nimport checkNodeEnv from '../scripts/check-node-env';\n\n// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's\n// at the dev webpack config is not accidentally run in a production environment\nif (process.env.NODE_ENV === 'production') {\n  checkNodeEnv('development');\n}\n\nconst port = process.env.PORT || 1212;\nconst manifest = path.resolve(webpackPaths.dllPath, 'renderer.json');\nconst skipDLLs =\n  module.parent?.filename.includes('webpack.config.renderer.dev.dll') ||\n  module.parent?.filename.includes('webpack.config.eslint');\n\n/**\n * Warn if the DLL is not built\n */\nif (\n  !skipDLLs &&\n  !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest))\n) {\n  console.log(\n    chalk.black.bgYellow.bold(\n      'The DLL files are missing. Sit back while we build them for you with \"npm run build-dll\"'\n    )\n  );\n  execSync('npm run postinstall');\n}\n\nconst configuration: webpack.Configuration = {\n  devtool: 'inline-source-map',\n\n  mode: 'development',\n\n  target: ['web', 'electron-renderer'],\n\n  entry: [\n    `webpack-dev-server/client?http://localhost:${port}/dist`,\n    'webpack/hot/only-dev-server',\n    path.join(webpackPaths.srcRendererPath, 'index.tsx'),\n  ],\n\n  output: {\n    path: webpackPaths.distRendererPath,\n    publicPath: '/',\n    filename: 'renderer.dev.js',\n    library: {\n      type: 'umd',\n    },\n  },\n\n  module: {\n    rules: [\n      {\n        test: /\\.s?css$/,\n        use: [\n          'style-loader',\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              sourceMap: true,\n              importLoaders: 1,\n            },\n          },\n          {\n            loader: 'sass-loader',\n            options: {\n              api: 'modern',\n            },\n          },\n        ],\n        include: /\\.module\\.s?(c|a)ss$/,\n      },\n      {\n        test: /\\.s?css$/,\n        use: [\n          'style-loader',\n          'css-loader',\n          {\n            loader: 'sass-loader',\n            options: {\n              api: 'modern',\n            },\n          },\n          {\n            loader: 'postcss-loader',\n            options: {\n              postcssOptions: {\n                plugins: [require('tailwindcss'), require('autoprefixer')],\n              },\n            },\n          },\n        ],\n        exclude: /\\.module\\.s?(c|a)ss$/,\n      },\n      // Fonts\n      {\n        test: /\\.(woff|woff2|eot|ttf|otf)$/i,\n        type: 'asset/resource',\n      },\n      // Images\n      {\n        test: /\\.(png|jpg|jpeg|gif)$/i,\n        type: 'asset/resource',\n      },\n      // SVG\n      {\n        test: /\\.svg$/,\n        use: [\n          {\n            loader: '@svgr/webpack',\n            options: {\n              prettier: false,\n              svgo: false,\n              svgoConfig: {\n                plugins: [{ removeViewBox: false }],\n              },\n              titleProp: true,\n              ref: true,\n            },\n          },\n          'file-loader',\n        ],\n      },\n      {\n        test: /\\.(mp3)$/i,\n        use: [\n          {\n            loader: 'file-loader',\n          },\n        ],\n      },\n    ],\n  },\n  plugins: [\n    ...(skipDLLs\n      ? []\n      : [\n          new webpack.DllReferencePlugin({\n            context: webpackPaths.dllPath,\n            manifest: require(manifest),\n            sourceType: 'var',\n          }),\n        ]),\n\n    new webpack.NoEmitOnErrorsPlugin(),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     *\n     * By default, use 'development' as NODE_ENV. This can be overriden with\n     * 'staging', for example, by changing the ENV variables in the npm scripts\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'development',\n    }),\n\n    new webpack.LoaderOptionsPlugin({\n      debug: true,\n    }),\n\n    new ReactRefreshWebpackPlugin(),\n\n    new HtmlWebpackPlugin({\n      filename: path.join('index.html'),\n      template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),\n      minify: {\n        collapseWhitespace: true,\n        removeAttributeQuotes: true,\n        removeComments: true,\n      },\n      isBrowser: false,\n      env: process.env.NODE_ENV,\n      isDevelopment: process.env.NODE_ENV !== 'production',\n      nodeModules: webpackPaths.appNodeModulesPath,\n    }),\n  ],\n\n  node: {\n    __dirname: false,\n    __filename: false,\n  },\n\n  devServer: {\n    port,\n    compress: true,\n    hot: true,\n    headers: { 'Access-Control-Allow-Origin': '*' },\n    static: {\n      publicPath: '/',\n    },\n    historyApiFallback: {\n      verbose: true,\n    },\n    setupMiddlewares(middlewares) {\n      console.log('Starting preload.js builder...');\n      const preloadProcess = spawn('npm', ['run', 'start:preload'], {\n        shell: true,\n        stdio: 'inherit',\n      })\n        .on('close', (code: number) => process.exit(code!))\n        .on('error', (spawnError) => console.error(spawnError));\n\n      console.log('Starting preload-webview.js builder...');\n      const preloadWebviewProcess = spawn(\n        'npm',\n        ['run', 'start:preloadWebview'],\n        {\n          shell: true,\n          stdio: 'inherit',\n        }\n      )\n        .on('close', (code: number) => process.exit(code!))\n        .on('error', (spawnError) => console.error(spawnError));\n\n      console.log('Starting Main Process...');\n      let args = ['run', 'start:main'];\n      if (process.env.MAIN_ARGS) {\n        args = args.concat(\n          ['--', ...process.env.MAIN_ARGS.matchAll(/\"[^\"]+\"|[^\\s\"]+/g)].flat()\n        );\n      }\n      spawn('npm', args, {\n        shell: true,\n        stdio: 'inherit',\n      })\n        .on('close', (code: number) => {\n          preloadProcess.kill();\n          preloadWebviewProcess.kill();\n          process.exit(code!);\n        })\n        .on('error', (spawnError) => console.error(spawnError));\n      return middlewares;\n    },\n  },\n};\n\nexport default merge(baseConfig, configuration);\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.config.renderer.prod.ts",
    "content": "/**\n * Build config for electron renderer process\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport HtmlWebpackPlugin from 'html-webpack-plugin';\nimport MiniCssExtractPlugin from 'mini-css-extract-plugin';\nimport { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';\nimport CssMinimizerPlugin from 'css-minimizer-webpack-plugin';\nimport { merge } from 'webpack-merge';\nimport TerserPlugin from 'terser-webpack-plugin';\nimport baseConfig from './webpack.config.base';\nimport webpackPaths from './webpack.paths';\nimport checkNodeEnv from '../scripts/check-node-env';\nimport deleteSourceMaps from '../scripts/delete-source-maps';\n\ncheckNodeEnv('production');\ndeleteSourceMaps();\n\nconst devtoolsConfig =\n  process.env.DEBUG_PROD === 'true'\n    ? {\n        devtool: 'source-map',\n      }\n    : {};\n\nconst configuration: webpack.Configuration = {\n  ...devtoolsConfig,\n\n  mode: 'production',\n\n  target: ['web', 'electron-renderer'],\n\n  entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],\n\n  externals: ['fsevents'],\n\n  output: {\n    path: webpackPaths.distRendererPath,\n    publicPath: './',\n    filename: 'renderer.js',\n    library: {\n      type: 'umd',\n    },\n  },\n\n  module: {\n    rules: [\n      {\n        test: /\\.s?(a|c)ss$/,\n        use: [\n          MiniCssExtractPlugin.loader,\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              sourceMap: true,\n              importLoaders: 1,\n            },\n          },\n          {\n            loader: 'sass-loader',\n            options: {\n              api: 'modern',\n            },\n          },\n        ],\n        include: /\\.module\\.s?(c|a)ss$/,\n      },\n      {\n        test: /\\.s?(a|c)ss$/,\n        use: [\n          MiniCssExtractPlugin.loader,\n          'css-loader',\n          {\n            loader: 'sass-loader',\n            options: {\n              api: 'modern',\n            },\n          },\n          {\n            loader: 'postcss-loader',\n            options: {\n              postcssOptions: {\n                plugins: [require('tailwindcss'), require('autoprefixer')],\n              },\n            },\n          },\n        ],\n        exclude: /\\.module\\.s?(c|a)ss$/,\n      },\n      // Fonts\n      {\n        test: /\\.(woff|woff2|eot|ttf|otf)$/i,\n        type: 'asset/resource',\n      },\n      // Images\n      {\n        test: /\\.(png|svg|jpg|jpeg|gif)$/i,\n        type: 'asset/resource',\n      },\n      // SVG\n      {\n        test: /\\.svg$/,\n        use: [\n          {\n            loader: '@svgr/webpack',\n            options: {\n              prettier: false,\n              svgo: false,\n              svgoConfig: {\n                plugins: [{ removeViewBox: false }],\n              },\n              titleProp: true,\n              ref: true,\n            },\n          },\n          'file-loader',\n        ],\n      },\n      {\n        test: /\\.(mp3)$/i,\n        use: [\n          {\n            loader: 'file-loader',\n          },\n        ],\n      },\n    ],\n  },\n\n  optimization: {\n    minimize: true,\n    minimizer: [\n      new TerserPlugin({\n        parallel: true,\n      }),\n      new CssMinimizerPlugin(),\n    ],\n  },\n\n  plugins: [\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production',\n      DEBUG_PROD: false,\n    }),\n\n    new MiniCssExtractPlugin({\n      filename: 'style.css',\n    }),\n\n    new BundleAnalyzerPlugin({\n      analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',\n    }),\n\n    new HtmlWebpackPlugin({\n      filename: 'index.html',\n      template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),\n      minify: {\n        collapseWhitespace: true,\n        removeAttributeQuotes: true,\n        removeComments: true,\n      },\n      isBrowser: false,\n      isDevelopment: process.env.NODE_ENV !== 'production',\n    }),\n\n    new webpack.DefinePlugin({\n      'process.type': '\"browser\"',\n    }),\n  ],\n};\n\nexport default merge(baseConfig, configuration);\n"
  },
  {
    "path": "desktop-app/.erb/configs/webpack.paths.ts",
    "content": "const path = require('path');\n\nconst rootPath = path.join(__dirname, '../..');\n\nconst dllPath = path.join(__dirname, '../dll');\n\nconst srcPath = path.join(rootPath, 'src');\nconst srcMainPath = path.join(srcPath, 'main');\nconst srcRendererPath = path.join(srcPath, 'renderer');\n\nconst releasePath = path.join(rootPath, 'release');\nconst appPath = path.join(releasePath, 'app');\nconst appPackagePath = path.join(appPath, 'package.json');\nconst appNodeModulesPath = path.join(appPath, 'node_modules');\nconst srcNodeModulesPath = path.join(srcPath, 'node_modules');\n\nconst distPath = path.join(appPath, 'dist');\nconst distMainPath = path.join(distPath, 'main');\nconst distRendererPath = path.join(distPath, 'renderer');\n\nconst buildPath = path.join(releasePath, 'build');\n\nexport default {\n  rootPath,\n  dllPath,\n  srcPath,\n  srcMainPath,\n  srcRendererPath,\n  releasePath,\n  appPath,\n  appPackagePath,\n  appNodeModulesPath,\n  srcNodeModulesPath,\n  distPath,\n  distMainPath,\n  distRendererPath,\n  buildPath,\n};\n"
  },
  {
    "path": "desktop-app/.erb/mocks/fileMock.js",
    "content": "export default 'test-file-stub';\n"
  },
  {
    "path": "desktop-app/.erb/scripts/.eslintrc",
    "content": "{\n  \"rules\": {\n    \"no-console\": \"off\",\n    \"global-require\": \"off\",\n    \"import/no-dynamic-require\": \"off\",\n    \"import/no-extraneous-dependencies\": \"off\"\n  }\n}\n"
  },
  {
    "path": "desktop-app/.erb/scripts/check-build-exists.ts",
    "content": "// Check if the renderer and main bundles are built\nimport path from 'path';\nimport chalk from 'chalk';\nimport fs from 'fs';\nimport webpackPaths from '../configs/webpack.paths';\n\nconst mainPath = path.join(webpackPaths.distMainPath, 'main.js');\nconst rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js');\n\nif (!fs.existsSync(mainPath)) {\n  throw new Error(\n    chalk.whiteBright.bgRed.bold(\n      'The main process is not built yet. Build it by running \"yarn run build:main\"'\n    )\n  );\n}\n\nif (!fs.existsSync(rendererPath)) {\n  throw new Error(\n    chalk.whiteBright.bgRed.bold(\n      'The renderer process is not built yet. Build it by running \"yarn run build:renderer\"'\n    )\n  );\n}\n"
  },
  {
    "path": "desktop-app/.erb/scripts/check-native-dep.js",
    "content": "import fs from 'fs';\nimport chalk from 'chalk';\nimport {execSync} from 'child_process';\nimport {dependencies} from '../../package.json';\n\nif (dependencies) {\n  const dependenciesKeys = Object.keys(dependencies);\n  const nativeDeps = fs\n    .readdirSync('node_modules')\n    .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));\n  if (nativeDeps.length === 0) {\n    process.exit(0);\n  }\n  try {\n    // Find the reason for why the dependency is installed. If it is installed\n    // because of a devDependency then that is okay. Warn when it is installed\n    // because of a dependency\n    const {dependencies: dependenciesObject} = JSON.parse(\n      execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()\n    );\n    const rootDependencies = Object.keys(dependenciesObject);\n    const filteredRootDependencies = rootDependencies.filter((rootDependency) =>\n      dependenciesKeys.includes(rootDependency)\n    );\n    if (filteredRootDependencies.length > 0) {\n      const plural = filteredRootDependencies.length > 1;\n      console.log(`\n ${chalk.whiteBright.bgYellow.bold('Webpack does not work with native dependencies.')}\n${chalk.bold(filteredRootDependencies.join(', '))} ${\n        plural ? 'are native dependencies' : 'is a native dependency'\n      } and should be installed inside of the \"./release/app\" folder.\n First, uninstall the packages from \"./package.json\":\n${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}\n ${chalk.bold('Then, instead of installing the package to the root \"./package.json\":')}\n${chalk.whiteBright.bgRed.bold('npm install your-package')}\n ${chalk.bold('Install the package to \"./release/app/package.json\"')}\n${chalk.whiteBright.bgGreen.bold('cd ./release/app && npm install your-package')}\n Read more about native dependencies at:\n${chalk.bold(\n  'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure'\n)}\n `);\n      process.exit(1);\n    }\n  } catch (e) {\n    console.log('Native dependencies could not be checked');\n  }\n}\n"
  },
  {
    "path": "desktop-app/.erb/scripts/check-node-env.js",
    "content": "import chalk from 'chalk';\n\nexport default function checkNodeEnv(expectedEnv) {\n  if (!expectedEnv) {\n    throw new Error('\"expectedEnv\" not set');\n  }\n\n  if (process.env.NODE_ENV !== expectedEnv) {\n    console.log(\n      chalk.whiteBright.bgRed.bold(\n        `\"process.env.NODE_ENV\" must be \"${expectedEnv}\" to use this webpack config`\n      )\n    );\n    process.exit(2);\n  }\n}\n"
  },
  {
    "path": "desktop-app/.erb/scripts/check-port-in-use.js",
    "content": "import chalk from 'chalk';\nimport detectPort from 'detect-port';\n\nconst port = process.env.PORT || '1212';\n\ndetectPort(port, (err, availablePort) => {\n  if (port !== String(availablePort)) {\n    throw new Error(\n      chalk.whiteBright.bgRed.bold(\n        `Port \"${port}\" on \"localhost\" is already in use. Please use another port. ex: PORT=4343 yarn start`\n      )\n    );\n  } else {\n    process.exit(0);\n  }\n});\n"
  },
  {
    "path": "desktop-app/.erb/scripts/clean.js",
    "content": "import rimraf from 'rimraf';\nimport webpackPaths from '../configs/webpack.paths';\n\nconst foldersToRemove = [webpackPaths.distPath, webpackPaths.buildPath, webpackPaths.dllPath];\n\nfoldersToRemove.forEach((folder) => {\n  rimraf.sync(folder);\n});\n"
  },
  {
    "path": "desktop-app/.erb/scripts/delete-source-maps.js",
    "content": "import path from 'path';\nimport rimraf from 'rimraf';\nimport webpackPaths from '../configs/webpack.paths';\n\nexport default function deleteSourceMaps() {\n  rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map'));\n  rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map'));\n}\n"
  },
  {
    "path": "desktop-app/.erb/scripts/electron-rebuild.js",
    "content": "import {execSync} from 'child_process';\nimport fs from 'fs';\nimport {dependencies} from '../../release/app/package.json';\nimport webpackPaths from '../configs/webpack.paths';\n\nif (Object.keys(dependencies || {}).length > 0 && fs.existsSync(webpackPaths.appNodeModulesPath)) {\n  const electronRebuildCmd =\n    '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .';\n  const cmd =\n    process.platform === 'win32' ? electronRebuildCmd.replace(/\\//g, '\\\\') : electronRebuildCmd;\n  execSync(cmd, {\n    cwd: webpackPaths.appPath,\n    stdio: 'inherit',\n  });\n}\n"
  },
  {
    "path": "desktop-app/.erb/scripts/link-modules.ts",
    "content": "import fs from 'fs';\nimport webpackPaths from '../configs/webpack.paths';\n\nconst {srcNodeModulesPath} = webpackPaths;\nconst {appNodeModulesPath} = webpackPaths;\n\nif (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {\n  fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction');\n}\n"
  },
  {
    "path": "desktop-app/.erb/scripts/notarize.js",
    "content": "const {notarize} = require('@electron/notarize');\n\nexports.default = async function notarizeMacos(context) {\n  const {electronPlatformName, appOutDir} = context;\n  if (electronPlatformName !== 'darwin') {\n    return;\n  }\n\n  if (process.env.CI !== 'true') {\n    console.warn('Skipping notarizing step. Packaging is not running in CI');\n    return;\n  }\n\n  if (\n    !('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env && 'APPLE_TEAM_ID' in process.env)\n  ) {\n    console.warn(\n      'Skipping notarizing step. APPLE_ID, APPLE_ID_PASS, and APPLE_TEAM_ID env variables must be set'\n    );\n    return;\n  }\n\n  const appName = context.packager.appInfo.productFilename;\n\n  await notarize({\n    appPath: `${appOutDir}/${appName}.app`,\n    appleId: process.env.APPLE_ID,\n    appleIdPassword: process.env.APPLE_ID_PASS,\n    teamId: process.env.APPLE_TEAM_ID,\n  });\n};\n"
  },
  {
    "path": "desktop-app/.eslintignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Coverage directory used by tools like istanbul\ncoverage\n.eslintcache\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# OSX\n.DS_Store\n\nrelease/app/dist\nrelease/build\n.erb/dll\n\n.idea\nnpm-debug.log.*\n*.css.d.ts\n*.sass.d.ts\n*.scss.d.ts\n\n# eslint ignores hidden directories by default:\n# https://github.com/eslint/eslint/issues/8429\n!.erb\n.erb/configs/\n"
  },
  {
    "path": "desktop-app/.eslintrc.js",
    "content": "module.exports = {\n  env: {\n    browser: true,\n    node: true,\n    es2021: true,\n  },\n  parser: '@typescript-eslint/parser',\n  extends: [\n    'eslint:recommended',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:react/recommended',\n    'plugin:react-hooks/recommended',\n    'plugin:import/recommended',\n    'plugin:import/typescript',\n    'plugin:jsx-a11y/recommended',\n    'plugin:promise/recommended',\n    'plugin:prettier/recommended', // must be last — disables conflicting rules\n  ],\n  rules: {\n    'import/no-extraneous-dependencies': 'off',\n    'import/no-unresolved': 'error',\n    'import/prefer-default-export': 'off',\n    'import/extensions': [\n      'error',\n      'ignorePackages',\n      {ts: 'never', tsx: 'never', js: 'never', jsx: 'never'},\n    ],\n\n    // Since React 17 and typescript 4.1 you can safely disable the rule\n    'react/react-in-jsx-scope': 'off',\n    'react/require-default-props': 'off',\n\n    // Relax rules that are too noisy for this codebase\n    '@typescript-eslint/no-var-requires': 'off',\n    '@typescript-eslint/no-explicit-any': 'warn',\n    '@typescript-eslint/no-empty-function': 'warn',\n  },\n  parserOptions: {\n    ecmaVersion: 2020,\n    sourceType: 'module',\n    project: './tsconfig.json',\n    tsconfigRootDir: __dirname,\n    createDefaultProgram: true,\n  },\n  settings: {\n    react: {\n      version: 'detect',\n    },\n    'import/resolver': {\n      // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below\n      node: {},\n      webpack: {\n        config: require.resolve('./.erb/configs/webpack.config.eslint.ts'),\n      },\n      typescript: {},\n    },\n    'import/parsers': {\n      '@typescript-eslint/parser': ['.ts', '.tsx'],\n    },\n  },\n};\n"
  },
  {
    "path": "desktop-app/.gitattributes",
    "content": "*       text    eol=lf\n*.exe   binary\n*.png   binary\n*.jpg   binary\n*.jpeg  binary\n*.ico   binary\n*.icns  binary\n*.eot   binary\n*.otf   binary\n*.ttf   binary\n*.woff  binary\n*.woff2 binary\n"
  },
  {
    "path": "desktop-app/.gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Coverage directory used by tools like istanbul\ncoverage\n.eslintcache\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# OSX\n.DS_Store\n\nrelease/app/dist\nrelease/build\n.erb/dll\n\n.idea\nnpm-debug.log.*\n*.css.d.ts\n*.sass.d.ts\n*.scss.d.ts\n"
  },
  {
    "path": "desktop-app/.husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": "desktop-app/.prettierrc",
    "content": "{\n  \"overrides\": [\n    {\n      \"files\": [\".prettierrc\", \".babelrc\", \".eslintrc\", \".stylelintrc\"],\n      \"options\": {\n        \"parser\": \"json\"\n      }\n    }\n  ],\n  \"arrowParens\": \"always\",\n  \"singleQuote\": true,\n  \"bracketSpacing\": false,\n  \"printWidth\": 100,\n  \"tailwindConfig\": \"./tailwind.config.js\",\n  \"trailingComma\": \"es5\"\n}"
  },
  {
    "path": "desktop-app/.vscode/settings.json",
    "content": "{\n  \"files.associations\": {\n    \".eslintrc\": \"jsonc\",\n    \".prettierrc\": \"jsonc\",\n    \".eslintignore\": \"ignore\"\n  },\n  \n  \"eslint.validate\": [\n    \"javascript\",\n    \"javascriptreact\",\n    \"html\",\n    \"typescriptreact\"\n  ],\n\n  \"javascript.validate.enable\": false,\n  \"javascript.format.enable\": false,\n  \"typescript.format.enable\": false,\n\n  \"search.exclude\": {\n    \".git\": true,\n    \".eslintcache\": true,\n    \".erb/dll\": true,\n    \"release/{build,app/dist}\": true,\n    \"node_modules\": true,\n    \"npm-debug.log.*\": true,\n    \"test/**/__snapshots__\": true,\n    \"package-lock.json\": true,\n    \"*.{css,sass,scss}.d.ts\": true\n  }\n}\n"
  },
  {
    "path": "desktop-app/CHANGELOG.md",
    "content": "# 2.1.0\n\n- Migrate to `css-minifier-webpack-plugin`\n\n# 2.0.1\n\n## Fixes\n\n- Fix broken css linking in production build\n\n# 2.0.0\n\n## Breaking Changes\n\n- drop redux\n- remove counter example app\n- simplify directory structure\n- move `dll` dir to `.erb` dir\n- fix icon/font import paths\n- migrate to `react-refresh` from `react-hot-loader`\n- migrate to webpack@5\n- migrate to electron@11\n- remove e2e tests and testcafe integration\n- rename `app` dir to more conventional `src` dir\n- rename `resources` dir to `assets`\n- simplify npm scripts\n- drop stylelint\n- simplify styling of boilerplate app\n- remove `START_HOT` env variable\n- notarize support\n- landing page boilerplate\n- docs updates\n- restore removed debugging support\n\n# 1.4.0\n\n- Migrate to `eslint-config-erb@2`\n- Rename `dev` npm script to `start`\n- GitHub Actions: only publish GitHub releases when on master branch\n\n# 1.3.1\n\n- Fix sass building bug ([#2540](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2540))\n- Fix CI bug related to E2E tests and network timeouts\n- Move automated dependency PRs to `next` ([#2554](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2554))\n- Bump dependencies to patch semver\n\n# 1.3.0\n\n- Fixes E2E tests ([#2516](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2516))\n- Fixes preload entrypoint ([#2503](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2503))\n- Downgrade to `electron@8`\n- Bump dependencies to latest semver\n\n# 1.2.0\n\n- Migrate to redux toolkit\n- Lazy load routes with react suspense\n- Drop support for azure-pipelines and use only github actions\n- Bump all deps to latest semver\n- Remove `test-e2e` script from tests (blocked on release of https://github.com/DevExpress/testcafe-browser-provider-electron/pull/65)\n- Swap `typed-css-modules-webpack-plugin` for `typings-for-css-modules-loader`\n- Use latest version of `eslint-config-erb`\n- Remove unnecessary file extensions from ts exclude\n- Add experimental support for vscode debugging\n- Revert https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2365 as default for users, provide as opt in option\n\n# 1.1.0\n\n- Fix #2402\n- Simplify configs (https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2406)\n\n# 1.0.0\n\n- Migrate to TypeScript from Flow ([#2363](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2363))\n- Use browserslist for `@babel/preset-env` targets ([#2368](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2368))\n- Use preload script, disable `nodeIntegration` in renderer process for [improved security](https://www.electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content) ([#2365](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2365))\n- Add support for azure pipelines ([#2369](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2369))\n- Disable sourcemaps in production\n\n# 0.18.1 (2019.12.12)\n\n- Fix HMR env bug ([#2343](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2343))\n- Bump all deps to latest semver\n- Bump to `electron@7`\n\n# 0.18.0 (2019.11.19)\n\n- Bump electron to `electron@6` (`electron@7` introduces breaking changes to testcafe end to end tests)\n- Revert back to [two `package.json` structure](https://www.electron.build/tutorials/two-package-structure)\n- Bump all deps to latest semver\n\n# 0.17.1 (2018.11.20)\n\n- Fix `yarn test-e2e` and testcafe for single package.json structure\n- Fixes incorrect path in `yarn start` script\n- Bumped deps\n- Bump g++ in travis\n- Change clone arguments to clone only master\n- Change babel config to target current electron version\n\nFor full change list, see https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2021\n\n# 0.17.0 (2018.10.30)\n\n- upgraded to `babel@7` (thanks to @vikr01 🎉🎉🎉)\n- migrated from [two `package.json` structure](https://www.electron.build/tutorials/two-package-structure) (thanks to @HyperSprite!)\n- initial auto update support (experimental)\n- migrate from greenkeeper to [renovate](https://renovatebot.com)\n- added issue template\n- use `babel-preset-env` to target current electron version\n- add [opencollective](https://opencollective.com/electron-react-boilerplate-594) banner message display in postinstall script (help support ERB 🙏)\n- fix failing ci issues\n\n# 0.16.0 (2018.10.3)\n\n- removed unused dependencies\n- migrate from `react-redux-router` to `connect-react-router`\n- move webpack configs to `./webpack` dir\n- use `g++` on travis when testing linux\n- migrate from `spectron` to `testcafe` for e2e tests\n- add linting support for config styles\n- changed stylelint config\n- temporarily disabled flow in appveyor to make ci pass\n- added necessary infra to publish releases from ci\n\n# 0.15.0 (2018.8.25)\n\n- Performance: cache webpack uglify results\n- Feature: add start minimized feature\n- Feature: lint and fix styles with prettier and stylelint\n- Feature: add greenkeeper support\n\n# 0.14.0 (2018.5.24)\n\n- Improved CI timings\n- Migrated README commands to yarn from npm\n- Improved vscode config\n- Updated all dependencies to latest semver\n- Fix `electron-rebuild` script bug\n- Migrated to `mini-css-extract-plugin` from `extract-text-plugin`\n- Added `optimize-css-assets-webpack-plugin`\n- Run `prettier` on json, css, scss, and more filetypes\n\n# 0.13.3 (2018.5.24)\n\n- Add git precommit hook, when git commit will use `prettier` to format git add code\n- Add format code function in `lint-fix` npm script which can use `prettier` to format project js code\n\n# 0.13.2 (2018.1.31)\n\n- Hot Module Reload (HMR) fixes\n- Bumped all dependencies to latest semver\n- Prevent error propagation of `CheckNativeDeps` script\n\n# 0.13.1 (2018.1.13)\n\n- Hot Module Reload (HMR) fixes\n- Bumped all dependencies to latest semver\n- Fixed electron-rebuild script\n- Fixed tests scripts to run on all platforms\n- Skip redux logs in console in test ENV\n\n# 0.13.0 (2018.1.6)\n\n#### Additions\n\n- Add native dependencies check on postinstall\n- Updated all dependencies to latest semver\n\n# 0.12.0 (2017.7.8)\n\n#### Misc\n\n- Removed `babel-polyfill`\n- Renamed and alphabetized npm scripts\n\n#### Breaking\n\n- Changed node dev `__dirname` and `__filename` to node built in fn's (https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/1035)\n- Renamed `src/bundle.js` to `src/renderer.prod.js` for consistency\n- Renamed `dll/vendor.js` to `dll/renderer.dev.dll.js` for consistency\n\n#### Additions\n\n- Enable node_modules cache on CI\n\n# 0.11.2 (2017.5.1)\n\nYay! Another patch release. This release mostly includes refactorings and router bug fixes. Huge thanks to @anthonyraymond!\n\n⚠️ Windows electron builds are failing because of [this issue](https://github.com/electron/electron/issues/9321). This is not an issue with the boilerplate ⚠️\n\n#### Breaking\n\n- **Renamed `./src/main.development.js` => `./src/main.{dev,prod}.js`:** [#963](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/963)\n\n#### Fixes\n\n- **Fixed reloading when not on `/` path:** [#958](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/958) [#949](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/949)\n\n#### Additions\n\n- **Added support for stylefmt:** [#960](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/960)\n\n# 0.11.1 (2017.4.23)\n\nYou can now debug the production build with devtools like so:\n\n```\nDEBUG_PROD=true npm run package\n```\n\n🎉🎉🎉\n\n#### Additions\n\n- **Added support for debugging production build:** [#fab245a](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/941/commits/fab245a077d02a09630f74270806c0c534a4ff95)\n\n#### Bug Fixes\n\n- **Fixed bug related to importing native dependencies:** [#933](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/933)\n\n#### Improvements\n\n- **Updated all deps to latest semver**\n\n# 0.11.0 (2017.4.19)\n\nHere's the most notable changes since `v0.10.0`. Its been about a year since a release has been pushed. Expect a new release to be published every 3-4 weeks.\n\n#### Breaking Changes\n\n- **Dropped support for node < 6**\n- **Refactored webpack config files**\n- **Migrate to two-package.json project structure**\n- **Updated all devDeps to latest semver**\n- **Migrated to Jest:** [#768](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/768)\n- **Migrated to `react-router@4`**\n- **Migrated to `electron-builder@4`**\n- **Migrated to `webpack@2`**\n- **Migrated to `react-hot-loader@3`**\n- **Changed default live reload server PORT to `1212` from `3000`**\n\n#### Additions\n\n- **Added support for Yarn:** [#451](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/451)\n- **Added support for Flow:** [#425](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/425)\n- **Added support for stylelint:** [#911](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/911)\n- **Added support for electron-builder:** [#876](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/876)\n- **Added optional support for SASS:** [#880](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/880)\n- **Added support for eslint-plugin-flowtype:** [#911](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/911)\n- **Added support for appveyor:** [#280](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/280)\n- **Added support for webpack dlls:** [#860](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/860)\n- **Route based code splitting:** [#884](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/884)\n- **Added support for Webpack Bundle Analyzer:** [#922](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/922)\n\n#### Improvements\n\n- **Parallelize renderer and main build processes when running `npm run build`**\n- **Dynamically generate electron app menu**\n- **Improved vscode integration:** [#856](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/856)\n\n#### Bug Fixes\n\n- **Fixed hot module replacement race condition bug:** [#917](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/917) [#920](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/920)\n\n# 0.10.0 (2016.4.18)\n\n#### Improvements\n\n- **Use Babel in main process with Webpack build:** [#201](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/201)\n- **Change targets to built-in support by webpack:** [#197](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/197)\n- **use es2015 syntax for webpack configs:** [#195](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/195)\n- **Open application when webcontent is loaded:** [#192](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/192)\n- **Upgraded dependencies**\n\n#### Bug fixed\n\n- **Fix `npm list electron-prebuilt` in package.js:** [#188](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/188)\n\n# 0.9.0 (2016.3.23)\n\n#### Improvements\n\n- **Added [redux-logger](https://github.com/fcomb/redux-logger)**\n- **Upgraded [react-router-redux](https://github.com/reactjs/react-router-redux) to v4**\n- **Upgraded dependencies**\n- **Added `npm run dev` command:** [#162](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/162)\n- **electron to v0.37.2**\n\n#### Breaking Changes\n\n- **css module as default:** [#154](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/154).\n- **set default NODE_ENV to production:** [#140](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/140)\n\n# 0.8.0 (2016.2.17)\n\n#### Bug fixed\n\n- **Fix lint errors**\n- **Fix Webpack publicPath for production builds**: [#119](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/119).\n- **package script now chooses correct OS icon extension**\n\n#### Improvements\n\n- **babel 6**\n- **Upgrade Dependencies**\n- **Enable CSS source maps**\n- **Add json-loader**: [#128](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/128).\n- **react-router 2.0 and react-router-redux 3.0**\n\n# 0.7.1 (2015.12.27)\n\n#### Bug fixed\n\n- **Fixed npm script on windows 10:** [#103](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/103).\n- **history and react-router version bump**: [#109](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/109), [#110](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/110).\n\n#### Improvements\n\n- **electron 0.36**\n\n# 0.7.0 (2015.12.16)\n\n#### Bug fixed\n\n- **Fixed process.env.NODE_ENV variable in webpack:** [#74](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/74).\n- **add missing object-assign**: [#76](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/76).\n- **packaging in npm@3:** [#77](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/77).\n- **compatibility in windows:** [#100](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/100).\n- **disable chrome debugger in production env:** [#102](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/102).\n\n#### Improvements\n\n- **redux**\n- **css-modules**\n- **upgrade to react-router 1.x**\n- **unit tests**\n- **e2e tests**\n- **travis-ci**\n- **upgrade to electron 0.35.x**\n- **use es2015**\n- **check dev engine for node and npm**\n\n# 0.6.5 (2015.11.7)\n\n#### Improvements\n\n- **Bump style-loader to 0.13**\n- **Bump css-loader to 0.22**\n\n# 0.6.4 (2015.10.27)\n\n#### Improvements\n\n- **Bump electron-debug to 0.3**\n\n# 0.6.3 (2015.10.26)\n\n#### Improvements\n\n- **Initialize ExtractTextPlugin once:** [#64](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/64).\n\n# 0.6.2 (2015.10.18)\n\n#### Bug fixed\n\n- **Babel plugins production env not be set properly:** [#57](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/57).\n\n# 0.6.1 (2015.10.17)\n\n#### Improvements\n\n- **Bump electron to v0.34.0**\n\n# 0.6.0 (2015.10.16)\n\n#### Breaking Changes\n\n- **From react-hot-loader to react-transform**\n\n# 0.5.2 (2015.10.15)\n\n#### Improvements\n\n- **Run tests with babel-register:** [#29](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/29).\n\n# 0.5.1 (2015.10.12)\n\n#### Bug fixed\n\n- **Fix #51:** use `path.join(__dirname` instead of `./`.\n\n# 0.5.0 (2015.10.11)\n\n#### Improvements\n\n- **Simplify webpack config** see [#50](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/50).\n\n#### Breaking Changes\n\n- **webpack configs**\n- **port changed:** changed default port from 2992 to 3000.\n- **npm scripts:** remove `start-dev` and `dev-server`. rename `hot-dev-server` to `hot-server`.\n\n# 0.4.3 (2015.9.22)\n\n#### Bug fixed\n\n- **Fix #45 zeromq crash:** bump version of `electron-prebuilt`.\n\n# 0.4.2 (2015.9.15)\n\n#### Bug fixed\n\n- **run start-hot breaks chrome refresh(CTRL+R) (#42)**: bump `electron-debug` to `0.2.1`\n\n# 0.4.1 (2015.9.11)\n\n#### Improvements\n\n- **use electron-prebuilt version for packaging (#33)**\n\n# 0.4.0 (2015.9.5)\n\n#### Improvements\n\n- **update dependencies**\n\n# 0.3.0 (2015.8.31)\n\n#### Improvements\n\n- **eslint-config-airbnb**\n\n# 0.2.10 (2015.8.27)\n\n#### Features\n\n- **custom placeholder icon**\n\n#### Improvements\n\n- **electron-renderer as target:** via [webpack-target-electron-renderer](https://github.com/chentsulin/webpack-target-electron-renderer)\n\n# 0.2.9 (2015.8.18)\n\n#### Bug fixed\n\n- **Fix hot-reload**\n\n# 0.2.8 (2015.8.13)\n\n#### Improvements\n\n- **bump electron-debug**\n- **babelrc**\n- **organize webpack scripts**\n\n# 0.2.7 (2015.7.9)\n\n#### Bug fixed\n\n- **defaultProps:** fix typos.\n\n# 0.2.6 (2015.7.3)\n\n#### Features\n\n- **menu**\n\n#### Bug fixed\n\n- **package.js:** include webpack build.\n\n# 0.2.5 (2015.7.1)\n\n#### Features\n\n- **NPM Script:** support multi-platform\n- **package:** `--all` option\n\n# 0.2.4 (2015.6.9)\n\n#### Bug fixed\n\n- **Eslint:** typo, [#17](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/17) and improve `.eslintrc`\n\n# 0.2.3 (2015.6.3)\n\n#### Features\n\n- **Package Version:** use latest release electron version as default\n- **Ignore Large peerDependencies**\n\n#### Bug fixed\n\n- **Npm Script:** typo, [#6](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/6)\n- **Missing css:** [#7](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/7)\n\n# 0.2.2 (2015.6.2)\n\n#### Features\n\n- **electron-debug**\n\n#### Bug fixed\n\n- **Webpack:** add `.json` and `.node` to extensions for imitating node require.\n- **Webpack:** set `node_modules` to externals for native module support.\n\n# 0.2.1 (2015.5.30)\n\n#### Bug fixed\n\n- **Webpack:** #1, change build target to `atom`.\n\n# 0.2.0 (2015.5.30)\n\n#### Features\n\n- **Ignore:** `test`, `tools`, `release` folder and devDependencies in `package.json`.\n- **Support asar**\n- **Support icon**\n\n# 0.1.0 (2015.5.27)\n\n#### Features\n\n- **Webpack:** babel, react-hot, ...\n- **Flux:** actions, api, components, containers, stores..\n- **Package:** darwin (osx), linux and win32 (windows) platform.\n"
  },
  {
    "path": "desktop-app/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at electronreactboilerplate@gmail.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "desktop-app/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-present Electron React Boilerplate\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "desktop-app/README.md",
    "content": "<img src=\".erb/img/erb-banner.svg\" width=\"100%\" />\n\n<br>\n\n<p>\n  Electron React Boilerplate uses <a href=\"https://electron.atom.io/\">Electron</a>, <a href=\"https://facebook.github.io/react/\">React</a>, <a href=\"https://github.com/reactjs/react-router\">React Router</a>, <a href=\"https://webpack.js.org/\">Webpack</a> and <a href=\"https://www.npmjs.com/package/react-refresh\">React Fast Refresh</a>.\n</p>\n\n<br>\n\n<div align=\"center\">\n\n[![Build Status][github-actions-status]][github-actions-url]\n[![Github Tag][github-tag-image]][github-tag-url]\n[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/Fjy3vfgy5q)\n\n[![OpenCollective](https://opencollective.com/electron-react-boilerplate-594/backers/badge.svg)](#backers)\n[![OpenCollective](https://opencollective.com/electron-react-boilerplate-594/sponsors/badge.svg)](#sponsors)\n[![StackOverflow][stackoverflow-img]][stackoverflow-url]\n\n</div>\n\n## Install\n\nClone the repo and install dependencies:\n\n```bash\ngit clone --depth 1 --branch main https://github.com/electron-react-boilerplate/electron-react-boilerplate.git your-project-name\ncd your-project-name\nnpm install\n```\n\n**Having issues installing? See our [debugging guide](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400)**\n\n## Starting Development\n\nStart the app in the `dev` environment:\n\n```bash\nnpm start\n```\n\n## Packaging for Production\n\nTo package apps for the local platform:\n\n```bash\nnpm run package\n```\n\n## Docs\n\nSee our [docs and guides here](https://electron-react-boilerplate.js.org/docs/installation)\n\n## Community\n\nJoin our Discord: https://discord.gg/Fjy3vfgy5q\n\n## Donations\n\n**Donations will ensure the following:**\n\n- 🔨 Long term maintenance of the project\n- 🛣 Progress on the [roadmap](https://electron-react-boilerplate.js.org/docs/roadmap)\n- 🐛 Quick responses to bug reports and help requests\n\n## Backers\n\nSupport us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/electron-react-boilerplate-594#backer)]\n\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/0/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/1/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/2/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/3/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/4/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/5/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/6/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/7/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/8/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/9/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/9/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/10/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/10/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/11/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/11/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/12/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/12/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/13/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/13/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/14/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/14/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/15/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/15/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/16/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/16/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/17/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/17/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/18/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/18/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/19/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/19/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/20/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/20/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/21/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/21/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/22/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/22/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/23/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/23/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/24/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/24/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/25/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/25/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/26/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/26/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/27/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/27/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/28/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/28/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/backer/29/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/backer/29/avatar.svg\"></a>\n\n## Sponsors\n\nBecome a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/electron-react-boilerplate-594-594#sponsor)]\n\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/0/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/1/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/2/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/3/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/4/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/5/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/6/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/7/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/8/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/9/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/9/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/10/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/10/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/11/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/11/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/12/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/12/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/13/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/13/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/14/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/14/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/15/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/15/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/16/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/16/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/17/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/17/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/18/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/18/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/19/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/19/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/20/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/20/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/21/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/21/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/22/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/22/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/23/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/23/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/24/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/24/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/25/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/25/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/26/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/26/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/27/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/27/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/28/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/28/avatar.svg\"></a>\n<a href=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/29/website\" target=\"_blank\"><img src=\"https://opencollective.com/electron-react-boilerplate-594/sponsor/29/avatar.svg\"></a>\n\n## Maintainers\n\n- [Amila Welihinda](https://github.com/amilajack)\n- [John Tran](https://github.com/jooohhn)\n- [C. T. Lin](https://github.com/chentsulin)\n- [Jhen-Jie Hong](https://github.com/jhen0409)\n\n## License\n\nMIT © [Electron React Boilerplate](https://github.com/electron-react-boilerplate)\n\n[github-actions-status]: https://github.com/electron-react-boilerplate/electron-react-boilerplate/workflows/Test/badge.svg\n[github-actions-url]: https://github.com/electron-react-boilerplate/electron-react-boilerplate/actions\n[github-tag-image]: https://img.shields.io/github/tag/electron-react-boilerplate/electron-react-boilerplate.svg?label=version\n[github-tag-url]: https://github.com/electron-react-boilerplate/electron-react-boilerplate/releases/latest\n[stackoverflow-img]: https://img.shields.io/badge/stackoverflow-electron_react_boilerplate-blue.svg\n[stackoverflow-url]: https://stackoverflow.com/questions/tagged/electron-react-boilerplate\n"
  },
  {
    "path": "desktop-app/assets/assets.d.ts",
    "content": "type Styles = Record<string, string>;\n\ndeclare module '*.svg' {\n  export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;\n\n  const content: string;\n  export default content;\n}\n\ndeclare module '*.png' {\n  const content: string;\n  export default content;\n}\n\ndeclare module '*.jpg' {\n  const content: string;\n  export default content;\n}\n\ndeclare module '*.scss' {\n  const content: Styles;\n  export default content;\n}\n\ndeclare module '*.sass' {\n  const content: Styles;\n  export default content;\n}\n\ndeclare module '*.css' {\n  const content: Styles;\n  export default content;\n}\n"
  },
  {
    "path": "desktop-app/assets/entitlements.mac.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <true/>\n    <key>com.apple.security.cs.allow-jit</key>\n    <true/>\n  </dict>\n</plist>\n"
  },
  {
    "path": "desktop-app/declarations.d.ts",
    "content": "declare module '*.mp3';\ndeclare module 'electron-args';\n"
  },
  {
    "path": "desktop-app/package.json",
    "content": "{\n  \"name\": \"Responsively-App\",\n  \"productName\": \"ResponsivelyApp\",\n  \"version\": \"1.18.0\",\n  \"description\": \"A developer-friendly browser for developing responsive web apps\",\n  \"keywords\": [\n    \"developer-tools\",\n    \"devtools\",\n    \"browser\",\n    \"web-dev\"\n  ],\n  \"homepage\": \"https://responsively.app\",\n  \"bugs\": {\n    \"url\": \"https://github.com/responsively-org/responsively-app/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/responsively-org/responsively-app.git\"\n  },\n  \"author\": {\n    \"name\": \"Responsively\",\n    \"email\": \"p.manoj.vivek@gmail.com\"\n  },\n  \"license\": \"MIT\",\n  \"main\": \"./src/main/main.ts\",\n  \"scripts\": {\n    \"build\": \"concurrently \\\"yarn run build:main\\\" \\\"yarn run build:renderer\\\"\",\n    \"build:main\": \"cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts\",\n    \"build:renderer\": \"cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts\",\n    \"postinstall\": \"yarn rimraf --glob node_modules/browser-sync/dist/**/*.map && ts-node .erb/scripts/check-native-dep.js && ts-node postinstall.ts && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts\",\n    \"lint\": \"cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx\",\n    \"package\": \"ts-node ./.erb/scripts/clean.js dist && yarn run build && electron-builder build --publish never\",\n    \"prepare\": \"cd .. && husky install desktop-app/.husky && chmod a+x desktop-app/.husky/pre-commit\",\n    \"rebuild\": \"electron-rebuild --parallel --types prod,dev,optional --module-dir release/app\",\n    \"dev\": \"yarn start\",\n    \"start\": \"ts-node ./.erb/scripts/check-port-in-use.js && yarn run start:renderer\",\n    \"start:main\": \"cross-env NODE_ENV=development electronmon ./src/main/dev-entry.cjs\",\n    \"start:preload\": \"cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts\",\n    \"start:preloadWebview\": \"cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload-webview.dev.ts\",\n    \"start:renderer\": \"cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest run --coverage\",\n    \"typecheck\": \"tsc --noEmit\"\n  },\n  \"lint-staged\": {\n    \"*.{js,jsx,ts,tsx}\": [\n      \"cross-env NODE_ENV=development eslint --cache --fix\"\n    ],\n    \"*.json,.{eslintrc,prettierrc}\": [\n      \"prettier --ignore-path .eslintignore --parser json --write\"\n    ],\n    \"*.{css,scss}\": [\n      \"prettier --ignore-path .eslintignore --single-quote --write\"\n    ],\n    \"*.{html,md,yml}\": [\n      \"prettier --ignore-path .eslintignore --single-quote --write\"\n    ]\n  },\n  \"browserslist\": [],\n  \"dependencies\": {\n    \"@fontsource/lato\": \"^5.0.17\",\n    \"@headlessui-float/react\": \"^0.12.0\",\n    \"@headlessui/react\": \"^1.7.4\",\n    \"@iconify/react\": \"^3.2.2\",\n    \"@reduxjs/toolkit\": \"^1.9.5\",\n    \"@scena/react-guides\": \"^0.28.2\",\n    \"autoprefixer\": \"^10.4.16\",\n    \"bluebird\": \"^3.7.2\",\n    \"browser-sync\": \"^2.29.3\",\n    \"classnames\": \"^2.3.1\",\n    \"electron-args\": \"^0.1.0\",\n    \"electron-debug\": \"^3.2.0\",\n    \"electron-log\": \"^4.4.8\",\n    \"electron-store\": \"^8.0.2\",\n    \"electron-updater\": \"^6.7.3\",\n    \"emitter\": \"^0.0.5\",\n    \"javascript-time-ago\": \"^2.5.10\",\n    \"mousetrap\": \"^1.6.5\",\n    \"os\": \"^0.1.2\",\n    \"postcss\": \"^8.4.31\",\n    \"re-resizable\": \"^6.9.9\",\n    \"react\": \"^18.2.0\",\n    \"react-detect-click-outside\": \"^1.1.7\",\n    \"react-dnd\": \"^16.0.1\",\n    \"react-dnd-html5-backend\": \"^16.0.1\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-markdown\": \"^8.0.7\",\n    \"react-masonry-component\": \"^6.3.0\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-router-dom\": \"^6.3.0\",\n    \"react-tabs\": \"^6.0.2\",\n    \"redux\": \"^4.0.4\",\n    \"redux-thunk\": \"^2.4.2\",\n    \"replace\": \"^1.2.1\",\n    \"tailwindcss\": \"^3.1.4\",\n    \"use-sound\": \"^4.0.1\",\n    \"uuid\": \"^9.0.0\"\n  },\n  \"devDependencies\": {\n    \"@electron/notarize\": \"^3.1.1\",\n    \"@electron/rebuild\": \"^4.0.3\",\n    \"@pmmmwh/react-refresh-webpack-plugin\": \"^0.5.10\",\n    \"@svgr/webpack\": \"^7.0.0\",\n    \"@tailwindcss/typography\": \"^0.5.9\",\n    \"@teamsupercell/typings-for-css-modules-loader\": \"^2.5.2\",\n    \"@testing-library/jest-dom\": \"^6.1.2\",\n    \"@testing-library/react\": \"^14.0.0\",\n    \"@types/bluebird\": \"^3.5.36\",\n    \"@types/browser-sync\": \"^2.27.3\",\n    \"@types/howler\": \"^2.2.7\",\n    \"@types/mousetrap\": \"^1.6.10\",\n    \"@types/node\": \"^20.14.0\",\n    \"@types/react\": \"^18.3.0\",\n    \"@types/react-dom\": \"^18.3.0\",\n    \"@types/react-test-renderer\": \"^18.0.7\",\n    \"@types/terser-webpack-plugin\": \"^5.0.4\",\n    \"@types/uuid\": \"^9.0.2\",\n    \"@types/webpack-bundle-analyzer\": \"^4.4.2\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.62.0\",\n    \"@typescript-eslint/parser\": \"^5.60.1\",\n    \"browserslist-config-erb\": \"^0.0.3\",\n    \"chalk\": \"^4.1.2\",\n    \"concurrently\": \"^8.2.1\",\n    \"core-js\": \"^3.33.2\",\n    \"cross-env\": \"^7.0.3\",\n    \"css-loader\": \"^6.7.3\",\n    \"css-minimizer-webpack-plugin\": \"^5.0.1\",\n    \"detect-port\": \"^1.3.0\",\n    \"electron\": \"40.6.1\",\n    \"electron-builder\": \"^26.0.12\",\n    \"electron-devtools-installer\": \"^4.0.0\",\n    \"electronmon\": \"^2.0.2\",\n    \"eslint\": \"^8.36.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-import-resolver-typescript\": \"^3.4.1\",\n    \"eslint-import-resolver-webpack\": \"^0.13.2\",\n    \"eslint-plugin-compat\": \"^4.1.2\",\n    \"eslint-plugin-import\": \"^2.27.5\",\n    \"eslint-plugin-jsx-a11y\": \"^6.6.1\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"eslint-plugin-promise\": \"^6.0.0\",\n    \"eslint-plugin-react\": \"^7.30.1\",\n    \"eslint-plugin-react-hooks\": \"^4.6.0\",\n    \"file-loader\": \"^6.2.0\",\n    \"html-webpack-plugin\": \"^5.5.0\",\n    \"husky\": \"^8.0.3\",\n    \"identity-obj-proxy\": \"^3.0.0\",\n    \"jsdom\": \"^25.0.1\",\n    \"lint-staged\": \"^13.2.2\",\n    \"mini-css-extract-plugin\": \"^2.6.1\",\n    \"postcss-loader\": \"^7.0.0\",\n    \"prettier\": \"^2.7.1\",\n    \"prettier-plugin-tailwindcss\": \"^0.2.3\",\n    \"react-refresh\": \"^0.14.0\",\n    \"react-test-renderer\": \"^18.2.0\",\n    \"replace-in-file\": \"^7.0.1\",\n    \"rimraf\": \"^4.2.0\",\n    \"sass\": \"^1.54.4\",\n    \"sass-loader\": \"^13.3.2\",\n    \"style-loader\": \"^3.3.1\",\n    \"terser-webpack-plugin\": \"^5.3.9\",\n    \"vitest\": \"^3.0.5\",\n    \"ts-loader\": \"^9.4.2\",\n    \"ts-node\": \"^10.9.1\",\n    \"tsconfig-paths-webpack-plugin\": \"^4.0.1\",\n    \"tsx\": \"^4.21.0\",\n    \"typescript\": \"^5.0.4\",\n    \"url-loader\": \"^4.1.1\",\n    \"webpack\": \"^5.76.0\",\n    \"webpack-bundle-analyzer\": \"^4.5.0\",\n    \"webpack-cli\": \"^5.1.4\",\n    \"webpack-dev-server\": \"^4.15.1\",\n    \"webpack-merge\": \"^5.9.0\"\n  },\n  \"build\": {\n    \"productName\": \"ResponsivelyApp\",\n    \"appId\": \"app.responsively\",\n    \"asar\": true,\n    \"asarUnpack\": \"**\\\\*.{node,dll}\",\n    \"files\": [\n      \"dist\",\n      \"node_modules\",\n      \"package.json\"\n    ],\n    \"afterSign\": \".erb/scripts/notarize.js\",\n    \"mac\": {\n      \"target\": {\n        \"target\": \"default\",\n        \"arch\": [\n          \"arm64\",\n          \"x64\"\n        ]\n      },\n      \"type\": \"distribution\",\n      \"hardenedRuntime\": true,\n      \"entitlements\": \"assets/entitlements.mac.plist\",\n      \"entitlementsInherit\": \"assets/entitlements.mac.plist\",\n      \"gatekeeperAssess\": false\n    },\n    \"dmg\": {\n      \"contents\": [\n        {\n          \"x\": 130,\n          \"y\": 220\n        },\n        {\n          \"x\": 410,\n          \"y\": 220,\n          \"type\": \"link\",\n          \"path\": \"/Applications\"\n        }\n      ]\n    },\n    \"win\": {\n      \"target\": [\n        \"nsis\"\n      ]\n    },\n    \"linux\": {\n      \"target\": [\n        \"AppImage\"\n      ],\n      \"category\": \"Development\"\n    },\n    \"directories\": {\n      \"app\": \"release/app\",\n      \"buildResources\": \"assets\",\n      \"output\": \"release/build\"\n    },\n    \"extraResources\": [\n      \"./assets/**\"\n    ],\n    \"publish\": {\n      \"provider\": \"github\"\n    }\n  },\n  \"collective\": {\n    \"url\": \"https://opencollective.com/responsively\"\n  },\n  \"resolutions\": {\n    \"@types/react\": \"^18.3.0\",\n    \"@types/react-dom\": \"^18.3.0\"\n  },\n  \"engines\": {\n    \"node\": \">=24.x\",\n    \"npm\": \">=7.x\"\n  },\n  \"electronmon\": {\n    \"patterns\": [\n      \"!**/**\",\n      \"src/main/**/*\",\n      \"src/store/**/*\",\n      \"src/common/**/*\"\n    ],\n    \"logLevel\": \"quiet\"\n  }\n}\n"
  },
  {
    "path": "desktop-app/postcss.config.js",
    "content": "/* eslint global-require: off, import/no-extraneous-dependencies: off */\n\nmodule.exports = {\n  plugins: [require('tailwindcss'), require('autoprefixer')],\n};\n"
  },
  {
    "path": "desktop-app/postinstall.ts",
    "content": "import {replaceInFile} from 'replace-in-file';\n\nasync function performReplacements() {\n  const replaceOptions = {\n    files: 'node_modules/browser-sync-ui/lib/UI.js',\n    from: /\"network-throttle\".*/,\n    to: '',\n  };\n\n  const howlerOptions = {\n    files: 'node_modules/use-sound/dist/types.d.ts',\n    from: '/// <reference types=\"howler\" />',\n    to: 'import { Howl } from \"howler\";',\n  };\n\n  try {\n    await replaceInFile(replaceOptions);\n    console.log('Replacement in UI.js completed successfully.');\n\n    await replaceInFile(howlerOptions);\n    console.log('Replacement in types.d.ts completed successfully.');\n  } catch (error) {\n    console.error('Error occurred during replacements:', error);\n  }\n}\n\nasync function performPostInstall() {\n  await performReplacements();\n}\n\nperformPostInstall();\n"
  },
  {
    "path": "desktop-app/release/app/package.json",
    "content": "{\n  \"name\": \"ResponsivelyApp\",\n  \"version\": \"1.18.0\",\n  \"description\": \"A developer-friendly browser for developing responsive web apps\",\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"Responsively\",\n    \"email\": \"p.manoj.vivek@gmail.com\"\n  },\n  \"main\": \"./dist/main/main.js\",\n  \"scripts\": {\n    \"electron-rebuild\": \"node -r ts-node/register ../../.erb/scripts/electron-rebuild.js\",\n    \"postinstall\": \"yarn run electron-rebuild && yarn run link-modules\",\n    \"link-modules\": \"node -r ts-node/register ../../.erb/scripts/link-modules.ts\"\n  },\n  \"dependencies\": {\n    \"browser-sync\": \"^2.27.12\"\n  }\n}\n"
  },
  {
    "path": "desktop-app/setupTests.ts",
    "content": "import '@testing-library/jest-dom/vitest';\n\nwindow.electron = {\n  ipcRenderer: {\n    sendMessage: vi.fn(),\n    on: vi.fn(),\n    once: vi.fn(),\n    invoke: vi.fn(),\n    removeListener: vi.fn(),\n    removeAllListeners: vi.fn(),\n  },\n  store: {\n    set: vi.fn(),\n    get: vi.fn(),\n  },\n};\n\nglobal.IntersectionObserver = vi.fn(() => ({\n  root: null,\n  rootMargin: '',\n  thresholds: [],\n  observe: vi.fn(),\n  unobserve: vi.fn(),\n  disconnect: vi.fn(),\n  takeRecords: vi.fn(),\n}));\n\nglobal.ResizeObserver = vi.fn(() => ({\n  observe: vi.fn(),\n  unobserve: vi.fn(),\n  disconnect: vi.fn(),\n}));\n"
  },
  {
    "path": "desktop-app/src/__tests__/App.test.tsx",
    "content": "// import { render } from '@testing-library/react';\n// import App from '../renderer/AppContent';\n\ndescribe('App', () => {\n  it('should render', () => {\n    // TODO Fix this\n    // expect(render(<App />)).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/common/constants.ts",
    "content": "/* eslint-disable  import/prefer-default-export */\n\nexport const DOCK_POSITION = {\n  BOTTOM: 'BOTTOM',\n  RIGHT: 'RIGHT',\n  UNDOCKED: 'UNDOCKED',\n} as const;\n\nexport const PREVIEW_LAYOUTS = {\n  COLUMN: 'COLUMN',\n  FLEX: 'FLEX',\n  INDIVIDUAL: 'INDIVIDUAL',\n  MASONRY: 'MASONRY',\n} as const;\n\nexport type PreviewLayout = (typeof PREVIEW_LAYOUTS)[keyof typeof PREVIEW_LAYOUTS];\n\nexport type Notification = {\n  id: string;\n  link?: string;\n  linkText?: string;\n  text: string;\n};\n\nexport interface OpenUrlArgs {\n  url: string;\n}\n\nexport const IPC_MAIN_CHANNELS = {\n  APP_META: 'app-meta',\n  PERMISSION_REQUEST: 'permission-request',\n  PERMISSION_RESPONSE: 'permission-response',\n  AUTH_REQUEST: 'auth-request',\n  AUTH_RESPONSE: 'auth-response',\n  OPEN_EXTERNAL: 'open-external',\n  OPEN_URL: 'open-url',\n  START_WATCHING_FILE: 'start-watching-file',\n  STOP_WATCHER: 'stop-watcher',\n  OPEN_ABOUT_DIALOG: 'open-about-dialog',\n  GET_SITE_PERMISSIONS: 'get-site-permissions',\n  UPDATE_SITE_PERMISSION: 'update-site-permission',\n  CLEAR_SITE_PERMISSIONS: 'clear-site-permissions',\n  PERMISSION_UPDATED: 'permission-updated',\n} as const;\n\nexport type Channels = (typeof IPC_MAIN_CHANNELS)[keyof typeof IPC_MAIN_CHANNELS];\n\nexport const PROTOCOL = 'responsively';\n\nexport const PERMISSION_TYPES = {\n  CAMERA: 'camera',\n  MICROPHONE: 'microphone',\n  LOCATION: 'geolocation',\n  NOTIFICATIONS: 'notifications',\n  CLIPBOARD: 'clipboard-read',\n  FULLSCREEN: 'fullscreen',\n  MIDI: 'midi',\n  POINTER_LOCK: 'pointerLock',\n} as const;\n\nexport type PermissionType = (typeof PERMISSION_TYPES)[keyof typeof PERMISSION_TYPES];\n\nexport interface SitePermission {\n  type: string;\n  state: 'GRANTED' | 'DENIED' | 'PROMPT' | 'UNKNOWN';\n  displayName: string;\n  icon: string;\n}\n"
  },
  {
    "path": "desktop-app/src/common/deviceList.ts",
    "content": "export interface Device {\n  id: string;\n  height: number;\n  width: number;\n  name: string;\n  userAgent: string;\n  type: string;\n  dpr: number;\n  isTouchCapable: boolean;\n  isMobileCapable: boolean;\n  capabilities: string[];\n  isCustom?: boolean;\n}\n\n/*\nIds range:\n  10000 - 19999: Apple devices\n  20000 - 29999: Google devices\n  30000 - 39999: Samsung devices\n  40000 - 49999: Microsoft devices\n  50000 - 59999: Other mobile devices\n  90000 - 99999: Other desktop devices\n\nAnd `uuid` as id for custom devices\n*/\n\nexport const defaultDevices: Device[] = [\n  {\n    id: '10001',\n    name: 'iPhone 4',\n    width: 320,\n    height: 480,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10002',\n    name: 'iPhone 5/SE',\n    width: 320,\n    height: 568,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10003',\n    name: 'iPhone SE',\n    width: 375,\n    height: 667,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10004',\n    name: 'iPhone 6/7/8',\n    width: 375,\n    height: 667,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10005',\n    name: 'iPhone 6/7/8 Plus',\n    width: 414,\n    height: 736,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10006',\n    name: 'iPhone X',\n    width: 375,\n    height: 812,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10007',\n    name: 'iPhone XR',\n    width: 414,\n    height: 896,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10008',\n    name: 'iPhone 12 Pro',\n    width: 390,\n    height: 844,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10009',\n    name: 'iPhone 13 Pro Max',\n    width: 428,\n    height: 926,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10010',\n    name: 'iPhone 14 Pro Max',\n    width: 430,\n    height: 932,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10011',\n    name: 'iPad Air',\n    width: 820,\n    height: 1180,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10012',\n    name: 'iPad Mini',\n    width: 768,\n    height: 1024,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10013',\n    name: 'iPad',\n    width: 768,\n    height: 1024,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10014',\n    name: 'iPad Pro',\n    width: 1024,\n    height: 1366,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10015',\n    name: 'MacBook Pro',\n    width: 1440,\n    height: 900,\n    dpr: 2,\n    capabilities: [],\n    userAgent:\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',\n    type: 'notebook',\n    isTouchCapable: false,\n    isMobileCapable: false,\n  },\n  {\n    id: '10016',\n    name: 'iPhone 14',\n    dpr: 3,\n    width: 390,\n    height: 844,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10017',\n    name: 'iPhone 14 Plus',\n    width: 428,\n    height: 926,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10018',\n    name: 'iPhone 14 Pro',\n    width: 393,\n    height: 852,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10019',\n    name: 'iPhone 15',\n    width: 393,\n    height: 852,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10020',\n    name: 'iPhone 15 Plus',\n    width: 430,\n    height: 932,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10021',\n    name: 'iPhone 15 Pro',\n    width: 393,\n    height: 852,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10022',\n    name: 'iPhone 16',\n    width: 393,\n    height: 852,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10023',\n    name: 'iPhone 16 Plus',\n    width: 430,\n    height: 932,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10024',\n    name: 'iPhone 16 Pro',\n    width: 393,\n    height: 852,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10025',\n    name: 'iPhone 17',\n    width: 393,\n    height: 852,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10026',\n    name: 'iPhone 17 Pro',\n    width: 393,\n    height: 852,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10027',\n    name: 'iPhone 17 Pro Max',\n    width: 430,\n    height: 932,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10028',\n    name: 'iPhone 17 Air',\n    width: 428,\n    height: 926,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10029',\n    name: 'iPhone 15 Pro Max',\n    width: 430,\n    height: 932,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10030',\n    name: 'iPhone 16 Pro Max',\n    width: 430,\n    height: 932,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10031',\n    name: 'iPad Pro M4',\n    width: 1152,\n    height: 1536,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPad; CPU OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10032',\n    name: 'iPad Air M2',\n    width: 820,\n    height: 1180,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPad; CPU OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '10033',\n    name: 'MacBook Air M3',\n    width: 1440,\n    height: 900,\n    dpr: 2,\n    capabilities: [],\n    userAgent:\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n    type: 'notebook',\n    isTouchCapable: false,\n    isMobileCapable: false,\n  },\n  {\n    id: '20001',\n    name: 'Nexus 4',\n    width: 384,\n    height: 640,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20002',\n    name: 'Nexus 5',\n    width: 360,\n    height: 640,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20003',\n    name: 'Nexus 5X',\n    width: 412,\n    height: 732,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20004',\n    name: 'Nexus 6',\n    width: 412,\n    height: 732,\n    dpr: 3.5,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20005',\n    name: 'Nexus 6P',\n    width: 412,\n    height: 732,\n    dpr: 3.5,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20006',\n    name: 'Nexus 7',\n    width: 600,\n    height: 960,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20007',\n    name: 'Nexus 10',\n    width: 800,\n    height: 1280,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20008',\n    name: 'Pixel 2',\n    width: 411,\n    height: 731,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20009',\n    name: 'Pixel 2 XL',\n    width: 411,\n    height: 823,\n    dpr: 3.5,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20010',\n    name: 'Pixel 3',\n    width: 393,\n    height: 786,\n    dpr: 2.75,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20011',\n    name: 'Pixel 3 XL',\n    width: 393,\n    height: 786,\n    dpr: 2.75,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 11; Pixel 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.181 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20012',\n    name: 'Pixel 4',\n    width: 353,\n    height: 745,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20013',\n    name: 'Pixel 5',\n    width: 393,\n    height: 851,\n    dpr: 2.75,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20014',\n    name: 'Nest Hub Max',\n    width: 1280,\n    height: 800,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.188 Safari/537.36 CrKey/1.54.250320',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20015',\n    name: 'Nest Hub',\n    width: 1024,\n    height: 600,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.109 Safari/537.36 CrKey/1.54.248666',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20016',\n    name: 'Pixel 7',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20017',\n    name: 'Pixel 7a',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20018',\n    name: 'Pixel 7 Pro',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20019',\n    name: 'Pixel 8',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20020',\n    name: 'Pixel 8 Pro',\n    width: 430,\n    height: 932,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20021',\n    name: 'Pixel 8a',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; Pixel 8a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20022',\n    name: 'Pixel Fold',\n    width: 734,\n    height: 1014,\n    dpr: 2.8,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; Pixel Fold) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20023',\n    name: 'Pixel Tablet',\n    width: 1600,\n    height: 2560,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; Pixel Tablet) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20031',\n    name: 'Pixel 9',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20032',\n    name: 'Pixel 9 Pro',\n    width: 430,\n    height: 932,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20033',\n    name: 'Pixel 9 Pro Fold',\n    width: 734,\n    height: 1014,\n    dpr: 2.8,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro Fold) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20034',\n    name: 'Pixel 9a',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 15; Pixel 9a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20035',\n    name: 'Pixel 10',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 16; Pixel 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '20036',\n    name: 'Pixel 10 Pro',\n    width: 430,\n    height: 932,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 16; Pixel 10 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30001',\n    name: 'Samsung Galaxy S8+',\n    width: 360,\n    height: 740,\n    dpr: 4,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30002',\n    name: 'Samsung Galaxy S20 Ultra',\n    width: 412,\n    height: 915,\n    dpr: 3.5,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30003',\n    name: 'Galaxy Fold',\n    width: 280,\n    height: 653,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 9.0; SAMSUNG SM-F900U Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30004',\n    name: 'Galaxy S21',\n    width: 360,\n    height: 800,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 9.0; SAMSUNG SM-G991 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30005',\n    name: 'Galaxy S21 Plus',\n    width: 384,\n    height: 854,\n    dpr: 2.8125,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 9.0; SAMSUNG SM-G996 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30006',\n    name: 'Galaxy S21 Ultra',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 9.0; SAMSUNG SM-G998 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30007',\n    name: 'Galaxy S20',\n    width: 360,\n    height: 800,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 9.0; SAMSUNG SM-G981 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30008',\n    name: 'Galaxy S20 Plus',\n    width: 384,\n    height: 854,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 9.0; SAMSUNG SM-G986 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30009',\n    name: 'Samsung Galaxy A51/71',\n    width: 412,\n    height: 914,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30010',\n    name: 'Galaxy S III',\n    width: 360,\n    height: 640,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30011',\n    name: 'Galaxy S5',\n    width: 360,\n    height: 640,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30012',\n    name: 'Galaxy S8',\n    width: 360,\n    height: 740,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30013',\n    name: 'Galaxy S9+',\n    width: 320,\n    height: 658,\n    dpr: 4.5,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30014',\n    name: 'Galaxy Tab S4',\n    width: 712,\n    height: 1138,\n    dpr: 2.25,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30015',\n    name: 'Galaxy Note II',\n    width: 360,\n    height: 640,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30016',\n    name: 'Galaxy Note 3',\n    width: 360,\n    height: 640,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30017',\n    name: 'Samsung S21 FE',\n    width: 360,\n    height: 800,\n    dpr: 420,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-G990B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/20.0 Chrome/106.0.5249.126 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30018',\n    name: 'Galaxy Z Fold 5',\n    width: 344,\n    height: 882,\n    dpr: 1,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-F946B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30019',\n    name: 'Galaxy Fold3 (Folded - Portrait)',\n    width: 320,\n    height: 872,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-F926U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30020',\n    name: 'Galaxy Fold3 (Folded - Landscape)',\n    width: 872,\n    height: 320,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-F926U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30021',\n    name: 'Galaxy Fold3 (Unfolded - Portrait)',\n    width: 590,\n    height: 736,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-F926U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30022',\n    name: 'Galaxy Fold3 (Unfolded - Landscape)',\n    width: 736,\n    height: 590,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-F926U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30023',\n    name: 'Galaxy S22',\n    width: 360,\n    height: 800,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30024',\n    name: 'Galaxy S22 Plus',\n    width: 384,\n    height: 854,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-S906B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30025',\n    name: 'Galaxy S22 Ultra',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30026',\n    name: 'Galaxy S23',\n    width: 360,\n    height: 800,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30027',\n    name: 'Galaxy S23 Plus',\n    width: 384,\n    height: 854,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; SM-S916B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30028',\n    name: 'Galaxy S23 Ultra',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30029',\n    name: 'Galaxy S24',\n    width: 360,\n    height: 800,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; SM-S921B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30030',\n    name: 'Galaxy S24 Plus',\n    width: 384,\n    height: 854,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; SM-S926B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30031',\n    name: 'Galaxy S24 Ultra',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30032',\n    name: 'Galaxy Z Flip 5',\n    width: 264,\n    height: 844,\n    dpr: 2.8,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-F731B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30033',\n    name: 'Galaxy Z Fold 4',\n    width: 344,\n    height: 882,\n    dpr: 2.8,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-F936B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '30034',\n    name: 'Galaxy Tab S9',\n    width: 1120,\n    height: 1600,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '40001',\n    name: 'Nokia Lumia 520',\n    width: 320,\n    height: 533,\n    dpr: 1.5,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '40002',\n    name: 'Microsoft Lumia 550',\n    width: 640,\n    height: 360,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '40003',\n    name: 'Microsoft Lumia 950',\n    width: 360,\n    height: 640,\n    dpr: 4,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '40004',\n    name: 'Surface Pro 7',\n    width: 912,\n    height: 1368,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '40005',\n    name: 'Surface Duo',\n    width: 540,\n    height: 720,\n    dpr: 2.5,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 11.0; Surface Duo) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '40006',\n    name: 'Surface Pro 9',\n    width: 1200,\n    height: 1800,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '40007',\n    name: 'Surface Pro 10',\n    width: 1200,\n    height: 1800,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50001',\n    name: 'BlackBerry Z30',\n    width: 360,\n    height: 640,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50002',\n    name: 'LG Optimus L70',\n    width: 384,\n    height: 640,\n    dpr: 1.25,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50003',\n    name: 'Nokia N9',\n    width: 480,\n    height: 854,\n    dpr: 1,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50004',\n    name: 'JioPhone 2',\n    width: 240,\n    height: 320,\n    dpr: 1,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50005',\n    name: 'Kindle Fire HDX',\n    width: 800,\n    height: 1280,\n    dpr: 2,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50006',\n    name: 'Blackberry PlayBook',\n    width: 600,\n    height: 1024,\n    dpr: 1,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50007',\n    name: 'Moto G4',\n    width: 360,\n    height: 640,\n    dpr: 3,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 6.0.1; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50008',\n    name: 'OnePlus 12',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; CPH2581) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50009',\n    name: 'Nothing Phone 2',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; A065) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50010',\n    name: 'Xiaomi 14',\n    width: 412,\n    height: 915,\n    dpr: 2.625,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 14; 23127PN0CC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50011',\n    name: 'Motorola Razr+',\n    width: 264,\n    height: 844,\n    dpr: 2.8,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; XT2321-1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '50012',\n    name: 'OnePlus Open',\n    width: 734,\n    height: 1014,\n    dpr: 2.8,\n    capabilities: ['touch', 'mobile'],\n    userAgent:\n      'Mozilla/5.0 (Linux; Android 13; CPH2551) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.131 Mobile Safari/537.36',\n    type: 'phone',\n    isTouchCapable: true,\n    isMobileCapable: true,\n  },\n  {\n    id: '90001',\n    name: 'laptopWithTouch',\n    width: 950,\n    height: 1280,\n    dpr: 1,\n    capabilities: ['touch'],\n    userAgent:\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',\n    type: 'notebook',\n    isTouchCapable: true,\n    isMobileCapable: false,\n  },\n  {\n    id: '90002',\n    name: 'laptopWithHiDPIScreen',\n    width: 900,\n    height: 1440,\n    dpr: 2,\n    capabilities: [],\n    userAgent:\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',\n    type: 'notebook',\n    isTouchCapable: false,\n    isMobileCapable: false,\n  },\n  {\n    id: '90003',\n    name: 'laptopWithMDPIScreen',\n    width: 800,\n    height: 1280,\n    dpr: 1,\n    capabilities: [],\n    userAgent:\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',\n    type: 'notebook',\n    isTouchCapable: false,\n    isMobileCapable: false,\n  },\n  {\n    id: '90004',\n    name: 'Asus Zenbook Fold',\n    width: 853,\n    height: 1280,\n    dpr: 2,\n    capabilities: ['touch', 'tablet'],\n    userAgent:\n      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36',\n    type: 'tablet',\n    isTouchCapable: true,\n    isMobileCapable: false,\n  },\n  {\n    id: '90005',\n    name: 'Surface Laptop Studio 2',\n    width: 1600,\n    height: 2400,\n    dpr: 2,\n    capabilities: ['touch'],\n    userAgent:\n      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n    type: 'notebook',\n    isTouchCapable: true,\n    isMobileCapable: false,\n  },\n];\n\nconst customDevices: () => Device[] = () => {\n  return typeof window !== 'undefined'\n    ? window.electron.store.get('deviceManager.customDevices')\n    : [];\n};\n\ntype DeviceMap = {[key: string]: Device};\n\nexport const getDevicesMap = (): DeviceMap => {\n  return [...defaultDevices, ...customDevices()].reduce((map: DeviceMap, device) => {\n    map[device.id] = device;\n    return map;\n  }, {});\n};\n"
  },
  {
    "path": "desktop-app/src/common/webViewUtils.ts",
    "content": "export function updateWebViewHeightAndScale(\n  webView: HTMLElement | Electron.WebviewTag,\n  pageHeight: number\n) {\n  webView.style.height = `${pageHeight}px`;\n  webView.style.transform = `scale(0.1)`;\n}\n"
  },
  {
    "path": "desktop-app/src/main/app-meta/index.ts",
    "content": "import {app, ipcMain, shell} from 'electron';\nimport path from 'path';\nimport {IPC_MAIN_CHANNELS} from '../../common/constants';\nimport store from '../../store';\n\nexport interface AppMetaResponse {\n  appVersion: string;\n  webviewPreloadPath: string;\n}\n\nexport const initAppMetaHandlers = () => {\n  ipcMain.handle(IPC_MAIN_CHANNELS.APP_META, async (): Promise<AppMetaResponse> => {\n    return {\n      webviewPreloadPath: app.isPackaged\n        ? path.join(__dirname, 'preload-webview.js')\n        : path.join(__dirname, '../../../.erb/dll/preload-webview.js'),\n      appVersion: app.getVersion(),\n    };\n  });\n\n  ipcMain.on('electron-store-get', async (event, val) => {\n    event.returnValue = store.get(val);\n  });\n  ipcMain.on('electron-store-set', async (_, key, val) => {\n    store.set(key, val);\n  });\n\n  ipcMain.on(IPC_MAIN_CHANNELS.OPEN_EXTERNAL, async (_, {url}) => {\n    console.log('Opening external url', url);\n    shell.openExternal(url);\n  });\n};\n"
  },
  {
    "path": "desktop-app/src/main/app-updater.ts",
    "content": "import {autoUpdater} from 'electron-updater';\n\nexport interface AppUpdaterStatus {\n  status: string;\n  version?: string;\n  lastChecked?: number;\n  progress?: number;\n  size?: number;\n  error?: Error;\n}\n\nexport class AppUpdater {\n  status = 'IDLE';\n\n  version?: string;\n\n  lastChecked?: number;\n\n  progress?: number;\n\n  size?: number;\n\n  error?: Error;\n\n  constructor() {\n    autoUpdater.logger = console;\n    autoUpdater.checkForUpdatesAndNotify();\n    autoUpdater.on('checking-for-update', () => {\n      this.status = 'CHECKING';\n      this.lastChecked = Date.now();\n    });\n    autoUpdater.on('update-available', (info) => {\n      this.status = 'AVAILABLE';\n      this.version = info.version;\n      this.lastChecked = Date.now();\n    });\n    autoUpdater.on('update-not-available', (info) => {\n      this.status = 'UP_TO_DATE';\n      this.lastChecked = Date.now();\n    });\n    autoUpdater.on('error', (err) => {\n      this.status = 'ERROR';\n      this.error = err;\n      this.lastChecked = Date.now();\n    });\n    autoUpdater.on('download-progress', (progressObj) => {\n      const logMessage = `Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}% (${progressObj.transferred}/${progressObj.total})`;\n      // eslint-disable-next-line no-console\n      console.log(logMessage);\n      this.status = `DOWNLOADING - ${progressObj.percent}%`;\n      this.progress = progressObj.percent;\n      this.size = progressObj.total;\n      this.lastChecked = Date.now();\n    });\n    autoUpdater.on('update-downloaded', (info) => {\n      this.status = 'DOWNLOADED (Restart to apply update)';\n      this.lastChecked = Date.now();\n    });\n  }\n\n  getStatus(): AppUpdaterStatus {\n    return {\n      status: this.status,\n      version: this.version,\n      lastChecked: this.lastChecked,\n      progress: this.progress,\n      size: this.size,\n      error: this.error,\n    };\n  }\n}\n"
  },
  {
    "path": "desktop-app/src/main/browser-sync.ts",
    "content": "/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport BrowserSync, {BrowserSyncInstance} from 'browser-sync';\nimport fs from 'fs-extra';\n\nexport const BROWSER_SYNC_PORT = 12719;\nexport const BROWSER_SYNC_HOST = `localhost:${BROWSER_SYNC_PORT}`;\nexport const BROWSER_SYNC_URL = `https://${BROWSER_SYNC_HOST}/browser-sync/browser-sync-client.js?v=2.27.10`;\n\nconst browserSyncEmbed: BrowserSyncInstance = BrowserSync.create('embed');\n\nlet created = false;\nlet filesWatcher: ReturnType<BrowserSyncInstance['watch']> | null = null;\nlet cssWatcher: ReturnType<BrowserSyncInstance['watch']> | null = null;\n\nexport async function initInstance(): Promise<BrowserSyncInstance> {\n  if (created) {\n    return browserSyncEmbed;\n  }\n  created = true;\n  return new Promise((resolve, reject) => {\n    browserSyncEmbed.init(\n      {\n        open: false,\n        localOnly: true,\n        https: true,\n        notify: false,\n        ui: false,\n        port: BROWSER_SYNC_PORT,\n      },\n      (err: Error, bs: BrowserSyncInstance) => {\n        if (err) {\n          return reject(err);\n        }\n        return resolve(bs);\n      }\n    );\n  });\n}\n\nexport function watchFiles(filePath: string) {\n  if (filePath && fs.existsSync(filePath)) {\n    const fileDir = filePath.substring(0, filePath.lastIndexOf('/'));\n\n    filesWatcher = browserSyncEmbed\n      // @ts-expect-error\n      .watch([filePath, `${fileDir}/**/**.js`])\n      .on('change', browserSyncEmbed.reload);\n\n    cssWatcher = browserSyncEmbed.watch(\n      `${fileDir}/**/**.css`,\n      // @ts-expect-error\n      (event: string, file: string) => {\n        if (event === 'change') {\n          browserSyncEmbed.reload(file);\n        }\n      }\n    );\n  }\n}\n\nexport async function stopWatchFiles() {\n  if (filesWatcher) {\n    // @ts-expect-error\n    await filesWatcher.close();\n  }\n  if (cssWatcher) {\n    // @ts-expect-error\n    await cssWatcher.close();\n  }\n}\n"
  },
  {
    "path": "desktop-app/src/main/cli.ts",
    "content": "import parseArgs from 'electron-args';\n\nlet binaryName = 'ResponsivelyApp';\n\nif (process.platform === 'darwin') {\n  binaryName = '/Applications/ResponsivelyApp.app/Contents/MacOS/ResponsivelyApp';\n}\n\nif (process.platform === 'win32') {\n  binaryName = 'ResponsivelyApp.exe';\n}\n\nconst cli = parseArgs(\n  `\n      ResponsivelyApp\n   \n      Usage\n        $ ${binaryName} [path]\n   \n      Options\n        --help     show help\n        --version  show version\n   \n      Examples\n        $ ${binaryName} https://example.com\n        $ ${binaryName} /path/to/index.html\n  `,\n  {\n    alias: {\n      h: 'help',\n    },\n  }\n);\n\nexport default cli;\n"
  },
  {
    "path": "desktop-app/src/main/dev-entry.cjs",
    "content": "// Dev-mode bootstrap: registers tsx before loading the TypeScript entry point.\n// Node 24 (Electron 40) handles .ts natively via ESM, bypassing CJS hooks.\n// Using a .cjs entry ensures tsx's CJS hook intercepts all .ts imports.\nrequire('tsx/cjs');\nrequire('./main.ts');\n"
  },
  {
    "path": "desktop-app/src/main/devtools/index.ts",
    "content": "import {BrowserWindow, ipcMain, webContents, WebContentsView} from 'electron';\nimport {DOCK_POSITION} from '../../common/constants';\nimport {DockPosition} from '../../renderer/store/features/devtools';\n\nlet devtoolsView: WebContentsView | undefined;\nlet devtoolsWebview: Electron.WebContents;\nlet mainWindow: BrowserWindow | undefined;\n\nexport interface OpenDevtoolsArgs {\n  webviewId: number;\n  dockPosition: DockPosition;\n  bounds?: Electron.Rectangle;\n}\n\nexport interface ResizeDevtoolsArgs {\n  bounds: Electron.Rectangle;\n}\n\nexport interface OpenDevtoolsResult {\n  status: boolean;\n}\n\nexport interface ToggleInspectorArgs {\n  webviewId: number;\n}\n\nexport interface ToggleInspectorResult {\n  status: boolean;\n}\n\nexport interface InspectElementArgs {\n  coords: {x: number; y: number};\n  webviewId: number;\n}\n\nconst onInspectNodeRequested = async (\n  backendNodeId: number,\n  dbg: Electron.Debugger,\n  webviewId: number\n) => {\n  const [\n    {\n      model: {\n        content: [x, y],\n      },\n    },\n  ] = await Promise.all([\n    dbg.sendCommand('DOM.getBoxModel', {\n      backendNodeId,\n    }),\n    dbg.sendCommand('Overlay.setInspectMode', {\n      mode: 'none',\n      highlightConfig: {},\n    }),\n  ]);\n\n  const args: InspectElementArgs = {\n    coords: {x, y},\n    webviewId,\n  };\n  mainWindow?.webContents.send('inspect-element', args);\n};\n\nconst onDebuggerEvent = async (\n  _: any,\n  method: string,\n  params: any,\n  dbg: Electron.Debugger,\n  webviewId: number\n) => {\n  switch (method) {\n    case 'Overlay.inspectNodeRequested':\n      await onInspectNodeRequested(params.backendNodeId, dbg, webviewId);\n      break;\n    default:\n      break;\n  }\n};\n\nconst enableInspector = async (\n  _: any,\n  args: ToggleInspectorArgs\n): Promise<ToggleInspectorResult> => {\n  const {webviewId} = args;\n  const webViewContents = webContents.fromId(webviewId);\n  if (webViewContents === undefined) {\n    return {status: false};\n  }\n\n  const dbg = webViewContents.debugger;\n  if (!dbg.isAttached()) {\n    dbg.attach();\n    dbg.on('message', (__: any, method: string, params: any) => {\n      onDebuggerEvent(__, method, params, dbg, webviewId);\n    });\n  }\n  await dbg.sendCommand('DOM.enable');\n  await dbg.sendCommand('Overlay.enable');\n  await dbg.sendCommand('Overlay.setInspectMode', {\n    mode: 'searchForNode',\n    highlightConfig: {\n      showInfo: true,\n      showStyles: true,\n      contentColor: {r: 111, g: 168, b: 220, a: 0.66},\n      paddingColor: {r: 147, g: 196, b: 125, a: 0.66},\n      borderColor: {r: 255, g: 229, b: 153, a: 0.66},\n      marginColor: {r: 246, g: 178, b: 107, a: 0.66},\n    },\n  });\n  return {status: true};\n};\n\nconst disableInspector = async (\n  _: any,\n  args: ToggleInspectorArgs\n): Promise<ToggleInspectorResult> => {\n  const {webviewId} = args;\n  const webViewContents = webContents.fromId(webviewId);\n  if (webViewContents === undefined) {\n    return {status: false};\n  }\n  const dbg = webViewContents.debugger;\n  try {\n    await dbg.sendCommand('Overlay.setInspectMode', {\n      mode: 'none',\n      highlightConfig: {},\n    });\n\n    dbg.removeAllListeners().detach();\n  } catch (err) {\n    // eslint-disable-next-line no-console\n    console.log('Error detaching debugger', err);\n  }\n  return {status: true};\n};\n\nconst openDevtools = async (_: any, arg: OpenDevtoolsArgs): Promise<OpenDevtoolsResult> => {\n  const {webviewId, dockPosition} = arg;\n  const optionalWebview = webContents.fromId(webviewId);\n  if (mainWindow == null || optionalWebview === undefined) {\n    return {status: false};\n  }\n  devtoolsWebview = optionalWebview;\n  if (dockPosition === DOCK_POSITION.UNDOCKED) {\n    devtoolsWebview.openDevTools({mode: 'detach'});\n    return {status: true};\n  }\n  devtoolsView = new WebContentsView();\n  devtoolsWebview.setDevToolsWebContents(devtoolsView.webContents);\n  devtoolsWebview.openDevTools();\n\n  devtoolsView.webContents\n    .executeJavaScript(\n      `\n      (async function () {\n        const sleep = ms => (new Promise(resolve => setTimeout(resolve, ms)));\n        var retryCount = 0;\n        var done = false;\n        while(retryCount < 10 && !done) {\n          try {\n            retryCount++;\n            document.querySelectorAll('div[slot=\"insertion-point-main\"]')[0].shadowRoot.querySelectorAll('.tabbed-pane-left-toolbar.toolbar')[0].style.display = 'none'\n            done = true\n          } catch(err){\n            await sleep(100);\n          }\n        }\n      })()\n    `\n    )\n    .catch((err) => {\n      // eslint-disable-next-line no-console\n      console.error('Error removing the native inspect button', err);\n    });\n\n  return {status: true};\n};\n\nconst resizeDevtools = async (_: any, arg: ResizeDevtoolsArgs) => {\n  if (devtoolsView == null || mainWindow == null) {\n    return;\n  }\n  try {\n    if (!mainWindow.contentView.children.includes(devtoolsView)) {\n      mainWindow.contentView.addChildView(devtoolsView);\n    }\n    devtoolsView.setBounds(arg.bounds);\n  } catch (err) {\n    // eslint-disable-next-line no-console\n    console.error('Error resizing devtools', err);\n  }\n};\n\nconst closeDevTools = async () => {\n  if (devtoolsWebview == null) {\n    return;\n  }\n  devtoolsWebview.closeDevTools();\n  if (devtoolsView == null) {\n    return;\n  }\n  mainWindow?.contentView.removeChildView(devtoolsView);\n  devtoolsView.webContents.close();\n  devtoolsView = undefined;\n};\n\nexport const initDevtoolsHandlers = (_mainWindow: BrowserWindow | undefined) => {\n  mainWindow = _mainWindow;\n\n  ipcMain.removeHandler('open-devtools');\n  ipcMain.handle('open-devtools', openDevtools);\n\n  ipcMain.removeHandler('resize-devtools');\n  ipcMain.handle('resize-devtools', resizeDevtools);\n\n  ipcMain.removeHandler('close-devtools');\n  ipcMain.handle('close-devtools', closeDevTools);\n\n  ipcMain.removeHandler('enable-inspector-overlay');\n  ipcMain.handle('enable-inspector-overlay', enableInspector);\n\n  ipcMain.removeHandler('disable-inspector-overlay');\n  ipcMain.handle('disable-inspector-overlay', disableInspector);\n};\n"
  },
  {
    "path": "desktop-app/src/main/http-basic-auth/index.ts",
    "content": "import {AuthInfo, app, BrowserWindow, ipcMain} from 'electron';\nimport {IPC_MAIN_CHANNELS} from '../../common/constants';\n\nexport type AuthRequestArgs = AuthInfo;\n\nexport interface AuthResponseArgs {\n  username: string;\n  password: string;\n  authInfo: AuthInfo;\n}\n\ntype Callback = (username: string, password: string) => void;\n\nconst inProgressAuthentications: {[key: string]: Callback[]} = {};\n\nconst handleLogin = async (\n  authInfo: AuthInfo,\n  mainWindow: BrowserWindow,\n  callback: (username: string, password: string) => void\n) => {\n  if (inProgressAuthentications[authInfo.host]) {\n    inProgressAuthentications[authInfo.host].push(callback);\n    return;\n  }\n  inProgressAuthentications[authInfo.host] = [callback];\n\n  mainWindow.webContents.send(IPC_MAIN_CHANNELS.AUTH_REQUEST, authInfo);\n  ipcMain.once(\n    IPC_MAIN_CHANNELS.AUTH_RESPONSE,\n    (_, {authInfo: respAuthInfo, username, password}: AuthResponseArgs) => {\n      inProgressAuthentications[respAuthInfo.host].forEach((cb) => cb(username, password));\n      delete inProgressAuthentications[respAuthInfo.host];\n    }\n  );\n};\n\nexport const initHttpBasicAuthHandlers = (mainWindow: BrowserWindow) => {\n  app.on('login', (event, _webContents, _request, authInfo, callback) => {\n    event.preventDefault();\n    handleLogin(authInfo, mainWindow, callback);\n  });\n};\n"
  },
  {
    "path": "desktop-app/src/main/main.ts",
    "content": "/* eslint global-require: off, no-console: off, promise/always-return: off */\n\n/**\n * This module executes inside of electron's main process. You can start\n * electron renderer process from here and communicate with the other processes\n * through IPC.\n *\n * When running `yarn run build` or `yarn run build:main`, this file is compiled to\n * `./src/main.js` using webpack. This gives us some performance wins.\n */\nimport path from 'path';\nimport {app, BrowserWindow, shell, screen, ipcMain} from 'electron';\nimport cli from './cli';\nimport {PROTOCOL} from '../common/constants';\nimport MenuBuilder from './menu';\nimport {resolveHtmlPath} from './util';\nimport {BROWSER_SYNC_HOST, initInstance, stopWatchFiles, watchFiles} from './browser-sync';\nimport store from '../store';\nimport {initWebviewContextMenu} from './webview-context-menu/register';\nimport {initScreenshotHandlers} from './screenshot';\nimport {initDevtoolsHandlers} from './devtools';\nimport {initWebviewStorageManagerHandlers} from './webview-storage-manager';\nimport {initNativeFunctionHandlers} from './native-functions';\nimport {WebPermissionHandlers} from './web-permissions';\nimport {initHttpBasicAuthHandlers} from './http-basic-auth';\nimport {initAppMetaHandlers} from './app-meta';\nimport {openUrl} from './protocol-handler';\nimport {AppUpdater} from './app-updater';\n\nlet windowShownOnOpen = false;\n\nif (process.defaultApp) {\n  if (process.argv.length >= 2) {\n    app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [path.resolve(process.argv[1])]);\n  }\n} else {\n  app.setAsDefaultProtocolClient(PROTOCOL);\n}\n\nlet urlToOpen: string | undefined = cli.input[0]?.includes('electronmon')\n  ? undefined\n  : cli.input[0];\n\nlet mainWindow: BrowserWindow | null = null;\n\ninitAppMetaHandlers();\ninitWebviewContextMenu();\ninitScreenshotHandlers();\ninitWebviewStorageManagerHandlers();\ninitNativeFunctionHandlers();\n\nif (process.env.NODE_ENV === 'production') {\n  const sourceMapSupport = require('source-map-support');\n  sourceMapSupport.install();\n}\n\nconst isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';\n\nif (isDebug) {\n  require('electron-debug')();\n}\n\nconst installExtensions = async () => {\n  const installer = require('electron-devtools-installer');\n  const {\n    REACT_DEVELOPER_TOOLS,\n    REDUX_DEVTOOLS,\n    EMBER_INSPECTOR,\n    VUEJS_DEVTOOLS,\n    APOLLO_DEVELOPER_TOOLS,\n  } = installer;\n  const forceDownload = !!process.env.UPGRADE_EXTENSIONS;\n  const extensions = [\n    REACT_DEVELOPER_TOOLS,\n    REDUX_DEVTOOLS,\n    EMBER_INSPECTOR,\n    VUEJS_DEVTOOLS,\n    APOLLO_DEVELOPER_TOOLS,\n  ];\n\n  return installer.default(extensions, forceDownload).catch(console.log);\n};\n\nconst createWindow = async () => {\n  windowShownOnOpen = false;\n  let isAppInitiated = false;\n  await installExtensions();\n\n  const setIsAppInitiated = () => {\n    isAppInitiated = true;\n  };\n\n  const RESOURCES_PATH = app.isPackaged\n    ? path.join(process.resourcesPath, 'assets')\n    : path.join(__dirname, '../../assets');\n\n  const getAssetPath = (...paths: string[]): string => {\n    return path.join(RESOURCES_PATH, ...paths);\n  };\n\n  const {width, height} = screen.getPrimaryDisplay().workAreaSize;\n\n  mainWindow = new BrowserWindow({\n    show: false,\n    width,\n    height,\n    icon: getAssetPath('icon.png'),\n    titleBarStyle: 'default',\n    webPreferences: {\n      preload: app.isPackaged\n        ? path.join(__dirname, 'preload.js')\n        : path.join(__dirname, '../../.erb/dll/preload.js'),\n      webviewTag: true,\n    },\n  });\n  initDevtoolsHandlers(mainWindow);\n  initHttpBasicAuthHandlers(mainWindow);\n  const webPermissionHandlers = WebPermissionHandlers(mainWindow);\n\n  // Add BROWSER_SYNC_HOST to the allowed Content-Security-Policy origins\n  mainWindow.webContents.session.webRequest.onHeadersReceived(async (details, callback) => {\n    if (details.responseHeaders?.['content-security-policy']) {\n      let cspHeader = details.responseHeaders['content-security-policy'][0];\n\n      cspHeader = cspHeader.replace('default-src', `default-src ${BROWSER_SYNC_HOST}`);\n      cspHeader = cspHeader.replace('script-src', `script-src ${BROWSER_SYNC_HOST}`);\n      cspHeader = cspHeader.replace('script-src-elem', `script-src-elem ${BROWSER_SYNC_HOST}`);\n      cspHeader = cspHeader.replace(\n        'connect-src',\n        `connect-src ${BROWSER_SYNC_HOST} wss://${BROWSER_SYNC_HOST} ws://${BROWSER_SYNC_HOST}`\n      );\n      cspHeader = cspHeader.replace('child-src', `child-src ${BROWSER_SYNC_HOST}`);\n      cspHeader = cspHeader.replace('worker-src', `worker-src ${BROWSER_SYNC_HOST}`); // Required when/if the browser-sync script is eventually relocated to a web worker\n\n      details.responseHeaders['content-security-policy'][0] = cspHeader;\n    }\n    callback({responseHeaders: details.responseHeaders});\n  });\n\n  mainWindow.loadURL(\n    `${resolveHtmlPath('index.html')}?urlToOpen=${encodeURI(urlToOpen ?? 'undefined')}`\n  );\n\n  const isWindows = process.platform === 'win32';\n  let needsFocusFix = false;\n  let triggeringProgrammaticBlur = false;\n\n  mainWindow.on('blur', () => {\n    if (!triggeringProgrammaticBlur) {\n      needsFocusFix = true;\n    }\n  });\n\n  mainWindow.on('focus', () => {\n    if (isWindows && needsFocusFix) {\n      needsFocusFix = false;\n      triggeringProgrammaticBlur = true;\n      setTimeout(function () {\n        mainWindow!.blur();\n        mainWindow!.focus();\n        setTimeout(function () {\n          triggeringProgrammaticBlur = false;\n        }, 100);\n      }, 100);\n    }\n  });\n\n  mainWindow.on('ready-to-show', async () => {\n    if (!isAppInitiated) {\n      await initInstance();\n      setIsAppInitiated();\n\n      if (!mainWindow) {\n        throw new Error('\"mainWindow\" is not defined');\n      }\n      webPermissionHandlers.init();\n      if (process.env.START_MINIMIZED) {\n        mainWindow.minimize();\n      } else {\n        mainWindow.showInactive();\n        if (!windowShownOnOpen) {\n          windowShownOnOpen = true;\n          mainWindow.show();\n        } else {\n          mainWindow.showInactive();\n        }\n      }\n    }\n  });\n\n  mainWindow.webContents.setWindowOpenHandler(({url}) => {\n    console.log('window open handler', url);\n    return {action: 'deny'};\n  });\n\n  mainWindow.on('closed', () => {\n    mainWindow = null;\n  });\n\n  const appUpdater = new AppUpdater();\n\n  const menuBuilder = new MenuBuilder(mainWindow, appUpdater);\n  menuBuilder.buildMenu();\n\n  // Open urls in the user's browser\n  mainWindow.webContents.setWindowOpenHandler((edata) => {\n    shell.openExternal(edata.url);\n    return {action: 'deny'};\n  });\n\n  ipcMain.on('start-watching-file', async (event, fileInfo) => {\n    let filePath = fileInfo.path.replace('file://', '');\n    if (process.platform === 'win32') {\n      filePath = filePath.replace(/^\\//, '');\n    }\n    app.addRecentDocument(filePath);\n    await stopWatchFiles();\n    watchFiles(filePath);\n  });\n\n  ipcMain.on('stop-watcher', async () => {\n    await stopWatchFiles();\n  });\n};\n\napp.on('open-url', async (event, url) => {\n  let actualURL = url.replace(`${PROTOCOL}://`, '');\n  if (actualURL.indexOf('//') !== -1 && actualURL.indexOf('://') === -1) {\n    // This hack is needed because the URL from the extension is missing the colon for some reason.\n    actualURL = actualURL.replace('//', '://');\n  }\n  if (mainWindow == null) {\n    // Will be handled by opened window\n    urlToOpen = actualURL;\n    await createWindow();\n    return;\n  }\n  windowShownOnOpen = false;\n  openUrl(actualURL, mainWindow);\n});\n\n/**\n * Add event listeners...\n */\n\napp.on('window-all-closed', () => {\n  // Respect the OSX convention of having the application in memory even\n  // after all windows have been closed\n  if (process.platform !== 'darwin') {\n    app.quit();\n  }\n});\n\napp.on('certificate-error', (event, _, url, __, ___, callback) => {\n  if (url.indexOf(BROWSER_SYNC_HOST) !== -1) {\n    event.preventDefault();\n    return callback(true);\n  }\n  console.log('certificate-error event', url, BROWSER_SYNC_HOST);\n  return callback(store.get('userPreferences.allowInsecureSSLConnections'));\n});\n\napp\n  .whenReady()\n  .then(() => {\n    createWindow();\n    app.on('activate', () => {\n      // On macOS it's common to re-create a window in the app when the\n      // dock icon is clicked and there are no other windows open.\n      if (mainWindow === null) createWindow();\n    });\n  })\n  .catch(console.log);\n"
  },
  {
    "path": "desktop-app/src/main/menu/help.ts",
    "content": "import {BrowserWindow, MenuItemConstructorOptions, ipcMain, shell} from 'electron';\n\nimport {EnvironmentInfo, getEnvironmentInfo} from '../util';\nimport {IPC_MAIN_CHANNELS} from '../../common/constants';\nimport {AppUpdater, AppUpdaterStatus} from '../app-updater';\n\nexport interface AboutDialogArgs {\n  environmentInfo: EnvironmentInfo;\n  updaterStatus: AppUpdaterStatus;\n}\n\nexport const subMenuHelp = (\n  mainWindow: BrowserWindow,\n  appUpdater: AppUpdater\n): MenuItemConstructorOptions => {\n  const environmentInfo = getEnvironmentInfo();\n  ipcMain.handle('get-about-info', async (_): Promise<AboutDialogArgs> => {\n    return {\n      environmentInfo,\n      updaterStatus: appUpdater.getStatus(),\n    };\n  });\n\n  return {\n    label: 'Help',\n    submenu: [\n      {\n        label: 'Learn More',\n        click() {\n          shell.openExternal('https://responsively.app');\n        },\n      },\n      {\n        label: 'Open Source',\n        click() {\n          shell.openExternal('https://github.com/responsively-org/responsively-app');\n        },\n      },\n      {\n        label: 'Join Discord',\n        click() {\n          shell.openExternal('https://responsively.app/join-discord/');\n        },\n      },\n      {\n        label: 'Search Issues',\n        click() {\n          shell.openExternal('https://github.com/responsively-org/responsively-app/issues');\n        },\n      },\n      {\n        label: 'Sponsor Responsively',\n        click() {\n          shell.openExternal(\n            'https://responsively.app/sponsor?utm_source=app&utm_medium=menu&utm_campaign=sponsor'\n          );\n        },\n      },\n      {\n        type: 'separator',\n      },\n      {\n        label: 'About',\n        accelerator: 'F1',\n        click: () => {\n          mainWindow.webContents.send(IPC_MAIN_CHANNELS.OPEN_ABOUT_DIALOG, {\n            environmentInfo,\n            updaterStatus: appUpdater.getStatus(),\n          });\n        },\n      },\n    ],\n  };\n};\n"
  },
  {
    "path": "desktop-app/src/main/menu/index.ts",
    "content": "import {app, Menu, BrowserWindow, MenuItemConstructorOptions} from 'electron';\nimport {subMenuHelp} from './help';\nimport {getViewMenu} from './view';\nimport {AppUpdater} from '../app-updater';\n\ninterface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {\n  selector?: string;\n  submenu?: DarwinMenuItemConstructorOptions[] | Menu;\n}\n\nexport interface ReloadArgs {\n  ignoreCache?: boolean;\n}\n\nexport default class MenuBuilder {\n  mainWindow: BrowserWindow;\n\n  appUpdater: AppUpdater;\n\n  constructor(mainWindow: BrowserWindow, appUpdater: AppUpdater) {\n    this.mainWindow = mainWindow;\n    this.appUpdater = appUpdater;\n  }\n\n  buildMenu(): Menu {\n    if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {\n      this.setupDevelopmentEnvironment();\n    }\n\n    const template =\n      process.platform === 'darwin' ? this.buildDarwinTemplate() : this.buildDefaultTemplate();\n\n    const menu = Menu.buildFromTemplate(template);\n    Menu.setApplicationMenu(menu);\n\n    return menu;\n  }\n\n  setupDevelopmentEnvironment(): void {\n    this.mainWindow.webContents.on('context-menu', (_, props) => {\n      const {x, y} = props;\n\n      Menu.buildFromTemplate([\n        {\n          label: 'Inspect element',\n          click: () => {\n            this.mainWindow.webContents.inspectElement(x, y);\n          },\n        },\n      ]).popup({window: this.mainWindow});\n    });\n  }\n\n  buildDarwinTemplate(): MenuItemConstructorOptions[] {\n    const subMenuAbout: DarwinMenuItemConstructorOptions = {\n      label: 'ResponsivelyApp',\n      submenu: [\n        {\n          label: 'About ResponsivelyApp',\n          selector: 'orderFrontStandardAboutPanel:',\n        },\n        {type: 'separator'},\n        {\n          label: 'Hide ResponsivelyApp',\n          accelerator: 'Command+H',\n          selector: 'hide:',\n        },\n        {\n          label: 'Hide Others',\n          accelerator: 'Command+Shift+H',\n          selector: 'hideOtherApplications:',\n        },\n        {label: 'Show All', selector: 'unhideAllApplications:'},\n        {type: 'separator'},\n        {\n          label: 'Quit',\n          accelerator: 'Command+Q',\n          click: () => {\n            app.quit();\n          },\n        },\n      ],\n    };\n    const subMenuEdit: DarwinMenuItemConstructorOptions = {\n      label: 'Edit',\n      submenu: [\n        {label: 'Undo', accelerator: 'Command+Z', selector: 'undo:'},\n        {label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:'},\n        {type: 'separator'},\n        {label: 'Cut', accelerator: 'Command+X', selector: 'cut:'},\n        {label: 'Copy', accelerator: 'Command+C', selector: 'copy:'},\n        {label: 'Paste', accelerator: 'Command+V', selector: 'paste:'},\n        {\n          label: 'Select All',\n          accelerator: 'Command+A',\n          selector: 'selectAll:',\n        },\n      ],\n    };\n\n    const subMenuWindow: DarwinMenuItemConstructorOptions = {\n      label: 'Window',\n      submenu: [\n        {\n          label: 'Minimize',\n          accelerator: 'Command+M',\n          selector: 'performMiniaturize:',\n        },\n        {label: 'Close', accelerator: 'Command+W', selector: 'performClose:'},\n        {type: 'separator'},\n        {label: 'Bring All to Front', selector: 'arrangeInFront:'},\n      ],\n    };\n\n    return [\n      subMenuAbout,\n      subMenuEdit,\n      getViewMenu(this.mainWindow),\n      subMenuWindow,\n      subMenuHelp(this.mainWindow, this.appUpdater),\n    ];\n  }\n\n  buildDefaultTemplate(): MenuItemConstructorOptions[] {\n    return [\n      {\n        label: '&File',\n        submenu: [\n          {\n            label: '&Open',\n            accelerator: 'Ctrl+O',\n          },\n          {\n            label: '&Close',\n            accelerator: 'Ctrl+W',\n            click: () => {\n              this.mainWindow.close();\n            },\n          },\n        ],\n      },\n      getViewMenu(this.mainWindow),\n      subMenuHelp(this.mainWindow, this.appUpdater),\n    ];\n  }\n}\n"
  },
  {
    "path": "desktop-app/src/main/menu/view.ts",
    "content": "import {BrowserWindow, MenuItemConstructorOptions} from 'electron';\n\nconst isMac = process.platform === 'darwin';\nconst isDev = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';\n\nconst getToggleFullScreen = (mainWindow: BrowserWindow): MenuItemConstructorOptions => ({\n  label: 'Toggle &Full Screen',\n  accelerator: isMac ? 'Ctrl+CommandOrControl+F' : 'F11',\n  click: () => {\n    mainWindow.setFullScreen(!mainWindow.isFullScreen());\n  },\n});\n\nconst getToggleDevTools = (mainWindow: BrowserWindow): MenuItemConstructorOptions => ({\n  label: 'Toggle &Developer Tools',\n  accelerator: isMac ? 'Alt+CommandOrControl+I' : 'Alt+Ctrl+I',\n  click: () => {\n    mainWindow.webContents.toggleDevTools();\n  },\n});\n\nconst getReloadMenu = (mainWindow: BrowserWindow): MenuItemConstructorOptions => ({\n  label: '&Reload',\n  accelerator: 'CommandOrControl+R',\n  click: () => {\n    if (isDev) {\n      mainWindow.webContents.reload();\n      return;\n    }\n    mainWindow.webContents.send('reload', {});\n  },\n});\n\nconst getReloadIgnoringCacheMenu = (mainWindow: BrowserWindow): MenuItemConstructorOptions => ({\n  label: 'Reload Ignoring Cache',\n  accelerator: 'CommandOrControl+Shift+R',\n  click: () => {\n    mainWindow.webContents.send('reload', {ignoreCache: true});\n  },\n});\n\nconst getViewMenuProd = (mainWindow: BrowserWindow): MenuItemConstructorOptions => ({\n  label: '&View',\n  submenu: [\n    getReloadMenu(mainWindow),\n    getReloadIgnoringCacheMenu(mainWindow),\n    getToggleFullScreen(mainWindow),\n  ],\n});\n\nconst getViewMenuDev = (mainWindow: BrowserWindow): MenuItemConstructorOptions => ({\n  label: '&View',\n  submenu: [\n    getReloadMenu(mainWindow),\n    getToggleDevTools(mainWindow),\n    getToggleFullScreen(mainWindow),\n  ],\n});\n\nexport const getViewMenu = isDev ? getViewMenuDev : getViewMenuProd;\n"
  },
  {
    "path": "desktop-app/src/main/native-functions/index.ts",
    "content": "import {clipboard, ipcMain, nativeTheme, webContents} from 'electron';\n\nexport interface DisableDefaultWindowOpenHandlerArgs {\n  webContentsId: number;\n}\n\nexport interface DisableDefaultWindowOpenHandlerResult {\n  done: boolean;\n}\n\nexport interface SetNativeThemeArgs {\n  theme: 'dark' | 'light';\n}\n\nexport interface SetNativeThemeResult {\n  done: boolean;\n}\n\nexport const initNativeFunctionHandlers = () => {\n  ipcMain.handle(\n    'disable-default-window-open-handler',\n    async (\n      _,\n      arg: DisableDefaultWindowOpenHandlerArgs\n    ): Promise<DisableDefaultWindowOpenHandlerResult> => {\n      webContents.fromId(arg.webContentsId)?.setWindowOpenHandler(() => {\n        return {action: 'deny'};\n      });\n      return {done: true};\n    }\n  );\n\n  ipcMain.handle(\n    'set-native-theme',\n    async (_, arg: SetNativeThemeArgs): Promise<SetNativeThemeResult> => {\n      const {theme} = arg;\n      nativeTheme.themeSource = theme;\n      return {done: true};\n    }\n  );\n\n  ipcMain.handle('copy-to-clipboard', async (_, arg: string): Promise<void> => {\n    clipboard.writeText(arg);\n  });\n};\n"
  },
  {
    "path": "desktop-app/src/main/preload-webview.ts",
    "content": "import {ipcRenderer} from 'electron';\n\nconst documentBodyInit = () => {\n  // Browser Sync\n  const bsScript = window.document.createElement('script');\n  bsScript.src = 'https://localhost:12719/browser-sync/browser-sync-client.js?v=2.27.10';\n  bsScript.async = true;\n  window.document.body.appendChild(bsScript);\n\n  // Context Menu\n  window.addEventListener('contextmenu', (e) => {\n    e.preventDefault();\n    ipcRenderer.send('show-context-menu', {\n      contextMenuMeta: {x: e.x, y: e.y},\n    });\n  });\n\n  window.addEventListener('wheel', (e) => {\n    ipcRenderer.sendToHost('pass-scroll-data', {\n      coordinates: {\n        x: e.deltaX,\n        y: e.deltaY,\n        scrollX: window.scrollX,\n        scrollY: window.scrollY,\n      },\n      innerHeight: document.body.scrollHeight,\n      innerWidth: window.innerWidth,\n    });\n  });\n\n  window.addEventListener('scroll', () => {\n    ipcRenderer.sendToHost('pass-scroll-data', {\n      coordinates: {\n        x: 0,\n        y: 0,\n        scrollX: window.scrollX,\n        scrollY: window.scrollY,\n      },\n      innerHeight: document.body.scrollHeight,\n      innerWidth: window.innerWidth,\n    });\n  });\n\n  // To detect if user is typing in an input field\n  const isUserTyping = () => {\n    const el = document.activeElement;\n    if (!el) return false;\n\n    return (\n      el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || (el as HTMLElement).isContentEditable\n    );\n  };\n\n  // Handle F key for fullscreen toggle\n  window.addEventListener('keydown', (e) => {\n    // Prevent fullscreen if user is typing\n    if (isUserTyping()) return;\n\n    if (e.key === 'f' || e.key === 'F') {\n      e.preventDefault();\n\n      // Check if already in fullscreen\n      if (document.fullscreenElement) {\n        // Exit fullscreen\n        document.exitFullscreen().catch((err) => {\n          // eslint-disable-next-line no-console\n          console.error('Error exiting fullscreen:', err);\n        });\n      } else {\n        // Request fullscreen\n        document.documentElement.requestFullscreen().catch((err) => {\n          // eslint-disable-next-line no-console\n          console.error('Error requesting fullscreen:', err);\n        });\n      }\n    }\n  });\n\n  window.addEventListener('dom-ready', () => {\n    const {body} = document;\n    const html = document.documentElement;\n\n    const height = Math.max(\n      body.scrollHeight,\n      body.offsetHeight,\n      html.clientHeight,\n      html.scrollHeight,\n      html.offsetHeight\n    );\n\n    ipcRenderer.sendToHost('pass-scroll-data', {\n      coordinates: {x: 0, y: 0},\n      innerHeight: height,\n      innerWidth: window.innerWidth,\n    });\n  });\n};\n\nipcRenderer.on('context-menu-command', (_, command) => {\n  ipcRenderer.sendToHost('context-menu-command', command);\n});\n\nconst documentBodyWaitHandle = setInterval(() => {\n  window.onerror = function logError(errorMsg, url, lineNumber) {\n    // eslint-disable-next-line no-console\n    console.log(`Unhandled error: ${errorMsg} ${url} ${lineNumber}`);\n    // Code to run when an error has occurred on the page\n  };\n\n  if (window?.document?.body) {\n    clearInterval(documentBodyWaitHandle);\n    try {\n      documentBodyInit();\n    } catch (err) {\n      // eslint-disable-next-line no-console\n      console.log('Error in documentBodyInit:', err);\n    }\n\n    return;\n  }\n  // eslint-disable-next-line no-console\n  console.log('document.body not ready');\n}, 300);\n"
  },
  {
    "path": "desktop-app/src/main/preload.ts",
    "content": "import {Channels} from 'common/constants';\nimport {contextBridge, ipcRenderer, IpcRendererEvent} from 'electron';\ncontextBridge.exposeInMainWorld('electron', {\n  ipcRenderer: {\n    sendMessage<T>(channel: Channels, args: T[]) {\n      ipcRenderer.send(channel, args);\n    },\n    on<T>(channel: Channels, func: (...args: T[]) => void) {\n      const subscription = (_event: IpcRendererEvent, ...args: T[]) => func(...args);\n      ipcRenderer.on(channel, subscription);\n\n      return () => ipcRenderer.removeListener(channel, subscription);\n    },\n    once<T>(channel: Channels, func: (...args: T[]) => void) {\n      ipcRenderer.once(channel, (_event, ...args) => func(...args));\n    },\n    invoke<T, P>(channel: Channels, ...args: T[]): Promise<P> {\n      return ipcRenderer.invoke(channel, ...args);\n    },\n    removeListener(channel: Channels, listener: (...args: any[]) => void) {\n      ipcRenderer.removeListener(channel, listener);\n    },\n    removeAllListeners(channel: Channels) {\n      ipcRenderer.removeAllListeners(channel);\n    },\n  },\n  store: {\n    get(val: any) {\n      return ipcRenderer.sendSync('electron-store-get', val);\n    },\n    set(property: string, val: any) {\n      ipcRenderer.send('electron-store-set', property, val);\n    },\n    // Other method you want to add like has(), reset(), etc.\n  },\n});\n\nwindow.onerror = function (errorMsg, url, lineNumber) {\n  // eslint-disable-next-line no-console\n  console.log(`Unhandled error: ${errorMsg} ${url} ${lineNumber}`);\n  // Code to run when an error has occurred on the page\n};\n"
  },
  {
    "path": "desktop-app/src/main/protocol-handler/index.ts",
    "content": "import {BrowserWindow} from 'electron';\nimport {IPC_MAIN_CHANNELS} from '../../common/constants';\n\n// eslint-disable-next-line import/prefer-default-export\nexport const openUrl = (url: string, mainWindow: BrowserWindow | null) => {\n  mainWindow?.webContents.send(IPC_MAIN_CHANNELS.OPEN_URL, {\n    url,\n  });\n};\n"
  },
  {
    "path": "desktop-app/src/main/screenshot/index.ts",
    "content": "/* eslint-disable promise/always-return */\nimport {Device} from 'common/deviceList';\nimport {ipcMain, shell, webContents} from 'electron';\nimport {writeFile, ensureDir} from 'fs-extra';\nimport path from 'path';\nimport store from '../../store';\n\nexport interface ScreenshotArgs {\n  webContentsId: number;\n  fullPage?: boolean;\n  device: Device;\n}\n\nexport interface ScreenshotAllArgs {\n  webContentsId: number;\n  device: Device;\n  previousHeight: string;\n  previousTransform: string;\n  pageHeight: number;\n}\n\nexport interface ScreenshotResult {\n  done: boolean;\n}\nconst captureImage = async (webContentsId: number): Promise<Electron.NativeImage | undefined> => {\n  const WebContents = webContents.fromId(webContentsId);\n\n  const isExecuted = await WebContents?.executeJavaScript(`\n    if (window.isExecuted) {\n      true;\n    }\n  `);\n\n  if (!isExecuted) {\n    await WebContents?.executeJavaScript(`\n      const bgColor = window.getComputedStyle(document.body).backgroundColor;\n      if (bgColor === 'rgba(0, 0, 0, 0)') {\n        document.body.style.backgroundColor = 'white';\n      }\n      window.isExecuted = true;\n    `);\n  }\n\n  const Image = await WebContents?.capturePage();\n  return Image;\n};\n\nconst quickScreenshot = async (arg: ScreenshotArgs): Promise<ScreenshotResult> => {\n  const {\n    webContentsId,\n    device: {name},\n  } = arg;\n  const image = await captureImage(webContentsId);\n  if (image === undefined) {\n    return {done: false};\n  }\n  const fileName = name.replaceAll('/', '-').replaceAll(':', '-');\n  const dir = store.get('userPreferences.screenshot.saveLocation');\n  const filePath = path.join(dir, `/${fileName}-${Date.now()}.jpeg`);\n  await ensureDir(dir);\n  await writeFile(filePath, image.toJPEG(100));\n  setTimeout(() => shell.showItemInFolder(filePath), 100);\n\n  return {done: true};\n};\n\nconst captureAllDecies = async (args: Array<ScreenshotAllArgs>): Promise<ScreenshotResult> => {\n  const screenShots = args.map((arg) => {\n    const {device, webContentsId} = arg;\n    const screenShotArg: ScreenshotArgs = {device, webContentsId};\n    return quickScreenshot(screenShotArg);\n  });\n\n  await Promise.all(screenShots);\n  return {done: true};\n};\n\nexport const initScreenshotHandlers = () => {\n  ipcMain.handle('screenshot', async (_, arg: ScreenshotArgs): Promise<ScreenshotResult> => {\n    return quickScreenshot(arg);\n  });\n\n  ipcMain.handle('screenshot:All', async (event, args: Array<ScreenshotAllArgs>) => {\n    return captureAllDecies(args);\n  });\n};\n"
  },
  {
    "path": "desktop-app/src/main/screenshot/webpage.ts",
    "content": "class WebPage {\n  webview: Electron.WebContents;\n\n  constructor(webview: Electron.WebContents) {\n    this.webview = webview;\n  }\n\n  async getPageHeight() {\n    return this.webview.executeJavaScript('document.body.scrollHeight');\n  }\n\n  async getViewportHeight() {\n    return this.webview.executeJavaScript('window.innerHeight');\n  }\n\n  async scrollTo(x: number, y: number) {\n    return this.webview.executeJavaScript(`window.scrollTo(${x}, ${y})`);\n  }\n}\n\nexport default WebPage;\n"
  },
  {
    "path": "desktop-app/src/main/util.ts",
    "content": "/* eslint import/prefer-default-export: off */\nimport {URL} from 'url';\nimport path from 'path';\nimport {app} from 'electron';\nimport fs from 'fs-extra';\nimport os from 'os';\n\nexport function resolveHtmlPath(htmlFileName: string) {\n  if (process.env.NODE_ENV === 'development') {\n    const port = process.env.PORT || 1212;\n    const url = new URL(`http://localhost:${port}`);\n    url.pathname = htmlFileName;\n    return url.href;\n  }\n  return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`;\n}\n\nlet isCliArgResult: boolean | undefined;\n\nexport function isValidCliArgURL(arg?: string): boolean {\n  if (isCliArgResult !== undefined) {\n    return isCliArgResult;\n  }\n  if (arg == null || arg === '') {\n    isCliArgResult = false;\n    return false;\n  }\n  try {\n    const url = new URL(arg);\n    if (url.protocol === 'http:' || url.protocol === 'https:' || url.protocol === 'file:') {\n      isCliArgResult = true;\n      return true;\n    }\n    // eslint-disable-next-line no-console\n    console.warn('Protocol not supported', url.protocol);\n  } catch (e) {\n    // eslint-disable-next-line no-console\n    console.warn('Not a valid URL', arg, e);\n  }\n  isCliArgResult = false;\n  return false;\n}\n\nexport const getPackageJson = () => {\n  let appPath;\n  if (process.env.NODE_ENV === 'production') appPath = app.getAppPath();\n  else appPath = process.cwd();\n\n  const pkgPath = path.join(appPath, 'package.json');\n  if (fs.existsSync(pkgPath)) {\n    const pkgContent = fs.readFileSync(pkgPath, 'utf-8');\n    return JSON.parse(pkgContent);\n  }\n  console.error(`cant find package.json in: '${appPath}'`);\n  return {};\n};\n\nexport interface EnvironmentInfo {\n  appVersion: string;\n  electronVersion: string;\n  chromeVersion: string;\n  nodeVersion: string;\n  v8Version: string;\n  osInfo: string;\n}\n\nexport const getEnvironmentInfo = (): EnvironmentInfo => {\n  const pkg = getPackageJson();\n  const appVersion = pkg.version || 'Unknown';\n  const electronVersion = process.versions.electron || 'Unknown';\n  const chromeVersion = process.versions.chrome || 'Unknown';\n  const nodeVersion = process.versions.node || 'Unknown';\n  const v8Version = process.versions.v8 || 'Unknown';\n  const osInfo = `${os.type()} ${os.arch()} ${os.release()}`.trim() || 'Unknown';\n\n  return {\n    appVersion,\n    electronVersion,\n    chromeVersion,\n    nodeVersion,\n    v8Version,\n    osInfo,\n  };\n};\n"
  },
  {
    "path": "desktop-app/src/main/web-permissions/PermissionsManager.ts",
    "content": "import {BrowserWindow, ipcMain, session} from 'electron';\nimport {IPC_MAIN_CHANNELS} from '../../common/constants';\nimport store from '../../store';\n\nexport interface PermissionRequestArg {\n  permission: string;\n  requestingOrigin: string;\n}\n\nexport interface PermissionResponseArg {\n  permissionRequest: PermissionRequestArg;\n  allow: boolean;\n}\n\nexport const PERMISSION_STATE = {\n  GRANTED: 'GRANTED',\n  DENIED: 'DENIED',\n  PROMPT: 'PROMPT',\n  UNKNOWN: 'UNKNOWN',\n} as const;\n\ntype PermissionState = (typeof PERMISSION_STATE)[keyof typeof PERMISSION_STATE];\n\ninterface PersistedPermission {\n  origin: string;\n  permissions: {\n    type: string;\n    status: PermissionState;\n  }[];\n}\n\ntype PermissionRecords = Record<string, PermissionState>;\n\ntype PermissionCallback = (permissionGranted: boolean) => void;\n\nconst loadPermissions = () => {\n  const permissions = (store.get('webPermissions') as PersistedPermission[])\n    .map(({origin, permissions: permissionRecords}) => {\n      return {\n        origin,\n        permissions: permissionRecords.reduce((acc, {type, status}) => {\n          acc[type] = status;\n          return acc;\n        }, {} as PermissionRecords),\n      };\n    })\n    .reduce((acc, {origin, permissions: permissionRecords}) => {\n      acc[origin] = permissionRecords;\n      return acc;\n    }, {} as Record<string, PermissionRecords>);\n\n  return permissions;\n};\n\nconst savePermissions = (permissions: Record<string, PermissionRecords>) => {\n  const persistedPermissions = Object.entries(permissions).map(([origin, permissionRecords]) => {\n    return {\n      origin,\n      permissions: Object.entries(permissionRecords)\n        .filter(([, status]) => {\n          return status === PERMISSION_STATE.GRANTED || status === PERMISSION_STATE.DENIED;\n        })\n        .map(([type, status]) => {\n          return {\n            type,\n            status,\n          };\n        }),\n    };\n  });\n  store.set('webPermissions', persistedPermissions);\n};\n\nclass PermissionsManager {\n  permissions: Record<string, PermissionRecords>;\n\n  callbacks: Record<string, PermissionCallback[]> = {};\n\n  mainWindow: BrowserWindow;\n\n  constructor(mainWindow: BrowserWindow) {\n    this.mainWindow = mainWindow;\n    this.permissions = loadPermissions();\n    const handler = (_event: Electron.IpcMainInvokeEvent, arg: PermissionResponseArg) => {\n      this.setPermissionState(\n        arg.permissionRequest.requestingOrigin,\n        arg.permissionRequest.permission,\n        arg.allow ? PERMISSION_STATE.GRANTED : PERMISSION_STATE.DENIED\n      );\n      const key = `${arg.permissionRequest.requestingOrigin}-${arg.permissionRequest.permission}`;\n      const callbacks = this.callbacks[key];\n\n      if (callbacks) {\n        callbacks.forEach((callback) => callback(arg.allow));\n        this.callbacks[key] = [];\n      }\n    };\n    const PERMISSION_RESPONSE_CHANNEL = IPC_MAIN_CHANNELS.PERMISSION_RESPONSE;\n    if (ipcMain.listeners(PERMISSION_RESPONSE_CHANNEL).length === 0) {\n      try {\n        ipcMain.handle(PERMISSION_RESPONSE_CHANNEL, handler);\n      } catch (e) {\n        // eslint-disable-next-line no-console\n        // eslint-disable-next-line no-console\n        console.error('Error adding listener for permission response channel', e);\n      }\n    }\n  }\n\n  getPermissionState(origin: string, type: string): PermissionState {\n    const permissions = this.permissions[origin];\n    return permissions ? permissions[type] : PERMISSION_STATE.UNKNOWN;\n  }\n\n  setPermissionState(origin: string, type: string, state: PermissionState) {\n    const permissions = this.permissions[origin];\n    if (permissions) {\n      permissions[type] = state;\n    } else {\n      this.permissions[origin] = {\n        [type]: state,\n      };\n    }\n    savePermissions(this.permissions);\n\n    this.mainWindow.webContents.send(IPC_MAIN_CHANNELS.PERMISSION_UPDATED, {\n      origin,\n      type,\n      state,\n    });\n  }\n\n  requestPermission(origin: string, type: string, callback: PermissionCallback): void {\n    this.permissions[origin] = this.permissions[origin] || {};\n    const currentState = this.permissions[origin][type];\n\n    const key = `${origin}-${type}`;\n    let callbacks = this.callbacks[key];\n    if (callbacks === undefined) {\n      callbacks = [];\n      this.callbacks[key] = callbacks;\n    }\n\n    // If permission is explicitly granted, allow immediately\n    if (currentState === PERMISSION_STATE.GRANTED) {\n      callback(true);\n      return;\n    }\n\n    // If permission is explicitly denied, block immediately\n    if (currentState === PERMISSION_STATE.DENIED) {\n      callback(false);\n      return;\n    }\n\n    // For PROMPT or UNKNOWN states, show permission dialog\n    // Set to PROMPT state to indicate it's requesting\n    if (currentState !== PERMISSION_STATE.PROMPT) {\n      this.permissions[origin][type] = PERMISSION_STATE.PROMPT;\n    }\n\n    callbacks.push(callback);\n\n    // Only show dialog if no other requests are pending for this permission\n    if (callbacks.length === 1) {\n      this.mainWindow.webContents.send(IPC_MAIN_CHANNELS.PERMISSION_REQUEST, {\n        permission: type,\n        requestingOrigin: origin,\n      });\n    }\n  }\n\n  getSitePermissions(origin: string) {\n    const permissions = this.permissions[origin] || {};\n    const commonPermissions = [\n      {type: 'camera', displayName: 'Camera', icon: 'mdi:camera'},\n      {type: 'microphone', displayName: 'Microphone', icon: 'mdi:microphone'},\n      {type: 'geolocation', displayName: 'Location', icon: 'mdi:map-marker'},\n      {type: 'notifications', displayName: 'Notifications', icon: 'mdi:bell'},\n      {\n        type: 'clipboard-read',\n        displayName: 'Clipboard',\n        icon: 'mdi:content-paste',\n      },\n      {type: 'fullscreen', displayName: 'Fullscreen', icon: 'mdi:fullscreen'},\n      {type: 'midi', displayName: 'MIDI Devices', icon: 'mdi:piano'},\n      {\n        type: 'pointerLock',\n        displayName: 'Pointer Lock',\n        icon: 'mdi:cursor-move',\n      },\n    ];\n\n    return commonPermissions.map((permission) => ({\n      ...permission,\n      state: permissions[permission.type] || PERMISSION_STATE.UNKNOWN,\n    }));\n  }\n\n  updateSitePermission(origin: string, type: string, state: PermissionState) {\n    // If changing to PROMPT state, clear any cached permission decisions in the session\n    if (state === PERMISSION_STATE.PROMPT) {\n      // Clear storage data for this origin to ensure fresh permission requests\n      session.defaultSession\n        .clearStorageData({\n          origin,\n          storages: [],\n          quotas: [],\n        })\n        .catch((e) => {\n          // eslint-disable-next-line no-console\n          console.error('Failed to clear storage data for origin:', origin, e);\n        });\n    }\n\n    this.setPermissionState(origin, type, state);\n  }\n\n  clearSitePermissions(origin: string) {\n    const permissions = this.permissions[origin];\n    if (permissions) {\n      // Clear storage data for this origin to ensure fresh permission requests\n      session.defaultSession\n        .clearStorageData({\n          origin,\n          storages: [],\n          quotas: [],\n        })\n        .catch((e) => {\n          // eslint-disable-next-line no-console\n          console.error('Failed to clear storage data for origin:', origin, e);\n        });\n\n      // Notify about each permission being cleared (reset to UNKNOWN)\n      Object.keys(permissions).forEach((type) => {\n        this.mainWindow.webContents.send(IPC_MAIN_CHANNELS.PERMISSION_UPDATED, {\n          origin,\n          type,\n          state: PERMISSION_STATE.UNKNOWN,\n        });\n      });\n    }\n\n    delete this.permissions[origin];\n    savePermissions(this.permissions);\n  }\n}\n\nexport default PermissionsManager;\n"
  },
  {
    "path": "desktop-app/src/main/web-permissions/index.ts",
    "content": "import {BrowserWindow, session, ipcMain} from 'electron';\nimport PermissionsManager, {PERMISSION_STATE} from './PermissionsManager';\nimport {IPC_MAIN_CHANNELS} from '../../common/constants';\nimport store from '../../store';\n\n// eslint-disable-next-line import/prefer-default-export\nexport const WebPermissionHandlers = (mainWindow: BrowserWindow) => {\n  const permissionsManager = new PermissionsManager(mainWindow);\n  return {\n    init: () => {\n      session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {\n        permissionsManager.requestPermission(\n          new URL(webContents.getURL()).origin,\n          permission,\n          callback\n        );\n      });\n\n      session.defaultSession.setPermissionCheckHandler(\n        (_webContents, permission, requestingOrigin) => {\n          const status = permissionsManager.getPermissionState(requestingOrigin, permission);\n          return status === PERMISSION_STATE.GRANTED;\n        }\n      );\n\n      session.defaultSession.webRequest.onBeforeSendHeaders(\n        {\n          urls: ['<all_urls>'],\n        },\n        (details, callback) => {\n          const acceptLanguage = store.get('userPreferences.webRequestHeaderAcceptLanguage');\n          if (acceptLanguage != null && acceptLanguage !== '') {\n            details.requestHeaders['Accept-Language'] = store.get(\n              'userPreferences.webRequestHeaderAcceptLanguage'\n            );\n          }\n          callback({requestHeaders: details.requestHeaders});\n        }\n      );\n\n      // Add IPC handlers for site permissions management\n      ipcMain.handle(IPC_MAIN_CHANNELS.GET_SITE_PERMISSIONS, (_event, origin: string) => {\n        return permissionsManager.getSitePermissions(origin);\n      });\n\n      ipcMain.handle(\n        IPC_MAIN_CHANNELS.UPDATE_SITE_PERMISSION,\n        (_event, {origin, type, state}: {origin: string; type: string; state: string}) => {\n          permissionsManager.updateSitePermission(\n            origin,\n            type,\n            state as (typeof PERMISSION_STATE)[keyof typeof PERMISSION_STATE]\n          );\n          return true;\n        }\n      );\n\n      ipcMain.handle(IPC_MAIN_CHANNELS.CLEAR_SITE_PERMISSIONS, (_event, origin: string) => {\n        permissionsManager.clearSitePermissions(origin);\n        return true;\n      });\n    },\n  };\n};\n"
  },
  {
    "path": "desktop-app/src/main/webview-context-menu/common.ts",
    "content": "interface ContextMenuMetadata {\n  id: string;\n  label: string;\n}\n\nexport const CONTEXT_MENUS: {[key: string]: ContextMenuMetadata} = {\n  INSPECT_ELEMENT: {id: 'INSPECT_ELEMENT', label: 'Inspect Element'},\n  OPEN_CONSOLE: {id: 'OPEN_CONSOLE', label: 'Open Console'},\n};\n"
  },
  {
    "path": "desktop-app/src/main/webview-context-menu/register.ts",
    "content": "import {BrowserWindow, ipcMain, Menu} from 'electron';\nimport {CONTEXT_MENUS} from './common';\n// import { webViewPubSub } from '../../renderer/lib/pubsub';\n// import { MOUSE_EVENTS } from '../ruler';\n\nexport const initWebviewContextMenu = () => {\n  ipcMain.removeAllListeners('show-context-menu');\n  ipcMain.on('show-context-menu', (event, ...args) => {\n    const template: Electron.MenuItemConstructorOptions[] = Object.values(CONTEXT_MENUS).map(\n      (menu) => {\n        return {\n          label: menu.label,\n          click: () => {\n            event.sender.send('context-menu-command', {\n              command: menu.id,\n              arg: args[0],\n            });\n          },\n        };\n      }\n    );\n    const menu = Menu.buildFromTemplate(template);\n    menu.popup(BrowserWindow.fromWebContents(event.sender) as Electron.PopupOptions);\n  });\n  // ipcMain.on('pass-scroll-data', (event, ...args) => {\n  //   console.log(args[0].coordinates);\n  //   webViewPubSub.publish(MOUSE_EVENTS.SCROLL, [args[0].coordinates]);\n  // });\n};\n\nexport default initWebviewContextMenu;\n"
  },
  {
    "path": "desktop-app/src/main/webview-storage-manager/index.ts",
    "content": "import {ClearStorageDataOptions, ipcMain, webContents} from 'electron';\n\nexport interface DeleteStorageArgs {\n  webContentsId: number;\n  storages?: string[];\n}\n\nexport interface DeleteStorageResult {\n  done: boolean;\n}\n\nconst deleteStorage = async (arg: DeleteStorageArgs): Promise<DeleteStorageResult> => {\n  const {webContentsId, storages} = arg;\n  if (storages?.length === 1 && storages[0] === 'network-cache') {\n    await webContents.fromId(webContentsId)?.session.clearCache();\n  } else {\n    await webContents\n      .fromId(webContentsId)\n      ?.session.clearStorageData({storages} as ClearStorageDataOptions);\n  }\n  return {done: true};\n};\n\nexport const initWebviewStorageManagerHandlers = () => {\n  ipcMain.handle(\n    'delete-storage',\n    async (_, arg: DeleteStorageArgs): Promise<DeleteStorageResult> => {\n      return deleteStorage(arg);\n    }\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/App.css",
    "content": "/*\n * @NOTE: Prepend a `~` to css file paths that are in your node_modules\n *        See https://github.com/webpack-contrib/sass-loader#imports\n */\n\n@import '~@fontsource/lato/300.css';\n@import '~@fontsource/lato/400.css';\n@import '~@fontsource/lato/400-italic.css';\n@import '~@fontsource/lato/700.css';\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml {\n  user-select: none;\n}\n"
  },
  {
    "path": "desktop-app/src/renderer/AppContent.tsx",
    "content": "import {Provider, useSelector} from 'react-redux';\n\nimport ToolBar from './components/ToolBar';\nimport Previewer from './components/Previewer';\nimport {store} from './store';\n\nimport './App.css';\nimport ThemeProvider from './context/ThemeProvider';\nimport type {AppView} from './store/features/ui';\nimport {APP_VIEWS, selectAppView} from './store/features/ui';\nimport DeviceManager from './components/DeviceManager';\nimport KeyboardShortcutsManager from './components/KeyboardShortcutsManager';\nimport {ReleaseNotes} from './components/ReleaseNotes';\nimport {Sponsorship} from './components/Sponsorship';\nimport {AboutDialog} from './components/AboutDialog';\n\nconst Browser = () => {\n  return (\n    <div className=\"h-screen gap-2 overflow-hidden pt-2\">\n      <ToolBar />\n      <Previewer />\n    </div>\n  );\n};\n\nconst getView = (appView: AppView) => {\n  switch (appView) {\n    case APP_VIEWS.BROWSER:\n      return <Browser />;\n    case APP_VIEWS.DEVICE_MANAGER:\n      return <DeviceManager />;\n    default:\n      return <Browser />;\n  }\n};\n\nconst ViewComponent = () => {\n  const appView = useSelector(selectAppView);\n\n  return <>{getView(appView)}</>;\n};\n\nconst AppContent = () => {\n  return (\n    <Provider store={store}>\n      <ThemeProvider>\n        <KeyboardShortcutsManager />\n        <ViewComponent />\n        <ReleaseNotes />\n        <Sponsorship />\n        <AboutDialog />\n      </ThemeProvider>\n    </Provider>\n  );\n};\nexport default AppContent;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/AboutDialog/index.tsx",
    "content": "import {useEffect, useRef, useState} from 'react';\nimport {IPC_MAIN_CHANNELS} from 'common/constants';\nimport {AboutDialogArgs} from 'main/menu/help';\nimport TimeAgo from 'javascript-time-ago';\nimport en from 'javascript-time-ago/locale/en';\nimport Icon from '../../assets/img/logo.png';\nimport Modal from '../Modal';\nimport Button from '../Button';\n\nTimeAgo.addLocale(en);\n\nconst timeAgo = new TimeAgo('en-US');\n\nexport const AboutDialog = () => {\n  const [show, setShow] = useState(false);\n  const [args, setArgs] = useState<AboutDialogArgs | null>(null);\n  const intervalRef = useRef<NodeJS.Timeout | null>(null);\n  const clearIntervalIfAvailable = () => {\n    if (intervalRef.current) {\n      clearInterval(intervalRef.current);\n      intervalRef.current = null;\n    }\n  };\n\n  useEffect(() => {\n    const onOpenAboutDialog = (arg: AboutDialogArgs) => {\n      setShow(true);\n      setArgs(arg);\n    };\n    window.electron.ipcRenderer.on<AboutDialogArgs>(\n      IPC_MAIN_CHANNELS.OPEN_ABOUT_DIALOG,\n      onOpenAboutDialog\n    );\n    return () => {\n      window.electron.ipcRenderer.removeListener(\n        IPC_MAIN_CHANNELS.OPEN_ABOUT_DIALOG,\n        onOpenAboutDialog\n      );\n    };\n  }, []);\n\n  useEffect(() => {\n    if (show) {\n      intervalRef.current = setInterval(() => {\n        window.electron.ipcRenderer\n          .invoke<null, AboutDialogArgs>('get-about-info')\n          .then((arg: AboutDialogArgs) => {\n            setArgs(arg);\n\n            return arg;\n          })\n          .catch((err) => {\n            // eslint-disable-next-line no-console\n            console.error('Error while refreshing about info', err);\n          });\n      }, 1000);\n    } else {\n      clearIntervalIfAvailable();\n    }\n  }, [show]);\n\n  return (\n    <Modal\n      isOpen={show}\n      onClose={() => setShow(false)}\n      title={\n        <div className=\"flex flex-col items-center justify-center\">\n          <img src={Icon} alt=\"Logo\" width={48} className=\"pb-2\" />\n          <div className=\"text-2xl\">Responsively App</div>\n          <div className=\"text-base text-gray-500\">\n            A dev-tool that aids faster and precise responsive web development.\n          </div>\n        </div>\n      }\n    >\n      <div className=\"flex flex-col items-center gap-6 pt-6 text-gray-700 dark:text-gray-300\">\n        <div className=\"flex w-3/4 flex-col gap-2 rounded border border-slate-300 p-4 dark:border-slate-700\">\n          <div className=\"flex justify-center text-lg\">Versions</div>\n          <div className=\"flex flex-col gap-[2px]\">\n            <div className=\"flex  justify-between\">\n              <span>App</span>\n              <span className=\"text-sm\">v{args?.environmentInfo.appVersion}</span>\n            </div>\n            <div className=\"flex justify-between\">\n              <span>Electron</span>\n              <span className=\"text-sm\">v{args?.environmentInfo.electronVersion}</span>\n            </div>\n            <div className=\"flex justify-between\">\n              <span>Chrome</span>\n              <span className=\"text-sm\">v{args?.environmentInfo.chromeVersion}</span>\n            </div>\n            <div className=\"flex justify-between\">\n              <span>Node.js</span>\n              <span className=\"text-sm\">v{args?.environmentInfo.nodeVersion}</span>\n            </div>\n            <div className=\"flex justify-between\">\n              <span>V8</span>\n              <span className=\"text-sm\">v{args?.environmentInfo.v8Version}</span>\n            </div>\n            <div className=\"flex justify-between\">\n              <span>OS</span>\n              <span className=\"text-sm\">{args?.environmentInfo.osInfo}</span>\n            </div>\n          </div>\n          <div className=\"mt-2 flex justify-center\">\n            <Button\n              isActive\n              isTextButton\n              className=\"w-fit\"\n              onClick={async () => {\n                window.electron.ipcRenderer.invoke<string, void>(\n                  'copy-to-clipboard',\n                  `App Version: ${args?.environmentInfo.appVersion}\\nElectron Version: ${args?.environmentInfo.electronVersion}\\nChrome Version: ${args?.environmentInfo.chromeVersion}\\nNode.js Version: ${args?.environmentInfo.nodeVersion}\\nV8 Version: ${args?.environmentInfo.v8Version}\\nOS: ${args?.environmentInfo.osInfo}`\n                );\n              }}\n            >\n              Copy\n            </Button>\n          </div>\n        </div>\n        <div className=\"flex w-3/4 flex-col gap-4 rounded border border-slate-300 p-4 dark:border-slate-700\">\n          <div className=\"flex justify-center text-lg\">Update Status</div>\n          <div className=\"flex flex-col gap-[2px]\">\n            <div className=\"flex  justify-between\">\n              <span>Status</span>\n              <span className=\"text-sm capitalize\">\n                {args?.updaterStatus.status.toLocaleLowerCase()}\n              </span>\n            </div>\n            {args?.updaterStatus.version != null ? (\n              <div className=\"flex justify-between\">\n                <span>New Version</span>\n                <span className=\"text-sm\">{args?.updaterStatus.version}</span>\n              </div>\n            ) : null}\n            {args?.updaterStatus.error != null ? (\n              <div className=\"flex justify-between\">\n                <span>Error</span>\n                <span className=\"w-1/2 overflow-auto text-sm\">\n                  {args?.updaterStatus.error.message}\n                </span>\n              </div>\n            ) : null}\n            <div className=\"flex justify-between\">\n              <span>Last Checked</span>\n              <span className=\"text-sm\">\n                {args?.updaterStatus.lastChecked != null\n                  ? timeAgo.format(args?.updaterStatus.lastChecked)\n                  : 'NA'}\n              </span>\n            </div>\n          </div>\n        </div>\n        <Button isPrimary isTextButton onClick={() => setShow(false)}>\n          Close\n        </Button>\n      </div>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Accordion/Accordion.tsx",
    "content": "export const Accordion = ({children}: {children: JSX.Element}) => {\n  return (\n    <div id=\"accordion-open\" data-accordion=\"open\">\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Accordion/AccordionItem.tsx",
    "content": "import {useState} from 'react';\n\ntype AccordionItemProps = {\n  title: string;\n  children: JSX.Element;\n};\n\nexport const AccordionItem = ({title, children}: AccordionItemProps) => {\n  const [isOpen, setIsOpen] = useState(true);\n\n  const toggle = () => {\n    setIsOpen(!isOpen);\n  };\n\n  return (\n    <div>\n      <h2>\n        <button\n          type=\"button\"\n          className=\"flex w-full items-center justify-between gap-3 border   border-gray-200 p-5 font-medium text-gray-500 hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:focus:ring-gray-800\"\n          onClick={toggle}\n          aria-expanded={isOpen}\n          aria-controls={`accordion-body-${title}`}\n        >\n          <span className=\"flex items-center\">{title}</span>\n          <svg\n            className={`h-3 w-3 ${isOpen ? 'rotate-180' : ''} shrink-0`}\n            aria-hidden=\"true\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            fill=\"none\"\n            viewBox=\"0 0 10 6\"\n          >\n            <path\n              stroke=\"currentColor\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth=\"2\"\n              d=\"M9 5 5 1 1 5\"\n            />\n          </svg>\n        </button>\n      </h2>\n      <div\n        id={`accordion-body-${title}`}\n        className={`${isOpen ? 'block' : 'hidden'}`}\n        aria-labelledby={`accordion-heading-${title}`}\n      >\n        <div className=\"border border-b-0 border-gray-200 p-5 dark:border-gray-700 dark:bg-gray-900\">\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Accordion/index.tsx",
    "content": "export {Accordion} from './Accordion';\nexport {AccordionItem} from './AccordionItem';\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Button/Button.test.tsx",
    "content": "import {act, render, screen} from '@testing-library/react';\nimport Button from './index';\n\nvi.mock('@iconify/react', () => ({\n  Icon: () => <div data-testid=\"icon\" />,\n}));\n\ndescribe('Button Component', () => {\n  it('renders with default props', () => {\n    render(<Button>Click me</Button>);\n    const buttonElement = screen.getByRole('button', {name: /click me/i});\n    expect(buttonElement).toBeInTheDocument();\n  });\n\n  it('applies custom class name', () => {\n    render(<Button className=\"custom-class\">Click me</Button>);\n    const buttonElement = screen.getByRole('button', {name: /click me/i});\n    expect(buttonElement).toHaveClass('custom-class');\n  });\n\n  it('renders loading icon when isLoading is true', () => {\n    render(<Button isLoading>Click me</Button>);\n    const loadingIcon = screen.getByTestId('icon');\n    expect(loadingIcon).toBeInTheDocument();\n  });\n\n  it('renders confirmation icon when loading is done', () => {\n    vi.useFakeTimers();\n    const {rerender} = render(<Button isLoading>Click me</Button>);\n\n    act(() => {\n      rerender(<Button isLoading={false}>Click me</Button>);\n      vi.runAllTimers(); // Use act to advance timers\n    });\n\n    const confirmationIcon = screen.getByTestId('icon');\n    expect(confirmationIcon).toBeInTheDocument();\n    vi.useRealTimers();\n  });\n\n  it('applies primary button styles', () => {\n    render(<Button isPrimary>Click me</Button>);\n    const buttonElement = screen.getByRole('button', {name: /click me/i});\n    expect(buttonElement).toHaveClass('bg-emerald-500');\n    expect(buttonElement).toHaveClass('text-white');\n  });\n\n  it('applies action button styles', () => {\n    render(<Button isActionButton>Click me</Button>);\n    const buttonElement = screen.getByRole('button', {name: /click me/i});\n    expect(buttonElement).toHaveClass('bg-slate-200');\n  });\n\n  it('applies subtle hover styles', () => {\n    render(<Button subtle>Click me</Button>);\n    const buttonElement = screen.getByRole('button', {name: /click me/i});\n    expect(buttonElement).toHaveClass('hover:bg-slate-200');\n  });\n\n  it('disables hover effects when disableHoverEffects is true', () => {\n    render(\n      <Button disableHoverEffects subtle>\n        Click me\n      </Button>\n    );\n    const buttonElement = screen.getByRole('button', {name: /click me/i});\n    expect(buttonElement).not.toHaveClass('hover:bg-slate-200');\n  });\n\n  it('renders children correctly when not loading or loading done', () => {\n    render(<Button>Click me</Button>);\n    const buttonElement = screen.getByText('Click me');\n    expect(buttonElement).toBeInTheDocument();\n  });\n\n  it('does not render children when loading or loading done', () => {\n    const {rerender} = render(<Button isLoading>Click me</Button>);\n    expect(screen.queryByText('Click me')).not.toBeInTheDocument();\n\n    act(() => {\n      rerender(<Button isLoading={false}>Click me</Button>);\n    });\n\n    expect(screen.queryByText('Click me')).not.toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Button/index.tsx",
    "content": "import React, {useEffect, useRef, useState} from 'react';\nimport cx from 'classnames';\nimport {Icon} from '@iconify/react';\n\ninterface CustomProps {\n  className?: string;\n  isActive?: boolean;\n  isLoading?: boolean;\n  isPrimary?: boolean;\n  isTextButton?: boolean;\n  disableHoverEffects?: boolean;\n  isActionButton?: boolean;\n  subtle?: boolean;\n  disabled?: boolean;\n}\n\nconst Button = ({\n  className = '',\n  isActive = false,\n  isLoading = false,\n  isPrimary = false,\n  isTextButton = false,\n  isActionButton = false,\n  subtle = false,\n  disableHoverEffects = false,\n  disabled = false,\n  children,\n  ...props\n}: CustomProps &\n  React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>) => {\n  const [isLoadingDone, setIsLoadingDone] = useState<boolean>(false);\n  const prevLoadingState = useRef(false);\n\n  useEffect(() => {\n    if (!isLoading && prevLoadingState.current === true) {\n      setIsLoadingDone(true);\n      setTimeout(() => {\n        setIsLoadingDone(false);\n      }, 800);\n    }\n    prevLoadingState.current = isLoading;\n  }, [isLoading]);\n\n  let hoverBg = 'hover:bg-slate-400';\n  let hoverBgDark = 'dark:hover:bg-slate-600';\n  if (subtle) {\n    hoverBg = 'hover:bg-slate-200';\n    hoverBgDark = 'dark:hover:bg-slate-700';\n  } else if (isPrimary) {\n    hoverBg = 'hover:bg-emerald-600';\n    hoverBgDark = 'dark:hover:bg-emerald-600';\n  }\n\n  return (\n    <button\n      className={cx(\n        {[className]: className?.length},\n        `flex items-center justify-center rounded-sm p-1 ${\n          disableHoverEffects === false ? `${hoverBg} ${hoverBgDark}` : ''\n        } focus:outline-none`,\n        {\n          'bg-slate-400/60': isActive && !isPrimary,\n          'dark:bg-slate-600/60': isActive && !isPrimary,\n          'bg-emerald-500 text-white': isPrimary,\n          'bg-slate-200': isActionButton,\n          'dark:bg-slate-700': isActionButton,\n          'px-2': isActionButton || isTextButton,\n          'cursor-not-allowed opacity-40': disabled,\n          'hover:bg-transparent dark:hover:bg-transparent': disabled,\n        }\n      )}\n      type=\"button\"\n      disabled={disabled}\n      // eslint-disable-next-line react/jsx-props-no-spreading\n      {...props}\n    >\n      {isLoading ? <Icon icon=\"line-md:loading-twotone-loop\" /> : null}\n      {isLoadingDone ? <Icon icon=\"line-md:circle-to-confirm-circle-transition\" /> : null}\n      {!isLoading && !isLoadingDone ? children : null}\n    </button>\n  );\n};\n\nexport default Button;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ButtonGroup/index.tsx",
    "content": "import {ReactElement} from 'react';\nimport cx from 'classnames';\n\ninterface Props {\n  buttons: {\n    content: ReactElement;\n    srContent: string;\n    onClick: () => void;\n    isActive: boolean;\n  }[];\n}\n\nexport const ButtonGroup = ({buttons}: Props) => {\n  return (\n    <span className=\"isolate inline-flex rounded-md shadow-sm\">\n      {buttons.map(({content, srContent, onClick, isActive}, index) => (\n        <button\n          key={srContent}\n          type=\"button\"\n          className={cx(\n            'relative inline-flex items-center px-2 py-2 text-slate-500 ring-1 ring-inset ring-slate-300 hover:bg-slate-300 focus:z-10 dark:text-slate-200 hover:dark:bg-slate-600',\n            {\n              'rounded-l-md': index === 0,\n              'rounded-r-md': index === buttons.length - 1,\n              'bg-slate-200 dark:bg-slate-600': isActive,\n            }\n          )}\n          onClick={onClick}\n        >\n          <span className=\"sr-only\">{srContent}</span>\n          {content}\n        </button>\n      ))}\n    </span>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ConfirmDialog/index.tsx",
    "content": "import {useEffect, useState} from 'react';\nimport Button from '../Button';\nimport Modal from '../Modal';\n\nexport const ConfirmDialog = ({\n  onClose,\n  onConfirm,\n  open,\n  confirmText,\n}: {\n  onClose?: () => void;\n  onConfirm?: () => void;\n  open: boolean;\n  confirmText?: string;\n}) => {\n  const [isOpen, setIsOpen] = useState(open);\n\n  useEffect(() => {\n    setIsOpen(open);\n  }, [open]);\n\n  const handleClose = () => {\n    if (onClose) {\n      onClose();\n    }\n    setIsOpen(false);\n  };\n\n  const handleConfirm = () => {\n    if (onConfirm) {\n      onConfirm();\n    }\n    setIsOpen(false);\n  };\n\n  return (\n    <Modal isOpen={isOpen} onClose={handleClose}>\n      <div\n        data-testid=\"confirm-dialog\"\n        className=\"mb-6 flex h-full w-full flex-col flex-wrap items-center justify-center bg-opacity-95\"\n      >\n        <h2 className=\"m-4 text-center text-2xl font-bold text-white\">\n          <p>{confirmText || 'Are you sure?'}</p>\n        </h2>\n        <div className=\"m-4 flex justify-center\">\n          <Button\n            onClick={handleConfirm}\n            className=\"mb-2 me-2 mr-4 rounded-lg bg-gray-800 px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700\"\n          >\n            Confirm\n          </Button>\n          <Button\n            onClick={handleClose}\n            className=\"mb-2 me-2 rounded-lg border border-gray-300 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:hover:border-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-700 \"\n          >\n            Cancel\n          </Button>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/DeviceDetailsModal.tsx",
    "content": "import {Device} from 'common/deviceList';\nimport {v4 as uuidv4} from 'uuid';\nimport {useEffect, useState} from 'react';\nimport Button from '../Button';\nimport Input from '../Input';\nimport Modal from '../Modal';\nimport Select from '../Select';\n\ninterface Props {\n  device?: Device;\n  onSaveDevice: (device: Device, isNew: boolean) => Promise<void>;\n  onRemoveDevice: (device: Device) => void;\n  existingDevices: Device[];\n  isCustom: boolean;\n  isOpen: boolean;\n  onClose: () => void;\n}\n\nconst DeviceDetailsModal = ({\n  onSaveDevice,\n  onRemoveDevice,\n  existingDevices,\n  device,\n  isOpen,\n  onClose,\n}: Props) => {\n  const [name, setName] = useState<string>(device?.name ?? '');\n  const [width, setWidth] = useState<number>(device?.width ?? 400);\n  const [height, setHeight] = useState<number>(device?.height ?? 600);\n  const [userAgent, setUserAgent] = useState<string>(device?.userAgent ?? '');\n  const [type, setType] = useState<string>(device?.type ?? 'phone');\n  const [dpr, setDpr] = useState<number>(device?.dpr ?? 1);\n  const [isTouchCapable, setIsTouchCapable] = useState<boolean>(device?.isTouchCapable ?? true);\n  const [isMobileCapable, setIsMobileCapable] = useState<boolean>(device?.isMobileCapable ?? true);\n\n  useEffect(() => {\n    if (device) {\n      setName(device.name);\n      setWidth(device.width);\n      setHeight(device.height);\n      setUserAgent(device.userAgent);\n      setType(device.type);\n      setDpr(device.dpr);\n      setIsTouchCapable(device.isTouchCapable);\n      setIsMobileCapable(device.isMobileCapable);\n    } else {\n      setName('');\n      setWidth(400);\n      setHeight(600);\n      setUserAgent('');\n      setType('phone');\n      setDpr(1);\n      setIsTouchCapable(true);\n      setIsMobileCapable(true);\n    }\n  }, [device]);\n\n  useEffect(() => {\n    const desktopUA =\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36';\n    const phoneUA =\n      'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1';\n    if ((type === 'phone' || type === 'tablet') && (userAgent === desktopUA || userAgent === '')) {\n      setUserAgent(phoneUA);\n      setIsMobileCapable(true);\n      setIsTouchCapable(true);\n    } else if (type === 'notebook' && (userAgent === phoneUA || userAgent === '')) {\n      setUserAgent(desktopUA);\n      setIsMobileCapable(false);\n      setIsTouchCapable(false);\n    }\n  }, [type, userAgent]);\n\n  const isNew = !device;\n  const isCustom = device != null ? device.isCustom ?? false : true;\n\n  const handleAddDevice = async (): Promise<void> => {\n    const existingDevice = existingDevices.find((d) => d.name === name);\n    const doesDeviceExist = existingDevice != null && (isNew || existingDevice.id !== device.id);\n\n    if (doesDeviceExist) {\n      // eslint-disable-next-line no-alert\n      return alert('Device With the name already exists, try with a different name');\n    }\n    const capabilities = [];\n    if (isTouchCapable) {\n      capabilities.push('touch');\n    }\n    if (isMobileCapable) {\n      capabilities.push('mobile');\n    }\n    await onSaveDevice(\n      {\n        id: device?.id ?? uuidv4(),\n        name,\n        width,\n        height,\n        userAgent,\n        type,\n        dpr,\n        isTouchCapable,\n        isMobileCapable,\n        capabilities,\n        isCustom,\n      },\n      isNew\n    );\n\n    return onClose();\n  };\n\n  return (\n    <>\n      <Modal\n        isOpen={isOpen}\n        onClose={onClose}\n        title={isNew ? 'Add Custom Device' : 'Device Details'}\n      >\n        <div className=\"flex flex-col gap-4\">\n          <div className=\"flex w-[420px] flex-col gap-2\">\n            <Input\n              label=\"Device Name\"\n              type=\"text\"\n              placeholder=\"My Mobile Device\"\n              value={name}\n              onChange={(e) => setName(e.target.value)}\n              disabled={!isCustom}\n            />\n            <Input\n              label=\"Device Width\"\n              type=\"number\"\n              placeholder=\"1200\"\n              min=\"100\"\n              value={width}\n              onChange={(e) => setWidth(parseInt(e.target.value, 10))}\n              disabled={!isCustom}\n            />\n            <Input\n              label=\"Device Height\"\n              type=\"number\"\n              placeholder=\"800\"\n              min=\"100\"\n              value={height}\n              onChange={(e) => setHeight(parseInt(e.target.value, 10))}\n              disabled={!isCustom}\n            />\n            <Input\n              label=\"Device DPR\"\n              type=\"number\"\n              min=\"1\"\n              step=\"0.1\"\n              value={dpr}\n              onChange={(e) => setDpr(parseFloat(e.target.value))}\n              disabled={!isCustom}\n            />\n            <Select\n              label=\"Device type\"\n              onChange={(e) => setType(e.target.value)}\n              value={type}\n              disabled={!isCustom}\n            >\n              <option value=\"notebook\">Desktop</option>\n              <option value=\"phone\">Phone</option>\n              <option value=\"tablet\">Tablet</option>\n            </Select>\n            <Input\n              label=\"User Agent String\"\n              type=\"text\"\n              placeholder=\"User agent string for this device's network requests\"\n              value={userAgent}\n              onChange={(e) => setUserAgent(e.target.value)}\n              disabled={!isCustom}\n            />\n            <Input\n              label=\"Touch Capable\"\n              type=\"checkbox\"\n              checked={isTouchCapable}\n              onChange={(e) => setIsTouchCapable(e.target.checked)}\n              disabled={!isCustom}\n            />\n            <Input\n              label=\"Mobile Capable (Rotatable)\"\n              type=\"checkbox\"\n              checked={isMobileCapable}\n              onChange={(e) => setIsMobileCapable(e.target.checked)}\n              disabled={!isCustom}\n            />\n          </div>\n          {isCustom ? (\n            <div className=\"flex flex-row justify-between\">\n              {device != null ? (\n                <Button\n                  className=\"bg-red-500 px-2 text-white hover:bg-red-700 dark:hover:bg-red-600\"\n                  onClick={async () => {\n                    await onRemoveDevice(device);\n                    onClose();\n                  }}\n                >\n                  Delete\n                </Button>\n              ) : (\n                <div />\n              )}\n\n              <div className=\"flex flex-row justify-end gap-2\">\n                <Button className=\"px-2\" onClick={onClose}>\n                  Cancel\n                </Button>\n                <Button className=\"px-2\" onClick={handleAddDevice} isActive>\n                  {isNew ? 'Add' : 'Save'}\n                </Button>\n              </div>\n            </div>\n          ) : (\n            <div className=\"flex flex-row justify-end gap-2\">\n              <Button className=\"px-2\" onClick={onClose}>\n                Close\n              </Button>\n            </div>\n          )}\n        </div>\n      </Modal>\n    </>\n  );\n};\n\nexport default DeviceDetailsModal;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/DeviceLabel.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport cx from 'classnames';\nimport {useDrag, useDrop} from 'react-dnd';\nimport {useDispatch, useSelector} from 'react-redux';\n\nimport {Device, getDevicesMap} from 'common/deviceList';\nimport {selectActiveSuite, setSuiteDevices} from 'renderer/store/features/device-manager';\nimport Button from '../Button';\n\nexport const DND_TYPE = 'Device';\n\ninterface Props {\n  device: Device;\n  enableDnd?: boolean;\n  moveDevice?: (device: Device, atIndex: number) => void;\n  onShowDeviceDetails: (device: Device) => void;\n  hideSelectionControls?: boolean;\n  disableSelectionControls?: boolean;\n}\n\nconst DeviceLabel = ({\n  device,\n  moveDevice = () => {},\n  enableDnd = false,\n  onShowDeviceDetails,\n  hideSelectionControls = false,\n  disableSelectionControls = false,\n}: Props) => {\n  const dispatch = useDispatch();\n  const activeSuite = useSelector(selectActiveSuite);\n  const devices = activeSuite.devices.map((id) => getDevicesMap()[id]);\n  const originalIndex = devices.indexOf(device);\n\n  const [{isDragging}, drag] = useDrag(\n    () => ({\n      type: DND_TYPE,\n      item: device,\n      collect: (monitor) => ({\n        isDragging: monitor.isDragging(),\n      }),\n      end: (draggedDevice, monitor) => {\n        const didDrop = monitor.didDrop();\n        if (!didDrop) {\n          moveDevice(draggedDevice, originalIndex);\n        }\n      },\n    }),\n    [device.name, originalIndex, moveDevice]\n  );\n\n  const [, drop] = useDrop(\n    () => ({\n      accept: DND_TYPE,\n      hover(draggedDevice: Device) {\n        if (draggedDevice.name !== device.name) {\n          moveDevice(draggedDevice, devices.indexOf(device));\n        }\n      },\n    }),\n    [moveDevice]\n  );\n\n  const opacity = isDragging ? 0 : 1;\n  const isChecked = devices.find((d) => d.name === device.name) != null;\n\n  return (\n    <div\n      className=\"flex w-fit items-center gap-2 rounded bg-slate-300 px-2 py-1 dark:bg-slate-600\"\n      ref={enableDnd ? (node) => drag(drop(node)) : null}\n      style={{opacity}}\n    >\n      {enableDnd ? <Icon icon=\"ic:baseline-drag-indicator\" /> : null}\n      <input\n        className={cx({\n          'pointer-events-none opacity-0': hideSelectionControls,\n        })}\n        type=\"checkbox\"\n        disabled={disableSelectionControls}\n        title={\n          // eslint-disable-next-line no-nested-ternary\n          disableSelectionControls\n            ? 'Cannot make the suite empty add another device to remove this one'\n            : isChecked\n            ? 'Click to remove the device'\n            : 'Click to add the device'\n        }\n        checked={isChecked}\n        onChange={(e) => {\n          if (e.target.checked) {\n            dispatch(\n              setSuiteDevices({\n                suite: activeSuite.id,\n                devices: [...activeSuite.devices, device.id],\n              })\n            );\n          } else {\n            dispatch(\n              setSuiteDevices({\n                suite: activeSuite.id,\n                devices: activeSuite.devices.filter((d) => d !== device.id),\n              })\n            );\n          }\n        }}\n      />\n\n      <div className=\"ml-1\">\n        {device.name}\n        <span className=\"ml-1 text-xs\">\n          {device.width}x{device.height}\n        </span>\n      </div>\n      <Button onClick={() => onShowDeviceDetails(device)}>\n        <Icon icon={device.isCustom ? 'ic:baseline-edit' : 'ic:baseline-info'} />\n      </Button>\n    </div>\n  );\n};\n\nexport default DeviceLabel;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/CreateSuiteButton/CreateSuiteModal.tsx",
    "content": "import {useEffect, useState} from 'react';\nimport {useDispatch} from 'react-redux';\nimport {v4 as uuidv4} from 'uuid';\n\nimport {addSuite} from 'renderer/store/features/device-manager';\n\nimport Button from '../../../Button';\nimport Input from '../../../Input';\nimport Modal from '../../../Modal';\n\ninterface Props {\n  isOpen: boolean;\n  onClose: () => void;\n}\n\nexport const CreateSuiteModal = ({isOpen, onClose}: Props) => {\n  const [name, setName] = useState<string>('');\n  const dispatch = useDispatch();\n\n  const handleAddSuite = async (): Promise<void> => {\n    if (name === '') {\n      // eslint-disable-next-line no-alert\n      return alert('Suite name cannot be empty. Please enter a name for the suite.');\n    }\n    dispatch(addSuite({id: uuidv4(), name, devices: ['10008']}));\n    return onClose();\n  };\n\n  return (\n    <>\n      <Modal isOpen={isOpen} onClose={onClose} title=\"Add Suite\">\n        <div className=\"flex flex-col gap-4\">\n          <div className=\"flex w-[420px] flex-col gap-2\">\n            <Input\n              label=\"Suite Name\"\n              type=\"text\"\n              placeholder=\"My Custom Suite\"\n              value={name}\n              onChange={(e) => setName(e.target.value)}\n            />\n          </div>\n          <div className=\"flex flex-row justify-between\">\n            <div className=\"flex flex-row justify-end gap-2\">\n              <Button className=\"px-2\" onClick={onClose}>\n                Cancel\n              </Button>\n              <Button className=\"px-2\" onClick={handleAddSuite} isActive>\n                Add\n              </Button>\n            </div>\n          </div>\n        </div>\n      </Modal>\n    </>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/CreateSuiteButton/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useState} from 'react';\nimport Button from 'renderer/components/Button';\nimport {CreateSuiteModal} from './CreateSuiteModal';\n\nexport const CreateSuiteButton = () => {\n  const [open, setOpen] = useState<boolean>(false);\n  return (\n    <div className=\"relative flex aspect-square h-full min-h-52 flex-shrink-0 flex-col items-center justify-center gap-4 rounded bg-white dark:bg-slate-900\">\n      <span className=\"absolute top-12\">Add Suite</span>\n      <Button className=\"aspect-square w-16 rounded-full\" onClick={() => setOpen(true)}>\n        <Icon icon=\"mdi:plus\" fontSize={30} />\n      </Button>\n      <CreateSuiteModal isOpen={open} onClose={() => setOpen(false)} />\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/ManageSuitesTool.test.tsx",
    "content": "import {render, screen, fireEvent, waitFor} from '@testing-library/react';\nimport {Device} from 'common/deviceList';\nimport {Provider, useDispatch} from 'react-redux';\nimport {configureStore} from '@reduxjs/toolkit';\nimport {ReactNode} from 'react';\nimport {type Mock} from 'vitest';\nimport {transformFile} from './utils';\nimport {ManageSuitesTool} from './ManageSuitesTool';\n\n// Import the mocked module — vi.mock is hoisted above imports, so this gets the mock\nimport deviceManagerReducer, {\n  addSuites,\n  deleteAllSuites,\n} from 'renderer/store/features/device-manager';\n\nvi.mock('renderer/store/features/device-manager', () => ({\n  addSuites: vi.fn(() => ({type: 'addSuites'})),\n  deleteAllSuites: vi.fn(() => ({type: 'deleteAllSuites'})),\n  default: vi.fn((state = {}) => state), // Mock the reducer as a function\n}));\n\nvi.mock('./utils', () => ({\n  transformFile: vi.fn(),\n}));\n\nvi.mock('renderer/components/FileUploader', () => ({\n  FileUploader: ({handleFileUpload}: {handleFileUpload: (file: File) => void}) => (\n    <button\n      type=\"button\"\n      data-testid=\"mock-file-uploader\"\n      onClick={() => handleFileUpload(new File(['{}'], 'test.json', {type: 'application/json'}))}\n    >\n      Mock File Uploader\n    </button>\n  ),\n}));\n\nvi.mock('./helpers', () => ({\n  onFileDownload: vi.fn(),\n  setCustomDevices: vi.fn(),\n}));\n\nvi.mock('react-redux', async (importOriginal) => {\n  const actual = await importOriginal<typeof import('react-redux')>();\n  return {\n    ...actual,\n    useDispatch: vi.fn(),\n  };\n});\n\nconst renderWithRedux = (\n  component: string | number | boolean | Iterable<ReactNode> | JSX.Element | null | undefined\n) => {\n  const store = configureStore({\n    reducer: {\n      deviceManager: deviceManagerReducer as any,\n    },\n  });\n\n  return {\n    ...render(<Provider store={store}>{component}</Provider>),\n    store,\n  };\n};\n\ndescribe('ManageSuitesTool', () => {\n  let setCustomDevicesStateMock: Mock;\n  const dispatchMock = vi.fn();\n\n  beforeEach(() => {\n    (useDispatch as Mock).mockReturnValue(dispatchMock);\n\n    setCustomDevicesStateMock = vi.fn();\n\n    renderWithRedux(<ManageSuitesTool setCustomDevicesState={setCustomDevicesStateMock} />);\n  });\n\n  it('renders the component correctly', () => {\n    expect(screen.getByTestId('download-btn')).toBeInTheDocument();\n    expect(screen.getByTestId('upload-btn')).toBeInTheDocument();\n    expect(screen.getByTestId('reset-btn')).toBeInTheDocument();\n  });\n\n  it('opens the modal when download button is clicked', () => {\n    fireEvent.click(screen.getByTestId('download-btn'));\n    expect(screen.getByText('Import your devices')).toBeInTheDocument();\n  });\n\n  it('opens the reset confirmation dialog when reset button is clicked', () => {\n    fireEvent.click(screen.getByTestId('reset-btn'));\n    expect(screen.getByText('Do you want to reset all settings?')).toBeInTheDocument();\n  });\n\n  it('closes the reset confirmation dialog when the close button is clicked', () => {\n    fireEvent.click(screen.getByTestId('reset-btn'));\n    fireEvent.click(screen.getByText('Cancel'));\n    expect(screen.queryByText('Do you want to reset all settings?')).not.toBeInTheDocument();\n  });\n\n  it('dispatches deleteAllSuites and clears custom devices on reset confirmation', async () => {\n    fireEvent.click(screen.getByTestId('reset-btn'));\n    fireEvent.click(screen.getByText('Confirm'));\n\n    await waitFor(() => {\n      expect(deleteAllSuites).toHaveBeenCalled();\n      expect(setCustomDevicesStateMock).toHaveBeenCalledWith([]);\n    });\n  });\n\n  it('handles successful file upload and processes custom devices and suites', async () => {\n    const mockSuites = [\n      {id: '1', name: 'first suite', devices: []},\n      {id: '2', name: 'second suite', devices: []},\n    ];\n\n    (transformFile as Mock).mockResolvedValue({\n      customDevices: ['device1', 'device2'],\n      suites: mockSuites,\n    });\n\n    fireEvent.click(screen.getByTestId('download-btn'));\n    fireEvent.click(screen.getByTestId('mock-file-uploader'));\n\n    await waitFor(() => {\n      expect(transformFile).toHaveBeenCalledWith(expect.any(File));\n      expect(dispatchMock).toHaveBeenCalledWith(addSuites(mockSuites));\n    });\n  });\n\n  it('handles error in file upload', async () => {\n    (transformFile as Mock).mockRejectedValue(new Error('File upload failed'));\n\n    fireEvent.click(screen.getByTestId('download-btn'));\n\n    fireEvent.click(screen.getByTestId('mock-file-uploader'));\n\n    await waitFor(() => {\n      expect(transformFile).toHaveBeenCalledWith(expect.any(File));\n      expect(screen.getByText('There has been an error, please try again.')).toBeInTheDocument();\n    });\n\n    fireEvent.click(screen.getByText('Close'));\n\n    expect(\n      screen.queryByText('There has been an error, please try again.')\n    ).not.toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/ManageSuitesTool.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport Button from 'renderer/components/Button';\nimport {useState} from 'react';\nimport {FileUploader} from 'renderer/components/FileUploader';\nimport Modal from 'renderer/components/Modal';\nimport {addSuites, deleteAllSuites} from 'renderer/store/features/device-manager';\nimport {useDispatch} from 'react-redux';\nimport {ConfirmDialog} from 'renderer/components/ConfirmDialog';\nimport {transformFile} from './utils';\nimport {onFileDownload, setCustomDevices} from './helpers';\nimport {ManageSuitesToolError} from './ManageSuitesToolError';\n\nexport const ManageSuitesTool = ({setCustomDevicesState}: any) => {\n  const [open, setOpen] = useState<boolean>(false);\n  const [resetConfirmation, setResetConfirmation] = useState<boolean>(false);\n\n  const [error, setError] = useState<boolean>(false);\n  const dispatch = useDispatch();\n\n  const onFileUpload = (fileUploaded: File) =>\n    transformFile(fileUploaded)\n      .then((fileTransformed) => {\n        const {customDevices, suites} = fileTransformed;\n\n        if (customDevices) {\n          const newCustomDevices = setCustomDevices(customDevices);\n          setCustomDevicesState(newCustomDevices);\n        }\n\n        if (suites) {\n          dispatch(addSuites(suites));\n        }\n\n        setOpen(false);\n\n        return null;\n      })\n      .catch(() => setError(true));\n\n  const onErrorClose = () => {\n    setError(false);\n    setOpen(false);\n  };\n\n  const clearCustomDevices = () => {\n    window.electron.store.set('deviceManager.customDevices', []);\n    setCustomDevicesState([]);\n  };\n\n  const onReset = () => {\n    dispatch(deleteAllSuites());\n    clearCustomDevices();\n    setResetConfirmation(false);\n  };\n\n  return (\n    <>\n      <div className=\"flex flex-row content-end justify-end\">\n        <Button\n          data-testid=\"download-btn\"\n          className=\"aspect-square w-12 rounded-full hover:!bg-slate-500\"\n          onClick={() => setOpen(true)}\n        >\n          <Icon icon=\"uil:export\" fontSize={18} />\n        </Button>\n        <Button\n          data-testid=\"upload-btn\"\n          className=\"aspect-square w-12 rounded-full hover:!bg-slate-500\"\n          onClick={onFileDownload}\n        >\n          <Icon icon=\"uil:import\" fontSize={18} />\n        </Button>\n        <Button\n          data-testid=\"reset-btn\"\n          className=\"aspect-square w-12 rounded-full hover:!bg-slate-500\"\n          onClick={() => setResetConfirmation(true)}\n        >\n          <Icon icon=\"uil:redo\" fontSize={18} />\n        </Button>\n      </div>\n      <ConfirmDialog\n        onConfirm={onReset}\n        onClose={() => setResetConfirmation(false)}\n        open={resetConfirmation}\n        confirmText=\"Do you want to reset all settings?\"\n      />\n      <Modal isOpen={open} onClose={() => setOpen(false)} title=\"Import your devices\">\n        <>\n          <FileUploader\n            acceptedFileTypes=\"application/json\"\n            multiple={false}\n            handleFileUpload={onFileUpload}\n          />\n          <div className=\"text-align align-items-center flex flex-row flex-nowrap text-orange-500\">\n            <Icon icon=\"mdi:alert\" />\n            <p className=\"pl-2\">\n              Duplicated imports will replace existing suites or custom devices.\n            </p>\n          </div>\n          {error && <ManageSuitesToolError onClose={onErrorClose} />}\n        </>\n      </Modal>\n    </>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/ManageSuitesToolError.test.tsx",
    "content": "import {render, screen, fireEvent} from '@testing-library/react';\nimport {ManageSuitesToolError} from './ManageSuitesToolError';\n\ndescribe('ManageSuitesToolError', () => {\n  it('renders the error message and close button', () => {\n    const onClose = vi.fn();\n    render(<ManageSuitesToolError onClose={onClose} />);\n\n    expect(screen.getByText('There has been an error, please try again.')).toBeInTheDocument();\n    expect(screen.getByText('Close')).toBeInTheDocument();\n  });\n\n  it('calls onClose when the close button is clicked', () => {\n    const onClose = vi.fn();\n    render(<ManageSuitesToolError onClose={onClose} />);\n\n    fireEvent.click(screen.getByText('Close'));\n    expect(onClose).toHaveBeenCalledTimes(1);\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/ManageSuitesToolError.tsx",
    "content": "import Button from 'renderer/components/Button';\n\nexport const ManageSuitesToolError = ({onClose}: {onClose: () => void}) => {\n  return (\n    <div\n      data-testid=\"manage-suites-error\"\n      className=\"absolute left-0 top-0 flex h-full w-full flex-col flex-wrap items-center justify-center bg-slate-600 bg-opacity-95\"\n    >\n      <div className=\"text-center text-sm text-white\">\n        <p>There has been an error, please try again.</p>\n      </div>\n      <Button onClick={onClose} className=\"p-2\">\n        Close\n      </Button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/helpers.test.tsx",
    "content": "import {Device} from 'common/deviceList';\nimport {fireEvent, render} from '@testing-library/react';\nimport Button from 'renderer/components/Button';\nimport {type Mock} from 'vitest';\nimport * as Helpers from './helpers';\n\nconst mockDefaultDevices = vi.hoisted(() => [\n  {\n    name: 'Device1',\n    width: 800,\n    height: 600,\n    id: '0',\n    userAgent: '',\n    type: '',\n    dpr: 0,\n    isTouchCapable: false,\n    isMobileCapable: false,\n    capabilities: [],\n  },\n]);\n\nvi.mock('common/deviceList', async (importOriginal) => {\n  const actual = await importOriginal<typeof import('common/deviceList')>();\n  return {\n    ...actual,\n    defaultDevices: mockDefaultDevices,\n  };\n});\n\ndescribe('onFileDownload', () => {\n  beforeAll(() => {\n    global.URL.createObjectURL = vi.fn(() => 'mockedURL');\n    global.URL.revokeObjectURL = vi.fn(); // Mocking revokeObjectURL too\n  });\n\n  afterEach(() => vi.clearAllMocks());\n\n  it('should get customDevices and suites from the store and download the file', () => {\n    const mockCustomDevices = [{name: 'Device1', width: 800, height: 600}];\n    const mockSuites = [{name: 'Suite1'}];\n\n    (window.electron.store.get as Mock).mockImplementation((key: string) => {\n      if (key === 'deviceManager.customDevices') {\n        return mockCustomDevices;\n      }\n      if (key === 'deviceManager.previewSuites') {\n        return mockSuites;\n      }\n      return null;\n    });\n\n    Helpers.onFileDownload();\n\n    expect(window.electron.store.get).toHaveBeenCalledWith('deviceManager.customDevices');\n    expect(window.electron.store.get).toHaveBeenCalledWith('deviceManager.previewSuites');\n    // Verify downloadFile was called by checking its side effects\n    // (vi.spyOn can't intercept internal ESM calls)\n    expect(global.URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob));\n  });\n});\n\ndescribe('downloadFile', () => {\n  it('should create and download a JSON file', () => {\n    const mockFileData = {key: 'value'};\n    const mockedUrl = 'http://localhost/#';\n\n    const createObjectURLSpy = vi.spyOn(URL, 'createObjectURL').mockReturnValue(mockedUrl);\n    const revokeObjectURLSpy = vi.spyOn(URL, 'revokeObjectURL');\n    const spyOnDownloadFileFn = vi.spyOn(Helpers, 'downloadFile');\n\n    const {getByTestId} = render(\n      <div>\n        <Button onClick={() => Helpers.downloadFile(mockFileData)} data-testid=\"mockDownloadBtn\">\n          Download\n        </Button>\n      </div>\n    );\n\n    const link = getByTestId('mockDownloadBtn');\n    fireEvent.click(link);\n\n    expect(spyOnDownloadFileFn).toHaveBeenCalled();\n    expect(createObjectURLSpy).toHaveBeenCalledWith(expect.any(Blob));\n    expect(revokeObjectURLSpy).toHaveBeenCalledWith(mockedUrl);\n  });\n});\n\ndescribe('setCustomDevices', () => {\n  it('should filter out default devices and store custom devices', () => {\n    const mockCustomDevices: Device[] = [\n      {\n        name: 'Device1',\n        width: 800,\n        height: 600,\n        id: '1',\n        userAgent: '',\n        type: '',\n        dpr: 0,\n        isTouchCapable: false,\n        isMobileCapable: false,\n        capabilities: [],\n      },\n      {\n        name: 'Device2',\n        width: 1024,\n        height: 768,\n        id: '2',\n        userAgent: '',\n        type: '',\n        dpr: 0,\n        isTouchCapable: false,\n        isMobileCapable: false,\n        capabilities: [],\n      },\n    ];\n\n    const filteredDevices = Helpers.setCustomDevices(mockCustomDevices);\n\n    expect(window.electron.store.set).not.toHaveBeenCalledWith('deviceManager.customDevices', [\n      mockCustomDevices[1],\n    ]);\n\n    expect(filteredDevices).toEqual(mockCustomDevices);\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/helpers.ts",
    "content": "import {defaultDevices, Device} from 'common/deviceList';\n\nexport const downloadFile = <T extends Record<string, unknown>>(fileData: T) => {\n  const jsonString = JSON.stringify(fileData, null, 2);\n  const blob = new Blob([jsonString], {type: 'application/json'});\n  const url = URL.createObjectURL(blob);\n  const link = document.createElement('a');\n\n  link.href = url;\n  link.download = `responsively_backup_${new Date().toLocaleDateString()}.json`;\n\n  document.body.appendChild(link);\n  link.click();\n  document.body.removeChild(link);\n  URL.revokeObjectURL(url);\n};\n\nexport const setCustomDevices = (customDevices: Device[]) => {\n  const importedCustomDevices = customDevices.filter(\n    (item: Device) => !defaultDevices.includes(item)\n  );\n\n  window.electron.store.set('deviceManager.customDevices', importedCustomDevices);\n\n  return importedCustomDevices;\n};\n\nexport const onFileDownload = () => {\n  const fileData = {\n    customDevices: window.electron.store.get('deviceManager.customDevices'),\n    suites: window.electron.store.get('deviceManager.previewSuites'),\n  };\n  downloadFile(fileData);\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/test.json",
    "content": "{\n    \"customDevices\": [\n        {\n            \"id\": \"123\",\n            \"name\": \"a new test\",\n            \"width\": 400,\n            \"height\": 600,\n            \"userAgent\": \"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1\",\n            \"typse\": \"phone\",\n            \"dpxxi\": 1,\n            \"isTouchCapable\": true,\n            \"isMobileCapable\": true,\n            \"capabilities\": [\n                \"touch\",\n                \"mobile\"\n            ],\n            \"isCustom\": true\n        }\n    ],\n    \"suites\": [\n            {\n                \"id\": \"default\",\n                \"name\": \"Default\",\n                \"devices\": [\n                    \"10008\"\n                ]\n            },\n            {\n                \"id\": \"a4c142fc-debd-4eaa-beba-aef60093151c\",\n                \"name\": \"my custom suite\",\n                \"devices\": [\n                    \"10008\",\n                    \"30014\"\n                ]\n            }\n        ]\n}\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/utils.test.ts",
    "content": "import {transformFile} from './utils';\n\ndescribe('transformFile', () => {\n  it('should parse JSON content of the file', async () => {\n    const jsonContent = {key: 'value'};\n    const file = new Blob([JSON.stringify(jsonContent)], {\n      type: 'application/json',\n    }) as File;\n    Object.defineProperty(file, 'name', {value: 'test.json'});\n\n    const result = await transformFile(file);\n    expect(result).toEqual(jsonContent);\n  });\n\n  it('should throw an error for invalid JSON', async () => {\n    const invalidJsonContent = \"{ key: 'value' }\"; // Invalid JSON\n    const file = new Blob([invalidJsonContent], {\n      type: 'application/json',\n    }) as File;\n    Object.defineProperty(file, 'name', {value: 'test.json'});\n\n    await expect(transformFile(file)).rejects.toThrow();\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/ManageSuitesTool/utils.ts",
    "content": "export const transformFile = (file: File): Promise<{[key: string]: any}> => {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n\n    reader.onload = () => {\n      try {\n        const jsonContent = JSON.parse(reader.result as string);\n        resolve(jsonContent);\n      } catch (error) {\n        reject(error);\n      }\n    };\n\n    reader.onerror = () => {\n      reject(reader.error);\n    };\n\n    reader.readAsText(file);\n  });\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/Suite.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport cx from 'classnames';\nimport {Device, getDevicesMap} from 'common/deviceList';\nimport {useDrop} from 'react-dnd';\nimport {useDispatch} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport {\n  PreviewSuite,\n  deleteSuite,\n  setActiveSuite,\n  setSuiteDevices,\n} from 'renderer/store/features/device-manager';\nimport DeviceLabel, {DND_TYPE} from '../DeviceLabel';\n\ninterface Props {\n  suite: PreviewSuite;\n  isActive: boolean;\n}\n\nexport const Suite = ({suite: {id, name, devices}, isActive}: Props) => {\n  const [, drop] = useDrop(() => ({accept: DND_TYPE}));\n  const dispatch = useDispatch();\n\n  const moveDevice = (device: Device, atIndex: number) => {\n    const newDevices = devices.filter((d) => d !== device.id);\n    newDevices.splice(atIndex, 0, device.id);\n    dispatch(setSuiteDevices({suite: id, devices: newDevices}));\n  };\n  return (\n    <div\n      className={cx('relative min-w-56 flex-shrink-0  rounded bg-white dark:bg-slate-900', {\n        'border-2 border-slate-500 ': isActive,\n      })}\n    >\n      {!isActive ? (\n        <div className=\"absolute flex h-full w-full items-center justify-center bg-gray-100 !bg-opacity-70 dark:bg-slate-800\">\n          <Button\n            className=\"aspect-square w-16 rounded-full hover:!bg-slate-500\"\n            onClick={() => dispatch(setActiveSuite(id))}\n          >\n            <Icon icon=\"mdi:eye-settings-outline\" fontSize={20} />\n          </Button>\n        </div>\n      ) : null}\n      <div className=\"flex flex-col gap-8 p-4 pb-8\">\n        <div className=\"flex justify-between\">\n          <p className=\"text-lg\">{name}</p>\n          {id !== 'default' ? (\n            <Button onClick={() => dispatch(deleteSuite(id))}>\n              <Icon icon=\"ic:twotone-delete\" />\n            </Button>\n          ) : null}\n        </div>\n        <div className=\"flex flex-col gap-2\" ref={drop}>\n          {devices.map((deviceId) => (\n            <DeviceLabel\n              device={getDevicesMap()[deviceId]}\n              onShowDeviceDetails={() => {}}\n              hideSelectionControls={!isActive}\n              disableSelectionControls={devices.length === 1}\n              enableDnd={isActive}\n              key={deviceId}\n              moveDevice={moveDevice}\n            />\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/PreviewSuites/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useSelector} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport {selectActiveSuite, selectSuites} from 'renderer/store/features/device-manager';\nimport {useState} from 'react';\nimport {FileUploader} from 'renderer/components/FileUploader';\nimport Modal from 'renderer/components/Modal';\nimport {Suite} from './Suite';\nimport {CreateSuiteButton} from './CreateSuiteButton';\nimport {ManageSuitesTool} from './ManageSuitesTool/ManageSuitesTool';\n\nexport const PreviewSuites = () => {\n  const suites = useSelector(selectSuites);\n  const activeSuite = useSelector(selectActiveSuite);\n\n  return (\n    <div className=\"flex flex-col\">\n      <div className=\"flex w-full items-center gap-4 overflow-x-auto\">\n        <div className=\"flex flex-shrink-0 gap-4\">\n          {suites.map((suite) => (\n            <Suite suite={suite} isActive={suite.id === activeSuite.id} key={suite.name} />\n          ))}\n        </div>\n        <CreateSuiteButton />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DeviceManager/index.tsx",
    "content": "import {useEffect, useState} from 'react';\nimport {Icon} from '@iconify/react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport {DndProvider} from 'react-dnd';\nimport {HTML5Backend} from 'react-dnd-html5-backend';\nimport {\n  selectActiveSuite,\n  setDevices,\n  setSuiteDevices,\n} from 'renderer/store/features/device-manager';\nimport {APP_VIEWS, setAppView} from 'renderer/store/features/ui';\nimport {defaultDevices, Device, getDevicesMap} from 'common/deviceList';\n\nimport Button from '../Button';\nimport DeviceLabel from './DeviceLabel';\nimport DeviceDetailsModal from './DeviceDetailsModal';\nimport {PreviewSuites} from './PreviewSuites';\nimport {ManageSuitesTool} from './PreviewSuites/ManageSuitesTool/ManageSuitesTool';\nimport {Divider} from '../Divider';\nimport {AccordionItem, Accordion} from '../Accordion';\n\nconst filterDevices = (devices: Device[], filter: string) => {\n  const sanitizedFilter = filter.trim().toLowerCase();\n\n  return devices.filter((device: Device) =>\n    `${device.name.toLowerCase()}${device.width}x${device.height}`.includes(sanitizedFilter)\n  );\n};\n\nconst DeviceManager = () => {\n  const [isDetailsModalOpen, setIsDetailsModalOpen] = useState<boolean>(false);\n  const [selectedDevice, setSelectedDevice] = useState<Device | undefined>(undefined);\n  const dispatch = useDispatch();\n  const activeSuite = useSelector(selectActiveSuite);\n  const devices = activeSuite.devices?.map((id) => getDevicesMap()[id]);\n  const [searchText, setSearchText] = useState<string>('');\n  const [filteredDevices, setFilteredDevices] = useState<Device[]>(defaultDevices);\n  const [customDevices, setCustomDevices] = useState<Device[]>(\n    window.electron.store.get('deviceManager.customDevices')\n  );\n  const [filteredCustomDevices, setFilteredCustomDevices] = useState<Device[]>(customDevices);\n\n  useEffect(() => {\n    setFilteredDevices(filterDevices(defaultDevices, searchText));\n    setFilteredCustomDevices(filterDevices(customDevices, searchText));\n  }, [customDevices, searchText]);\n\n  const saveCustomDevices = (newCustomDevices: Device[]) => {\n    setCustomDevices(newCustomDevices);\n    window.electron.store.set('deviceManager.customDevices', newCustomDevices);\n    setFilteredCustomDevices(filterDevices(newCustomDevices, searchText));\n  };\n\n  const onSaveDevice = async (device: Device, isNew: boolean) => {\n    const newCustomDevices = isNew\n      ? [...customDevices, device]\n      : customDevices.map((d) => (d.id === device.id ? device : d));\n    saveCustomDevices(newCustomDevices);\n    if (isNew) {\n      dispatch(\n        setSuiteDevices({\n          suite: activeSuite.id,\n          devices: [...activeSuite.devices, device.id],\n        })\n      );\n    }\n  };\n\n  const onRemoveDevice = (device: Device) => {\n    const newCustomDevices = customDevices.filter((d) => d.id !== device.id);\n    saveCustomDevices(newCustomDevices);\n    dispatch(\n      setSuiteDevices({\n        suite: activeSuite.id,\n        devices: activeSuite.devices.filter((d) => d !== device.id),\n      })\n    );\n  };\n\n  const onShowDeviceDetails = (device: Device) => {\n    setSelectedDevice(device);\n    setIsDetailsModalOpen(true);\n  };\n\n  return (\n    <div className=\"mx-auto flex w-4/5 flex-col gap-4 rounded-lg p-8\">\n      <div className=\"flex w-full justify-end text-3xl\">\n        <Button onClick={() => dispatch(setAppView(APP_VIEWS.BROWSER))}>\n          <Icon icon=\"ic:round-close\" fontSize={18} />\n        </Button>\n      </div>\n      <div>\n        <div className=\"flex items-center justify-end justify-between \">\n          <h2 className=\"text-2xl font-bold\">Device Manager</h2>\n          <ManageSuitesTool setCustomDevicesState={setCustomDevices} />\n        </div>\n        <Divider />\n        <Accordion>\n          <AccordionItem title=\"MANAGE SUITES\">\n            <PreviewSuites />\n          </AccordionItem>\n        </Accordion>\n        <Divider />\n        <div className=\"my-4 flex items-start justify-end justify-between\">\n          <div className=\"flex w-fit flex-col items-start px-1\">\n            <h2 className=\"text-2xl font-bold\">Manage Devices</h2>\n          </div>\n          <div className=\"flex w-fit items-center bg-white px-1 dark:bg-slate-900\">\n            <Icon icon=\"ic:outline-search\" height={24} />\n            <input\n              className=\"w-60 rounded bg-inherit px-2 py-1 focus:outline-none\"\n              placeholder=\"Search ...\"\n              value={searchText}\n              onChange={(e) => setSearchText(e.target.value)}\n            />\n          </div>\n        </div>\n        <Accordion>\n          <>\n            <AccordionItem title=\"DEFAULT DEVICES\">\n              <div className=\"ml-4 flex flex-row flex-wrap gap-4\">\n                {filteredDevices.map((device) => (\n                  <DeviceLabel\n                    device={device}\n                    key={device.id}\n                    onShowDeviceDetails={onShowDeviceDetails}\n                    disableSelectionControls={\n                      devices.find((d) => d.id === device.id) != null && devices.length === 1\n                    }\n                  />\n                ))}\n                {filteredDevices.length === 0 ? (\n                  <div className=\"m-10 flex w-full items-center justify-center\">\n                    Sorry, no matching devices found.\n                    <Icon icon=\"mdi:emoticon-sad-outline\" className=\"ml-1\" />\n                  </div>\n                ) : null}\n              </div>\n            </AccordionItem>\n            <AccordionItem title=\"CUSTOM DEVICES\">\n              <div className=\"ml-4 flex flex-row flex-wrap gap-4\">\n                {filteredCustomDevices.map((device) => (\n                  <DeviceLabel\n                    device={device}\n                    key={device.id}\n                    onShowDeviceDetails={onShowDeviceDetails}\n                  />\n                ))}\n                {customDevices.length === 0 ? (\n                  <div className=\"m-10 flex w-full flex-col items-center justify-center\">\n                    <span>No custom devices added yet!</span>\n                    <Button\n                      className=\"m-4 rounded-l\"\n                      onClick={() => setIsDetailsModalOpen(true)}\n                      isActive\n                    >\n                      <Icon icon=\"ic:baseline-add\" />\n                      <span className=\"pl-2 pr-2\">Add Custom Device</span>\n                    </Button>\n                  </div>\n                ) : null}\n                {customDevices.length > 0 && filteredCustomDevices.length === 0 ? (\n                  <div className=\"m-10 flex w-full items-center justify-center\">\n                    Sorry, no matching devices found.\n                    <Icon icon=\"mdi:emoticon-sad-outline\" className=\"ml-1\" />\n                  </div>\n                ) : null}\n                <Button\n                  className={\n                    customDevices.length < 1 || filteredCustomDevices.length < 1\n                      ? 'hidden'\n                      : 'rounded-l'\n                  }\n                  onClick={() => setIsDetailsModalOpen(true)}\n                  isActive\n                >\n                  <Icon icon=\"ic:baseline-add\" />\n                  <span className=\"pl-2 pr-2\">Add Custom Device</span>\n                </Button>\n              </div>\n            </AccordionItem>\n          </>\n        </Accordion>\n      </div>\n      <DeviceDetailsModal\n        onSaveDevice={onSaveDevice}\n        existingDevices={[...defaultDevices, ...customDevices]}\n        isCustom\n        isOpen={isDetailsModalOpen}\n        onClose={() => {\n          setSelectedDevice(undefined);\n          setIsDetailsModalOpen(false);\n        }}\n        device={selectedDevice}\n        onRemoveDevice={onRemoveDevice}\n      />\n    </div>\n  );\n};\n\nconst DeviceManagerWithDnd = () => (\n  <DndProvider backend={HTML5Backend}>\n    <DeviceManager />\n  </DndProvider>\n);\n\nexport default DeviceManagerWithDnd;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Divider/index.tsx",
    "content": "export const Divider = () => <div className=\"my-1 w-full border-t border-gray-400 opacity-30\" />;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/DropDown/index.tsx",
    "content": "import {Menu, Transition} from '@headlessui/react';\nimport {Float} from '@headlessui-float/react';\nimport {Icon} from '@iconify/react';\nimport cx from 'classnames';\nimport {Fragment} from 'react';\n\ninterface Separator {\n  type: 'separator';\n}\n\ninterface Option {\n  type?: 'option';\n  label: JSX.Element | string;\n  onClick: (() => void) | null;\n}\n\ntype OptionOrSeparator = Option | Separator;\n\ninterface Props {\n  label: JSX.Element | string;\n  options: OptionOrSeparator[];\n  className?: string | null;\n}\n\nexport function DropDown({label, options, className}: Props) {\n  return (\n    <div className=\"relative text-right\">\n      <Menu as=\"div\" className={`inline-block text-left ${className}`}>\n        <Float placement=\"bottom-end\" flip portal>\n          <Menu.Button className=\"inline-flex w-full justify-center gap-1 rounded-md bg-opacity-20 p-2 text-sm font-medium hover:bg-slate-300 hover:bg-opacity-30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 dark:hover:bg-slate-700\">\n            {label}\n            <Icon icon=\"mdi:chevron-down\" />\n          </Menu.Button>\n          <Transition\n            as={Fragment}\n            enter=\"transition ease-out duration-100\"\n            enterFrom=\"transform opacity-0 scale-95\"\n            enterTo=\"transform opacity-100 scale-100\"\n            leave=\"transition ease-in duration-75\"\n            leaveFrom=\"transform opacity-100 scale-100\"\n            leaveTo=\"transform opacity-0 scale-95\"\n          >\n            <Menu.Items className=\"z-50 mt-2 w-fit origin-top-right divide-y divide-slate-100 rounded-md bg-slate-100 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-slate-900\">\n              <div className=\"px-1 py-1 \">\n                {options.map((option, idx) => {\n                  if (option.type === 'separator') {\n                    return (\n                      <div\n                        className=\"m-1 border-t-[1px] border-t-slate-500\"\n                        // eslint-disable-next-line react/no-array-index-key\n                        key={`divider-${idx}`}\n                      />\n                    );\n                  }\n                  return (\n                    // eslint-disable-next-line react/no-array-index-key\n                    <Menu.Item key={idx.toString()}>\n                      {({active}) =>\n                        option.onClick !== null ? (\n                          <button\n                            className={cx(\n                              'group flex w-full items-center rounded-md px-2 py-2 text-sm',\n                              {'bg-slate-200 dark:bg-slate-800': active}\n                            )}\n                            type=\"button\"\n                            onClick={option.onClick}\n                          >\n                            {option.label}\n                          </button>\n                        ) : (\n                          <div\n                            className={cx('group mt-2 flex w-full items-center rounded-md px-2')}\n                          >\n                            {option.label}\n                          </div>\n                        )\n                      }\n                    </Menu.Item>\n                  );\n                })}\n              </div>\n            </Menu.Items>\n          </Transition>\n        </Float>\n      </Menu>\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app/src/renderer/components/FileUploader/FileUploader.test.tsx",
    "content": "import {render, fireEvent} from '@testing-library/react';\nimport {type Mock} from 'vitest';\nimport {FileUploader, FileUploaderProps} from './FileUploader';\nimport {useFileUpload} from './hooks';\n\nvi.mock('./hooks');\n\nconst mockHandleFileUpload = vi.fn();\nconst mockHandleUpload = vi.fn();\nconst mockResetUploadedFile = vi.fn();\n\ndescribe('FileUploader', () => {\n  beforeEach(() => {\n    (useFileUpload as Mock).mockReturnValue({\n      uploadedFile: null,\n      handleUpload: mockHandleUpload,\n      resetUploadedFile: mockResetUploadedFile,\n    });\n  });\n\n  const renderComponent = (props?: FileUploaderProps) =>\n    render(\n      <FileUploader\n        handleFileUpload={props?.handleFileUpload || mockHandleFileUpload}\n        multiple={props?.multiple || false}\n        acceptedFileTypes={props?.acceptedFileTypes || '*/*'}\n      />\n    );\n\n  it('renders the component', () => {\n    const {getByTestId} = renderComponent();\n\n    const fileInput = getByTestId('fileUploader');\n\n    expect(fileInput).toBeInTheDocument();\n  });\n\n  it('calls handleUpload when file input changes', () => {\n    const {getByTestId} = renderComponent();\n    const fileInput = getByTestId('fileUploader');\n    fireEvent.change(fileInput, {\n      target: {files: [new File(['content'], 'file.txt')]},\n    });\n    expect(mockHandleUpload).toHaveBeenCalled();\n  });\n\n  it('calls handleFileUpload when uploadedFile is set', () => {\n    const mockFile = new File(['content'], 'file.txt');\n    (useFileUpload as Mock).mockReturnValue({\n      uploadedFile: mockFile,\n      handleUpload: mockHandleUpload,\n      resetUploadedFile: mockResetUploadedFile,\n    });\n    renderComponent();\n    expect(mockHandleFileUpload).toHaveBeenCalledWith(mockFile);\n  });\n\n  it('sets the accept attribute correctly', () => {\n    const {getByTestId} = renderComponent({\n      acceptedFileTypes: 'application/json',\n      handleFileUpload: mockHandleFileUpload,\n    });\n    const fileInput = getByTestId('fileUploader');\n    expect(fileInput).toHaveAttribute('accept', 'application/json');\n  });\n\n  it('allows multiple file uploads when multiple prop is true', () => {\n    const {getByTestId} = renderComponent({\n      multiple: true,\n      handleFileUpload: mockHandleFileUpload,\n    });\n    const fileInput = getByTestId('fileUploader');\n    expect(fileInput).toHaveAttribute('multiple');\n  });\n\n  it('does not allow multiple file uploads when multiple prop is false', () => {\n    const {getByTestId} = renderComponent({\n      multiple: false,\n      handleFileUpload: mockHandleFileUpload,\n    });\n    const fileInput = getByTestId('fileUploader');\n    expect(fileInput).not.toHaveAttribute('multiple');\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/FileUploader/FileUploader.tsx",
    "content": "import {useEffect, useRef, useState} from 'react';\nimport {useFileUpload} from './hooks';\n\nexport type FileUploaderProps = {\n  handleFileUpload: (file: File) => void;\n  multiple?: boolean;\n  acceptedFileTypes?: string;\n  showFileName?: boolean;\n  initialFileName?: string;\n};\n\nexport const FileUploader = ({\n  handleFileUpload,\n  multiple,\n  acceptedFileTypes,\n  showFileName = false,\n  initialFileName,\n}: FileUploaderProps) => {\n  const {uploadedFile, handleUpload, resetUploadedFile} = useFileUpload();\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const [displayFileName, setDisplayFileName] = useState<string | null>(null);\n\n  useEffect(() => {\n    if (uploadedFile) {\n      handleFileUpload(uploadedFile);\n      resetUploadedFile();\n      if (!showFileName && fileInputRef.current) {\n        fileInputRef.current.value = '';\n      }\n    }\n  }, [handleFileUpload, resetUploadedFile, uploadedFile, showFileName]);\n\n  useEffect(() => {\n    if (initialFileName !== undefined) {\n      setDisplayFileName(initialFileName || null);\n    }\n  }, [initialFileName]);\n\n  const handleButtonClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  const getFileName = () => {\n    if (fileInputRef.current?.files && fileInputRef.current.files.length > 0) {\n      return fileInputRef.current.files[0].name;\n    }\n    return displayFileName;\n  };\n\n  const fileName = showFileName ? getFileName() : null;\n\n  if (showFileName) {\n    return (\n      <div className=\"flex flex-col gap-2\" data-testid=\"file-uploader\">\n        <div className=\"flex items-center gap-2\">\n          <button\n            type=\"button\"\n            onClick={handleButtonClick}\n            className=\"rounded border border-slate-400 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700\"\n          >\n            Choose File\n          </button>\n          <span className=\"text-sm text-slate-600 dark:text-slate-400\">\n            {fileName || 'No file chosen'}\n          </span>\n        </div>\n        <input\n          ref={fileInputRef}\n          type=\"file\"\n          onChange={handleUpload}\n          multiple={multiple || false}\n          accept={acceptedFileTypes || '*/*'}\n          aria-label=\"Upload file\"\n          data-testid=\"fileUploader\"\n          className=\"hidden\"\n        />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex flex-row flex-wrap\" data-testid=\"file-uploader\">\n      <input\n        ref={fileInputRef}\n        type=\"file\"\n        onChange={handleUpload}\n        multiple={multiple || false}\n        accept={acceptedFileTypes || '*/*'}\n        aria-label=\"Upload file\"\n        data-testid=\"fileUploader\"\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/FileUploader/hooks/index.ts",
    "content": "export {useFileUpload} from './useFileUpload';\n"
  },
  {
    "path": "desktop-app/src/renderer/components/FileUploader/hooks/useFileUpload.test.tsx",
    "content": "import {act, renderHook} from '@testing-library/react';\nimport {useFileUpload} from './useFileUpload';\n\ndescribe('useFileUpload', () => {\n  it('should initialize with null uploadedFile', () => {\n    const {result} = renderHook(() => useFileUpload());\n\n    expect(result.current.uploadedFile).toBeNull();\n  });\n\n  it('should set uploadedFile when handleUpload is called with a file', () => {\n    const {result} = renderHook(() => useFileUpload());\n    const mockFile = new File(['dummy content'], 'example.png', {\n      type: 'image/png',\n    });\n\n    act(() => {\n      result.current.handleUpload({\n        target: {\n          files: [mockFile],\n        },\n      } as unknown as React.ChangeEvent<HTMLInputElement>);\n    });\n\n    expect(result.current.uploadedFile).toEqual(mockFile);\n  });\n\n  it('should reset uploadedFile when resetUploadedFile is called', () => {\n    const {result} = renderHook(() => useFileUpload());\n    const mockFile = new File(['dummy content'], 'example.png', {\n      type: 'image/png',\n    });\n\n    act(() => {\n      result.current.handleUpload({\n        target: {\n          files: [mockFile],\n        },\n      } as unknown as React.ChangeEvent<HTMLInputElement>);\n    });\n\n    expect(result.current.uploadedFile).toEqual(mockFile);\n\n    act(() => {\n      result.current.resetUploadedFile();\n    });\n\n    expect(result.current.uploadedFile).toBeNull();\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/FileUploader/hooks/useFileUpload.tsx",
    "content": "import {useState} from 'react';\n\nexport const useFileUpload = () => {\n  const [uploadedFile, setUploadedFile] = useState<File | null>(null);\n\n  const handleUpload = (event: React.ChangeEvent<HTMLInputElement>) => {\n    if (event?.target?.files || event?.target?.files?.length) {\n      setUploadedFile(event.target.files[0]);\n    }\n  };\n\n  const resetUploadedFile = () => {\n    setUploadedFile(null);\n  };\n\n  return {\n    uploadedFile,\n    handleUpload,\n    resetUploadedFile,\n  };\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/FileUploader/index.ts",
    "content": "export {FileUploader} from './FileUploader';\nexport {useFileUpload} from './hooks';\n"
  },
  {
    "path": "desktop-app/src/renderer/components/InfoPopups/index.tsx",
    "content": "import {useEffect, useState} from 'react';\nimport {isReleaseNotesUnseen, ReleaseNotes} from '../ReleaseNotes';\nimport {Sponsorship} from '../Sponsorship';\n\nexport const InfoPopups = () => {\n  const [showReleaseNotes, setShowReleaseNotes] = useState<boolean>(false);\n  const [showSponsorship, setShowSponsorship] = useState<boolean>(false);\n\n  useEffect(() => {\n    (async () => {\n      if (await isReleaseNotesUnseen()) {\n        setShowReleaseNotes(true);\n        return;\n      }\n      setShowSponsorship(true);\n    })();\n  }, []);\n\n  if (showReleaseNotes) {\n    return <ReleaseNotes />;\n  }\n\n  if (showSponsorship) {\n    return <Sponsorship />;\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Input/index.tsx",
    "content": "import {useId} from 'react';\nimport cx from 'classnames';\n\ninterface Props {\n  label: string;\n}\n\nconst Input = ({\n  label,\n  ...props\n}: Props &\n  React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>) => {\n  const id = useId();\n  const isCheckbox = props.type === 'checkbox';\n  return (\n    <div\n      className={cx('flex gap-1', {\n        'flex-col': !isCheckbox,\n        'flex-row-reverse justify-end': isCheckbox,\n      })}\n    >\n      <label htmlFor={id}>{label}</label>\n      <input\n        type=\"text\"\n        id={id}\n        className=\"rounded-sm bg-white p-1 px-1 dark:bg-slate-900\"\n        /* eslint-disable-next-line react/jsx-props-no-spreading */\n        {...props}\n      />\n    </div>\n  );\n};\n\nexport default Input;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/KeyboardShortcutsManager/constants.ts",
    "content": "export const SHORTCUT_CHANNEL = {\n  BACK: 'BACK',\n  BOOKMARK: 'BOOKMARK',\n  DELETE_ALL: 'DELETE_ALL',\n  DELETE_CACHE: 'DELETE_CACHE',\n  DELETE_COOKIES: 'DELETE_COOKIES',\n  DELETE_STORAGE: 'DELETE_STORAGE',\n  EDIT_URL: 'EDIT_URL',\n  FORWARD: 'FORWARD',\n  INSPECT_ELEMENTS: 'INSPECT_ELEMENTS',\n  PREVIEW_LAYOUT: 'PREVIEW_LAYOUT',\n  RELOAD: 'RELOAD',\n  ROTATE_ALL: 'ROTATE_ALL',\n  SCREENSHOT_ALL: 'SCREENSHOT_ALL',\n  THEME: 'THEME',\n  TOGGLE_RULERS: 'TOGGLE_RULERS',\n  ZOOM_IN: 'ZOOM_IN',\n  ZOOM_OUT: 'ZOOM_OUT',\n} as const;\n\nexport type ShortcutChannel = (typeof SHORTCUT_CHANNEL)[keyof typeof SHORTCUT_CHANNEL];\n\nexport const SHORTCUT_KEYS: {[key in ShortcutChannel]: string[]} = {\n  [SHORTCUT_CHANNEL.BACK]: ['alt+left'],\n  [SHORTCUT_CHANNEL.BOOKMARK]: ['mod+d'],\n  [SHORTCUT_CHANNEL.DELETE_ALL]: ['mod+alt+del', 'mod+alt+backspace'],\n  [SHORTCUT_CHANNEL.DELETE_CACHE]: ['mod+alt+z'],\n  [SHORTCUT_CHANNEL.DELETE_COOKIES]: ['mod+alt+a'],\n  [SHORTCUT_CHANNEL.DELETE_STORAGE]: ['mod+alt+q'],\n  [SHORTCUT_CHANNEL.EDIT_URL]: ['mod+l'],\n  [SHORTCUT_CHANNEL.FORWARD]: ['alt+right'],\n  [SHORTCUT_CHANNEL.INSPECT_ELEMENTS]: ['mod+i'],\n  [SHORTCUT_CHANNEL.PREVIEW_LAYOUT]: ['mod+shift+l'],\n  [SHORTCUT_CHANNEL.RELOAD]: ['mod+r'],\n  [SHORTCUT_CHANNEL.ROTATE_ALL]: ['mod+alt+r'],\n  [SHORTCUT_CHANNEL.SCREENSHOT_ALL]: ['mod+s'],\n  [SHORTCUT_CHANNEL.THEME]: ['mod+t'],\n  [SHORTCUT_CHANNEL.TOGGLE_RULERS]: ['alt+r'],\n  [SHORTCUT_CHANNEL.ZOOM_IN]: ['mod+=', 'mod++', 'mod+shift+='],\n  [SHORTCUT_CHANNEL.ZOOM_OUT]: ['mod+-'],\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/KeyboardShortcutsManager/index.tsx",
    "content": "import {SHORTCUT_KEYS, ShortcutChannel} from './constants';\nimport useMousetrapEmitter from './useMousetrapEmitter';\n\nconst KeyboardShortcutsManager = () => {\n  // eslint-disable-next-line no-restricted-syntax\n  for (const [channel, keys] of Object.entries(SHORTCUT_KEYS)) {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useMousetrapEmitter(keys, channel as ShortcutChannel);\n  }\n\n  return null;\n};\n\nexport default KeyboardShortcutsManager;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/KeyboardShortcutsManager/useKeyboardShortcut.ts",
    "content": "import {useEffect} from 'react';\nimport {ShortcutChannel} from './constants';\nimport {keyboardShortcutsPubsub} from './useMousetrapEmitter';\n\nconst useKeyboardShortcut = (eventChannel: ShortcutChannel, callback: () => void) => {\n  useEffect(() => {\n    keyboardShortcutsPubsub.subscribe(eventChannel, callback);\n    return () => {\n      keyboardShortcutsPubsub.unsubscribe(eventChannel, callback);\n    };\n  }, [eventChannel, callback]);\n  return null;\n};\n\nexport default useKeyboardShortcut;\nexport * from './constants';\n"
  },
  {
    "path": "desktop-app/src/renderer/components/KeyboardShortcutsManager/useMousetrapEmitter.ts",
    "content": "import {useEffect} from 'react';\nimport Mousetrap from 'mousetrap';\nimport PubSub from 'renderer/lib/pubsub';\nimport {ShortcutChannel} from './constants';\n\nexport const keyboardShortcutsPubsub = new PubSub();\n\nconst useMousetrapEmitter = (\n  accelerator: string | string[],\n  eventChannel: ShortcutChannel,\n  action?: string | undefined\n) => {\n  useEffect(() => {\n    const callback = (_e: Mousetrap.ExtendedKeyboardEvent, _combo: string) => {\n      keyboardShortcutsPubsub.publish(eventChannel).catch((err) => {\n        // eslint-disable-next-line no-console\n        console.error('useMousetrapEmitter: callback: error: ', err);\n      });\n    };\n    Mousetrap.bind(accelerator, callback, action);\n\n    return () => {\n      Mousetrap.unbind(accelerator, action);\n    };\n  }, [accelerator, eventChannel, action]);\n\n  return null;\n};\n\nexport default useMousetrapEmitter;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Modal/index.tsx",
    "content": "import {Dialog, Transition} from '@headlessui/react';\nimport {Fragment} from 'react';\n\ninterface Props {\n  isOpen: boolean;\n  onClose: () => void;\n  title?: JSX.Element | string;\n  description?: JSX.Element | string;\n  children?: JSX.Element | string;\n}\n\nconst Modal = ({isOpen, onClose, title, description, children}: Props) => {\n  return (\n    <Transition appear show={isOpen} as={Fragment}>\n      <Dialog onClose={onClose} className=\"relative z-50\" as=\"div\">\n        <Transition.Child\n          as={Fragment}\n          enter=\"ease-out duration-300\"\n          enterFrom=\"opacity-0\"\n          enterTo=\"opacity-100\"\n          leave=\"ease-in duration-200\"\n          leaveFrom=\"opacity-100\"\n          leaveTo=\"opacity-0\"\n        >\n          <div className=\"fixed inset-0 bg-black/50\" aria-hidden=\"true\" />\n        </Transition.Child>\n        <div className=\"fixed inset-0 overflow-y-auto\">\n          <div className=\"flex min-h-full items-center justify-center p-4\">\n            <Transition.Child\n              as={Fragment}\n              enter=\"ease-out duration-300\"\n              enterFrom=\"opacity-0 scale-95\"\n              enterTo=\"opacity-100 scale-100\"\n              leave=\"ease-in duration-200\"\n              leaveFrom=\"opacity-100 scale-100\"\n              leaveTo=\"opacity-0 scale-95\"\n            >\n              <Dialog.Panel\n                className={`flex w-fit min-w-[320px] flex-col gap-4 rounded bg-slate-200 text-light-normal dark:bg-slate-800 dark:text-dark-normal ${\n                  title ? 'p-8' : 'px-8 py-4'\n                }`}\n              >\n                <div>\n                  <Dialog.Title className=\"text-xl font-medium leading-6\">{title}</Dialog.Title>\n                  <Dialog.Description>{description}</Dialog.Description>\n                </div>\n\n                {children}\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n};\n\nexport default Modal;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ModalLoader/index.tsx",
    "content": "import Modal from '../Modal';\n\ninterface Props {\n  isOpen: boolean;\n  onClose: () => void;\n  title: JSX.Element | string;\n}\n\nconst ModalLoader = ({isOpen, onClose, title}: Props) => {\n  return (\n    <Modal isOpen={isOpen} onClose={onClose} title={title}>\n      <div className=\"flex flex-col items-center justify-center gap-4\">Capturing screen...</div>\n    </Modal>\n  );\n};\n\nexport default ModalLoader;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Notifications/Notification.tsx",
    "content": "import {IPC_MAIN_CHANNELS, Notification as NotificationType} from 'common/constants';\nimport Button from '../Button';\n\nconst Notification = ({notification}: {notification: NotificationType}) => {\n  const handleLinkClick = (url: string) => {\n    window.electron.ipcRenderer.sendMessage(IPC_MAIN_CHANNELS.OPEN_EXTERNAL, {\n      url,\n    });\n  };\n\n  return (\n    <div className=\"mb-2 text-sm text-white\">\n      <p> {notification.text} </p>\n      {notification.link && notification.linkText && (\n        <Button\n          isPrimary\n          title={notification.linkText}\n          onClick={() => notification.link && handleLinkClick(notification.link)}\n          className=\"mt-2\"\n        >\n          {notification.linkText}\n        </Button>\n      )}\n    </div>\n  );\n};\n\nexport default Notification;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Notifications/NotificationEmptyStatus.tsx",
    "content": "const NotificationEmptyStatus = () => {\n  return (\n    <div className=\"mb-2\">\n      <p>You are all caught up! No new notifications at the moment.</p>\n    </div>\n  );\n};\n\nexport default NotificationEmptyStatus;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Notifications/Notifications.tsx",
    "content": "import {useSelector} from 'react-redux';\nimport {selectNotifications} from 'renderer/store/features/renderer';\nimport {v4 as uuidv4} from 'uuid';\nimport {Notification as NotificationType} from 'common/constants';\nimport Notification from './Notification';\nimport NotificationEmptyStatus from './NotificationEmptyStatus';\n\nconst Notifications = () => {\n  const notificationsState = useSelector(selectNotifications);\n\n  return (\n    <div className=\"mb-4 max-h-[200px] overflow-y-auto rounded-lg p-1 px-4 shadow-lg dark:bg-slate-900\">\n      <span className=\"text-md\">Notifications</span>\n      <div className=\"mt-2\">\n        {(!notificationsState || (notificationsState && notificationsState?.length === 0)) && (\n          <NotificationEmptyStatus />\n        )}\n        {notificationsState &&\n          notificationsState?.length > 0 &&\n          notificationsState?.map((notification: NotificationType) => (\n            <Notification key={uuidv4()} notification={notification} />\n          ))}\n      </div>\n    </div>\n  );\n};\n\nexport default Notifications;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Notifications/NotificationsBubble.tsx",
    "content": "const NotificationsBubble = () => {\n  return (\n    <span className=\"absolute right-0 top-0 flex h-2 w-2\">\n      <span className=\"absolute inline-flex h-full w-full animate-ping rounded-full bg-red-400 opacity-75\" />\n      <span className=\"relative inline-flex h-2 w-2 rounded-full bg-red-500\" />\n    </span>\n  );\n};\n\nexport default NotificationsBubble;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/ColorBlindnessTools/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport cx from 'classnames';\nimport {useCallback, useEffect, useState} from 'react';\nimport {DropDown} from 'renderer/components/DropDown';\nimport {COLOR_BLINDNESS_CHANNEL} from 'renderer/components/ToolBar/ColorBlindnessControls';\nimport {\n  BLUE_YELLOW,\n  FULL,\n  RED_GREEN,\n  SIMULATIONS,\n  SUNLIGHT,\n  VISUAL_IMPAIRMENTS,\n  VisionSimulationDropDown,\n} from 'renderer/components/VisionSimulationDropDown';\nimport {webViewPubSub} from 'renderer/lib/pubsub';\n\ninterface InjectedCss {\n  key: string;\n  css: string;\n  js: string | null;\n  name: string;\n}\n\ninterface Props {\n  webview: Electron.WebviewTag | null;\n}\n\nexport const ColorBlindnessTools = ({webview}: Props) => {\n  const [injectCss, setInjectCss] = useState<InjectedCss>();\n\n  const reApplyCss = useCallback(async () => {\n    if (webview === null) {\n      return;\n    }\n    if (injectCss === undefined) {\n      return;\n    }\n    const key = await webview.insertCSS(injectCss.css);\n    if (injectCss.js != null) {\n      await webview.executeJavaScript(injectCss.js);\n    }\n\n    setInjectCss({...injectCss, key});\n  }, [webview, injectCss, setInjectCss]);\n\n  const applyCss = useCallback(\n    async (debugType: string, css: string, js: string | null = null) => {\n      if (webview === null) {\n        return;\n      }\n      if (css === undefined) {\n        return;\n      }\n\n      if (injectCss !== undefined) {\n        if (injectCss.name === debugType) {\n          return;\n        }\n        if (injectCss.js !== null) {\n          webview.reload();\n        }\n        await webview.removeInsertedCSS(injectCss.key);\n        setInjectCss(undefined);\n      }\n\n      try {\n        const key = await webview.insertCSS(css);\n        if (js !== null) {\n          await webview.executeJavaScript(js);\n        }\n        setInjectCss({key, css, name: debugType, js});\n      } catch (error) {\n        // eslint-disable-next-line no-console\n        console.error('Error inserting css', error);\n        // dispatch(setCss(undefined));\n        setInjectCss(undefined);\n      }\n    },\n    [setInjectCss, webview, injectCss]\n  );\n\n  const clearSimulation = useCallback(async () => {\n    if (webview === null) {\n      return;\n    }\n    if (injectCss === undefined) {\n      return;\n    }\n    await webview.removeInsertedCSS(injectCss.key);\n    setInjectCss(undefined);\n  }, [webview, injectCss, setInjectCss]);\n\n  useEffect(() => {\n    if (webview === null) {\n      return () => {};\n    }\n    const handler = async () => {\n      reApplyCss();\n    };\n\n    webview.addEventListener('did-navigate', handler);\n\n    return () => {\n      webview.removeEventListener('did-navigate', handler);\n    };\n  }, [webview, reApplyCss]);\n\n  const applyColorDeficiency = useCallback(\n    async (colorDeficiency: string) => {\n      const xsltPath =\n        'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgdmVyc2lvbj0iMS4xIj4KICA8ZGVmcz4KICAgIDxmaWx0ZXIgaWQ9InByb3Rhbm9waWEiPgogICAgICA8ZmVDb2xvck1hdHJpeAogICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgIHR5cGU9Im1hdHJpeCIKICAgICAgICB2YWx1ZXM9IjAuNTY3LCAwLjQzMywgMCwgICAgIDAsIDAKICAgICAgICAgICAgICAgIDAuNTU4LCAwLjQ0MiwgMCwgICAgIDAsIDAKICAgICAgICAgICAgICAgIDAsICAgICAwLjI0MiwgMC43NTgsIDAsIDAKICAgICAgICAgICAgICAgIDAsICAgICAwLCAgICAgMCwgICAgIDEsIDAiLz4KICAgIDwvZmlsdGVyPgogICAgPGZpbHRlciBpZD0icHJvdGFub21hbHkiPgogICAgICA8ZmVDb2xvck1hdHJpeAogICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgIHR5cGU9Im1hdHJpeCIKICAgICAgICB2YWx1ZXM9IjAuODE3LCAwLjE4MywgMCwgICAgIDAsIDAKICAgICAgICAgICAgICAgIDAuMzMzLCAwLjY2NywgMCwgICAgIDAsIDAKICAgICAgICAgICAgICAgIDAsICAgICAwLjEyNSwgMC44NzUsIDAsIDAKICAgICAgICAgICAgICAgIDAsICAgICAwLCAgICAgMCwgICAgIDEsIDAiLz4KICAgIDwvZmlsdGVyPgogICAgPGZpbHRlciBpZD0iZGV1dGVyYW5vcGlhIj4KICAgICAgPGZlQ29sb3JNYXRyaXgKICAgICAgICBpbj0iU291cmNlR3JhcGhpYyIKICAgICAgICB0eXBlPSJtYXRyaXgiCiAgICAgICAgdmFsdWVzPSIwLjYyNSwgMC4zNzUsIDAsICAgMCwgMAogICAgICAgICAgICAgICAgMC43LCAgIDAuMywgICAwLCAgIDAsIDAKICAgICAgICAgICAgICAgIDAsICAgICAwLjMsICAgMC43LCAwLCAwCiAgICAgICAgICAgICAgICAwLCAgICAgMCwgICAgIDAsICAgMSwgMCIvPgogICAgPC9maWx0ZXI+CiAgICA8ZmlsdGVyIGlkPSJkZXV0ZXJhbm9tYWx5Ij4KICAgICAgPGZlQ29sb3JNYXRyaXgKICAgICAgICBpbj0iU291cmNlR3JhcGhpYyIKICAgICAgICB0eXBlPSJtYXRyaXgiCiAgICAgICAgdmFsdWVzPSIwLjgsICAgMC4yLCAgIDAsICAgICAwLCAwCiAgICAgICAgICAgICAgICAwLjI1OCwgMC43NDIsIDAsICAgICAwLCAwCiAgICAgICAgICAgICAgICAwLCAgICAgMC4xNDIsIDAuODU4LCAwLCAwCiAgICAgICAgICAgICAgICAwLCAgICAgMCwgICAgIDAsICAgICAxLCAwIi8+CiAgICA8L2ZpbHRlcj4KICAgIDxmaWx0ZXIgaWQ9InRyaXRhbm9waWEiPgogICAgICA8ZmVDb2xvck1hdHJpeAogICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgIHR5cGU9Im1hdHJpeCIKICAgICAgICB2YWx1ZXM9IjAuOTUsIDAuMDUsICAwLCAgICAgMCwgMAogICAgICAgICAgICAgICAgMCwgICAgMC40MzMsIDAuNTY3LCAwLCAwCiAgICAgICAgICAgICAgICAwLCAgICAwLjQ3NSwgMC41MjUsIDAsIDAKICAgICAgICAgICAgICAgIDAsICAgIDAsICAgICAwLCAgICAgMSwgMCIvPgogICAgPC9maWx0ZXI+CiAgICA8ZmlsdGVyIGlkPSJ0cml0YW5vbWFseSI+CiAgICAgIDxmZUNvbG9yTWF0cml4CiAgICAgICAgaW49IlNvdXJjZUdyYXBoaWMiCiAgICAgICAgdHlwZT0ibWF0cml4IgogICAgICAgIHZhbHVlcz0iMC45NjcsIDAuMDMzLCAwLCAgICAgMCwgMAogICAgICAgICAgICAgICAgMCwgICAgIDAuNzMzLCAwLjI2NywgMCwgMAogICAgICAgICAgICAgICAgMCwgICAgIDAuMTgzLCAwLjgxNywgMCwgMAogICAgICAgICAgICAgICAgMCwgICAgIDAsICAgICAwLCAgICAgMSwgMCIvPgogICAgPC9maWx0ZXI+CiAgICA8ZmlsdGVyIGlkPSJhY2hyb21hdG9wc2lhIj4KICAgICAgPGZlQ29sb3JNYXRyaXgKICAgICAgICBpbj0iU291cmNlR3JhcGhpYyIKICAgICAgICB0eXBlPSJtYXRyaXgiCiAgICAgICAgdmFsdWVzPSIwLjI5OSwgMC41ODcsIDAuMTE0LCAwLCAwCiAgICAgICAgICAgICAgICAwLjI5OSwgMC41ODcsIDAuMTE0LCAwLCAwCiAgICAgICAgICAgICAgICAwLjI5OSwgMC41ODcsIDAuMTE0LCAwLCAwCiAgICAgICAgICAgICAgICAwLCAgICAgMCwgICAgIDAsICAgICAxLCAwIi8+CiAgICA8L2ZpbHRlcj4KICAgIDxmaWx0ZXIgaWQ9ImFjaHJvbWF0b21hbHkiPgogICAgICA8ZmVDb2xvck1hdHJpeAogICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgIHR5cGU9Im1hdHJpeCIKICAgICAgICB2YWx1ZXM9IjAuNjE4LCAwLjMyMCwgMC4wNjIsIDAsIDAKICAgICAgICAgICAgICAgIDAuMTYzLCAwLjc3NSwgMC4wNjIsIDAsIDAKICAgICAgICAgICAgICAgIDAuMTYzLCAwLjMyMCwgMC41MTYsIDAsIDAKICAgICAgICAgICAgICAgIDAsICAgICAwLCAgICAgMCwgICAgIDEsIDAiLz4KICAgIDwvZmlsdGVyPgogIDwvZGVmcz4KPC9zdmc+Cg==';\n      const css = `\n      body {\n       -webkit-filter: url('${xsltPath}#${colorDeficiency}');\n        filter: url('${xsltPath}#${colorDeficiency}');\n      }\n      `;\n      return applyCss(colorDeficiency, css);\n    },\n    [applyCss]\n  );\n\n  const applySunlight = useCallback(\n    async (condition: string) => {\n      const css = 'body {backdrop-filter: brightness(0.5) !important;}';\n      return applyCss(condition, css);\n    },\n    [applyCss]\n  );\n\n  const applyVisualImpairment = useCallback(\n    async (visualImpairment: string) => {\n      const blur =\n        'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJnYXVzc2lhbl9ibHVyIj4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIGluPSJTb3VyY2VHcmFwaGljIiBzdGREZXZpYXRpb249IjEwIiAvPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8L3N2Zz4=';\n\n      const impairments: {[key: string]: string} = {\n        [SIMULATIONS.CATARACT]: `body {\n       -webkit-filter: url('${blur}#gaussian_blur');\n        filter: url('${blur}#gaussian_blur');\n      }`,\n        [SIMULATIONS.GLAUCOME]: `#bigoverlay {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  width: 100%;\n  height: 100%;\n}\n\n#spotlight {\n  border-radius: 50%;\n  width: 300vmax;\n  height: 300vmax;\n  box-shadow: 0 0 5vmax 110vmax inset black;\n  position: absolute;\n  z-index: -1;\n  left: -75vmax;\n  top: -75vmax;\n}`,\n        [SIMULATIONS.FAR]: `body { filter: blur(2px); }`,\n        [SIMULATIONS.COLOR_CONTRAST_LOSS]: `body { filter: grayscale(0.5) contrast(0.8); }`,\n      };\n      const css = impairments[visualImpairment.toLowerCase()];\n      let js = null;\n      if (visualImpairment.toLowerCase() === SIMULATIONS.GLAUCOME) {\n        js = String(`var div = document.createElement('div');\n  div.innerHTML ='<div class=\"bigoverlay\" id=\"bigoverlay\"><div class=\"spotlight\" id=\"spotlight\"></div></div>';\n  var body = document.body;\n  body.appendChild(div);\n  function handleMouseMove(){\n      var eventDoc, doc, body;\n      eventDoc = (event.target && event.target.ownerDocument) || document;\n      doc = eventDoc.documentElement;\n      body = eventDoc.body;\n    event.pageX = event.clientX +\n      (doc && doc.scrollLeft || body && body.scrollLeft || 0) -\n      (doc && doc.clientLeft || body && body.clientLeft || 0);\n    event.pageY = event.clientY +\n      (doc && doc.scrollTop  || body && body.scrollTop  || 0) -\n      (doc && doc.clientTop  || body && body.clientTop  || 0 );\n    const spotlight = document.getElementById(\"spotlight\");\n    const boundingRect = spotlight.getBoundingClientRect();\n    spotlight.style.left = (event.pageX - boundingRect.width / 2) + \"px\"\n    spotlight.style.top = (event.pageY - boundingRect.height / 2) + \"px\"\n  };document.onmousemove = handleMouseMove;0`);\n      }\n      return applyCss(visualImpairment, css, js);\n    },\n    [applyCss]\n  );\n\n  const applySimulation = useCallback(\n    async (simulation = '') => {\n      if (\n        RED_GREEN.indexOf(simulation) !== -1 ||\n        BLUE_YELLOW.indexOf(simulation) !== -1 ||\n        FULL.indexOf(simulation) !== -1\n      ) {\n        return applyColorDeficiency(simulation);\n      }\n\n      if (VISUAL_IMPAIRMENTS.indexOf(simulation) !== -1) {\n        return applyVisualImpairment(simulation);\n      }\n\n      if (SUNLIGHT.indexOf(simulation) !== -1) {\n        return applySunlight(simulation);\n      }\n\n      return clearSimulation();\n    },\n    [applyColorDeficiency, applyVisualImpairment, applySunlight, clearSimulation]\n  );\n\n  useEffect(() => {\n    const handler = ({simulationName}: {simulationName: string}) => {\n      applySimulation(simulationName);\n    };\n    webViewPubSub.subscribe(COLOR_BLINDNESS_CHANNEL, handler);\n\n    return () => {\n      webViewPubSub.unsubscribe(COLOR_BLINDNESS_CHANNEL, handler);\n    };\n  }, [applySimulation]);\n\n  return <VisionSimulationDropDown simulationName={injectCss?.name} onChange={applySimulation} />;\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/DesignOverlay/index.test.tsx",
    "content": "import {render, screen, fireEvent} from '@testing-library/react';\nimport {Provider} from 'react-redux';\nimport {configureStore} from '@reduxjs/toolkit';\nimport designOverlayReducer from 'renderer/store/features/design-overlay';\nimport rulersReducer, {Coordinates} from 'renderer/store/features/ruler';\nimport DesignOverlay from './index';\n\n// Mock GuideGrid component\nvi.mock('../../Guides', () => ({\n  __esModule: true,\n  default: () => <div data-testid=\"guide-grid\">GuideGrid</div>,\n}));\n\nconst mockCoordinates: Coordinates = {\n  deltaX: 0,\n  deltaY: 0,\n  scrollX: 0,\n  scrollY: 0,\n  innerWidth: 390,\n  innerHeight: 844,\n};\n\nconst createStore = (overlayState = {}) =>\n  configureStore({\n    reducer: {\n      designOverlay: designOverlayReducer,\n      rulers: rulersReducer,\n    },\n    preloadedState: {\n      designOverlay: overlayState,\n      rulers: {\n        '390x844': {\n          isRulerEnabled: false,\n          rulerCoordinates: mockCoordinates,\n        },\n      },\n    },\n  });\n\nconst renderWithRedux = (component: React.ReactElement, overlayState = {}) => {\n  const store = createStore(overlayState);\n  return render(<Provider store={store}>{component}</Provider>);\n};\n\ndescribe('DesignOverlay', () => {\n  const defaultProps = {\n    resolution: '390x844',\n    scaledWidth: 390,\n    scaledHeight: 844,\n    zoomFactor: 1,\n    coordinates: mockCoordinates,\n    position: 'overlay' as const,\n    rulerMargin: 0,\n    width: 390,\n    height: 844,\n  };\n\n  it('does not render when overlay is disabled', () => {\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />\n    );\n\n    expect(screen.queryByAltText('Design overlay')).not.toBeInTheDocument();\n  });\n\n  it('does not render when no image is set', () => {\n    const overlayState = {\n      '390x844': {\n        image: '',\n        opacity: 50,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    expect(screen.queryByAltText('Design overlay')).not.toBeInTheDocument();\n  });\n\n  it('renders image when overlay is enabled', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    const image = screen.getByAltText('Design overlay');\n    expect(image).toBeInTheDocument();\n    expect(image).toHaveStyle({opacity: '0.5'});\n  });\n\n  it('applies correct opacity in overlay mode', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 75,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    const image = screen.getByAltText('Design overlay');\n    expect(image).toHaveStyle({opacity: '0.75'});\n  });\n\n  it('applies 100% opacity in side mode', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'side' as const,\n        enabled: true,\n      },\n    };\n\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position=\"side\"\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    const image = screen.getByAltText('Design overlay');\n    expect(image).toHaveStyle({opacity: '1'});\n  });\n\n  it('shows GuideGrid only in side mode', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'side' as const,\n        enabled: true,\n      },\n    };\n\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position=\"side\"\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    expect(screen.getByTestId('guide-grid')).toBeInTheDocument();\n  });\n\n  it('does not show GuideGrid in overlay mode', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    expect(screen.queryByTestId('guide-grid')).not.toBeInTheDocument();\n  });\n\n  it('renders divider line in overlay mode', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    const {container} = renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    const divider = container.querySelector('[style*=\"cursor: col-resize\"]');\n    expect(divider).toBeInTheDocument();\n  });\n\n  it('applies clip-path based on divider position in overlay mode', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    const image = screen.getByAltText('Design overlay');\n    expect(image).toHaveStyle({clipPath: 'none'});\n  });\n\n  it('allows dragging the divider line', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    const {container} = renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={defaultProps.zoomFactor}\n        coordinates={defaultProps.coordinates}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    const divider = container.querySelector('[style*=\"cursor: col-resize\"]');\n    expect(divider).toBeInTheDocument();\n    expect(divider).not.toBeNull();\n\n    expect(divider).not.toBeNull();\n    fireEvent.mouseDown(divider!);\n    expect(document.body.style.cursor).toBe('col-resize');\n  });\n\n  it('applies scroll synchronization with zoomFactor', () => {\n    const overlayState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    // Use values that allow scrolling: innerWidth/Height must be greater than the viewport\n    // With zoomFactor 0.5: viewportWidth = 390/0.5 = 780, viewportHeight = 844/0.5 = 1688\n    // We need innerWidth > 780 and innerHeight > 1688 for scrolling to be available\n    const coordinatesWithScroll: Coordinates = {\n      deltaX: 0,\n      deltaY: 0,\n      scrollX: 100,\n      scrollY: 200,\n      innerWidth: 1000,\n      innerHeight: 2000,\n    };\n\n    renderWithRedux(\n      <DesignOverlay\n        resolution={defaultProps.resolution}\n        scaledWidth={defaultProps.scaledWidth}\n        scaledHeight={defaultProps.scaledHeight}\n        zoomFactor={0.5}\n        coordinates={coordinatesWithScroll}\n        position={defaultProps.position}\n        rulerMargin={defaultProps.rulerMargin}\n        width={defaultProps.width}\n        height={defaultProps.height}\n      />,\n      overlayState\n    );\n\n    const image = screen.getByAltText('Design overlay');\n    // scrollX * zoomFactor = 100 * 0.5 = 50\n    // scrollY * zoomFactor = 200 * 0.5 = 100\n    const {transform} = image.style;\n    expect(transform).toContain('-50px');\n    expect(transform).toContain('-100px');\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/DesignOverlay/index.tsx",
    "content": "import {useEffect, useRef, useState, useCallback} from 'react';\nimport {useSelector} from 'react-redux';\nimport type {RootState} from 'renderer/store';\nimport {DesignOverlayPosition, selectDesignOverlay} from 'renderer/store/features/design-overlay';\nimport {Coordinates, selectRulerEnabled} from 'renderer/store/features/ruler';\nimport GuideGrid from '../../Guides';\n\ninterface Props {\n  resolution: string;\n  scaledWidth: number;\n  scaledHeight: number;\n  zoomFactor: number;\n  coordinates: Coordinates;\n  position: DesignOverlayPosition;\n  rulerMargin: number;\n  width: number;\n  height: number;\n}\n\nconst DesignOverlay = ({\n  resolution,\n  scaledWidth,\n  scaledHeight,\n  zoomFactor,\n  coordinates,\n  position,\n  rulerMargin,\n  width,\n  height,\n}: Props) => {\n  const overlayRef = useRef<HTMLDivElement>(null);\n  const imageRef = useRef<HTMLImageElement>(null);\n  const dividerRef = useRef<HTMLDivElement>(null);\n  const overlay = useSelector((state: RootState) => selectDesignOverlay(state)(resolution));\n  const rulerEnabled = useSelector(selectRulerEnabled);\n\n  const isOverlayMode = position === 'overlay';\n  const [dividerPosition, setDividerPosition] = useState(0);\n  const [isDragging, setIsDragging] = useState(false);\n\n  // Handler to drag the divider line\n  const handleMouseDown = useCallback(\n    (e: React.MouseEvent) => {\n      if (!isOverlayMode) return;\n      e.preventDefault();\n      setIsDragging(true);\n    },\n    [isOverlayMode]\n  );\n\n  // Handler to move the mouse over the entire overlay when dragging\n  const handleOverlayMouseMove = useCallback(\n    (e: React.MouseEvent) => {\n      if (!isDragging || !isOverlayMode || !overlayRef.current) return;\n\n      e.preventDefault();\n      e.stopPropagation();\n\n      const rect = overlayRef.current.getBoundingClientRect();\n      const x = e.clientX - rect.left;\n      const percentage = Math.max(0, Math.min(100, (x / rect.width) * 100));\n      setDividerPosition(percentage);\n    },\n    [isDragging, isOverlayMode]\n  );\n\n  const handleOverlayMouseUp = useCallback(() => {\n    setIsDragging(false);\n  }, []);\n\n  // Add global listeners to capture events when the mouse leaves the overlay\n  useEffect(() => {\n    if (!isDragging) return undefined;\n\n    const handleGlobalMouseMove = (e: MouseEvent) => {\n      if (!isDragging || !isOverlayMode || !overlayRef.current) return;\n\n      e.preventDefault();\n      e.stopPropagation();\n\n      const rect = overlayRef.current.getBoundingClientRect();\n      const x = e.clientX - rect.left;\n\n      // Calculate percentage, limiting when outside the area\n      let percentage: number;\n      if (x < 0) {\n        percentage = 0;\n      } else if (x > rect.width) {\n        percentage = 100;\n      } else {\n        percentage = (x / rect.width) * 100;\n        percentage = Math.max(0, Math.min(100, percentage));\n      }\n\n      setDividerPosition(percentage);\n    };\n\n    const handleGlobalMouseUp = () => {\n      setIsDragging(false);\n    };\n\n    // Add global listeners for when the mouse leaves the overlay\n    window.addEventListener('mousemove', handleGlobalMouseMove, {\n      capture: true,\n      passive: false,\n    });\n    window.addEventListener('mouseup', handleGlobalMouseUp, {capture: true});\n\n    document.body.style.cursor = 'col-resize';\n    document.body.style.userSelect = 'none';\n\n    return () => {\n      window.removeEventListener('mousemove', handleGlobalMouseMove, {\n        capture: true,\n      });\n      window.removeEventListener('mouseup', handleGlobalMouseUp, {\n        capture: true,\n      });\n      document.body.style.cursor = '';\n      document.body.style.userSelect = '';\n    };\n  }, [isDragging, isOverlayMode]);\n\n  useEffect(() => {\n    if (!overlay?.enabled || !overlay.image || !imageRef.current) {\n      if (imageRef.current) {\n        imageRef.current.style.transform = 'translate(0px, 0px)';\n      }\n      return;\n    }\n\n    const scrollY = coordinates.scrollY || 0;\n    const scrollX = coordinates.scrollX || 0;\n\n    const viewportHeight = scaledHeight / zoomFactor;\n    const viewportWidth = scaledWidth / zoomFactor;\n\n    const maxScrollY = Math.max(0, coordinates.innerHeight - viewportHeight);\n    const maxScrollX = Math.max(0, coordinates.innerWidth - viewportWidth);\n\n    // Clamp scroll position to prevent scrolling beyond content\n    const clampedScrollY = Math.max(0, Math.min(scrollY, maxScrollY));\n    const clampedScrollX = Math.max(0, Math.min(scrollX, maxScrollX));\n\n    // Scale scroll positions by zoomFactor to match the scaled image dimensions\n    // The webview has (width x height) dimensions, but the image is scaled (scaledWidth x scaledHeight)\n    if (imageRef.current) {\n      imageRef.current.style.transform = `translate(${-clampedScrollX * zoomFactor}px, ${\n        -clampedScrollY * zoomFactor\n      }px)`;\n\n      // Apply clip-path to hide the right part of the image based on the divider position\n      // Only apply clip-path when dragging or when divider has been moved from initial position\n      if (isOverlayMode) {\n        if (dividerPosition === 0 && !isDragging) {\n          imageRef.current.style.clipPath = 'none';\n        } else {\n          imageRef.current.style.clipPath = `inset(0 ${100 - dividerPosition}% 0 0)`;\n        }\n      } else {\n        imageRef.current.style.clipPath = 'none';\n      }\n    }\n  }, [\n    coordinates,\n    scaledHeight,\n    scaledWidth,\n    zoomFactor,\n    overlay?.enabled,\n    overlay?.image,\n    isOverlayMode,\n    dividerPosition,\n    isDragging,\n  ]);\n\n  if (!overlay?.enabled || !overlay.image) {\n    return null;\n  }\n\n  const isSideMode = position === 'side';\n\n  const opacity = isSideMode ? 1 : overlay.opacity / 100;\n  const hasRuler = rulerEnabled(resolution);\n\n  const marginLeft = isOverlayMode ? rulerMargin : scaledWidth + rulerMargin + 30;\n  const marginTop = rulerMargin;\n\n  const containerStyle: React.CSSProperties = isOverlayMode\n    ? {\n        position: 'absolute',\n        top: marginTop,\n        left: marginLeft,\n        height: scaledHeight,\n        width: scaledWidth,\n        // When dragging, allow events on the entire overlay\n        pointerEvents: isDragging ? 'auto' : 'none',\n        zIndex: 10,\n        overflow: 'hidden',\n        cursor: isDragging ? 'col-resize' : 'default',\n      }\n    : {\n        height: hasRuler ? scaledHeight + 30 : scaledHeight,\n        width: hasRuler ? scaledWidth + 30 : scaledWidth,\n      };\n\n  const containerClassName = isOverlayMode\n    ? ''\n    : 'relative origin-top-left overflow-hidden bg-white';\n\n  return (\n    // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n    <div\n      ref={overlayRef}\n      style={containerStyle}\n      className={containerClassName}\n      onMouseMove={isDragging ? handleOverlayMouseMove : undefined}\n      onMouseUp={isDragging ? handleOverlayMouseUp : undefined}\n      onMouseLeave={isDragging ? handleOverlayMouseUp : undefined}\n      role={isDragging ? 'slider' : undefined}\n      aria-label={isDragging ? 'Design overlay divider' : undefined}\n    >\n      {isSideMode && (\n        <GuideGrid\n          scaledHeight={scaledHeight}\n          scaledWidth={scaledWidth}\n          height={height}\n          width={width}\n          coordinates={coordinates}\n          zoomFactor={zoomFactor}\n          night={false}\n          enabled={hasRuler}\n          defaultGuides={[]}\n        />\n      )}\n      <div\n        className=\"relative\"\n        style={{\n          backgroundColor: isOverlayMode ? 'transparent' : 'white',\n        }}\n      >\n        <img\n          ref={imageRef}\n          src={overlay.image}\n          alt=\"Design overlay\"\n          style={{\n            width: `${scaledWidth}px`,\n            height: 'auto',\n            minHeight: `${scaledHeight}px`,\n            objectFit: 'cover',\n            objectPosition: 'top left',\n            display: 'block',\n            opacity,\n            marginLeft: isSideMode && hasRuler ? '30px' : 0,\n            marginTop: isSideMode && hasRuler ? '30px' : 0,\n          }}\n        />\n      </div>\n\n      {/* Draggable divider line only in overlay mode */}\n      {isOverlayMode && (\n        <div\n          ref={dividerRef}\n          onMouseDown={handleMouseDown}\n          role=\"slider\"\n          aria-label=\"Design overlay divider\"\n          aria-valuemin={0}\n          aria-valuemax={100}\n          aria-valuenow={dividerPosition}\n          tabIndex={0}\n          style={{\n            position: 'absolute',\n            left: `${dividerPosition}%`,\n            top: 0,\n            bottom: 0,\n            width: '8px', // Larger area for easier click (invisible)\n            cursor: 'col-resize',\n            pointerEvents: 'auto',\n            zIndex: 20,\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n            transform: 'translateX(-50%)',\n          }}\n        >\n          {/* Visual line - always visible with 60% opacity */}\n          <div\n            style={{\n              position: 'absolute',\n              left: '50%',\n              top: 0,\n              bottom: 0,\n              width: '1px',\n              backgroundColor: 'rgba(255, 255, 255, 0.6)',\n              transform: 'translateX(-50%)',\n              pointerEvents: 'none',\n            }}\n          />\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default DesignOverlay;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/DesignOverlayControls/index.test.tsx",
    "content": "import {render, screen, fireEvent, waitFor} from '@testing-library/react';\nimport {Provider} from 'react-redux';\nimport {configureStore} from '@reduxjs/toolkit';\nimport designOverlayReducer from 'renderer/store/features/design-overlay';\nimport type {Device} from 'common/deviceList';\nimport {type Mock} from 'vitest';\nimport DesignOverlayControls from './index';\n\n// Mock electron.store\nconst mockStore = {\n  get: vi.fn(),\n  set: vi.fn(),\n};\n\nbeforeEach(() => {\n  vi.clearAllMocks();\n  (window.electron.store.get as Mock) = mockStore.get;\n  (window.electron.store.set as Mock) = mockStore.set;\n  mockStore.get.mockReturnValue({});\n});\n\n// Mock FileUploader component\nvi.mock('renderer/components/FileUploader', () => ({\n  FileUploader: ({handleFileUpload}: {handleFileUpload: (file: File) => void}) => (\n    <div data-testid=\"file-uploader\">\n      <input\n        type=\"file\"\n        data-testid=\"file-input\"\n        onChange={(e) => {\n          const file = (e.target as HTMLInputElement).files?.[0];\n          if (file) handleFileUpload(file);\n        }}\n      />\n    </div>\n  ),\n}));\n\n// Mock Modal component\nvi.mock('renderer/components/Modal', () => ({\n  __esModule: true,\n  default: ({isOpen, onClose, title, children}: any) =>\n    isOpen ? (\n      <div data-testid=\"modal\">\n        <h2>{title}</h2>\n        {children}\n        <button type=\"button\" onClick={onClose}>\n          Close\n        </button>\n      </div>\n    ) : null,\n}));\n\n// Mock Button component\nvi.mock('renderer/components/Button', () => ({\n  __esModule: true,\n  default: ({children, onClick, isPrimary, isTextButton}: any) => (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      data-primary={isPrimary}\n      data-text-button={isTextButton}\n    >\n      {children}\n    </button>\n  ),\n}));\n\nconst mockDevice: Device = {\n  id: '10019',\n  type: 'phone',\n  dpr: 3,\n  capabilities: ['touch', 'mobile'],\n  isTouchCapable: true,\n  isMobileCapable: true,\n  name: 'iPhone 13',\n  width: 390,\n  height: 844,\n  userAgent:\n    'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',\n};\n\nconst createStore = (initialState = {}) =>\n  configureStore({\n    reducer: {\n      designOverlay: designOverlayReducer,\n    },\n    preloadedState: {\n      designOverlay: initialState,\n    },\n  });\n\nconst renderWithRedux = (component: React.ReactElement, initialState = {}) => {\n  const store = createStore(initialState);\n  return {\n    ...render(<Provider store={store}>{component}</Provider>),\n    store,\n  };\n};\n\ndescribe('DesignOverlayControls', () => {\n  const mockOnClose = vi.fn();\n\n  beforeEach(() => {\n    mockOnClose.mockClear();\n  });\n\n  it('renders modal when isOpen is true', () => {\n    renderWithRedux(<DesignOverlayControls device={mockDevice} isOpen onClose={mockOnClose} />);\n\n    expect(screen.getByTestId('modal')).toBeInTheDocument();\n    expect(screen.getByText('Design Overlay Settings')).toBeInTheDocument();\n  });\n\n  it('does not render modal when isOpen is false', () => {\n    renderWithRedux(\n      <DesignOverlayControls device={mockDevice} isOpen={false} onClose={mockOnClose} />\n    );\n\n    expect(screen.queryByTestId('modal')).not.toBeInTheDocument();\n  });\n\n  it('calls onClose when cancel button is clicked', () => {\n    renderWithRedux(<DesignOverlayControls device={mockDevice} isOpen onClose={mockOnClose} />);\n\n    const cancelButton = screen.getByText('Cancel');\n    fireEvent.click(cancelButton);\n\n    expect(mockOnClose).toHaveBeenCalledTimes(1);\n  });\n\n  it('shows opacity slider when image is uploaded', async () => {\n    const {store} = renderWithRedux(\n      <DesignOverlayControls device={mockDevice} isOpen onClose={mockOnClose} />\n    );\n\n    const fileInput = screen.getByTestId('file-input');\n    const mockFile = new File(['dummy'], 'test.png', {type: 'image/png'});\n\n    fireEvent.change(fileInput, {target: {files: [mockFile]}});\n\n    await waitFor(() => {\n      expect(screen.getByRole('slider')).toBeInTheDocument();\n    });\n  });\n\n  it('updates opacity when slider is moved', async () => {\n    const {store} = renderWithRedux(\n      <DesignOverlayControls device={mockDevice} isOpen onClose={mockOnClose} />\n    );\n\n    const fileInput = screen.getByTestId('file-input');\n    const mockFile = new File(['dummy'], 'test.png', {type: 'image/png'});\n\n    fireEvent.change(fileInput, {target: {files: [mockFile]}});\n\n    await waitFor(() => {\n      const opacitySlider = screen.getByRole('slider');\n      expect(opacitySlider).toBeInTheDocument();\n\n      fireEvent.change(opacitySlider, {target: {value: '75'}});\n      expect(opacitySlider).toHaveValue('75');\n    });\n  });\n\n  it('saves overlay when save button is clicked', async () => {\n    const {store} = renderWithRedux(\n      <DesignOverlayControls device={mockDevice} isOpen onClose={mockOnClose} />\n    );\n\n    const fileInput = screen.getByTestId('file-input');\n    const mockFile = new File(['dummy'], 'test.png', {type: 'image/png'});\n\n    fireEvent.change(fileInput, {target: {files: [mockFile]}});\n\n    await waitFor(() => {\n      const saveButton = screen.getByText('Save');\n      fireEvent.click(saveButton);\n\n      const state = store.getState();\n      const overlay = state.designOverlay['390x844'];\n      expect(overlay).toBeDefined();\n      expect(overlay?.enabled).toBe(true);\n      expect(mockOnClose).toHaveBeenCalled();\n    });\n  });\n\n  it('removes overlay when remove button is clicked', async () => {\n    const initialState = {\n      '390x844': {\n        image: 'data:image/png;base64,test',\n        opacity: 50,\n        position: 'overlay' as const,\n        enabled: true,\n      },\n    };\n\n    const {store} = renderWithRedux(\n      <DesignOverlayControls device={mockDevice} isOpen onClose={mockOnClose} />,\n      initialState\n    );\n\n    await waitFor(() => {\n      const removeButton = screen.getByText('Remove');\n      fireEvent.click(removeButton);\n\n      const state = store.getState();\n      expect(state.designOverlay['390x844']).toBeUndefined();\n      expect(mockOnClose).toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/DesignOverlayControls/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport type {Device} from 'common/deviceList';\nimport {useEffect, useState} from 'react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport {FileUploader} from 'renderer/components/FileUploader';\nimport Modal from 'renderer/components/Modal';\nimport type {RootState} from 'renderer/store';\nimport {\n  DesignOverlayPosition,\n  removeDesignOverlay,\n  selectDesignOverlay,\n  setDesignOverlay,\n} from 'renderer/store/features/design-overlay';\n\ninterface Props {\n  device: Device;\n  isOpen: boolean;\n  onClose: () => void;\n}\n\nconst DesignOverlayControls = ({device, isOpen, onClose}: Props) => {\n  const dispatch = useDispatch();\n  const resolution = `${device.width}x${device.height}`;\n  const existingOverlay = useSelector((state: RootState) => selectDesignOverlay(state)(resolution));\n\n  const [image, setImage] = useState<string>(existingOverlay?.image || '');\n  const [fileName, setFileName] = useState<string>(existingOverlay?.fileName || '');\n  const [opacity, setOpacity] = useState<number>(existingOverlay?.opacity ?? 50);\n  const [position, setPosition] = useState<DesignOverlayPosition>(\n    existingOverlay?.position || 'overlay'\n  );\n  const [enabled, setEnabled] = useState<boolean>(existingOverlay?.enabled ?? false);\n\n  useEffect(() => {\n    if (existingOverlay) {\n      setImage(existingOverlay.image);\n      setFileName(existingOverlay.fileName || '');\n      setOpacity(existingOverlay.opacity);\n      setPosition(existingOverlay.position);\n      setEnabled(existingOverlay.enabled);\n    }\n  }, [existingOverlay]);\n\n  const handleFileUpload = (file: File) => {\n    if (!file.type.startsWith('image/')) {\n      // eslint-disable-next-line no-console\n      console.error('File is not an image');\n      return;\n    }\n\n    setFileName(file.name);\n\n    const reader = new FileReader();\n    reader.onloadend = () => {\n      const base64String = reader.result as string;\n      setImage(base64String);\n    };\n    reader.readAsDataURL(file);\n  };\n\n  const handleSave = () => {\n    if (!image) {\n      return;\n    }\n\n    dispatch(\n      setDesignOverlay({\n        resolution,\n        overlayState: {\n          image,\n          opacity,\n          position,\n          enabled: true,\n          fileName,\n        },\n      })\n    );\n    onClose();\n  };\n\n  const handleRemove = () => {\n    dispatch(removeDesignOverlay({resolution}));\n    setImage('');\n    setFileName('');\n    setOpacity(50);\n    setPosition('overlay');\n    setEnabled(false);\n    onClose();\n  };\n\n  const handleToggleEnabled = () => {\n    if (!image) {\n      return;\n    }\n\n    const newEnabled = !enabled;\n    setEnabled(newEnabled);\n    dispatch(\n      setDesignOverlay({\n        resolution,\n        overlayState: {\n          image,\n          opacity,\n          position,\n          enabled: newEnabled,\n          fileName,\n        },\n      })\n    );\n  };\n\n  return (\n    <Modal isOpen={isOpen} onClose={onClose} title=\"Design Overlay Settings\">\n      <div className=\"flex max-w-[420px] flex-col gap-4\">\n        {/* File Uploader */}\n        <div className=\"flex flex-col gap-2\">\n          <div className=\"text-sm font-medium\">Upload Design Image</div>\n          <FileUploader\n            handleFileUpload={handleFileUpload}\n            acceptedFileTypes=\"image/*\"\n            showFileName\n            initialFileName={fileName}\n          />\n        </div>\n\n        {image && (\n          <>\n            {/* Opacity slider */}\n            <div className=\"flex flex-col gap-2\">\n              <div className=\"text-sm font-medium\">Opacity: {opacity}</div>\n              <input\n                type=\"range\"\n                min=\"0\"\n                max=\"100\"\n                value={opacity}\n                onChange={(e) => setOpacity(parseInt(e.target.value, 10))}\n                className=\"h-2 w-full cursor-pointer appearance-none rounded-lg bg-slate-300 dark:bg-slate-600\"\n              />\n            </div>\n\n            {/* Position Selector */}\n            <div className=\"flex flex-col gap-2\">\n              <div className=\"text-sm font-medium\">Position</div>\n              <div className=\"flex gap-2\">\n                <Button\n                  onClick={() => setPosition('overlay')}\n                  isActive={position === 'overlay'}\n                  className=\"flex-1\"\n                >\n                  <Icon icon=\"lucide:layers\" className=\"mr-1\" />\n                  Overlay\n                </Button>\n                <Button\n                  onClick={() => setPosition('side')}\n                  isActive={position === 'side'}\n                  className=\"flex-1\"\n                >\n                  <Icon icon=\"lucide:layout-grid\" className=\"mr-1\" />\n                  Side-by-side\n                </Button>\n              </div>\n            </div>\n\n            {/* Enable Toggle */}\n            <div className=\"flex items-center gap-2\">\n              <input\n                type=\"checkbox\"\n                id=\"enabled\"\n                checked={enabled}\n                onChange={handleToggleEnabled}\n                className=\"h-4 w-4 cursor-pointer rounded border-slate-400\"\n              />\n              {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}\n              <label className=\"text-sm font-medium\" htmlFor=\"enabled\">\n                Enable overlay\n              </label>\n            </div>\n          </>\n        )}\n\n        {/* Action Buttons */}\n        <div className=\"flex justify-end gap-2\">\n          {image && (\n            <Button onClick={handleRemove} className=\"px-4\">\n              <Icon icon=\"lucide:trash-2\" className=\"mr-1\" />\n              Remove\n            </Button>\n          )}\n          <Button onClick={onClose} className=\"px-4\">\n            Cancel\n          </Button>\n          {image && (\n            <Button onClick={handleSave} isPrimary className=\"px-4\">\n              Save\n            </Button>\n          )}\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default DesignOverlayControls;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/Toolbar.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useState} from 'react';\nimport Button from 'renderer/components/Button';\nimport useSound from 'use-sound';\nimport {ScreenshotArgs, ScreenshotResult} from 'main/screenshot';\nimport {Device} from 'common/deviceList';\nimport WebPage from 'main/screenshot/webpage';\n\nimport screenshotSfx from 'renderer/assets/sfx/screenshot.mp3';\nimport {updateWebViewHeightAndScale} from 'common/webViewUtils';\nimport {ColorBlindnessTools} from './ColorBlindnessTools';\nimport DesignOverlayControls from './DesignOverlayControls';\n\ninterface Props {\n  webview: Electron.WebviewTag | null;\n  device: Device;\n  setScreenshotInProgress: (value: boolean) => void;\n  openDevTools: () => void;\n  toggleRuler: () => void;\n  onRotate: (state: boolean) => void;\n  onIndividualLayoutHandler: (device: Device) => void;\n  isIndividualLayout: boolean;\n  isDeviceRotationEnabled: boolean;\n}\n\nconst Toolbar = ({\n  webview,\n  device,\n  setScreenshotInProgress,\n  openDevTools,\n  toggleRuler,\n  onRotate,\n  onIndividualLayoutHandler,\n  isIndividualLayout,\n  isDeviceRotationEnabled,\n}: Props) => {\n  const [eventMirroringOff, setEventMirroringOff] = useState<boolean>(false);\n  const [playScreenshotDone] = useSound(screenshotSfx, {volume: 0.5});\n  const [screenshotLoading, setScreenshotLoading] = useState<boolean>(false);\n  const [fullScreenshotLoading, setFullScreenshotLoading] = useState<boolean>(false);\n  const [rotated, setRotated] = useState<boolean>(false);\n  const [isDesignOverlayModalOpen, setIsDesignOverlayModalOpen] = useState<boolean>(false);\n\n  const refreshView = () => {\n    if (webview) {\n      webview.reload();\n    }\n  };\n\n  const toggleEventMirroring = async () => {\n    if (webview === null) {\n      return;\n    }\n    try {\n      await webview.executeJavaScript(\n        `\n        if(window.___browserSync___){\n          window.___browserSync___.socket.${eventMirroringOff ? 'open' : 'close'}()\n        }\n        true\n      `\n      );\n      setEventMirroringOff(!eventMirroringOff);\n    } catch (error) {\n      // eslint-disable-next-line no-console\n      console.error('Error while toggleing event mirroring', error);\n    }\n  };\n\n  const quickScreenshot = async () => {\n    if (webview === null) {\n      return;\n    }\n    setScreenshotLoading(true);\n    try {\n      await window.electron.ipcRenderer.invoke<ScreenshotArgs, ScreenshotResult>('screenshot', {\n        webContentsId: webview.getWebContentsId(),\n        device,\n      });\n      playScreenshotDone();\n    } catch (error) {\n      // eslint-disable-next-line no-console\n      console.error('Error while taking quick screenshot', error);\n    }\n    setScreenshotLoading(false);\n  };\n\n  const fullScreenshot = async () => {\n    if (webview === null) {\n      return;\n    }\n    setFullScreenshotLoading(true);\n    try {\n      const webviewTag = window.document.getElementById(device.name);\n      if (webviewTag === null) {\n        return;\n      }\n      setScreenshotInProgress(true);\n      const webPage = new WebPage(webview as unknown as Electron.WebContents);\n      const pageHeight = await webPage.getPageHeight();\n\n      const previousHeight = webviewTag.style.height;\n      const previousTransform = webviewTag.style.transform;\n      updateWebViewHeightAndScale(webviewTag, pageHeight);\n\n      await new Promise((resolve) => setTimeout(resolve, 1000));\n\n      await window.electron.ipcRenderer.invoke<ScreenshotArgs, ScreenshotResult>('screenshot', {\n        webContentsId: webview.getWebContentsId(),\n        device,\n      });\n\n      webviewTag.style.height = previousHeight;\n      webviewTag.style.transform = previousTransform;\n      setScreenshotInProgress(false);\n      playScreenshotDone();\n    } catch (error) {\n      // eslint-disable-next-line no-console\n      console.error('Error while taking full screenshot', error);\n    }\n    setFullScreenshotLoading(false);\n  };\n\n  const toggleRulers = async () => {\n    if (webview === null) {\n      return;\n    }\n    toggleRuler();\n  };\n\n  const rotate = async () => {\n    setRotated(!rotated);\n    onRotate(!rotated);\n  };\n\n  const scrollToTop = () => {\n    if (webview) {\n      webview.executeJavaScript('window.scrollTo({ top: 0, behavior: \"smooth\" })', false);\n    }\n  };\n\n  return (\n    <div className=\"flex items-center justify-between gap-1\">\n      <div className=\"my-1 inline-flex max-w-[78%] items-center gap-1 overflow-x-auto\">\n        <Button onClick={refreshView} title=\"Refresh This View\">\n          <Icon icon=\"ic:round-refresh\" />\n        </Button>\n        <Button onClick={quickScreenshot} isLoading={screenshotLoading} title=\"Quick Screenshot\">\n          <div className=\"relative h-4 w-4\">\n            <Icon icon=\"ic:outline-photo-camera\" className=\"absolute left-0 top-0\" />\n            <Icon\n              icon=\"clarity:lightning-solid\"\n              className=\"absolute right-[-2px] top-[-1px]\"\n              height={8}\n            />\n          </div>\n        </Button>\n        <Button\n          onClick={fullScreenshot}\n          isLoading={fullScreenshotLoading}\n          title=\"Full Page Screenshot\"\n        >\n          <Icon icon=\"ic:outline-photo-camera\" />\n        </Button>\n        <Button onClick={() => setIsDesignOverlayModalOpen(true)} title=\"Design Overlay\">\n          <Icon icon=\"lucide:layers\" />\n        </Button>\n        <Button\n          onClick={toggleEventMirroring}\n          isActive={eventMirroringOff}\n          title=\"Disable Event Mirroring\"\n        >\n          <Icon icon=\"fluent:plug-disconnected-24-regular\" />\n        </Button>\n        <Button onClick={openDevTools} title=\"Open Devtools\">\n          <Icon icon=\"ic:round-code\" />\n        </Button>\n        <Button\n          onClick={rotate}\n          disabled={!isDeviceRotationEnabled}\n          title={\n            isDeviceRotationEnabled\n              ? 'Rotate This Device'\n              : 'Rotation not available for non-mobile devices'\n          }\n        >\n          <Icon icon={rotated ? 'mdi:phone-rotate-portrait' : 'mdi:phone-rotate-landscape'} />\n        </Button>\n        <Button onClick={scrollToTop} title=\"Scroll to Top\">\n          <Icon icon=\"ic:baseline-arrow-upward\" />\n        </Button>\n        <Button onClick={toggleRulers} title=\"Show rulers\">\n          <Icon icon=\"tdesign:measurement-1\" />\n        </Button>\n        <ColorBlindnessTools webview={webview} />\n      </div>\n      <Button\n        onClick={() => onIndividualLayoutHandler(device)}\n        title={`${isIndividualLayout ? 'Disable' : 'Enable'} Individual Layout`}\n      >\n        <Icon icon={isIndividualLayout ? 'ic:twotone-zoom-in-map' : 'ic:twotone-zoom-out-map'} />\n      </Button>\n      <DesignOverlayControls\n        device={device}\n        isOpen={isDesignOverlayModalOpen}\n        onClose={() => setIsDesignOverlayModalOpen(false)}\n      />\n    </div>\n  );\n};\n\nexport default Toolbar;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/assets.ts",
    "content": "export const grid = (size: number) =>\n  `body:after{background-image:linear-gradient(to right,rgb(203 213 225) 1px,transparent 1px),linear-gradient(to bottom,rgb(203 213 225) 1px,transparent 1px);background-size:${size}rem ${size}rem;background-position:center center;position:absolute;top:0;bottom:0;left:0;display:block;right:0;height:400%;width:100%;z-index:1;content:\"\";pointer-events:none;opacity:.5}`;\nexport const layout = `body{outline:1px solid #2980b9!important}article{outline:1px solid #3498db!important}nav{outline:1px solid #0088c3!important}aside{outline:1px solid #33a0ce!important}section{outline:1px solid #66b8da!important}header{outline:1px solid #99cfe7!important}footer{outline:1px solid #cce7f3!important}h1{outline:1px solid #162544!important}h2{outline:1px solid #314e6e!important}h3{outline:1px solid #3e5e85!important}h4{outline:1px solid #449baf!important}h5{outline:1px solid #c7d1cb!important}h6{outline:1px solid #4371d0!important}main{outline:1px solid #2f4f90!important}address{outline:1px solid #1a2c51!important}div{outline:1px solid #036cdb!important}p{outline:1px solid #ac050b!important}hr{outline:1px solid #ff063f!important}pre{outline:1px solid #850440!important}blockquote{outline:1px solid #f1b8e7!important}ol{outline:1px solid #ff050c!important}ul{outline:1px solid #d90416!important}li{outline:1px solid #d90416!important}dl{outline:1px solid #fd3427!important}dt{outline:1px solid #ff0043!important}dd{outline:1px solid #e80174!important}figure{outline:1px solid #f0b!important}figcaption{outline:1px solid #bf0032!important}table{outline:1px solid #0c9!important}caption{outline:1px solid #37ffc4!important}thead{outline:1px solid #98daca!important}tbody{outline:1px solid #64a7a0!important}tfoot{outline:1px solid #22746b!important}tr{outline:1px solid #86c0b2!important}th{outline:1px solid #a1e7d6!important}td{outline:1px solid #3f5a54!important}col{outline:1px solid #6c9a8f!important}colgroup{outline:1px solid #6c9a9d!important}button{outline:1px solid #da8301!important}datalist{outline:1px solid #c06000!important}fieldset{outline:1px solid #d95100!important}form{outline:1px solid #d23600!important}input{outline:1px solid #fca600!important}keygen{outline:1px solid #b31e00!important}label{outline:1px solid #ee8900!important}legend{outline:1px solid #de6d00!important}meter{outline:1px solid #e8630c!important}optgroup{outline:1px solid #b33600!important}option{outline:1px solid #ff8a00!important}output{outline:1px solid #ff9619!important}progress{outline:1px solid #e57c00!important}select{outline:1px solid #e26e0f!important}textarea{outline:1px solid #cc5400!important}details{outline:1px solid #33848f!important}summary{outline:1px solid #60a1a6!important}command{outline:1px solid #438da1!important}menu{outline:1px solid #449da6!important}del{outline:1px solid #bf0000!important}ins{outline:1px solid #400000!important}img{outline:1px solid #22746b!important}iframe{outline:1px solid #64a7a0!important}embed{outline:1px solid #98daca!important}object{outline:1px solid #0c9!important}param{outline:1px solid #37ffc4!important}video{outline:1px solid #6ee866!important}audio{outline:1px solid #027353!important}source{outline:1px solid #012426!important}canvas{outline:1px solid #a2f570!important}track{outline:1px solid #59a600!important}map{outline:1px solid #7be500!important}area{outline:1px solid #305900!important}a{outline:1px solid #ff62ab!important}em{outline:1px solid #800b41!important}strong{outline:1px solid #ff1583!important}i{outline:1px solid #803156!important}b{outline:1px solid #cc1169!important}u{outline:1px solid #ff0430!important}s{outline:1px solid #f805e3!important}small{outline:1px solid #d107b2!important}abbr{outline:1px solid #4a0263!important}q{outline:1px solid #240018!important}cite{outline:1px solid #64003c!important}dfn{outline:1px solid #b4005a!important}sub{outline:1px solid #dba0c8!important}sup{outline:1px solid #cc0256!important}time{outline:1px solid #d6606d!important}code{outline:1px solid #e04251!important}kbd{outline:1px solid #5e001f!important}samp{outline:1px solid #9c0033!important}var{outline:1px solid #d90047!important}mark{outline:1px solid #ff0053!important}bdi{outline:1px solid #bf3668!important}bdo{outline:1px solid #6f1400!important}ruby{outline:1px solid #ff7b93!important}rt{outline:1px solid #ff2f54!important}rp{outline:1px solid #803e49!important}span{outline:1px solid #cc2643!important}br{outline:1px solid #db687d!important}wbr{outline:1px solid #db175b!important}article:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}nav:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}aside:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}section:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}header:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}footer:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}h1:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}h2:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}h3:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}h4:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}h5:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}h6:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}main:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}address:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}div:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}p:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}hr:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}pre:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}blockquote:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}ol:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}ul:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}li:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}dl:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}dt:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}dd:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}figure:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}figcaption:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}table:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}caption:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}thead:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}tbody:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}tfoot:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}tr:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}th:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}td:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}col:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}colgroup:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}button:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}datalist:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}fieldset:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}form:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}input:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}keygen:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}label:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}legend:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}meter:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}optgroup:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}option:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}output:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}progress:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}select:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}textarea:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}details:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}summary:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}command:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}menu:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}del:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}ins:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}img:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}iframe:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}embed:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}object:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}param:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}video:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}audio:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}source:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}canvas:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}track:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}map:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}area:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}a:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}em:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}strong:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}i:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}b:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}u:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}s:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}small:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}abbr:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}q:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}cite:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}dfn:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}sub:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}sup:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}time:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}code:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}kbd:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}samp:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}var:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}mark:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}bdi:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}bdo:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}ruby:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}rt:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}rp:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}span:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}br:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}wbr:hover{-webkit-box-shadow:0 0 1rem rgba(0,0,0,.6);box-shadow:0 0 1rem rgba(0,0,0,.6);}\n#debugCSSInfoBar {visibility:hidden; position: fixed; bottom: 0; width: 100%; padding: 20px 0; margin:0; background-color: #333; color: #eee; font-size: 16px; text-align: center; outline: none !important;-webkit-box-shadow:none !important;box-shadow:none !important;}\n#debugCSSInfoBar.show{visibility:visible;}\n#debugCSSInfoBar p {font-family: monospace; font-size: 16px; padding: 0; margin: 0; outline: none !important;-webkit-box-shadow:none !important;box-shadow:none !important;background-color:inherit !important;}\n#debugCSSInfoBar b {color:#0088C3;outline: none !important;-webkit-box-shadow:none !important;box-shadow:none !important;background-color:inherit !important}`;\nexport const a11ycss = `:not(colgroup)>col:after,:not(dl)>dd:after,:not(dl)>dt:after,:not(dt,dd)+dd:after,:not(fieldset)>legend:after,:not(figure)>figcaption:after,:not(img,object,embed,svg,canvas)[height]:after,:not(img,object,embed,svg,canvas)[width]:after,:not(select)>optgroup:after,:not(select,optgroup)>option:after,:not(tr)>td:after,:not(tr)>th:after,:not(ul,ol)>li:after,[accesskey]:after,[aria-hidden=true]:not(:empty):after,[aria-required]+:before,[class*=NaN]:after,[class*=null]:after,[class*=search]:not([role=search]):after,[class*=undefined]:after,[class=\" \"]:after,[class=\"\"]:after,[class^=\"--\"]:after,[class^=\"-0\"]:after,[class^=\"-1\"]:after,[class^=\"-2\"]:after,[class^=\"-3\"]:after,[class^=\"-4\"]:after,[class^=\"-5\"]:after,[class^=\"-6\"]:after,[class^=\"-7\"]:after,[class^=\"-8\"]:after,[class^=\"-9\"]:after,[class^=\"0\"]:after,[class^=\"1\"]:after,[class^=\"2\"]:after,[class^=\"3\"]:after,[class^=\"4\"]:after,[class^=\"5\"]:after,[class^=\"6\"]:after,[class^=\"7\"]:after,[class^=\"8\"]:after,[class^=\"9\"]:after,[datetime]:after,[dir=rtl]:not([lang=ar],[lang=he]):after,[dir]:not([dir=rtl],[dir=ltr],[dir=auto]):after,[download]:after,[dropzone]:after,[hidden]:not(:empty):after,[href$=\".apng\"]:not(link):after,[href$=\".doc\"]:not(link):after,[href$=\".docx\"]:not(link):after,[href$=\".gif\"]:not(link):after,[href$=\".ics\"]:not(link):after,[href$=\".jpg\"]:not(link):after,[href$=\".mov\"]:not(link):after,[href$=\".mp3\"]:not(link):after,[href$=\".mp4\"]:not(link):after,[href$=\".ogg\"]:not(link):after,[href$=\".pdf\"]:not(link):after,[href$=\".png\"]:not(link):after,[href$=\".rar\"]:not(link):after,[href$=\".svg\"]:not(link):after,[href$=\".svgz\"]:not(link):after,[href$=\".txt\"]:not(link):after,[href$=\".webp\"]:not(link):after,[href$=\".xls\"]:not(link):after,[href$=\".zip\"]:not(link):after,[href^=\"http:\"]:after,[href^=mailto]:after,[href^=tel]:after,[id*=\" \"]:after,[id*=NaN]:after,[id*=null]:after,[id*=search]:not([role=search]):after,[id*=undefined]:after,[id=\" \"]:after,[id=\"\"]:after,[id^=\"--\"]:after,[id^=\"-0\"]:after,[id^=\"-1\"]:after,[id^=\"-2\"]:after,[id^=\"-3\"]:after,[id^=\"-4\"]:after,[id^=\"-5\"]:after,[id^=\"-6\"]:after,[id^=\"-7\"]:after,[id^=\"-8\"]:after,[id^=\"-9\"]:after,[id^=\"0\"]:after,[id^=\"1\"]:after,[id^=\"2\"]:after,[id^=\"3\"]:after,[id^=\"4\"]:after,[id^=\"5\"]:after,[id^=\"6\"]:after,[id^=\"7\"]:after,[id^=\"8\"]:after,[id^=\"9\"]:after,[lang*=\" \"]:after,[lang=ar] [lang]:not([dir=ltr]):after,[lang=ar]:not([dir=rtl]):after,[lang=he] [lang]:not([dir=ltr]):after,[lang=he]:not([dir=rtl]):after,[onabort]:after,[onafterprint]:after,[onbeforeprint]:after,[onbeforeunload]:after,[onblur]:after,[oncanplay]:after,[oncanplaythrough]:after,[onchage]:after,[onclick]:after,[oncontextmenu]:after,[ondblclick]:after,[ondrag]:after,[ondragend]:after,[ondragenter]:after,[ondragleave]:after,[ondragover]:after,[ondragstart]:after,[ondrop]:after,[ondurationchange]:after,[onemptied]:after,[onended]:after,[onerror]:after,[onfocus]:after,[onformchange]:after,[onforminput]:after,[onhaschange]:after,[oninput]:after,[oninvalid]:after,[onkeydown]:after,[onkeypress]:after,[onkeyup]:after,[onload]:after,[onloadeddata]:after,[onloadedmetadata]:after,[onloadstart]:after,[onmessage]:after,[onmousedown]:after,[onmousemove]:after,[onmouseout]:after,[onmouseover]:after,[onmouseup]:after,[onmousewheel]:after,[onoffline]:after,[ononline]:after,[onpagehide]:after,[onpageshow]:after,[onpause]:after,[onplay]:after,[onplaying]:after,[onpopstate]:after,[onprogress]:after,[onratechange]:after,[onreadystatechange]:after,[onredo]:after,[onreset]:after,[onresize]:after,[onscroll]:after,[onseeked]:after,[onseeking]:after,[onselect]:after,[onstalled]:after,[onstorage]:after,[onsubmit]:after,[onsuspend]:after,[ontimeupdate]:after,[onundo]:after,[onunload]:after,[onvolumechange]:after,[onwaiting]:after,[placeholder]:not([title],[aria-label],[aria-labelledby])+:before,[required]+:before,[role=banner]~[role=banner]:after,[role=checkbox]:not([aria-checked]):after,[role=combobox]:not([aria-expanded]):after,[role=contentinfo]~[role=contentinfo]:after,[role=heading]:not([aria-level]):after,[role=img]:not([aria-hidden=true],[aria-label],[aria-labelledby]):after,[role=main]~[role=main]:after,[role=scrollbar]:not([aria-controls]):after,[role=scrollbar]:not([aria-orientation]):after,[role=scrollbar]:not([aria-valuemax]):after,[role=scrollbar]:not([aria-valuemin]):after,[role=scrollbar]:not([aria-valuenow]):after,[role=search]~[role=search]:after,[role=slider]:not([aria-valuemax]):after,[role=slider]:not([aria-valuemin]):after,[role=slider]:not([aria-valuenow]):after,[role=spinbutton]:not([aria-valuemax]):after,[role=spinbutton]:not([aria-valuemin]):after,[role=spinbutton]:not([aria-valuenow]):after,[src^=\"http:\"]:after,[style]:after,[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]):after,[target$=blank]:after,[target$=blank]:not([rel*=noopener]):after,[target$=blank]:not([rel*=noreferrer]):after,[target$=blank]:not([rel]):after,[type=checkbox]:not(:only-of-type,[name])+:before,[type=radio]+:before,[type=radio]:not([name])+:before,a a[href]:after,a audio[controls]:after,a button:after,a details:after,a embed:after,a iframe:after,a img[usemap]:after,a input[type]:not([hidden]):after,a label:after,a select:after,a textarea:after,a video[controls]:after,a:empty:not([title],[aria-label],[aria-labelledby]):after,a:empty[aria-label=\"\"]:after,a:empty[aria-labelledby=\"\"]:after,a:empty[title=\"\"]:after,a:not([href]):after,a[charset]:after,a[coords]:after,a[datafld]:after,a[datasrc]:after,a[href=\" \"]:after,a[href=\"\"]:after,a[href=\"#\"]:not([role=button]):after,a[href^=javascript]:not([role=button]):after,a[methods]:after,a[name]:after,a[rev]:after,a[role=button]:after,a[shape]:after,a[urn]:after,abbr div:after,abbr:not([title]):after,abbr[title=\" \"]:after,abbr[title=\"\"]:after,acronym:after,address address:after,address article:after,address aside:after,address footer:after,address h1:after,address h2:after,address h3:after,address h4:after,address h5:after,address h6:after,address header:after,address nav:after,address section:after,applet:after,applet[datafld]:after,applet[datasrc]:after,area:not([alt])+:before,area:not([href])[alt=\"\"][aria-describedby]+:before,area:not([href])[alt=\"\"][aria-label]+:before,area:not([href])[alt=\"\"][aria-labelledby]+:before,area:not([href])[alt=\"\"][title]+:before,area:not([href])[alt]:not([alt=\"\"])+:before,area[alt$=\".apng\"]+:before,area[alt$=\".doc\"]+:before,area[alt$=\".docx\"]+:before,area[alt$=\".gif\"]+:before,area[alt$=\".ics\"]+:before,area[alt$=\".jpg\"]+:before,area[alt$=\".mov\"]+:before,area[alt$=\".mp3\"]+:before,area[alt$=\".mp4\"]+:before,area[alt$=\".ogg\"]+:before,area[alt$=\".pdf\"]+:before,area[alt$=\".png\"]+:before,area[alt$=\".rar\"]+:before,area[alt$=\".svg\"]+:before,area[alt$=\".svgz\"]+:before,area[alt$=\".txt\"]+:before,area[alt$=\".webp\"]+:before,area[alt$=\".xls\"]+:before,area[alt$=\".zip\"]+:before,area[alt=\" \"]+:before,area[alt=\"\"]+:before,area[alt][title]+:before,area[aria-hidden=true]:not(:empty)+:before,area[class*=NaN]+:before,area[class*=null]+:before,area[class*=undefined]+:before,area[class=\" \"]+:before,area[class=\"\"]+:before,area[height]:not(img,object,embed,svg,canvas)+:before,area[hidden]:not(:empty)+:before,area[hreflang]+:before,area[id*=\" \"]+:before,area[id*=NaN]+:before,area[id*=null]+:before,area[id*=undefined]+:before,area[id=\" \"]+:before,area[id=\"\"]+:before,area[lang*=\" \"]+:before,area[nohref]+:before,area[role=checkbox]:not([aria-checked])+:before,area[role=combobox]:not([aria-expanded])+:before,area[role=presentation]+:before,area[role=slider]:not([aria-valuemax])+:before,area[role=slider]:not([aria-valuemin])+:before,area[role=slider]:not([aria-valuenow])+:before,area[role=spinbutton]:not([aria-valuemax])+:before,area[role=spinbutton]:not([aria-valuemin])+:before,area[role=spinbutton]:not([aria-valuenow])+:before,area[style]+:before,area[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,area[type]+:before,area[width]:not(img,object,embed,svg,canvas)+:before,article main:after,article>article:first-child:after,article>aside:first-child:after,article>section:first-child:after,aside main:after,aside>article:first-child:after,aside>aside:first-child:after,aside>section:first-child:after,audio+:before,audio:not([controls])+:before,audio[aria-hidden=true]:not(:empty)+:before,audio[autoplay]+:before,audio[class*=NaN]+:before,audio[class*=null]+:before,audio[class*=undefined]+:before,audio[class=\" \"]+:before,audio[class=\"\"]+:before,audio[height]:not(img,object,embed,svg,canvas)+:before,audio[hidden]:not(:empty)+:before,audio[id*=\" \"]+:before,audio[id*=NaN]+:before,audio[id*=null]+:before,audio[id*=undefined]+:before,audio[id=\" \"]+:before,audio[id=\"\"]+:before,audio[lang*=\" \"]+:before,audio[role=checkbox]:not([aria-checked])+:before,audio[role=combobox]:not([aria-expanded])+:before,audio[role=slider]:not([aria-valuemax])+:before,audio[role=slider]:not([aria-valuemin])+:before,audio[role=slider]:not([aria-valuenow])+:before,audio[role=spinbutton]:not([aria-valuemax])+:before,audio[role=spinbutton]:not([aria-valuemin])+:before,audio[role=spinbutton]:not([aria-valuenow])+:before,audio[style]+:before,audio[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,audio[width]:not(img,object,embed,svg,canvas)+:before,b div:after,base[aria-hidden=true]:not(:empty)+:before,base[class*=NaN]+:before,base[class*=null]+:before,base[class*=undefined]+:before,base[class=\" \"]+:before,base[class=\"\"]+:before,base[height]:not(img,object,embed,svg,canvas)+:before,base[hidden]:not(:empty)+:before,base[id*=\" \"]+:before,base[id*=NaN]+:before,base[id*=null]+:before,base[id*=undefined]+:before,base[id=\" \"]+:before,base[id=\"\"]+:before,base[lang*=\" \"]+:before,base[role=checkbox]:not([aria-checked])+:before,base[role=combobox]:not([aria-expanded])+:before,base[role=slider]:not([aria-valuemax])+:before,base[role=slider]:not([aria-valuemin])+:before,base[role=slider]:not([aria-valuenow])+:before,base[role=spinbutton]:not([aria-valuemax])+:before,base[role=spinbutton]:not([aria-valuemin])+:before,base[role=spinbutton]:not([aria-valuenow])+:before,base[style]+:before,base[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,base[width]:not(img,object,embed,svg,canvas)+:before,basefont:after,bgsound:after,big:after,blink:after,body :empty:not([hidden],[aria-hidden],[src],button,a,iframe,textarea,area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr,title):after,body[alink]:after,body[background]:after,body[bgcolor]:after,body[link]:after,body[marginbottom]:after,body[marginheight]:after,body[marginleft]:after,body[marginright]:after,body[margintop]:after,body[marginwidth]:after,body[text]:after,body[vlink]:after,br[aria-hidden=true]:not(:empty)+:before,br[class*=NaN]+:before,br[class*=null]+:before,br[class*=undefined]+:before,br[class=\" \"]+:before,br[class=\"\"]+:before,br[clear]+:before,br[height]:not(img,object,embed,svg,canvas)+:before,br[hidden]:not(:empty)+:before,br[id*=\" \"]+:before,br[id*=NaN]+:before,br[id*=null]+:before,br[id*=undefined]+:before,br[id=\" \"]+:before,br[id=\"\"]+:before,br[lang*=\" \"]+:before,br[role=checkbox]:not([aria-checked])+:before,br[role=combobox]:not([aria-expanded])+:before,br[role=slider]:not([aria-valuemax])+:before,br[role=slider]:not([aria-valuemin])+:before,br[role=slider]:not([aria-valuenow])+:before,br[role=spinbutton]:not([aria-valuemax])+:before,br[role=spinbutton]:not([aria-valuemin])+:before,br[role=spinbutton]:not([aria-valuenow])+:before,br[style]+:before,br[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,br[width]:not(img,object,embed,svg,canvas)+:before,button a[href]:after,button audio[controls]:after,button button:after,button details:after,button embed:after,button iframe:after,button img[usemap]:after,button input[type]:not([hidden]):after,button label:after,button select:after,button textarea:after,button video[controls]:after,button:empty:not([aria-label],[aria-labelledby],[title]):after,button:not([type],[form],[formaction],[formtarget]):after,button[aria-label=\"\"]:after,button[aria-labelledby=\"\"]:after,button[class*=disabled]:not([disabled],[readonly]):after,button[datafld]:after,button[dataformatas]:after,button[datasrc]:after,button[title=\"\"]:after,button[type=button][formaction]:after,button[type=button][formenctype]:after,button[type=button][formmethod]:after,button[type=button][formnovalidate]:after,button[type=button][formtarget]:after,button[type=reset][formaction]:after,button[type=reset][formenctype]:after,button[type=reset][formmethod]:after,button[type=reset][formnovalidate]:after,button[type=reset][formtarget]:after,canvas[aria-hidden=true][aria-describedby]:after,canvas[aria-hidden=true][aria-label]:after,canvas[aria-hidden=true][aria-labelledby]:after,canvas[aria-hidden=true][title]:after,canvas[role=presentation]:after,caption[align]:after,center:after,cite div:after,code div:after,col[align]:after,col[aria-hidden=true]:not(:empty)+:before,col[char]:after,col[charoff]:after,col[class*=NaN]+:before,col[class*=null]+:before,col[class*=undefined]+:before,col[class=\" \"]+:before,col[class=\"\"]+:before,col[height]:not(img,object,embed,svg,canvas)+:before,col[hidden]:not(:empty)+:before,col[id*=\" \"]+:before,col[id*=NaN]+:before,col[id*=null]+:before,col[id*=undefined]+:before,col[id=\" \"]+:before,col[id=\"\"]+:before,col[lang*=\" \"]+:before,col[role=checkbox]:not([aria-checked])+:before,col[role=combobox]:not([aria-expanded])+:before,col[role=slider]:not([aria-valuemax])+:before,col[role=slider]:not([aria-valuemin])+:before,col[role=slider]:not([aria-valuenow])+:before,col[role=spinbutton]:not([aria-valuemax])+:before,col[role=spinbutton]:not([aria-valuemin])+:before,col[role=spinbutton]:not([aria-valuenow])+:before,col[style]+:before,col[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,col[valign]:after,col[width]:after,col[width]:not(img,object,embed,svg,canvas)+:before,colgroup :not(col):after,command[aria-hidden=true]:not(:empty)+:before,command[class*=NaN]+:before,command[class*=null]+:before,command[class*=undefined]+:before,command[class=\" \"]+:before,command[class=\"\"]+:before,command[height]:not(img,object,embed,svg,canvas)+:before,command[hidden]:not(:empty)+:before,command[id*=\" \"]+:before,command[id*=NaN]+:before,command[id*=null]+:before,command[id*=undefined]+:before,command[id=\" \"]+:before,command[id=\"\"]+:before,command[lang*=\" \"]+:before,command[role=checkbox]:not([aria-checked])+:before,command[role=combobox]:not([aria-expanded])+:before,command[role=slider]:not([aria-valuemax])+:before,command[role=slider]:not([aria-valuemin])+:before,command[role=slider]:not([aria-valuenow])+:before,command[role=spinbutton]:not([aria-valuemax])+:before,command[role=spinbutton]:not([aria-valuemin])+:before,command[role=spinbutton]:not([aria-valuenow])+:before,command[style]+:before,command[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,command[width]:not(img,object,embed,svg,canvas)+:before,details>:not(summary):first-child:after,details>summary:not(:first-child):after,dir:after,div[align]:after,div[datafld]:after,div[dataformatas]:after,div[datasrc]:after,dl>:not(dt,dd,div):after,dl[compact]:after,dt+:not(dd):after,em div:after,embed[align]+:before,embed[aria-hidden=true]:not(:empty)+:before,embed[class*=NaN]+:before,embed[class*=null]+:before,embed[class*=undefined]+:before,embed[class=\" \"]+:before,embed[class=\"\"]+:before,embed[height]:not(img,object,embed,svg,canvas)+:before,embed[hidden]:not(:empty)+:before,embed[hspace]+:before,embed[id*=\" \"]+:before,embed[id*=NaN]+:before,embed[id*=null]+:before,embed[id*=undefined]+:before,embed[id=\" \"]+:before,embed[id=\"\"]+:before,embed[lang*=\" \"]+:before,embed[name]+:before,embed[name]:after,embed[role=checkbox]:not([aria-checked])+:before,embed[role=combobox]:not([aria-expanded])+:before,embed[role=presentation]+:before,embed[role=slider]:not([aria-valuemax])+:before,embed[role=slider]:not([aria-valuemin])+:before,embed[role=slider]:not([aria-valuenow])+:before,embed[role=spinbutton]:not([aria-valuemax])+:before,embed[role=spinbutton]:not([aria-valuemin])+:before,embed[role=spinbutton]:not([aria-valuenow])+:before,embed[style]+:before,embed[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,embed[type=image][alt$=\".apng\"]:after,embed[type=image][alt$=\".doc\"]:after,embed[type=image][alt$=\".docx\"]:after,embed[type=image][alt$=\".gif\"]:after,embed[type=image][alt$=\".ics\"]:after,embed[type=image][alt$=\".jpg\"]:after,embed[type=image][alt$=\".mov\"]:after,embed[type=image][alt$=\".mp3\"]:after,embed[type=image][alt$=\".mp4\"]:after,embed[type=image][alt$=\".ogg\"]:after,embed[type=image][alt$=\".pdf\"]:after,embed[type=image][alt$=\".png\"]:after,embed[type=image][alt$=\".rar\"]:after,embed[type=image][alt$=\".svg\"]:after,embed[type=image][alt$=\".svgz\"]:after,embed[type=image][alt$=\".txt\"]:after,embed[type=image][alt$=\".webp\"]:after,embed[type=image][alt$=\".xls\"]:after,embed[type=image][alt$=\".zip\"]:after,embed[type=image][alt=\"\"]:after,embed[type=image][aria-hidden=true][aria-describedby]+:before,embed[type=image][aria-hidden=true][aria-label]+:before,embed[type=image][aria-hidden=true][aria-labelledby]+:before,embed[type=image][aria-hidden=true][title]+:before,embed[vspace]+:before,embed[width]:not(img,object,embed,svg,canvas)+:before,fieldset>:not(legend):first-child:after,fieldset>legend:not(:first-child):after,fieldset[datafld]:after,figcaption:not(:first-child,:last-child):after,figcaption:not(:first-of-type):after,figure:not([role=group]):after,font:after,footer main:after,form form:after,form:not([action]):after,form[accept]:after,form[action=\" \"]:after,form[action=\"\"]:after,frame:after,frame[datafld]:after,frame[datasrc]:after,frameset:after,h1[align]:after,h2[align]:after,h3[align]:after,h4[align]:after,h5[align]:after,h6[align]:after,head :first-child:not([charset])~link:last-of-type:before,head[profile]:after,header main:after,hgroup:after,hr[align]+:before,hr[aria-hidden=true]:not(:empty)+:before,hr[class*=NaN]+:before,hr[class*=null]+:before,hr[class*=undefined]+:before,hr[class=\" \"]+:before,hr[class=\"\"]+:before,hr[color]+:before,hr[height]:not(img,object,embed,svg,canvas)+:before,hr[hidden]:not(:empty)+:before,hr[id*=\" \"]+:before,hr[id*=NaN]+:before,hr[id*=null]+:before,hr[id*=undefined]+:before,hr[id=\" \"]+:before,hr[id=\"\"]+:before,hr[lang*=\" \"]+:before,hr[noshade]+:before,hr[role=checkbox]:not([aria-checked])+:before,hr[role=combobox]:not([aria-expanded])+:before,hr[role=slider]:not([aria-valuemax])+:before,hr[role=slider]:not([aria-valuemin])+:before,hr[role=slider]:not([aria-valuenow])+:before,hr[role=spinbutton]:not([aria-valuemax])+:before,hr[role=spinbutton]:not([aria-valuemin])+:before,hr[role=spinbutton]:not([aria-valuenow])+:before,hr[size]+:before,hr[style]+:before,hr[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,hr[width]+:before,hr[width]:not(img,object,embed,svg,canvas)+:before,html:not([lang]):after,html[lang*=\" \"]:after,html[lang=\"\"]:after,html[version]:after,i div:after,iframe:not([title])+:before,iframe[align]+:before,iframe[allowtransparency]+:before,iframe[aria-hidden=true]:not(:empty)+:before,iframe[class*=NaN]+:before,iframe[class*=null]+:before,iframe[class*=undefined]+:before,iframe[class=\" \"]+:before,iframe[class=\"\"]+:before,iframe[datafld]+:before,iframe[datasrc]+:before,iframe[frameborder]+:before,iframe[framespacing]+:before,iframe[height]:not(img,object,embed,svg,canvas)+:before,iframe[hidden]:not(:empty)+:before,iframe[hspace]+:before,iframe[id*=\" \"]+:before,iframe[id*=NaN]+:before,iframe[id*=null]+:before,iframe[id*=undefined]+:before,iframe[id=\" \"]+:before,iframe[id=\"\"]+:before,iframe[lang*=\" \"]+:before,iframe[longdesc]+:before,iframe[marginheight]+:before,iframe[marginwidth]+:before,iframe[role=checkbox]:not([aria-checked])+:before,iframe[role=combobox]:not([aria-expanded])+:before,iframe[role=slider]:not([aria-valuemax])+:before,iframe[role=slider]:not([aria-valuemin])+:before,iframe[role=slider]:not([aria-valuenow])+:before,iframe[role=spinbutton]:not([aria-valuemax])+:before,iframe[role=spinbutton]:not([aria-valuemin])+:before,iframe[role=spinbutton]:not([aria-valuenow])+:before,iframe[scrolling]+:before,iframe[style]+:before,iframe[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,iframe[title=\" \"]+:before,iframe[title=\"\"]+:before,iframe[vspace]+:before,iframe[width]:not(img,object,embed,svg,canvas)+:before,img:not([alt])+:before,img:not([src],[srcset]):after,img[align]+:before,img[alt$=\".apng\"]+:before,img[alt$=\".doc\"]+:before,img[alt$=\".docx\"]+:before,img[alt$=\".gif\"]+:before,img[alt$=\".ics\"]+:before,img[alt$=\".jpg\"]+:before,img[alt$=\".mov\"]+:before,img[alt$=\".mp3\"]+:before,img[alt$=\".mp4\"]+:before,img[alt$=\".ogg\"]+:before,img[alt$=\".pdf\"]+:before,img[alt$=\".png\"]+:before,img[alt$=\".rar\"]+:before,img[alt$=\".svg\"]+:before,img[alt$=\".svgz\"]+:before,img[alt$=\".txt\"]+:before,img[alt$=\".webp\"]+:before,img[alt$=\".xls\"]+:before,img[alt$=\".zip\"]+:before,img[alt=\" \"]+:before,img[alt=\"\"]+:before,img[alt=\"\"][aria-describedby]+:before,img[alt=\"\"][aria-label]+:before,img[alt=\"\"][aria-labelledby]+:before,img[alt=\"\"][title]+:before,img[alt][title]+:before,img[aria-hidden=true]:not(:empty)+:before,img[border]+:before,img[class*=NaN]+:before,img[class*=null]+:before,img[class*=undefined]+:before,img[class=\" \"]+:before,img[class=\"\"]+:before,img[datafld]+:before,img[datasrc]+:before,img[height]:not(img,object,embed,svg,canvas)+:before,img[hidden]:not(:empty)+:before,img[hspace]+:before,img[id*=\" \"]+:before,img[id*=NaN]+:before,img[id*=null]+:before,img[id*=undefined]+:before,img[id=\" \"]+:before,img[id=\"\"]+:before,img[lang*=\" \"]+:before,img[longdesc]+:before,img[lowsrc]+:before,img[name]+:before,img[name]:after,img[role=checkbox]:not([aria-checked])+:before,img[role=combobox]:not([aria-expanded])+:before,img[role=presentation]+:before,img[role=slider]:not([aria-valuemax])+:before,img[role=slider]:not([aria-valuemin])+:before,img[role=slider]:not([aria-valuenow])+:before,img[role=spinbutton]:not([aria-valuemax])+:before,img[role=spinbutton]:not([aria-valuemin])+:before,img[role=spinbutton]:not([aria-valuenow])+:before,img[src*=\"1px.gif\"]:not([role=presentation])+:before,img[src*=\"1x1.gif\"]:not([role=presentation])+:before,img[src*=\"clear.gif\"]:not([role=presentation])+:before,img[src*=\"dotclear.gif\"]:not([role=presentation])+:before,img[src*=\"pixel-1x1-clear.gif\"]:not([role=presentation])+:before,img[src*=\"spacer.gif\"]:not([role=presentation])+:before,img[src*=\"transparent.gif\"]:not([role=presentation])+:before,img[src=\" \"]:after,img[src=\"\"]:after,img[src=\"#\"]:after,img[src=\"/\"]:after,img[srcset=\" \"]:after,img[srcset=\"\"]:after,img[srcset=\"#\"]:after,img[srcset=\"/\"]:after,img[style]+:before,img[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,img[vspace]+:before,img[width]:not(img,object,embed,svg,canvas)+:before,input:not([type=button],[type=submit],[type=hidden],[type=reset],[type=image],[id],[aria-label],[title],[aria-labelledby])+:before,input:not([type])+:before,input[align]+:before,input[aria-hidden=true]:not(:empty)+:before,input[aria-required]+:before,input[class*=NaN]+:before,input[class*=null]+:before,input[class*=search]:not([role=search])+:before,input[class*=undefined]+:before,input[class=\" \"]+:before,input[class=\"\"]+:before,input[datafld]+:before,input[dataformatas]+:before,input[datasrc]+:before,input[height]:not(img,object,embed,svg,canvas)+:before,input[hidden]:not(:empty)+:before,input[hspace]+:before,input[id*=\" \"]+:before,input[id*=NaN]+:before,input[id*=null]+:before,input[id*=search]:not([role=search])+:before,input[id*=undefined]+:before,input[id=\" \"]+:before,input[id=\"\"]+:before,input[inputmode]+:before,input[ismap]+:before,input[lang*=\" \"]+:before,input[required]+:before,input[role=checkbox]:not([aria-checked])+:before,input[role=combobox]:not([aria-expanded])+:before,input[role=slider]:not([aria-valuemax])+:before,input[role=slider]:not([aria-valuemin])+:before,input[role=slider]:not([aria-valuenow])+:before,input[role=spinbutton]:not([aria-valuemax])+:before,input[role=spinbutton]:not([aria-valuemin])+:before,input[role=spinbutton]:not([aria-valuenow])+:before,input[style]+:before,input[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,input[type=\" \"]+:before,input[type=\"\"]+:before,input[type=button]:not([value],[title],[aria-label],[aria-labelledby])+:before,input[type=image]:not([alt])+:before,input[type=image]:not([src],[srcset]):after,input[type=image][alt$=\".apng\"]+:before,input[type=image][alt$=\".doc\"]+:before,input[type=image][alt$=\".docx\"]+:before,input[type=image][alt$=\".gif\"]+:before,input[type=image][alt$=\".ics\"]+:before,input[type=image][alt$=\".jpg\"]+:before,input[type=image][alt$=\".mov\"]+:before,input[type=image][alt$=\".mp3\"]+:before,input[type=image][alt$=\".mp4\"]+:before,input[type=image][alt$=\".ogg\"]+:before,input[type=image][alt$=\".pdf\"]+:before,input[type=image][alt$=\".png\"]+:before,input[type=image][alt$=\".rar\"]+:before,input[type=image][alt$=\".svg\"]+:before,input[type=image][alt$=\".svgz\"]+:before,input[type=image][alt$=\".txt\"]+:before,input[type=image][alt$=\".webp\"]+:before,input[type=image][alt$=\".xls\"]+:before,input[type=image][alt$=\".zip\"]+:before,input[type=image][alt=\" \"]+:before,input[type=image][alt=\"\"]+:before,input[type=image][src=\" \"]:after,input[type=image][src=\"\"]:after,input[type=image][src=\"#\"]:after,input[type=image][src=\"/\"]:after,input[type=image][srcset=\" \"]:after,input[type=image][srcset=\"\"]:after,input[type=image][srcset=\"#\"]:after,input[type=image][srcset=\"/\"]:after,input[type=reset]:not([value],[title],[aria-label],[aria-labelledby])+:before,input[type=submit]:not([value],[title],[aria-label],[aria-labelledby])+:before,input[usemap]+:before,input[vspace]+:before,input[width]:not(img,object,embed,svg,canvas)+:before,isindex:after,keygen:after,keygen[aria-hidden=true]:not(:empty)+:before,keygen[class*=NaN]+:before,keygen[class*=null]+:before,keygen[class*=undefined]+:before,keygen[class=\" \"]+:before,keygen[class=\"\"]+:before,keygen[height]:not(img,object,embed,svg,canvas)+:before,keygen[hidden]:not(:empty)+:before,keygen[id*=\" \"]+:before,keygen[id*=NaN]+:before,keygen[id*=null]+:before,keygen[id*=undefined]+:before,keygen[id=\" \"]+:before,keygen[id=\"\"]+:before,keygen[lang*=\" \"]+:before,keygen[role=checkbox]:not([aria-checked])+:before,keygen[role=combobox]:not([aria-expanded])+:before,keygen[role=slider]:not([aria-valuemax])+:before,keygen[role=slider]:not([aria-valuemin])+:before,keygen[role=slider]:not([aria-valuenow])+:before,keygen[role=spinbutton]:not([aria-valuemax])+:before,keygen[role=spinbutton]:not([aria-valuemin])+:before,keygen[role=spinbutton]:not([aria-valuenow])+:before,keygen[style]+:before,keygen[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,keygen[width]:not(img,object,embed,svg,canvas)+:before,label div:after,label label:after,label:not([for]):after,label[datafld]:after,label[dataformatas]:after,label[datasrc]:after,label[for=\" \"]:after,label[for=\"\"]:after,legend[align]:after,legend[datafld]:after,legend[dataformatas]:after,legend[datasrc]:after,li[type]:after,link[aria-hidden=true]:not(:empty)+:before,link[charset]:after,link[charset]~link:last-of-type:before,link[class*=NaN]+:before,link[class*=null]+:before,link[class*=undefined]+:before,link[class=\" \"]+:before,link[class=\"\"]+:before,link[height]:not(img,object,embed,svg,canvas)+:before,link[hidden]:not(:empty)+:before,link[id*=\" \"]+:before,link[id*=NaN]+:before,link[id*=null]+:before,link[id*=undefined]+:before,link[id=\" \"]+:before,link[id=\"\"]+:before,link[lang*=\" \"]+:before,link[methods]:after,link[methods]~link:last-of-type:before,link[rev]:after,link[rev]~link:last-of-type:before,link[role=checkbox]:not([aria-checked])+:before,link[role=combobox]:not([aria-expanded])+:before,link[role=slider]:not([aria-valuemax])+:before,link[role=slider]:not([aria-valuemin])+:before,link[role=slider]:not([aria-valuenow])+:before,link[role=spinbutton]:not([aria-valuemax])+:before,link[role=spinbutton]:not([aria-valuemin])+:before,link[role=spinbutton]:not([aria-valuenow])+:before,link[style]+:before,link[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,link[target]:after,link[target]~link:last-of-type:before,link[urn]:after,link[urn]~link:last-of-type:before,link[width]:not(img,object,embed,svg,canvas)+:before,listing:after,main~main:not([hidden]):after,map[name*=\" \"]:after,marquee:after,marquee[datafld]:after,marquee[dataformatas]:after,marquee[datasrc]:after,menu:after,menuitem:after,meta[aria-hidden=true]:not(:empty)+:before,meta[charset]:not([charset=utf-8],[charset=UTF-8])~link:last-of-type:before,meta[class*=NaN]+:before,meta[class*=null]+:before,meta[class*=undefined]+:before,meta[class=\" \"]+:before,meta[class=\"\"]+:before,meta[height]:not(img,object,embed,svg,canvas)+:before,meta[hidden]:not(:empty)+:before,meta[http-equiv=content-language]~link:last-of-type:before,meta[http-equiv=content-type]~link:last-of-type:before,meta[http-equiv=set-cookie]~link:last-of-type:before,meta[id*=\" \"]+:before,meta[id*=NaN]+:before,meta[id*=null]+:before,meta[id*=undefined]+:before,meta[id=\" \"]+:before,meta[id=\"\"]+:before,meta[lang*=\" \"]+:before,meta[name=viewport][content*=\"user-scalable=no\"]~link:last-of-type:before,meta[name=viewport][content*=maximum-scale]~link:last-of-type:before,meta[name=viewport][content*=minimum-scale]~link:last-of-type:before,meta[role=checkbox]:not([aria-checked])+:before,meta[role=combobox]:not([aria-expanded])+:before,meta[role=slider]:not([aria-valuemax])+:before,meta[role=slider]:not([aria-valuemin])+:before,meta[role=slider]:not([aria-valuenow])+:before,meta[role=spinbutton]:not([aria-valuemax])+:before,meta[role=spinbutton]:not([aria-valuemin])+:before,meta[role=spinbutton]:not([aria-valuenow])+:before,meta[scheme]~link:last-of-type:before,meta[style]+:before,meta[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,meta[width]:not(img,object,embed,svg,canvas)+:before,meter meter:after,multicol:after,nav main:after,nextid:after,nobr:after,noembed:after,noframes:after,object[align]+:before,object[archive]+:before,object[border]+:before,object[classid]+:before,object[code]+:before,object[codebase]+:before,object[codetype]+:before,object[datafld]+:before,object[dataformatas]+:before,object[datasrc]+:before,object[declare]+:before,object[hspace]+:before,object[role=presentation]:after,object[standby]+:before,object[type=image][alt$=\".apng\"]:after,object[type=image][alt$=\".doc\"]:after,object[type=image][alt$=\".docx\"]:after,object[type=image][alt$=\".gif\"]:after,object[type=image][alt$=\".ics\"]:after,object[type=image][alt$=\".jpg\"]:after,object[type=image][alt$=\".mov\"]:after,object[type=image][alt$=\".mp3\"]:after,object[type=image][alt$=\".mp4\"]:after,object[type=image][alt$=\".ogg\"]:after,object[type=image][alt$=\".pdf\"]:after,object[type=image][alt$=\".png\"]:after,object[type=image][alt$=\".rar\"]:after,object[type=image][alt$=\".svg\"]:after,object[type=image][alt$=\".svgz\"]:after,object[type=image][alt$=\".txt\"]:after,object[type=image][alt$=\".webp\"]:after,object[type=image][alt$=\".xls\"]:after,object[type=image][alt$=\".zip\"]:after,object[type=image][alt=\"\"]:after,object[type=image][aria-hidden=true][aria-describedby]:after,object[type=image][aria-hidden=true][aria-label]:after,object[type=image][aria-hidden=true][aria-labelledby]:after,object[type=image][aria-hidden=true][title]:after,object[vspace]+:before,ol>:not(li):after,ol[compact]:after,optgroup:not([label])+:before,optgroup>:not(option):after,option[dataformatas]:after,option[datasrc]:after,option[name]:after,p[align]:after,param[aria-hidden=true]:not(:empty)+:before,param[class*=NaN]+:before,param[class*=null]+:before,param[class*=undefined]+:before,param[class=\" \"]+:before,param[class=\"\"]+:before,param[datafld]:after,param[height]:not(img,object,embed,svg,canvas)+:before,param[hidden]:not(:empty)+:before,param[id*=\" \"]+:before,param[id*=NaN]+:before,param[id*=null]+:before,param[id*=undefined]+:before,param[id=\" \"]+:before,param[id=\"\"]+:before,param[lang*=\" \"]+:before,param[role=checkbox]:not([aria-checked])+:before,param[role=combobox]:not([aria-expanded])+:before,param[role=slider]:not([aria-valuemax])+:before,param[role=slider]:not([aria-valuemin])+:before,param[role=slider]:not([aria-valuenow])+:before,param[role=spinbutton]:not([aria-valuemax])+:before,param[role=spinbutton]:not([aria-valuemin])+:before,param[role=spinbutton]:not([aria-valuenow])+:before,param[style]+:before,param[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,param[type]:after,param[valuetype]:after,param[width]:not(img,object,embed,svg,canvas)+:before,plaintext:after,pre[width]:after,progress progress:after,q div:after,rb:after,rtc:after,script[event]:after,script[event]~link:last-of-type:before,script[for]:after,script[for]~link:last-of-type:before,script[language]:after,script[language]~link:last-of-type:before,section>section:first-child:after,select:not([id],[aria-label],[aria-labelledby])+:before,select>:not(option,optgroup):after,select[aria-hidden=true]:not(:empty)+:before,select[aria-required]+:before,select[class*=NaN]+:before,select[class*=null]+:before,select[class*=undefined]+:before,select[class=\" \"]+:before,select[class=\"\"]+:before,select[datafld]+:before,select[dataformatas]+:before,select[datasrc]+:before,select[height]:not(img,object,embed,svg,canvas)+:before,select[hidden]:not(:empty)+:before,select[id*=\" \"]+:before,select[id*=NaN]+:before,select[id*=null]+:before,select[id*=undefined]+:before,select[id=\" \"]+:before,select[id=\"\"]+:before,select[lang*=\" \"]+:before,select[required]+:before,select[required]:not([multiple])[size=\"1\"]+:before,select[required]:not([multiple],[size])+:before,select[role=checkbox]:not([aria-checked])+:before,select[role=combobox]:not([aria-expanded])+:before,select[role=slider]:not([aria-valuemax])+:before,select[role=slider]:not([aria-valuemin])+:before,select[role=slider]:not([aria-valuenow])+:before,select[role=spinbutton]:not([aria-valuemax])+:before,select[role=spinbutton]:not([aria-valuemin])+:before,select[role=spinbutton]:not([aria-valuenow])+:before,select[style]+:before,select[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,select[width]:not(img,object,embed,svg,canvas)+:before,small div:after,source[aria-hidden=true]:not(:empty)+:before,source[class*=NaN]+:before,source[class*=null]+:before,source[class*=undefined]+:before,source[class=\" \"]+:before,source[class=\"\"]+:before,source[height]:not(img,object,embed,svg,canvas)+:before,source[hidden]:not(:empty)+:before,source[id*=\" \"]+:before,source[id*=NaN]+:before,source[id*=null]+:before,source[id*=undefined]+:before,source[id=\" \"]+:before,source[id=\"\"]+:before,source[lang*=\" \"]+:before,source[role=checkbox]:not([aria-checked])+:before,source[role=combobox]:not([aria-expanded])+:before,source[role=slider]:not([aria-valuemax])+:before,source[role=slider]:not([aria-valuemin])+:before,source[role=slider]:not([aria-valuenow])+:before,source[role=spinbutton]:not([aria-valuemax])+:before,source[role=spinbutton]:not([aria-valuemin])+:before,source[role=spinbutton]:not([aria-valuenow])+:before,source[style]+:before,source[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,source[width]:not(img,object,embed,svg,canvas)+:before,spacer:after,span div:after,span[datafld]:after,span[dataformatas]:after,span[datasrc]:after,strike:after,strong div:after,svg:not([aria-hidden=true],[role=img])+:before,svg[aria-hidden=true]:not(:empty)+:before,svg[aria-hidden=true][aria-describedby]+:before,svg[aria-hidden=true][aria-label]+:before,svg[aria-hidden=true][aria-labelledby]+:before,svg[aria-hidden=true][title]+:before,svg[aria-label][title]+:before,svg[class*=NaN]+:before,svg[class*=null]+:before,svg[class*=undefined]+:before,svg[class=\" \"]+:before,svg[class=\"\"]+:before,svg[height]:not(img,object,embed,svg,canvas)+:before,svg[hidden]:not(:empty)+:before,svg[id*=\" \"]+:before,svg[id*=NaN]+:before,svg[id*=null]+:before,svg[id*=undefined]+:before,svg[id=\" \"]+:before,svg[id=\"\"]+:before,svg[lang*=\" \"]+:before,svg[role=checkbox]:not([aria-checked])+:before,svg[role=combobox]:not([aria-expanded])+:before,svg[role=img]:not([aria-hidden=true],[aria-label],[aria-labelledby])+:before,svg[role=presentation]+:before,svg[role=slider]:not([aria-valuemax])+:before,svg[role=slider]:not([aria-valuemin])+:before,svg[role=slider]:not([aria-valuenow])+:before,svg[role=spinbutton]:not([aria-valuemax])+:before,svg[role=spinbutton]:not([aria-valuemin])+:before,svg[role=spinbutton]:not([aria-valuenow])+:before,svg[style]+:before,svg[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,svg[width]:not(img,object,embed,svg,canvas)+:before,table table:after,table:not([role=presentation])>:first-child:not(caption):after,table:not([role=presentation])>caption+tbody:after,table:not([role=presentation])>caption:not(:first-child):after,table:not([role=presentation])>tbody:first-child:after,table:not([role=presentation])>tbody>tr:only-child:after,table:not([role=presentation])>tr:only-child:after,table>:not(thead,tfoot,tbody,tr,colgroup,caption):after,table>tbody~colgroup:after,table>tbody~tfoot:after,table>tbody~thead:after,table>tfoot~colgroup:after,table>tfoot~thead:after,table[align]:after,table[background]:after,table[bgcolor]:after,table[cellpadding]:after,table[cellspacing]:after,table[dataformatas]:after,table[datapagesize]:after,table[datasrc]:after,table[frame]:after,table[role=presentation] [axis]:after,table[role=presentation] [headers]:after,table[role=presentation] [scope]:after,table[role=presentation] caption:after,table[role=presentation] colgroup:after,table[role=presentation] tfoot:after,table[role=presentation] th:after,table[role=presentation] thead:after,table[role=presentation]:after,table[rules]:after,table[summary]:after,table[width]:after,tbody[align]:after,tbody[background]:after,tbody[char]:after,tbody[charoff]:after,tbody[valign]:after,td[abbr]:after,td[align]:after,td[axis]:after,td[background]:after,td[bgcolor]:after,td[char]:after,td[charoff]:after,td[height]:after,td[nowrap]:after,td[scope]:after,td[valign]:after,td[width]:after,textarea:not([id],[aria-label],[aria-labelledby])+:before,textarea[aria-hidden=true]:not(:empty)+:before,textarea[aria-required]+:before,textarea[class*=NaN]+:before,textarea[class*=null]+:before,textarea[class*=undefined]+:before,textarea[class=\" \"]+:before,textarea[class=\"\"]+:before,textarea[datafld]+:before,textarea[datasrc]+:before,textarea[height]:not(img,object,embed,svg,canvas)+:before,textarea[hidden]:not(:empty)+:before,textarea[id*=\" \"]+:before,textarea[id*=NaN]+:before,textarea[id*=null]+:before,textarea[id*=undefined]+:before,textarea[id=\" \"]+:before,textarea[id=\"\"]+:before,textarea[lang*=\" \"]+:before,textarea[required]+:before,textarea[role=checkbox]:not([aria-checked])+:before,textarea[role=combobox]:not([aria-expanded])+:before,textarea[role=slider]:not([aria-valuemax])+:before,textarea[role=slider]:not([aria-valuemin])+:before,textarea[role=slider]:not([aria-valuenow])+:before,textarea[role=spinbutton]:not([aria-valuemax])+:before,textarea[role=spinbutton]:not([aria-valuemin])+:before,textarea[role=spinbutton]:not([aria-valuenow])+:before,textarea[style]+:before,textarea[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,textarea[width]:not(img,object,embed,svg,canvas)+:before,tfoot[align]:after,tfoot[background]:after,tfoot[char]:after,tfoot[charoff]:after,tfoot[valign]:after,th:not([scope],[id]):after,th[align]:after,th[axis]:after,th[background]:after,th[bgcolor]:after,th[char]:after,th[charoff]:after,th[height]:after,th[nowrap]:after,th[scope]:after,th[valign]:after,th[width]:after,thead[align]:after,thead[background]:after,thead[char]:after,thead[charoff]:after,thead[valign]:after,time:after,title:empty:after,tr>:not(td,th):after,tr[align]:after,tr[background]:after,tr[bgcolor]:after,tr[char]:after,tr[charoff]:after,tr[valign]:after,track[aria-hidden=true]:not(:empty)+:before,track[class*=NaN]+:before,track[class*=null]+:before,track[class*=undefined]+:before,track[class=\" \"]+:before,track[class=\"\"]+:before,track[height]:not(img,object,embed,svg,canvas)+:before,track[hidden]:not(:empty)+:before,track[id*=\" \"]+:before,track[id*=NaN]+:before,track[id*=null]+:before,track[id*=undefined]+:before,track[id=\" \"]+:before,track[id=\"\"]+:before,track[lang*=\" \"]+:before,track[role=checkbox]:not([aria-checked])+:before,track[role=combobox]:not([aria-expanded])+:before,track[role=slider]:not([aria-valuemax])+:before,track[role=slider]:not([aria-valuemin])+:before,track[role=slider]:not([aria-valuenow])+:before,track[role=spinbutton]:not([aria-valuemax])+:before,track[role=spinbutton]:not([aria-valuemin])+:before,track[role=spinbutton]:not([aria-valuenow])+:before,track[style]+:before,track[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,track[width]:not(img,object,embed,svg,canvas)+:before,tt:after,ul>:not(li):after,ul[compact]:after,ul[type]:after,video+:before,video:not([controls])+:before,video[aria-hidden=true]:not(:empty)+:before,video[autoplay]+:before,video[class*=NaN]+:before,video[class*=null]+:before,video[class*=undefined]+:before,video[class=\" \"]+:before,video[class=\"\"]+:before,video[height]:not(img,object,embed,svg,canvas)+:before,video[hidden]:not(:empty)+:before,video[id*=\" \"]+:before,video[id*=NaN]+:before,video[id*=null]+:before,video[id*=undefined]+:before,video[id=\" \"]+:before,video[id=\"\"]+:before,video[lang*=\" \"]+:before,video[role=checkbox]:not([aria-checked])+:before,video[role=combobox]:not([aria-expanded])+:before,video[role=slider]:not([aria-valuemax])+:before,video[role=slider]:not([aria-valuemin])+:before,video[role=slider]:not([aria-valuenow])+:before,video[role=spinbutton]:not([aria-valuemax])+:before,video[role=spinbutton]:not([aria-valuemin])+:before,video[role=spinbutton]:not([aria-valuenow])+:before,video[style]+:before,video[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,video[width]:not(img,object,embed,svg,canvas)+:before,wbr[aria-hidden=true]:not(:empty)+:before,wbr[class*=NaN]+:before,wbr[class*=null]+:before,wbr[class*=undefined]+:before,wbr[class=\" \"]+:before,wbr[class=\"\"]+:before,wbr[height]:not(img,object,embed,svg,canvas)+:before,wbr[hidden]:not(:empty)+:before,wbr[id*=\" \"]+:before,wbr[id*=NaN]+:before,wbr[id*=null]+:before,wbr[id*=undefined]+:before,wbr[id=\" \"]+:before,wbr[id=\"\"]+:before,wbr[lang*=\" \"]+:before,wbr[role=checkbox]:not([aria-checked])+:before,wbr[role=combobox]:not([aria-expanded])+:before,wbr[role=slider]:not([aria-valuemax])+:before,wbr[role=slider]:not([aria-valuemin])+:before,wbr[role=slider]:not([aria-valuenow])+:before,wbr[role=spinbutton]:not([aria-valuemax])+:before,wbr[role=spinbutton]:not([aria-valuemin])+:before,wbr[role=spinbutton]:not([aria-valuenow])+:before,wbr[style]+:before,wbr[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,wbr[width]:not(img,object,embed,svg,canvas)+:before,xmp:after{border-radius:0!important;color:#fff!important;contain:content;display:block!important;font:700 normal 14px/1.5 sans-serif!important;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif!important;height:auto!important;max-width:100vw!important;padding:4px!important;pointer-events:none!important;position:absolute!important;text-decoration:none!important;text-shadow:none!important;text-transform:none!important;-webkit-transform:none!important;transform:none!important;white-space:pre!important;width:auto!important}:not(img,object,embed,svg,canvas)[height],:not(img,object,embed,svg,canvas)[width],[accesskey],[class^=\"--\"],[class^=\"-0\"],[class^=\"-1\"],[class^=\"-2\"],[class^=\"-3\"],[class^=\"-4\"],[class^=\"-5\"],[class^=\"-6\"],[class^=\"-7\"],[class^=\"-8\"],[class^=\"-9\"],[class^=\"0\"],[class^=\"1\"],[class^=\"2\"],[class^=\"3\"],[class^=\"4\"],[class^=\"5\"],[class^=\"6\"],[class^=\"7\"],[class^=\"8\"],[class^=\"9\"],[dir]:not([dir=rtl],[dir=ltr],[dir=auto]),[id*=\" \"],[id^=\"--\"],[id^=\"-0\"],[id^=\"-1\"],[id^=\"-2\"],[id^=\"-3\"],[id^=\"-4\"],[id^=\"-5\"],[id^=\"-6\"],[id^=\"-7\"],[id^=\"-8\"],[id^=\"-9\"],[id^=\"0\"],[id^=\"1\"],[id^=\"2\"],[id^=\"3\"],[id^=\"4\"],[id^=\"5\"],[id^=\"6\"],[id^=\"7\"],[id^=\"8\"],[id^=\"9\"],[lang*=\" \"],[onabort],[onafterprint],[onbeforeprint],[onbeforeunload],[onblur],[oncanplay],[oncanplaythrough],[onchage],[onclick],[oncontextmenu],[ondblclick],[ondrag],[ondragend],[ondragenter],[ondragleave],[ondragover],[ondragstart],[ondrop],[ondurationchange],[onemptied],[onended],[onerror],[onfocus],[onformchange],[onforminput],[onhaschange],[oninput],[oninvalid],[onkeydown],[onkeypress],[onkeyup],[onload],[onloadeddata],[onloadedmetadata],[onloadstart],[onmessage],[onmousedown],[onmousemove],[onmouseout],[onmouseover],[onmouseup],[onmousewheel],[onoffline],[ononline],[onpagehide],[onpageshow],[onpause],[onplay],[onplaying],[onpopstate],[onprogress],[onratechange],[onreadystatechange],[onredo],[onreset],[onresize],[onscroll],[onseeked],[onseeking],[onselect],[onstalled],[onstorage],[onsubmit],[onsuspend],[ontimeupdate],[onundo],[onunload],[onvolumechange],[onwaiting],[role=checkbox]:not([aria-checked]),[role=combobox]:not([aria-expanded]),[role=img]:not([aria-hidden=true],[aria-label],[aria-labelledby]),[role=scrollbar]:not([aria-controls]),[role=scrollbar]:not([aria-orientation]),[role=scrollbar]:not([aria-valuemax]),[role=scrollbar]:not([aria-valuemin]),[role=scrollbar]:not([aria-valuenow]),[role=slider]:not([aria-valuemax]),[role=slider]:not([aria-valuemin]),[role=slider]:not([aria-valuenow]),[role=spinbutton]:not([aria-valuemax]),[role=spinbutton]:not([aria-valuemin]),[role=spinbutton]:not([aria-valuenow]),[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),[type=checkbox]:not(:only-of-type,[name]),[type=radio],[type=radio]:not([name]),a a[href],a audio[controls],a button,a details,a embed,a iframe,a img[usemap],a input[type]:not([hidden]),a label,a select,a textarea,a video[controls],a:empty:not([title],[aria-label],[aria-labelledby]),a:empty[aria-label=\"\"],a:empty[aria-labelledby=\"\"],a:empty[title=\"\"],a[href=\" \"],a[href=\"\"],area:not([alt]),area[alt=\" \"],area[height]:not(img,object,embed,svg,canvas),area[id*=\" \"],area[lang*=\" \"],area[role=checkbox]:not([aria-checked]),area[role=combobox]:not([aria-expanded]),area[role=slider]:not([aria-valuemax]),area[role=slider]:not([aria-valuemin]),area[role=slider]:not([aria-valuenow]),area[role=spinbutton]:not([aria-valuemax]),area[role=spinbutton]:not([aria-valuemin]),area[role=spinbutton]:not([aria-valuenow]),area[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),area[width]:not(img,object,embed,svg,canvas),audio[height]:not(img,object,embed,svg,canvas),audio[id*=\" \"],audio[lang*=\" \"],audio[role=checkbox]:not([aria-checked]),audio[role=combobox]:not([aria-expanded]),audio[role=slider]:not([aria-valuemax]),audio[role=slider]:not([aria-valuemin]),audio[role=slider]:not([aria-valuenow]),audio[role=spinbutton]:not([aria-valuemax]),audio[role=spinbutton]:not([aria-valuemin]),audio[role=spinbutton]:not([aria-valuenow]),audio[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),audio[width]:not(img,object,embed,svg,canvas),base[height]:not(img,object,embed,svg,canvas),base[id*=\" \"],base[lang*=\" \"],base[role=checkbox]:not([aria-checked]),base[role=combobox]:not([aria-expanded]),base[role=slider]:not([aria-valuemax]),base[role=slider]:not([aria-valuemin]),base[role=slider]:not([aria-valuenow]),base[role=spinbutton]:not([aria-valuemax]),base[role=spinbutton]:not([aria-valuemin]),base[role=spinbutton]:not([aria-valuenow]),base[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),base[width]:not(img,object,embed,svg,canvas),br[height]:not(img,object,embed,svg,canvas),br[id*=\" \"],br[lang*=\" \"],br[role=checkbox]:not([aria-checked]),br[role=combobox]:not([aria-expanded]),br[role=slider]:not([aria-valuemax]),br[role=slider]:not([aria-valuemin]),br[role=slider]:not([aria-valuenow]),br[role=spinbutton]:not([aria-valuemax]),br[role=spinbutton]:not([aria-valuemin]),br[role=spinbutton]:not([aria-valuenow]),br[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),br[width]:not(img,object,embed,svg,canvas),button a[href],button audio[controls],button button,button details,button embed,button iframe,button img[usemap],button input[type]:not([hidden]),button label,button select,button textarea,button video[controls],button:empty:not([aria-label],[aria-labelledby],[title]),button:not([type],[form],[formaction],[formtarget]),button[aria-label=\"\"],button[aria-labelledby=\"\"],button[class*=disabled]:not([disabled],[readonly]),button[title=\"\"],button[type=button][formaction],button[type=button][formenctype],button[type=button][formmethod],button[type=button][formnovalidate],button[type=button][formtarget],button[type=reset][formaction],button[type=reset][formenctype],button[type=reset][formmethod],button[type=reset][formnovalidate],button[type=reset][formtarget],col[height]:not(img,object,embed,svg,canvas),col[id*=\" \"],col[lang*=\" \"],col[role=checkbox]:not([aria-checked]),col[role=combobox]:not([aria-expanded]),col[role=slider]:not([aria-valuemax]),col[role=slider]:not([aria-valuemin]),col[role=slider]:not([aria-valuenow]),col[role=spinbutton]:not([aria-valuemax]),col[role=spinbutton]:not([aria-valuemin]),col[role=spinbutton]:not([aria-valuenow]),col[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),col[width]:not(img,object,embed,svg,canvas),command[height]:not(img,object,embed,svg,canvas),command[id*=\" \"],command[lang*=\" \"],command[role=checkbox]:not([aria-checked]),command[role=combobox]:not([aria-expanded]),command[role=slider]:not([aria-valuemax]),command[role=slider]:not([aria-valuemin]),command[role=slider]:not([aria-valuenow]),command[role=spinbutton]:not([aria-valuemax]),command[role=spinbutton]:not([aria-valuemin]),command[role=spinbutton]:not([aria-valuenow]),command[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),command[width]:not(img,object,embed,svg,canvas),embed[height]:not(img,object,embed,svg,canvas),embed[id*=\" \"],embed[lang*=\" \"],embed[role=checkbox]:not([aria-checked]),embed[role=combobox]:not([aria-expanded]),embed[role=slider]:not([aria-valuemax]),embed[role=slider]:not([aria-valuemin]),embed[role=slider]:not([aria-valuenow]),embed[role=spinbutton]:not([aria-valuemax]),embed[role=spinbutton]:not([aria-valuemin]),embed[role=spinbutton]:not([aria-valuenow]),embed[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),embed[width]:not(img,object,embed,svg,canvas),form form,form:not([action]),form[action=\" \"],form[action=\"\"],head :first-child:not([charset]),hr[height]:not(img,object,embed,svg,canvas),hr[id*=\" \"],hr[lang*=\" \"],hr[role=checkbox]:not([aria-checked]),hr[role=combobox]:not([aria-expanded]),hr[role=slider]:not([aria-valuemax]),hr[role=slider]:not([aria-valuemin]),hr[role=slider]:not([aria-valuenow]),hr[role=spinbutton]:not([aria-valuemax]),hr[role=spinbutton]:not([aria-valuemin]),hr[role=spinbutton]:not([aria-valuenow]),hr[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),hr[width]:not(img,object,embed,svg,canvas),html:not([lang]),html[lang*=\" \"],html[lang=\"\"],iframe:not([title]),iframe[height]:not(img,object,embed,svg,canvas),iframe[id*=\" \"],iframe[lang*=\" \"],iframe[role=checkbox]:not([aria-checked]),iframe[role=combobox]:not([aria-expanded]),iframe[role=slider]:not([aria-valuemax]),iframe[role=slider]:not([aria-valuemin]),iframe[role=slider]:not([aria-valuenow]),iframe[role=spinbutton]:not([aria-valuemax]),iframe[role=spinbutton]:not([aria-valuemin]),iframe[role=spinbutton]:not([aria-valuenow]),iframe[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),iframe[title=\" \"],iframe[title=\"\"],iframe[width]:not(img,object,embed,svg,canvas),img:not([alt]),img:not([src],[srcset]),img[alt=\" \"],img[height]:not(img,object,embed,svg,canvas),img[id*=\" \"],img[lang*=\" \"],img[role=checkbox]:not([aria-checked]),img[role=combobox]:not([aria-expanded]),img[role=slider]:not([aria-valuemax]),img[role=slider]:not([aria-valuemin]),img[role=slider]:not([aria-valuenow]),img[role=spinbutton]:not([aria-valuemax]),img[role=spinbutton]:not([aria-valuemin]),img[role=spinbutton]:not([aria-valuenow]),img[src=\" \"],img[src=\"\"],img[src=\"#\"],img[src=\"/\"],img[srcset=\" \"],img[srcset=\"\"],img[srcset=\"#\"],img[srcset=\"/\"],img[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),img[width]:not(img,object,embed,svg,canvas),input:not([type=button],[type=submit],[type=hidden],[type=reset],[type=image],[id],[aria-label],[title],[aria-labelledby]),input:not([type]),input[height]:not(img,object,embed,svg,canvas),input[id*=\" \"],input[lang*=\" \"],input[role=checkbox]:not([aria-checked]),input[role=combobox]:not([aria-expanded]),input[role=slider]:not([aria-valuemax]),input[role=slider]:not([aria-valuemin]),input[role=slider]:not([aria-valuenow]),input[role=spinbutton]:not([aria-valuemax]),input[role=spinbutton]:not([aria-valuemin]),input[role=spinbutton]:not([aria-valuenow]),input[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),input[type=\" \"],input[type=\"\"],input[type=button]:not([value],[title],[aria-label],[aria-labelledby]),input[type=image]:not([alt]),input[type=image]:not([src],[srcset]),input[type=image][alt=\" \"],input[type=image][src=\" \"],input[type=image][src=\"\"],input[type=image][src=\"#\"],input[type=image][src=\"/\"],input[type=image][srcset=\" \"],input[type=image][srcset=\"\"],input[type=image][srcset=\"#\"],input[type=image][srcset=\"/\"],input[type=reset]:not([value],[title],[aria-label],[aria-labelledby]),input[type=submit]:not([value],[title],[aria-label],[aria-labelledby]),input[width]:not(img,object,embed,svg,canvas),keygen[height]:not(img,object,embed,svg,canvas),keygen[id*=\" \"],keygen[lang*=\" \"],keygen[role=checkbox]:not([aria-checked]),keygen[role=combobox]:not([aria-expanded]),keygen[role=slider]:not([aria-valuemax]),keygen[role=slider]:not([aria-valuemin]),keygen[role=slider]:not([aria-valuenow]),keygen[role=spinbutton]:not([aria-valuemax]),keygen[role=spinbutton]:not([aria-valuemin]),keygen[role=spinbutton]:not([aria-valuenow]),keygen[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),keygen[width]:not(img,object,embed,svg,canvas),label label,label[for=\" \"],label[for=\"\"],link[height]:not(img,object,embed,svg,canvas),link[id*=\" \"],link[lang*=\" \"],link[role=checkbox]:not([aria-checked]),link[role=combobox]:not([aria-expanded]),link[role=slider]:not([aria-valuemax]),link[role=slider]:not([aria-valuemin]),link[role=slider]:not([aria-valuenow]),link[role=spinbutton]:not([aria-valuemax]),link[role=spinbutton]:not([aria-valuemin]),link[role=spinbutton]:not([aria-valuenow]),link[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),link[width]:not(img,object,embed,svg,canvas),map[name*=\" \"],meta[charset]:not([charset=utf-8],[charset=UTF-8]),meta[height]:not(img,object,embed,svg,canvas),meta[id*=\" \"],meta[lang*=\" \"],meta[name=viewport][content*=\"user-scalable=no\"],meta[name=viewport][content*=maximum-scale],meta[name=viewport][content*=minimum-scale],meta[role=checkbox]:not([aria-checked]),meta[role=combobox]:not([aria-expanded]),meta[role=slider]:not([aria-valuemax]),meta[role=slider]:not([aria-valuemin]),meta[role=slider]:not([aria-valuenow]),meta[role=spinbutton]:not([aria-valuemax]),meta[role=spinbutton]:not([aria-valuemin]),meta[role=spinbutton]:not([aria-valuenow]),meta[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),meta[width]:not(img,object,embed,svg,canvas),meter meter,optgroup:not([label]),param[height]:not(img,object,embed,svg,canvas),param[id*=\" \"],param[lang*=\" \"],param[role=checkbox]:not([aria-checked]),param[role=combobox]:not([aria-expanded]),param[role=slider]:not([aria-valuemax]),param[role=slider]:not([aria-valuemin]),param[role=slider]:not([aria-valuenow]),param[role=spinbutton]:not([aria-valuemax]),param[role=spinbutton]:not([aria-valuemin]),param[role=spinbutton]:not([aria-valuenow]),param[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),param[width]:not(img,object,embed,svg,canvas),progress progress,select:not([id],[aria-label],[aria-labelledby]),select[height]:not(img,object,embed,svg,canvas),select[id*=\" \"],select[lang*=\" \"],select[role=checkbox]:not([aria-checked]),select[role=combobox]:not([aria-expanded]),select[role=slider]:not([aria-valuemax]),select[role=slider]:not([aria-valuemin]),select[role=slider]:not([aria-valuenow]),select[role=spinbutton]:not([aria-valuemax]),select[role=spinbutton]:not([aria-valuemin]),select[role=spinbutton]:not([aria-valuenow]),select[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),select[width]:not(img,object,embed,svg,canvas),source[height]:not(img,object,embed,svg,canvas),source[id*=\" \"],source[lang*=\" \"],source[role=checkbox]:not([aria-checked]),source[role=combobox]:not([aria-expanded]),source[role=slider]:not([aria-valuemax]),source[role=slider]:not([aria-valuemin]),source[role=slider]:not([aria-valuenow]),source[role=spinbutton]:not([aria-valuemax]),source[role=spinbutton]:not([aria-valuemin]),source[role=spinbutton]:not([aria-valuenow]),source[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),source[width]:not(img,object,embed,svg,canvas),svg[height]:not(img,object,embed,svg,canvas),svg[id*=\" \"],svg[lang*=\" \"],svg[role=checkbox]:not([aria-checked]),svg[role=combobox]:not([aria-expanded]),svg[role=img]:not([aria-hidden=true],[aria-label],[aria-labelledby]),svg[role=slider]:not([aria-valuemax]),svg[role=slider]:not([aria-valuemin]),svg[role=slider]:not([aria-valuenow]),svg[role=spinbutton]:not([aria-valuemax]),svg[role=spinbutton]:not([aria-valuemin]),svg[role=spinbutton]:not([aria-valuenow]),svg[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),svg[width]:not(img,object,embed,svg,canvas),table[role=presentation] [axis],table[role=presentation] [headers],table[role=presentation] [scope],table[role=presentation] caption,table[role=presentation] colgroup,table[role=presentation] tfoot,table[role=presentation] th,table[role=presentation] thead,textarea:not([id],[aria-label],[aria-labelledby]),textarea[height]:not(img,object,embed,svg,canvas),textarea[id*=\" \"],textarea[lang*=\" \"],textarea[role=checkbox]:not([aria-checked]),textarea[role=combobox]:not([aria-expanded]),textarea[role=slider]:not([aria-valuemax]),textarea[role=slider]:not([aria-valuemin]),textarea[role=slider]:not([aria-valuenow]),textarea[role=spinbutton]:not([aria-valuemax]),textarea[role=spinbutton]:not([aria-valuemin]),textarea[role=spinbutton]:not([aria-valuenow]),textarea[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),textarea[width]:not(img,object,embed,svg,canvas),title:empty,track[height]:not(img,object,embed,svg,canvas),track[id*=\" \"],track[lang*=\" \"],track[role=checkbox]:not([aria-checked]),track[role=combobox]:not([aria-expanded]),track[role=slider]:not([aria-valuemax]),track[role=slider]:not([aria-valuemin]),track[role=slider]:not([aria-valuenow]),track[role=spinbutton]:not([aria-valuemax]),track[role=spinbutton]:not([aria-valuemin]),track[role=spinbutton]:not([aria-valuenow]),track[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),track[width]:not(img,object,embed,svg,canvas),video[height]:not(img,object,embed,svg,canvas),video[id*=\" \"],video[lang*=\" \"],video[role=checkbox]:not([aria-checked]),video[role=combobox]:not([aria-expanded]),video[role=slider]:not([aria-valuemax]),video[role=slider]:not([aria-valuemin]),video[role=slider]:not([aria-valuenow]),video[role=spinbutton]:not([aria-valuemax]),video[role=spinbutton]:not([aria-valuemin]),video[role=spinbutton]:not([aria-valuenow]),video[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),video[width]:not(img,object,embed,svg,canvas),wbr[height]:not(img,object,embed,svg,canvas),wbr[id*=\" \"],wbr[lang*=\" \"],wbr[role=checkbox]:not([aria-checked]),wbr[role=combobox]:not([aria-expanded]),wbr[role=slider]:not([aria-valuemax]),wbr[role=slider]:not([aria-valuemin]),wbr[role=slider]:not([aria-valuenow]),wbr[role=spinbutton]:not([aria-valuemax]),wbr[role=spinbutton]:not([aria-valuemin]),wbr[role=spinbutton]:not([aria-valuenow]),wbr[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]),wbr[width]:not(img,object,embed,svg,canvas){counter-increment:error!important;outline:4px solid #d90b0b!important;outline-offset:-4px!important}:not(colgroup)>col,:not(dl)>dd,:not(dl)>dt,:not(dt,dd)+dd,:not(fieldset)>legend,:not(figure)>figcaption,:not(select)>optgroup,:not(select,optgroup)>option,:not(tr)>td,:not(tr)>th,:not(ul,ol)>li,[class*=NaN],[class*=null],[class*=undefined],[dir=rtl]:not([lang=ar],[lang=he]),[id*=NaN],[id*=null],[id*=undefined],[lang=ar] [lang]:not([dir=ltr]),[lang=ar]:not([dir=rtl]),[lang=he] [lang]:not([dir=ltr]),[lang=he]:not([dir=rtl]),[role=heading]:not([aria-level]),[style],[target$=blank]:not([rel*=noopener]),[target$=blank]:not([rel*=noreferrer]),[target$=blank]:not([rel]),a[href=\"#\"]:not([role=button]),a[href^=javascript]:not([role=button]),abbr div,abbr:not([title]),abbr[title=\" \"],abbr[title=\"\"],address address,address article,address aside,address footer,address h1,address h2,address h3,address h4,address h5,address h6,address header,address nav,address section,area:not([href])[alt=\"\"][aria-describedby],area:not([href])[alt=\"\"][aria-label],area:not([href])[alt=\"\"][aria-labelledby],area:not([href])[alt=\"\"][title],area:not([href])[alt]:not([alt=\"\"]),area[alt$=\".apng\"],area[alt$=\".doc\"],area[alt$=\".docx\"],area[alt$=\".gif\"],area[alt$=\".ics\"],area[alt$=\".jpg\"],area[alt$=\".mov\"],area[alt$=\".mp3\"],area[alt$=\".mp4\"],area[alt$=\".ogg\"],area[alt$=\".pdf\"],area[alt$=\".png\"],area[alt$=\".rar\"],area[alt$=\".svg\"],area[alt$=\".svgz\"],area[alt$=\".txt\"],area[alt$=\".webp\"],area[alt$=\".xls\"],area[alt$=\".zip\"],area[alt=\"\"],area[class*=NaN],area[class*=null],area[class*=undefined],area[id*=NaN],area[id*=null],area[id*=undefined],area[role=presentation],area[style],article main,article>article:first-child,article>aside:first-child,article>section:first-child,aside main,aside>article:first-child,aside>aside:first-child,aside>section:first-child,audio:not([controls]),audio[autoplay],audio[class*=NaN],audio[class*=null],audio[class*=undefined],audio[id*=NaN],audio[id*=null],audio[id*=undefined],audio[style],b div,base[class*=NaN],base[class*=null],base[class*=undefined],base[id*=NaN],base[id*=null],base[id*=undefined],base[style],body :empty:not([hidden],[aria-hidden],[src],button,a,iframe,textarea,area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr,title),br[class*=NaN],br[class*=null],br[class*=undefined],br[id*=NaN],br[id*=null],br[id*=undefined],br[style],canvas[aria-hidden=true][aria-describedby],canvas[aria-hidden=true][aria-label],canvas[aria-hidden=true][aria-labelledby],canvas[aria-hidden=true][title],canvas[role=presentation],cite div,code div,col[class*=NaN],col[class*=null],col[class*=undefined],col[id*=NaN],col[id*=null],col[id*=undefined],col[style],colgroup :not(col),command[class*=NaN],command[class*=null],command[class*=undefined],command[id*=NaN],command[id*=null],command[id*=undefined],command[style],details>:not(summary):first-child,details>summary:not(:first-child),dl>:not(dt,dd,div),dt+:not(dd),em div,embed[class*=NaN],embed[class*=null],embed[class*=undefined],embed[id*=NaN],embed[id*=null],embed[id*=undefined],embed[role=presentation],embed[style],embed[type=image][alt$=\".apng\"],embed[type=image][alt$=\".doc\"],embed[type=image][alt$=\".docx\"],embed[type=image][alt$=\".gif\"],embed[type=image][alt$=\".ics\"],embed[type=image][alt$=\".jpg\"],embed[type=image][alt$=\".mov\"],embed[type=image][alt$=\".mp3\"],embed[type=image][alt$=\".mp4\"],embed[type=image][alt$=\".ogg\"],embed[type=image][alt$=\".pdf\"],embed[type=image][alt$=\".png\"],embed[type=image][alt$=\".rar\"],embed[type=image][alt$=\".svg\"],embed[type=image][alt$=\".svgz\"],embed[type=image][alt$=\".txt\"],embed[type=image][alt$=\".webp\"],embed[type=image][alt$=\".xls\"],embed[type=image][alt$=\".zip\"],embed[type=image][alt=\"\"],embed[type=image][aria-hidden=true][aria-describedby],embed[type=image][aria-hidden=true][aria-label],embed[type=image][aria-hidden=true][aria-labelledby],embed[type=image][aria-hidden=true][title],fieldset>:not(legend):first-child,fieldset>legend:not(:first-child),figure:not([role=group]),footer main,header main,hr[class*=NaN],hr[class*=null],hr[class*=undefined],hr[id*=NaN],hr[id*=null],hr[id*=undefined],hr[style],i div,iframe[class*=NaN],iframe[class*=null],iframe[class*=undefined],iframe[id*=NaN],iframe[id*=null],iframe[id*=undefined],iframe[style],img[alt$=\".apng\"],img[alt$=\".doc\"],img[alt$=\".docx\"],img[alt$=\".gif\"],img[alt$=\".ics\"],img[alt$=\".jpg\"],img[alt$=\".mov\"],img[alt$=\".mp3\"],img[alt$=\".mp4\"],img[alt$=\".ogg\"],img[alt$=\".pdf\"],img[alt$=\".png\"],img[alt$=\".rar\"],img[alt$=\".svg\"],img[alt$=\".svgz\"],img[alt$=\".txt\"],img[alt$=\".webp\"],img[alt$=\".xls\"],img[alt$=\".zip\"],img[alt=\"\"],img[alt=\"\"][aria-describedby],img[alt=\"\"][aria-label],img[alt=\"\"][aria-labelledby],img[alt=\"\"][title],img[class*=NaN],img[class*=null],img[class*=undefined],img[id*=NaN],img[id*=null],img[id*=undefined],img[role=presentation],img[src*=\"1px.gif\"]:not([role=presentation]),img[src*=\"1x1.gif\"]:not([role=presentation]),img[src*=\"clear.gif\"]:not([role=presentation]),img[src*=\"dotclear.gif\"]:not([role=presentation]),img[src*=\"pixel-1x1-clear.gif\"]:not([role=presentation]),img[src*=\"spacer.gif\"]:not([role=presentation]),img[src*=\"transparent.gif\"]:not([role=presentation]),img[style],input[class*=NaN],input[class*=null],input[class*=undefined],input[id*=NaN],input[id*=null],input[id*=undefined],input[style],input[type=image][alt$=\".apng\"],input[type=image][alt$=\".doc\"],input[type=image][alt$=\".docx\"],input[type=image][alt$=\".gif\"],input[type=image][alt$=\".ics\"],input[type=image][alt$=\".jpg\"],input[type=image][alt$=\".mov\"],input[type=image][alt$=\".mp3\"],input[type=image][alt$=\".mp4\"],input[type=image][alt$=\".ogg\"],input[type=image][alt$=\".pdf\"],input[type=image][alt$=\".png\"],input[type=image][alt$=\".rar\"],input[type=image][alt$=\".svg\"],input[type=image][alt$=\".svgz\"],input[type=image][alt$=\".txt\"],input[type=image][alt$=\".webp\"],input[type=image][alt$=\".xls\"],input[type=image][alt$=\".zip\"],input[type=image][alt=\"\"],keygen[class*=NaN],keygen[class*=null],keygen[class*=undefined],keygen[id*=NaN],keygen[id*=null],keygen[id*=undefined],keygen[style],label div,label:not([for]),link[class*=NaN],link[class*=null],link[class*=undefined],link[id*=NaN],link[id*=null],link[id*=undefined],link[style],meta[class*=NaN],meta[class*=null],meta[class*=undefined],meta[id*=NaN],meta[id*=null],meta[id*=undefined],meta[style],nav main,object[role=presentation],object[type=image][alt$=\".apng\"],object[type=image][alt$=\".doc\"],object[type=image][alt$=\".docx\"],object[type=image][alt$=\".gif\"],object[type=image][alt$=\".ics\"],object[type=image][alt$=\".jpg\"],object[type=image][alt$=\".mov\"],object[type=image][alt$=\".mp3\"],object[type=image][alt$=\".mp4\"],object[type=image][alt$=\".ogg\"],object[type=image][alt$=\".pdf\"],object[type=image][alt$=\".png\"],object[type=image][alt$=\".rar\"],object[type=image][alt$=\".svg\"],object[type=image][alt$=\".svgz\"],object[type=image][alt$=\".txt\"],object[type=image][alt$=\".webp\"],object[type=image][alt$=\".xls\"],object[type=image][alt$=\".zip\"],object[type=image][alt=\"\"],object[type=image][aria-hidden=true][aria-describedby],object[type=image][aria-hidden=true][aria-label],object[type=image][aria-hidden=true][aria-labelledby],object[type=image][aria-hidden=true][title],ol>:not(li),optgroup>:not(option),param[class*=NaN],param[class*=null],param[class*=undefined],param[id*=NaN],param[id*=null],param[id*=undefined],param[style],q div,section>section:first-child,select>:not(option,optgroup),select[class*=NaN],select[class*=null],select[class*=undefined],select[id*=NaN],select[id*=null],select[id*=undefined],select[style],small div,source[class*=NaN],source[class*=null],source[class*=undefined],source[id*=NaN],source[id*=null],source[id*=undefined],source[style],span div,strong div,svg:not([aria-hidden=true],[role=img]),svg[aria-hidden=true][aria-describedby],svg[aria-hidden=true][aria-label],svg[aria-hidden=true][aria-labelledby],svg[aria-hidden=true][title],svg[class*=NaN],svg[class*=null],svg[class*=undefined],svg[id*=NaN],svg[id*=null],svg[id*=undefined],svg[role=presentation],svg[style],table table,table:not([role=presentation])>:first-child:not(caption),table:not([role=presentation])>caption+tbody,table:not([role=presentation])>caption:not(:first-child),table:not([role=presentation])>tbody:first-child,table:not([role=presentation])>tbody>tr:only-child,table:not([role=presentation])>tr:only-child,table>:not(thead,tfoot,tbody,tr,colgroup,caption),table>tbody~colgroup,table>tbody~tfoot,table>tbody~thead,table>tfoot~colgroup,table>tfoot~thead,textarea[class*=NaN],textarea[class*=null],textarea[class*=undefined],textarea[id*=NaN],textarea[id*=null],textarea[id*=undefined],textarea[style],th:not([scope],[id]),tr>:not(td,th),track[class*=NaN],track[class*=null],track[class*=undefined],track[id*=NaN],track[id*=null],track[id*=undefined],track[style],ul>:not(li),video:not([controls]),video[autoplay],video[class*=NaN],video[class*=null],video[class*=undefined],video[id*=NaN],video[id*=null],video[id*=undefined],video[style],wbr[class*=NaN],wbr[class*=null],wbr[class*=undefined],wbr[id*=NaN],wbr[id*=null],wbr[id*=undefined],wbr[style]{counter-increment:warning!important;outline:4px solid #f50!important;outline-offset:-4px!important}[dropzone],a[charset],a[coords],a[datafld],a[datasrc],a[methods],a[name],a[rev],a[shape],a[urn],acronym,applet,applet[datafld],applet[datasrc],area[hreflang],area[nohref],area[type],basefont,bgsound,big,blink,body[alink],body[background],body[bgcolor],body[link],body[marginbottom],body[marginheight],body[marginleft],body[marginright],body[margintop],body[marginwidth],body[text],body[vlink],br[clear],button[datafld],button[dataformatas],button[datasrc],caption[align],center,col[align],col[char],col[charoff],col[valign],col[width],dir,div[align],div[datafld],div[dataformatas],div[datasrc],dl[compact],embed[align],embed[hspace],embed[name],embed[vspace],fieldset[datafld],font,form[accept],frame,frame[datafld],frame[datasrc],frameset,h1[align],h2[align],h3[align],h4[align],h5[align],h6[align],head[profile],hgroup,hr[align],hr[color],hr[noshade],hr[size],hr[width],html[version],iframe[align],iframe[allowtransparency],iframe[datafld],iframe[datasrc],iframe[frameborder],iframe[framespacing],iframe[hspace],iframe[longdesc],iframe[marginheight],iframe[marginwidth],iframe[scrolling],iframe[vspace],img[align],img[border],img[datafld],img[datasrc],img[hspace],img[longdesc],img[lowsrc],img[name],img[vspace],input[align],input[datafld],input[dataformatas],input[datasrc],input[hspace],input[inputmode],input[ismap],input[usemap],input[vspace],isindex,keygen,label[datafld],label[dataformatas],label[datasrc],legend[align],legend[datafld],legend[dataformatas],legend[datasrc],li[type],link[charset],link[methods],link[rev],link[target],link[urn],listing,marquee,marquee[datafld],marquee[dataformatas],marquee[datasrc],menu,menuitem,meta[http-equiv=content-language],meta[http-equiv=content-type],meta[http-equiv=set-cookie],meta[scheme],multicol,nextid,nobr,noembed,noframes,object[align],object[archive],object[border],object[classid],object[code],object[codebase],object[codetype],object[datafld],object[dataformatas],object[datasrc],object[declare],object[hspace],object[standby],object[vspace],ol[compact],option[dataformatas],option[datasrc],option[name],p[align],param[datafld],param[type],param[valuetype],plaintext,pre[width],rb,rtc,script[event],script[for],script[language],select[datafld],select[dataformatas],select[datasrc],spacer,span[datafld],span[dataformatas],span[datasrc],strike,table[align],table[background],table[bgcolor],table[cellpadding],table[cellspacing],table[dataformatas],table[datapagesize],table[datasrc],table[frame],table[rules],table[summary],table[width],tbody[align],tbody[background],tbody[char],tbody[charoff],tbody[valign],td[abbr],td[align],td[axis],td[background],td[bgcolor],td[char],td[charoff],td[height],td[nowrap],td[scope],td[valign],td[width],textarea[datafld],textarea[datasrc],tfoot[align],tfoot[background],tfoot[char],tfoot[charoff],tfoot[valign],th[align],th[axis],th[background],th[bgcolor],th[char],th[charoff],th[height],th[nowrap],th[valign],th[width],thead[align],thead[background],thead[char],thead[charoff],thead[valign],tr[align],tr[background],tr[bgcolor],tr[char],tr[charoff],tr[valign],tt,ul[compact],ul[type],xmp{counter-increment:obsolete!important;outline:4px solid #4169e1!important;outline-offset:-4px!important}[aria-hidden=true]:not(:empty),[aria-required],[class*=search]:not([role=search]),[class=\" \"],[class=\"\"],[datetime],[download],[hidden]:not(:empty),[href$=\".apng\"]:not(link),[href$=\".doc\"]:not(link),[href$=\".docx\"]:not(link),[href$=\".gif\"]:not(link),[href$=\".ics\"]:not(link),[href$=\".jpg\"]:not(link),[href$=\".mov\"]:not(link),[href$=\".mp3\"]:not(link),[href$=\".mp4\"]:not(link),[href$=\".ogg\"]:not(link),[href$=\".pdf\"]:not(link),[href$=\".png\"]:not(link),[href$=\".rar\"]:not(link),[href$=\".svg\"]:not(link),[href$=\".svgz\"]:not(link),[href$=\".txt\"]:not(link),[href$=\".webp\"]:not(link),[href$=\".xls\"]:not(link),[href$=\".zip\"]:not(link),[href^=\"http:\"],[href^=mailto],[href^=tel],[id*=search]:not([role=search]),[id=\" \"],[id=\"\"],[placeholder]:not([title],[aria-label],[aria-labelledby]),[required],[role=banner]~[role=banner],[role=contentinfo]~[role=contentinfo],[role=main]~[role=main],[role=search]~[role=search],[src^=\"http:\"],[target$=blank],a:not([href]),a[role=button],area[alt][title],area[aria-hidden=true]:not(:empty),area[class=\" \"],area[class=\"\"],area[hidden]:not(:empty),area[id=\" \"],area[id=\"\"],audio,audio[aria-hidden=true]:not(:empty),audio[class=\" \"],audio[class=\"\"],audio[hidden]:not(:empty),audio[id=\" \"],audio[id=\"\"],base[aria-hidden=true]:not(:empty),base[class=\" \"],base[class=\"\"],base[hidden]:not(:empty),base[id=\" \"],base[id=\"\"],br[aria-hidden=true]:not(:empty),br[class=\" \"],br[class=\"\"],br[hidden]:not(:empty),br[id=\" \"],br[id=\"\"],col[aria-hidden=true]:not(:empty),col[class=\" \"],col[class=\"\"],col[hidden]:not(:empty),col[id=\" \"],col[id=\"\"],command[aria-hidden=true]:not(:empty),command[class=\" \"],command[class=\"\"],command[hidden]:not(:empty),command[id=\" \"],command[id=\"\"],embed[aria-hidden=true]:not(:empty),embed[class=\" \"],embed[class=\"\"],embed[hidden]:not(:empty),embed[id=\" \"],embed[id=\"\"],figcaption:not(:first-child,:last-child),figcaption:not(:first-of-type),hr[aria-hidden=true]:not(:empty),hr[class=\" \"],hr[class=\"\"],hr[hidden]:not(:empty),hr[id=\" \"],hr[id=\"\"],iframe[aria-hidden=true]:not(:empty),iframe[class=\" \"],iframe[class=\"\"],iframe[hidden]:not(:empty),iframe[id=\" \"],iframe[id=\"\"],img[alt][title],img[aria-hidden=true]:not(:empty),img[class=\" \"],img[class=\"\"],img[hidden]:not(:empty),img[id=\" \"],img[id=\"\"],input[aria-hidden=true]:not(:empty),input[aria-required],input[class*=search]:not([role=search]),input[class=\" \"],input[class=\"\"],input[hidden]:not(:empty),input[id*=search]:not([role=search]),input[id=\" \"],input[id=\"\"],input[required],keygen[aria-hidden=true]:not(:empty),keygen[class=\" \"],keygen[class=\"\"],keygen[hidden]:not(:empty),keygen[id=\" \"],keygen[id=\"\"],link[aria-hidden=true]:not(:empty),link[class=\" \"],link[class=\"\"],link[hidden]:not(:empty),link[id=\" \"],link[id=\"\"],main~main:not([hidden]),meta[aria-hidden=true]:not(:empty),meta[class=\" \"],meta[class=\"\"],meta[hidden]:not(:empty),meta[id=\" \"],meta[id=\"\"],param[aria-hidden=true]:not(:empty),param[class=\" \"],param[class=\"\"],param[hidden]:not(:empty),param[id=\" \"],param[id=\"\"],select[aria-hidden=true]:not(:empty),select[aria-required],select[class=\" \"],select[class=\"\"],select[hidden]:not(:empty),select[id=\" \"],select[id=\"\"],select[required],select[required]:not([multiple])[size=\"1\"],select[required]:not([multiple],[size]),source[aria-hidden=true]:not(:empty),source[class=\" \"],source[class=\"\"],source[hidden]:not(:empty),source[id=\" \"],source[id=\"\"],svg[aria-hidden=true]:not(:empty),svg[aria-label][title],svg[class=\" \"],svg[class=\"\"],svg[hidden]:not(:empty),svg[id=\" \"],svg[id=\"\"],table[role=presentation],textarea[aria-hidden=true]:not(:empty),textarea[aria-required],textarea[class=\" \"],textarea[class=\"\"],textarea[hidden]:not(:empty),textarea[id=\" \"],textarea[id=\"\"],textarea[required],th[scope],time,track[aria-hidden=true]:not(:empty),track[class=\" \"],track[class=\"\"],track[hidden]:not(:empty),track[id=\" \"],track[id=\"\"],video,video[aria-hidden=true]:not(:empty),video[class=\" \"],video[class=\"\"],video[hidden]:not(:empty),video[id=\" \"],video[id=\"\"],wbr[aria-hidden=true]:not(:empty),wbr[class=\" \"],wbr[class=\"\"],wbr[hidden]:not(:empty),wbr[id=\" \"],wbr[id=\"\"]{counter-increment:advice!important;outline:4px solid green!important;outline-offset:-4px!important}[role=search] [class*=search]:not([role=search]),[role=search] [id*=search]:not([role=search]),fieldset [type=radio],form button:not([type],[form],[formaction],[formtarget]),label [placeholder]:not([title],[aria-label],[aria-labelledby]),svg :empty:not(title,desc,[hidden],[aria-hidden],[src],button,a,iframe,textarea,area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr){counter-increment:none!important;outline:0!important}[role=search] [class*=search]:not([role=search])+:before,[role=search] [class*=search]:not([role=search]):after,[role=search] [id*=search]:not([role=search])+:before,[role=search] [id*=search]:not([role=search]):after,fieldset [type=radio]+:before,fieldset [type=radio]:after,form button:not([type],[form],[formaction],[formtarget])+:before,form button:not([type],[form],[formaction],[formtarget]):after,label [placeholder]:not([title],[aria-label],[aria-labelledby])+:before,label [placeholder]:not([title],[aria-label],[aria-labelledby]):after,svg :empty:not(title,desc,[hidden],[aria-hidden],[src],button,a,iframe,textarea,area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr)+:before,svg :empty:not(title,desc,[hidden],[aria-hidden],[src],button,a,iframe,textarea,area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr):after{content:\"\"!important;display:none!important}head :first-child:not([charset]),head :first-child:not([charset])~link:last-of-type,link[charset],link[charset]~link:last-of-type,link[methods],link[methods]~link:last-of-type,link[rev],link[rev]~link:last-of-type,link[target],link[target]~link:last-of-type,link[urn],link[urn]~link:last-of-type,meta[charset]:not([charset=utf-8],[charset=UTF-8]),meta[charset]:not([charset=utf-8],[charset=UTF-8])~link:last-of-type,meta[http-equiv=content-language],meta[http-equiv=content-language]~link:last-of-type,meta[http-equiv=content-type],meta[http-equiv=content-type]~link:last-of-type,meta[http-equiv=set-cookie],meta[http-equiv=set-cookie]~link:last-of-type,meta[name=viewport][content*=\"user-scalable=no\"],meta[name=viewport][content*=\"user-scalable=no\"]~link:last-of-type,meta[name=viewport][content*=maximum-scale],meta[name=viewport][content*=maximum-scale]~link:last-of-type,meta[name=viewport][content*=minimum-scale],meta[name=viewport][content*=minimum-scale]~link:last-of-type,meta[scheme],meta[scheme]~link:last-of-type,script[event],script[event]~link:last-of-type,script[for],script[for]~link:last-of-type,script[language],script[language]~link:last-of-type,title:empty{display:block!important}html{counter-reset:error warning obsolete advice}body:after{background-color:#3e4b55;background-image:linear-gradient(180deg,transparent,transparent 1.4em,#d90b0b 0,#d90b0b 1.6em,transparent 0,transparent 2.9em,#f50 0,#f50 3.1em,transparent 0,transparent 4.4em,#4169e1 0,#4169e1 4.6em,transparent 0,transparent 5.9em,green 0,green 6.1em,transparent 0,transparent);background-position:.5em 0;background-repeat:no-repeat;background-size:.5em 100%;color:#fcf9e9;contain:content;content:\"Errors\" \": \" counter(error) \"\\\\a\" \"Warnings\" \": \" counter(warning) \"\\\\a\" \"Obsoletes\" \": \" counter(obsolete) \"\\\\a\" \"Advices\" \": \" counter(advice) \"\\\\a\";font:700 18px/1.5 sans-serif;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;height:auto;inset:auto auto 1em 1em;padding:.75em 1em .75em 1.5em;position:fixed;white-space:pre;width:auto;z-index:2147483647}head{display:block}head *{display:none}select[required]:not([multiple])[size=\"1\"]+:before,select[required]:not([multiple],[size])+:before{background:green!important;content:'<select required> should start with <option value=\"\">'!important;z-index:2147483644!important}[class=\" \"]:after,[class=\"\"]:after,area[class=\" \"]+:before,area[class=\"\"]+:before,audio[class=\" \"]+:before,audio[class=\"\"]+:before,base[class=\" \"]+:before,base[class=\"\"]+:before,br[class=\" \"]+:before,br[class=\"\"]+:before,col[class=\" \"]+:before,col[class=\"\"]+:before,command[class=\" \"]+:before,command[class=\"\"]+:before,embed[class=\" \"]+:before,embed[class=\"\"]+:before,hr[class=\" \"]+:before,hr[class=\"\"]+:before,iframe[class=\" \"]+:before,iframe[class=\"\"]+:before,img[class=\" \"]+:before,img[class=\"\"]+:before,input[class=\" \"]+:before,input[class=\"\"]+:before,keygen[class=\" \"]+:before,keygen[class=\"\"]+:before,link[class=\" \"]+:before,link[class=\"\"]+:before,meta[class=\" \"]+:before,meta[class=\"\"]+:before,param[class=\" \"]+:before,param[class=\"\"]+:before,select[class=\" \"]+:before,select[class=\"\"]+:before,source[class=\" \"]+:before,source[class=\"\"]+:before,svg[class=\" \"]+:before,svg[class=\"\"]+:before,textarea[class=\" \"]+:before,textarea[class=\"\"]+:before,track[class=\" \"]+:before,track[class=\"\"]+:before,video[class=\" \"]+:before,video[class=\"\"]+:before,wbr[class=\" \"]+:before,wbr[class=\"\"]+:before{background:green!important;content:\"Empty [class]\"!important;z-index:2147483644!important}[id=\" \"]:after,[id=\"\"]:after,area[id=\" \"]+:before,area[id=\"\"]+:before,audio[id=\" \"]+:before,audio[id=\"\"]+:before,base[id=\" \"]+:before,base[id=\"\"]+:before,br[id=\" \"]+:before,br[id=\"\"]+:before,col[id=\" \"]+:before,col[id=\"\"]+:before,command[id=\" \"]+:before,command[id=\"\"]+:before,embed[id=\" \"]+:before,embed[id=\"\"]+:before,hr[id=\" \"]+:before,hr[id=\"\"]+:before,iframe[id=\" \"]+:before,iframe[id=\"\"]+:before,img[id=\" \"]+:before,img[id=\"\"]+:before,input[id=\" \"]+:before,input[id=\"\"]+:before,keygen[id=\" \"]+:before,keygen[id=\"\"]+:before,link[id=\" \"]+:before,link[id=\"\"]+:before,meta[id=\" \"]+:before,meta[id=\"\"]+:before,param[id=\" \"]+:before,param[id=\"\"]+:before,select[id=\" \"]+:before,select[id=\"\"]+:before,source[id=\" \"]+:before,source[id=\"\"]+:before,svg[id=\" \"]+:before,svg[id=\"\"]+:before,textarea[id=\" \"]+:before,textarea[id=\"\"]+:before,track[id=\" \"]+:before,track[id=\"\"]+:before,video[id=\" \"]+:before,video[id=\"\"]+:before,wbr[id=\" \"]+:before,wbr[id=\"\"]+:before{background:green!important;content:\"Empty [id]\"!important;z-index:2147483644!important}main~main:not([hidden]):after{background:green!important;content:\"Second <main>\"!important;z-index:2147483644!important}figcaption:not(:first-of-type):after{background:green!important;content:\"Second <figcaption>\"!important;z-index:2147483644!important}figcaption:not(:first-child,:last-child):after{background:green!important;content:\"Wrong order for <figcaption>\"!important;z-index:2147483644!important}[href^=mailto]:after{content:\"Is email valid?\"!important}[href^=mailto]:after,[href^=tel]:after{background:green!important;z-index:2147483644!important}[href^=tel]:after{content:\"Is phone number valid?\"!important}a[role=button]:after{content:\"[role=button] on link\"!important}[target$=blank]:after,a[role=button]:after{background:green!important;z-index:2147483644!important}[target$=blank]:after{content:\"Link opening a new tab\"!important}[download]:after,[href$=\".apng\"]:not(link):after,[href$=\".doc\"]:not(link):after,[href$=\".docx\"]:not(link):after,[href$=\".gif\"]:not(link):after,[href$=\".ics\"]:not(link):after,[href$=\".jpg\"]:not(link):after,[href$=\".mov\"]:not(link):after,[href$=\".mp3\"]:not(link):after,[href$=\".mp4\"]:not(link):after,[href$=\".ogg\"]:not(link):after,[href$=\".pdf\"]:not(link):after,[href$=\".png\"]:not(link):after,[href$=\".rar\"]:not(link):after,[href$=\".svg\"]:not(link):after,[href$=\".svgz\"]:not(link):after,[href$=\".txt\"]:not(link):after,[href$=\".webp\"]:not(link):after,[href$=\".xls\"]:not(link):after,[href$=\".zip\"]:not(link):after{background:green!important;content:\"Are file format & size displayed?\"!important;z-index:2147483644!important}[role=banner]~[role=banner]:after,[role=contentinfo]~[role=contentinfo]:after,[role=main]~[role=main]:after,[role=search]~[role=search]:after{background:green!important;content:\"Second \" attr(role) \" role\"!important;z-index:2147483644!important}[class*=search]:not([role=search]):after,[id*=search]:not([role=search]):after,input[class*=search]:not([role=search])+:before,input[id*=search]:not([role=search])+:before{background:green!important;content:\"Is [role=search] defined?\"!important;z-index:2147483644!important}[aria-required]+:before,[required]+:before,input[aria-required]+:before,input[required]+:before,select[aria-required]+:before,select[required]+:before,textarea[aria-required]+:before,textarea[required]+:before{background:green!important;content:\"[required] field: is it explicit?\"!important;z-index:2147483644!important}[aria-hidden=true]:not(:empty):after,[hidden]:not(:empty):after,area[aria-hidden=true]:not(:empty)+:before,area[hidden]:not(:empty)+:before,audio[aria-hidden=true]:not(:empty)+:before,audio[hidden]:not(:empty)+:before,base[aria-hidden=true]:not(:empty)+:before,base[hidden]:not(:empty)+:before,br[aria-hidden=true]:not(:empty)+:before,br[hidden]:not(:empty)+:before,col[aria-hidden=true]:not(:empty)+:before,col[hidden]:not(:empty)+:before,command[aria-hidden=true]:not(:empty)+:before,command[hidden]:not(:empty)+:before,embed[aria-hidden=true]:not(:empty)+:before,embed[hidden]:not(:empty)+:before,hr[aria-hidden=true]:not(:empty)+:before,hr[hidden]:not(:empty)+:before,iframe[aria-hidden=true]:not(:empty)+:before,iframe[hidden]:not(:empty)+:before,img[aria-hidden=true]:not(:empty)+:before,img[hidden]:not(:empty)+:before,input[aria-hidden=true]:not(:empty)+:before,input[hidden]:not(:empty)+:before,keygen[aria-hidden=true]:not(:empty)+:before,keygen[hidden]:not(:empty)+:before,link[aria-hidden=true]:not(:empty)+:before,link[hidden]:not(:empty)+:before,meta[aria-hidden=true]:not(:empty)+:before,meta[hidden]:not(:empty)+:before,param[aria-hidden=true]:not(:empty)+:before,param[hidden]:not(:empty)+:before,select[aria-hidden=true]:not(:empty)+:before,select[hidden]:not(:empty)+:before,source[aria-hidden=true]:not(:empty)+:before,source[hidden]:not(:empty)+:before,svg[aria-hidden=true]:not(:empty)+:before,svg[hidden]:not(:empty)+:before,textarea[aria-hidden=true]:not(:empty)+:before,textarea[hidden]:not(:empty)+:before,track[aria-hidden=true]:not(:empty)+:before,track[hidden]:not(:empty)+:before,video[aria-hidden=true]:not(:empty)+:before,video[hidden]:not(:empty)+:before,wbr[aria-hidden=true]:not(:empty)+:before,wbr[hidden]:not(:empty)+:before{background:green!important;content:\"Hidden element\"!important;z-index:2147483644!important}[placeholder]:not([title],[aria-label],[aria-labelledby])+:before{background:green!important;content:\"Placeholder can’t replace <label>\"!important;z-index:2147483644!important}audio+:before,video+:before{background:green!important;content:\"Missing alternate version\"!important;z-index:2147483644!important}area[alt][title]+:before,img[alt][title]+:before,svg[aria-label][title]+:before{background:green!important;content:\"[title] should equal to alternative\"!important;z-index:2147483644!important}[datetime]:after,time:after{content:\"Is date valid?\"!important}[datetime]:after,th[scope]:after,time:after{background:green!important;z-index:2147483644!important}th[scope]:after{content:\"Does <th> apply to \" attr(scope) \"?\"!important}table[role=presentation]:after{content:\"Layout table\"!important}a:not([href]):after,table[role=presentation]:after{background:green!important;z-index:2147483644!important}a:not([href]):after{content:\"Link without target\"!important}[href^=\"http:\"]:after,[src^=\"http:\"]:after{background:green!important;content:\"No HTTPS\"!important;z-index:2147483644!important}acronym:after,applet:after,basefont:after,bgsound:after,big:after,blink:after,center:after,dir:after,font:after,frame:after,frameset:after,hgroup:after,isindex:after,keygen:after,listing:after,marquee:after,menu:after,menuitem:after,multicol:after,nextid:after,nobr:after,noembed:after,noframes:after,plaintext:after,rb:after,rtc:after,spacer:after,strike:after,tt:after,xmp:after{background:#4169e1!important;content:\"Obsolete element\"!important;z-index:2147483645!important}[dropzone]:after,a[charset]:after,a[coords]:after,a[datafld]:after,a[datasrc]:after,a[methods]:after,a[name]:after,a[rev]:after,a[shape]:after,a[urn]:after,applet[datafld]:after,applet[datasrc]:after,area[hreflang]+:before,area[nohref]+:before,area[type]+:before,body[alink]:after,body[background]:after,body[bgcolor]:after,body[link]:after,body[marginbottom]:after,body[marginheight]:after,body[marginleft]:after,body[marginright]:after,body[margintop]:after,body[marginwidth]:after,body[text]:after,body[vlink]:after,br[clear]+:before,button[datafld]:after,button[dataformatas]:after,button[datasrc]:after,caption[align]:after,col[align]:after,col[char]:after,col[charoff]:after,col[valign]:after,col[width]:after,div[align]:after,div[datafld]:after,div[dataformatas]:after,div[datasrc]:after,dl[compact]:after,embed[align]+:before,embed[hspace]+:before,embed[name]+:before,embed[name]:after,embed[vspace]+:before,fieldset[datafld]:after,form[accept]:after,frame[datafld]:after,frame[datasrc]:after,h1[align]:after,h2[align]:after,h3[align]:after,h4[align]:after,h5[align]:after,h6[align]:after,head[profile]:after,hr[align]+:before,hr[color]+:before,hr[noshade]+:before,hr[size]+:before,hr[width]+:before,html[version]:after,iframe[align]+:before,iframe[allowtransparency]+:before,iframe[datafld]+:before,iframe[datasrc]+:before,iframe[frameborder]+:before,iframe[framespacing]+:before,iframe[hspace]+:before,iframe[longdesc]+:before,iframe[marginheight]+:before,iframe[marginwidth]+:before,iframe[scrolling]+:before,iframe[vspace]+:before,img[align]+:before,img[border]+:before,img[datafld]+:before,img[datasrc]+:before,img[hspace]+:before,img[longdesc]+:before,img[lowsrc]+:before,img[name]+:before,img[name]:after,img[vspace]+:before,input[align]+:before,input[datafld]+:before,input[dataformatas]+:before,input[datasrc]+:before,input[hspace]+:before,input[inputmode]+:before,input[ismap]+:before,input[usemap]+:before,input[vspace]+:before,label[datafld]:after,label[dataformatas]:after,label[datasrc]:after,legend[align]:after,legend[datafld]:after,legend[dataformatas]:after,legend[datasrc]:after,li[type]:after,link[charset]:after,link[charset]~link:last-of-type:before,link[methods]:after,link[methods]~link:last-of-type:before,link[rev]:after,link[rev]~link:last-of-type:before,link[target]:after,link[target]~link:last-of-type:before,link[urn]:after,link[urn]~link:last-of-type:before,marquee[datafld]:after,marquee[dataformatas]:after,marquee[datasrc]:after,meta[http-equiv=content-language]~link:last-of-type:before,meta[http-equiv=content-type]~link:last-of-type:before,meta[http-equiv=set-cookie]~link:last-of-type:before,meta[scheme]~link:last-of-type:before,object[align]+:before,object[archive]+:before,object[border]+:before,object[classid]+:before,object[code]+:before,object[codebase]+:before,object[codetype]+:before,object[datafld]+:before,object[dataformatas]+:before,object[datasrc]+:before,object[declare]+:before,object[hspace]+:before,object[standby]+:before,object[vspace]+:before,ol[compact]:after,option[dataformatas]:after,option[datasrc]:after,option[name]:after,p[align]:after,param[datafld]:after,param[type]:after,param[valuetype]:after,pre[width]:after,script[event]:after,script[event]~link:last-of-type:before,script[for]:after,script[for]~link:last-of-type:before,script[language]:after,script[language]~link:last-of-type:before,select[datafld]+:before,select[dataformatas]+:before,select[datasrc]+:before,span[datafld]:after,span[dataformatas]:after,span[datasrc]:after,table[align]:after,table[background]:after,table[bgcolor]:after,table[cellpadding]:after,table[cellspacing]:after,table[dataformatas]:after,table[datapagesize]:after,table[datasrc]:after,table[frame]:after,table[rules]:after,table[summary]:after,table[width]:after,tbody[align]:after,tbody[background]:after,tbody[char]:after,tbody[charoff]:after,tbody[valign]:after,td[abbr]:after,td[align]:after,td[axis]:after,td[background]:after,td[bgcolor]:after,td[char]:after,td[charoff]:after,td[height]:after,td[nowrap]:after,td[scope]:after,td[valign]:after,td[width]:after,textarea[datafld]+:before,textarea[datasrc]+:before,tfoot[align]:after,tfoot[background]:after,tfoot[char]:after,tfoot[charoff]:after,tfoot[valign]:after,th[align]:after,th[axis]:after,th[background]:after,th[bgcolor]:after,th[char]:after,th[charoff]:after,th[height]:after,th[nowrap]:after,th[valign]:after,th[width]:after,thead[align]:after,thead[background]:after,thead[char]:after,thead[charoff]:after,thead[valign]:after,tr[align]:after,tr[background]:after,tr[bgcolor]:after,tr[char]:after,tr[charoff]:after,tr[valign]:after,ul[compact]:after,ul[type]:after{background:#4169e1!important;content:\"Obsolete attribute\"!important;z-index:2147483645!important}:not(ul,ol)>li:after,ol>:not(li):after,ul>:not(li):after{background:#f50!important;content:\"Invalid <li> nesting\"!important;z-index:2147483646!important}:not(dt,dd)+dd:after,dt+:not(dd):after{background:#f50!important;content:\"Invalid <dd> order\"!important;z-index:2147483646!important}:not(dl)>dd:after,:not(dl)>dt:after,dl>:not(dt,dd,div):after{background:#f50!important;content:\"Invalid <dl> child\"!important;z-index:2147483646!important}:not(figure)>figcaption:after{background:#f50!important;content:\"<figcaption> outside <figure>\"!important;z-index:2147483646!important}figure:not([role=group]):after{background:#f50!important;content:\"<figure> without [role=group]\"!important;z-index:2147483646!important}:not(colgroup)>col:after,:not(fieldset)>legend:after,:not(select)>optgroup:after,:not(select,optgroup)>option:after,:not(tr)>td:after,:not(tr)>th:after,address address:after,address article:after,address aside:after,address footer:after,address h1:after,address h2:after,address h3:after,address h4:after,address h5:after,address h6:after,address header:after,address nav:after,address section:after,article main:after,aside main:after,colgroup :not(col):after,footer main:after,header main:after,nav main:after,optgroup>:not(option):after,select>:not(option,optgroup):after,table>:not(thead,tfoot,tbody,tr,colgroup,caption):after,tr>:not(td,th):after{background:#f50!important;content:\"Invalid nesting\"!important;z-index:2147483646!important}abbr div:after,b div:after,cite div:after,code div:after,em div:after,i div:after,label div:after,q div:after,small div:after,span div:after,strong div:after{background:#f50!important;content:\"<div> in inline element\"!important;z-index:2147483646!important}article>article:first-child:after,article>aside:first-child:after,article>section:first-child:after,aside>article:first-child:after,aside>aside:first-child:after,aside>section:first-child:after,section>section:first-child:after{background:#f50!important;content:\"Misused sectioning tag\"!important;z-index:2147483646!important}fieldset>:not(legend):first-child:after,fieldset>legend:not(:first-child):after{background:#f50!important;content:\"<fieldset> must start with <legend>\"!important;z-index:2147483646!important}details>:not(summary):first-child:after,details>summary:not(:first-child):after{background:#f50!important;content:\"<details> must start with <summary>\"!important;z-index:2147483646!important}abbr:not([title]):after,abbr[title=\" \"]:after,abbr[title=\"\"]:after{background:#f50!important;content:\"Empty [title]\"!important;z-index:2147483646!important}area[alt=\"\"]+:before,embed[type=image][alt=\"\"]:after,img[alt=\"\"]+:before,input[type=image][alt=\"\"]+:before,object[type=image][alt=\"\"]:after{background:#f50!important;content:\"Empty [alt]\"!important;z-index:2147483646!important}area[alt$=\".apng\"]+:before,area[alt$=\".doc\"]+:before,area[alt$=\".docx\"]+:before,area[alt$=\".gif\"]+:before,area[alt$=\".ics\"]+:before,area[alt$=\".jpg\"]+:before,area[alt$=\".mov\"]+:before,area[alt$=\".mp3\"]+:before,area[alt$=\".mp4\"]+:before,area[alt$=\".ogg\"]+:before,area[alt$=\".pdf\"]+:before,area[alt$=\".png\"]+:before,area[alt$=\".rar\"]+:before,area[alt$=\".svg\"]+:before,area[alt$=\".svgz\"]+:before,area[alt$=\".txt\"]+:before,area[alt$=\".webp\"]+:before,area[alt$=\".xls\"]+:before,area[alt$=\".zip\"]+:before,embed[type=image][alt$=\".apng\"]:after,embed[type=image][alt$=\".doc\"]:after,embed[type=image][alt$=\".docx\"]:after,embed[type=image][alt$=\".gif\"]:after,embed[type=image][alt$=\".ics\"]:after,embed[type=image][alt$=\".jpg\"]:after,embed[type=image][alt$=\".mov\"]:after,embed[type=image][alt$=\".mp3\"]:after,embed[type=image][alt$=\".mp4\"]:after,embed[type=image][alt$=\".ogg\"]:after,embed[type=image][alt$=\".pdf\"]:after,embed[type=image][alt$=\".png\"]:after,embed[type=image][alt$=\".rar\"]:after,embed[type=image][alt$=\".svg\"]:after,embed[type=image][alt$=\".svgz\"]:after,embed[type=image][alt$=\".txt\"]:after,embed[type=image][alt$=\".webp\"]:after,embed[type=image][alt$=\".xls\"]:after,embed[type=image][alt$=\".zip\"]:after,img[alt$=\".apng\"]+:before,img[alt$=\".doc\"]+:before,img[alt$=\".docx\"]+:before,img[alt$=\".gif\"]+:before,img[alt$=\".ics\"]+:before,img[alt$=\".jpg\"]+:before,img[alt$=\".mov\"]+:before,img[alt$=\".mp3\"]+:before,img[alt$=\".mp4\"]+:before,img[alt$=\".ogg\"]+:before,img[alt$=\".pdf\"]+:before,img[alt$=\".png\"]+:before,img[alt$=\".rar\"]+:before,img[alt$=\".svg\"]+:before,img[alt$=\".svgz\"]+:before,img[alt$=\".txt\"]+:before,img[alt$=\".webp\"]+:before,img[alt$=\".xls\"]+:before,img[alt$=\".zip\"]+:before,input[type=image][alt$=\".apng\"]+:before,input[type=image][alt$=\".doc\"]+:before,input[type=image][alt$=\".docx\"]+:before,input[type=image][alt$=\".gif\"]+:before,input[type=image][alt$=\".ics\"]+:before,input[type=image][alt$=\".jpg\"]+:before,input[type=image][alt$=\".mov\"]+:before,input[type=image][alt$=\".mp3\"]+:before,input[type=image][alt$=\".mp4\"]+:before,input[type=image][alt$=\".ogg\"]+:before,input[type=image][alt$=\".pdf\"]+:before,input[type=image][alt$=\".png\"]+:before,input[type=image][alt$=\".rar\"]+:before,input[type=image][alt$=\".svg\"]+:before,input[type=image][alt$=\".svgz\"]+:before,input[type=image][alt$=\".txt\"]+:before,input[type=image][alt$=\".webp\"]+:before,input[type=image][alt$=\".xls\"]+:before,input[type=image][alt$=\".zip\"]+:before,object[type=image][alt$=\".apng\"]:after,object[type=image][alt$=\".doc\"]:after,object[type=image][alt$=\".docx\"]:after,object[type=image][alt$=\".gif\"]:after,object[type=image][alt$=\".ics\"]:after,object[type=image][alt$=\".jpg\"]:after,object[type=image][alt$=\".mov\"]:after,object[type=image][alt$=\".mp3\"]:after,object[type=image][alt$=\".mp4\"]:after,object[type=image][alt$=\".ogg\"]:after,object[type=image][alt$=\".pdf\"]:after,object[type=image][alt$=\".png\"]:after,object[type=image][alt$=\".rar\"]:after,object[type=image][alt$=\".svg\"]:after,object[type=image][alt$=\".svgz\"]:after,object[type=image][alt$=\".txt\"]:after,object[type=image][alt$=\".webp\"]:after,object[type=image][alt$=\".xls\"]:after,object[type=image][alt$=\".zip\"]:after{background:#f50!important;content:\"File name [alt]\"!important;z-index:2147483646!important}area:not([href])[alt=\"\"][aria-describedby]+:before,area:not([href])[alt=\"\"][aria-label]+:before,area:not([href])[alt=\"\"][aria-labelledby]+:before,area:not([href])[alt=\"\"][title]+:before,area:not([href])[alt]:not([alt=\"\"])+:before,canvas[aria-hidden=true][aria-describedby]:after,canvas[aria-hidden=true][aria-label]:after,canvas[aria-hidden=true][aria-labelledby]:after,canvas[aria-hidden=true][title]:after,embed[type=image][aria-hidden=true][aria-describedby]+:before,embed[type=image][aria-hidden=true][aria-label]+:before,embed[type=image][aria-hidden=true][aria-labelledby]+:before,embed[type=image][aria-hidden=true][title]+:before,img[alt=\"\"][aria-describedby]+:before,img[alt=\"\"][aria-label]+:before,img[alt=\"\"][aria-labelledby]+:before,img[alt=\"\"][title]+:before,object[type=image][aria-hidden=true][aria-describedby]:after,object[type=image][aria-hidden=true][aria-label]:after,object[type=image][aria-hidden=true][aria-labelledby]:after,object[type=image][aria-hidden=true][title]:after,svg[aria-hidden=true][aria-describedby]+:before,svg[aria-hidden=true][aria-label]+:before,svg[aria-hidden=true][aria-labelledby]+:before,svg[aria-hidden=true][title]+:before{background:#f50!important;content:\"Semantic attribute on a decorative image\"!important;z-index:2147483646!important}area[role=presentation]+:before,canvas[role=presentation]:after,embed[role=presentation]+:before,img[role=presentation]+:before,object[role=presentation]:after,svg[role=presentation]+:before{background:#f50!important;content:\"[role=presentation] on an image\"!important;z-index:2147483646!important}svg:not([aria-hidden=true],[role=img])+:before{background:#f50!important;content:\"Is this SVG decorative?\"!important;z-index:2147483646!important}audio[autoplay]+:before,video[autoplay]+:before{background:#f50!important;content:\"Autoplay\"!important;z-index:2147483646!important}audio:not([controls])+:before,video:not([controls])+:before{background:#f50!important;content:\"Missing [controls]\"!important;z-index:2147483646!important}body :empty:not([hidden],[aria-hidden],[src],button,a,iframe,textarea,area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr,title){min-height:1px;min-width:1px}body :empty:not([hidden],[aria-hidden],[src],button,a,iframe,textarea,area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr,title):after{background:#f50!important;content:\"Empty element\"!important;z-index:2147483646!important}table:not([role=presentation])>tbody>tr:only-child:after,table:not([role=presentation])>tr:only-child:after{background:#f50!important;content:\"Single row\"!important;z-index:2147483646!important}table table:after{background:#f50!important;content:\"Nested <table>s\"!important;z-index:2147483646!important}table:not([role=presentation])>:first-child:not(caption):after,table:not([role=presentation])>caption:not(:first-child):after{background:#f50!important;content:\"Data table must start with <caption>\"!important;z-index:2147483646!important}table>tbody~colgroup:after,table>tbody~tfoot:after,table>tbody~thead:after,table>tfoot~colgroup:after,table>tfoot~thead:after{background:#f50!important;content:\"Wrong order for <thead>, <tfoot> or <tbody>\"!important;z-index:2147483646!important}table:not([role=presentation])>caption+tbody:after,table:not([role=presentation])>tbody:first-child:after{background:#f50!important;content:\"Missing <thead>\"!important;z-index:2147483646!important}th:not([scope],[id]):after{background:#f50!important;content:\"Missing [scope] or [id]\"!important;z-index:2147483646!important}img[src*=\"1px.gif\"]:not([role=presentation])+:before,img[src*=\"1x1.gif\"]:not([role=presentation])+:before,img[src*=\"clear.gif\"]:not([role=presentation])+:before,img[src*=\"dotclear.gif\"]:not([role=presentation])+:before,img[src*=\"pixel-1x1-clear.gif\"]:not([role=presentation])+:before,img[src*=\"spacer.gif\"]:not([role=presentation])+:before,img[src*=\"transparent.gif\"]:not([role=presentation])+:before{background:#f50!important;content:\"spacer.gif\"!important;z-index:2147483646!important}[class*=NaN]:after,[class*=null]:after,[class*=undefined]:after,area[class*=NaN]+:before,area[class*=null]+:before,area[class*=undefined]+:before,audio[class*=NaN]+:before,audio[class*=null]+:before,audio[class*=undefined]+:before,base[class*=NaN]+:before,base[class*=null]+:before,base[class*=undefined]+:before,br[class*=NaN]+:before,br[class*=null]+:before,br[class*=undefined]+:before,col[class*=NaN]+:before,col[class*=null]+:before,col[class*=undefined]+:before,command[class*=NaN]+:before,command[class*=null]+:before,command[class*=undefined]+:before,embed[class*=NaN]+:before,embed[class*=null]+:before,embed[class*=undefined]+:before,hr[class*=NaN]+:before,hr[class*=null]+:before,hr[class*=undefined]+:before,iframe[class*=NaN]+:before,iframe[class*=null]+:before,iframe[class*=undefined]+:before,img[class*=NaN]+:before,img[class*=null]+:before,img[class*=undefined]+:before,input[class*=NaN]+:before,input[class*=null]+:before,input[class*=undefined]+:before,keygen[class*=NaN]+:before,keygen[class*=null]+:before,keygen[class*=undefined]+:before,link[class*=NaN]+:before,link[class*=null]+:before,link[class*=undefined]+:before,meta[class*=NaN]+:before,meta[class*=null]+:before,meta[class*=undefined]+:before,param[class*=NaN]+:before,param[class*=null]+:before,param[class*=undefined]+:before,select[class*=NaN]+:before,select[class*=null]+:before,select[class*=undefined]+:before,source[class*=NaN]+:before,source[class*=null]+:before,source[class*=undefined]+:before,svg[class*=NaN]+:before,svg[class*=null]+:before,svg[class*=undefined]+:before,textarea[class*=NaN]+:before,textarea[class*=null]+:before,textarea[class*=undefined]+:before,track[class*=NaN]+:before,track[class*=null]+:before,track[class*=undefined]+:before,video[class*=NaN]+:before,video[class*=null]+:before,video[class*=undefined]+:before,wbr[class*=NaN]+:before,wbr[class*=null]+:before,wbr[class*=undefined]+:before{background:#f50!important;content:\"Bad computed value for [class]\"!important;z-index:2147483646!important}[id*=NaN]:after,[id*=null]:after,[id*=undefined]:after,area[id*=NaN]+:before,area[id*=null]+:before,area[id*=undefined]+:before,audio[id*=NaN]+:before,audio[id*=null]+:before,audio[id*=undefined]+:before,base[id*=NaN]+:before,base[id*=null]+:before,base[id*=undefined]+:before,br[id*=NaN]+:before,br[id*=null]+:before,br[id*=undefined]+:before,col[id*=NaN]+:before,col[id*=null]+:before,col[id*=undefined]+:before,command[id*=NaN]+:before,command[id*=null]+:before,command[id*=undefined]+:before,embed[id*=NaN]+:before,embed[id*=null]+:before,embed[id*=undefined]+:before,hr[id*=NaN]+:before,hr[id*=null]+:before,hr[id*=undefined]+:before,iframe[id*=NaN]+:before,iframe[id*=null]+:before,iframe[id*=undefined]+:before,img[id*=NaN]+:before,img[id*=null]+:before,img[id*=undefined]+:before,input[id*=NaN]+:before,input[id*=null]+:before,input[id*=undefined]+:before,keygen[id*=NaN]+:before,keygen[id*=null]+:before,keygen[id*=undefined]+:before,link[id*=NaN]+:before,link[id*=null]+:before,link[id*=undefined]+:before,meta[id*=NaN]+:before,meta[id*=null]+:before,meta[id*=undefined]+:before,param[id*=NaN]+:before,param[id*=null]+:before,param[id*=undefined]+:before,select[id*=NaN]+:before,select[id*=null]+:before,select[id*=undefined]+:before,source[id*=NaN]+:before,source[id*=null]+:before,source[id*=undefined]+:before,svg[id*=NaN]+:before,svg[id*=null]+:before,svg[id*=undefined]+:before,textarea[id*=NaN]+:before,textarea[id*=null]+:before,textarea[id*=undefined]+:before,track[id*=NaN]+:before,track[id*=null]+:before,track[id*=undefined]+:before,video[id*=NaN]+:before,video[id*=null]+:before,video[id*=undefined]+:before,wbr[id*=NaN]+:before,wbr[id*=null]+:before,wbr[id*=undefined]+:before{background:#f50!important;content:\"Bad computed value for [id]\"!important;z-index:2147483646!important}a[href^=javascript]:not([role=button]):after{background:#f50!important;content:\"[href] starting with javascript:\"!important;z-index:2147483646!important}a[href=\"#\"]:not([role=button]):after{background:#f50!important;content:\"[href=\" attr(href) \"]\"!important;z-index:2147483646!important}[style]:after,area[style]+:before,audio[style]+:before,base[style]+:before,br[style]+:before,col[style]+:before,command[style]+:before,embed[style]+:before,hr[style]+:before,iframe[style]+:before,img[style]+:before,input[style]+:before,keygen[style]+:before,link[style]+:before,meta[style]+:before,param[style]+:before,select[style]+:before,source[style]+:before,svg[style]+:before,textarea[style]+:before,track[style]+:before,video[style]+:before,wbr[style]+:before{background:#f50!important;content:\"Inline styles\"!important;z-index:2147483646!important}[target$=blank]:not([rel*=noopener]):after,[target$=blank]:not([rel*=noreferrer]):after,[target$=blank]:not([rel]):after{background:#f50!important;content:\"Unsafe [target]\"!important;z-index:2147483646!important}[role=heading]:not([aria-level]):after{background:#f50!important;content:\"No [aria-level]\"!important;z-index:2147483646!important}label:not([for]):after{background:#f50!important;content:\"Missing [for]\"!important;z-index:2147483646!important}[dir=rtl]:not([lang=ar],[lang=he]):after,[lang=ar] [lang]:not([dir=ltr]):after,[lang=ar]:not([dir=rtl]):after,[lang=he] [lang]:not([dir=ltr]):after,[lang=he]:not([dir=rtl]):after{background:#f50!important;content:\"Inconsistent [lang] and [dir]\"!important;z-index:2147483646!important}[id*=\" \"]:after,[lang*=\" \"]:after,area[id*=\" \"]+:before,area[lang*=\" \"]+:before,audio[id*=\" \"]+:before,audio[lang*=\" \"]+:before,base[id*=\" \"]+:before,base[lang*=\" \"]+:before,br[id*=\" \"]+:before,br[lang*=\" \"]+:before,col[id*=\" \"]+:before,col[lang*=\" \"]+:before,command[id*=\" \"]+:before,command[lang*=\" \"]+:before,embed[id*=\" \"]+:before,embed[lang*=\" \"]+:before,hr[id*=\" \"]+:before,hr[lang*=\" \"]+:before,iframe[id*=\" \"]+:before,iframe[lang*=\" \"]+:before,img[id*=\" \"]+:before,img[lang*=\" \"]+:before,input[id*=\" \"]+:before,input[lang*=\" \"]+:before,keygen[id*=\" \"]+:before,keygen[lang*=\" \"]+:before,link[id*=\" \"]+:before,link[lang*=\" \"]+:before,map[name*=\" \"]:after,meta[id*=\" \"]+:before,meta[lang*=\" \"]+:before,param[id*=\" \"]+:before,param[lang*=\" \"]+:before,select[id*=\" \"]+:before,select[lang*=\" \"]+:before,source[id*=\" \"]+:before,source[lang*=\" \"]+:before,svg[id*=\" \"]+:before,svg[lang*=\" \"]+:before,textarea[id*=\" \"]+:before,textarea[lang*=\" \"]+:before,track[id*=\" \"]+:before,track[lang*=\" \"]+:before,video[id*=\" \"]+:before,video[lang*=\" \"]+:before,wbr[id*=\" \"]+:before,wbr[lang*=\" \"]+:before{background:#d90b0b!important;content:\"Attribute can't contain whitespace\"!important;z-index:2147483647!important}[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"]):after,area[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,audio[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,base[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,br[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,col[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,command[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,embed[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,hr[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,iframe[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,img[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,input[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,keygen[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,link[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,meta[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,param[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,select[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,source[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,svg[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,textarea[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,track[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,video[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before,wbr[tabindex]:not([tabindex=\"0\"],[tabindex^=\"-\"])+:before{background:#d90b0b!important;content:\"Disrupting tab order\"!important;z-index:2147483647!important}a[href=\" \"]:after,a[href=\"\"]:after{background:#d90b0b!important;content:\"Empty [href]\"!important;z-index:2147483647!important}a:empty:not([title],[aria-label],[aria-labelledby]):after,a:empty[aria-label=\"\"]:after,a:empty[aria-labelledby=\"\"]:after,a:empty[title=\"\"]:after{background:#d90b0b!important;content:\"Empty link…\"!important;z-index:2147483647!important}area:not([alt])+:before,area[alt=\" \"]+:before,img:not([alt])+:before,img[alt=\" \"]+:before,input[type=image]:not([alt])+:before,input[type=image][alt=\" \"]+:before{background:#d90b0b!important;content:\"Missing [alt]\"!important;z-index:2147483647!important}[role=img]:not([aria-hidden=true],[aria-label],[aria-labelledby]):after,svg[role=img]:not([aria-hidden=true],[aria-label],[aria-labelledby])+:before{background:#d90b0b!important;content:\"Missing [aria-label] and [aria-labelledby]\"!important;z-index:2147483647!important}img:not([src],[srcset]):after,input[type=image]:not([src],[srcset]):after{background:#d90b0b!important;content:\"Missing source\"!important;z-index:2147483647!important}img[src=\" \"]:after,img[src=\"\"]:after,img[src=\"#\"]:after,img[src=\"/\"]:after,input[type=image][src=\" \"]:after,input[type=image][src=\"\"]:after,input[type=image][src=\"#\"]:after,input[type=image][src=\"/\"]:after{background:#d90b0b!important;content:\"Invalid [src]\"!important;z-index:2147483647!important}img[srcset=\" \"]:after,img[srcset=\"\"]:after,img[srcset=\"#\"]:after,img[srcset=\"/\"]:after,input[type=image][srcset=\" \"]:after,input[type=image][srcset=\"\"]:after,input[type=image][srcset=\"#\"]:after,input[type=image][srcset=\"/\"]:after{background:#d90b0b!important;content:\"Invalid [srcset]\"!important;z-index:2147483647!important}label[for=\" \"]:after,label[for=\"\"]:after{background:#d90b0b!important;content:\"Empty [for]\"!important;z-index:2147483647!important}input:not([type=button],[type=submit],[type=hidden],[type=reset],[type=image],[id],[aria-label],[title],[aria-labelledby])+:before,select:not([id],[aria-label],[aria-labelledby])+:before,textarea:not([id],[aria-label],[aria-labelledby])+:before{background:#d90b0b!important;content:\"Unlabelled field\"!important;z-index:2147483647!important}input[type=button]:not([value],[title],[aria-label],[aria-labelledby])+:before,input[type=reset]:not([value],[title],[aria-label],[aria-labelledby])+:before,input[type=submit]:not([value],[title],[aria-label],[aria-labelledby])+:before{background:#d90b0b!important;content:\"Untitled input\"!important;z-index:2147483647!important}button:empty:not([aria-label],[aria-labelledby],[title]):after{background:#d90b0b!important;content:\"Empty and untitled button\"!important;z-index:2147483647!important}button[title=\"\"]:after{content:\"Empty [title]\"!important}button[aria-label=\"\"]:after,button[title=\"\"]:after{background:#d90b0b!important;z-index:2147483647!important}button[aria-label=\"\"]:after{content:\"Empty [aria-label]\"!important}button[aria-labelledby=\"\"]:after{background:#d90b0b!important;content:\"Empty [aria-labelledby]\"!important;z-index:2147483647!important}button:not([type],[form],[formaction],[formtarget]):after{background:#d90b0b!important;content:\"Missing [type]\"!important;z-index:2147483647!important}button[type=button][formaction]:after,button[type=button][formenctype]:after,button[type=button][formmethod]:after,button[type=button][formnovalidate]:after,button[type=button][formtarget]:after,button[type=reset][formaction]:after,button[type=reset][formenctype]:after,button[type=reset][formmethod]:after,button[type=reset][formnovalidate]:after,button[type=reset][formtarget]:after{background:#d90b0b!important;content:\"[type=\" attr(type) \"] shouldn’t have form submission attributes\"!important;z-index:2147483647!important}button[class*=disabled]:not([disabled],[readonly]):after{background:#d90b0b!important;content:\"Use [disabled] or [readonly]\"!important;z-index:2147483647!important}input:not([type])+:before,input[type=\" \"]+:before,input[type=\"\"]+:before{background:#d90b0b!important;content:\"Missing [type]\"!important;z-index:2147483647!important}optgroup:not([label])+:before{background:#d90b0b!important;content:\"<optgroup> without [label]\"!important;z-index:2147483647!important}iframe:not([title])+:before,iframe[title=\" \"]+:before,iframe[title=\"\"]+:before{background:#d90b0b!important;content:\"Missing [title] for <iframe>\"!important;z-index:2147483647!important}form:not([action]):after,form[action=\" \"]:after,form[action=\"\"]:after{background:#d90b0b!important;content:\"Missing [action]\"!important;z-index:2147483647!important}html:not([lang]):after,html[lang*=\" \"]:after,html[lang=\"\"]:after{background:#d90b0b!important;content:\"Missing [lang]\"!important;z-index:2147483647!important}table[role=presentation] [axis]:after,table[role=presentation] [headers]:after,table[role=presentation] [scope]:after,table[role=presentation] caption:after,table[role=presentation] colgroup:after,table[role=presentation] tfoot:after,table[role=presentation] th:after,table[role=presentation] thead:after{background:#d90b0b!important;content:\"Semantic thing in layout table\"!important;z-index:2147483647!important}:not(img,object,embed,svg,canvas)[height]:after,:not(img,object,embed,svg,canvas)[width]:after,area[height]:not(img,object,embed,svg,canvas)+:before,area[width]:not(img,object,embed,svg,canvas)+:before,audio[height]:not(img,object,embed,svg,canvas)+:before,audio[width]:not(img,object,embed,svg,canvas)+:before,base[height]:not(img,object,embed,svg,canvas)+:before,base[width]:not(img,object,embed,svg,canvas)+:before,br[height]:not(img,object,embed,svg,canvas)+:before,br[width]:not(img,object,embed,svg,canvas)+:before,col[height]:not(img,object,embed,svg,canvas)+:before,col[width]:not(img,object,embed,svg,canvas)+:before,command[height]:not(img,object,embed,svg,canvas)+:before,command[width]:not(img,object,embed,svg,canvas)+:before,embed[height]:not(img,object,embed,svg,canvas)+:before,embed[width]:not(img,object,embed,svg,canvas)+:before,hr[height]:not(img,object,embed,svg,canvas)+:before,hr[width]:not(img,object,embed,svg,canvas)+:before,iframe[height]:not(img,object,embed,svg,canvas)+:before,iframe[width]:not(img,object,embed,svg,canvas)+:before,img[height]:not(img,object,embed,svg,canvas)+:before,img[width]:not(img,object,embed,svg,canvas)+:before,input[height]:not(img,object,embed,svg,canvas)+:before,input[width]:not(img,object,embed,svg,canvas)+:before,keygen[height]:not(img,object,embed,svg,canvas)+:before,keygen[width]:not(img,object,embed,svg,canvas)+:before,link[height]:not(img,object,embed,svg,canvas)+:before,link[width]:not(img,object,embed,svg,canvas)+:before,meta[height]:not(img,object,embed,svg,canvas)+:before,meta[width]:not(img,object,embed,svg,canvas)+:before,param[height]:not(img,object,embed,svg,canvas)+:before,param[width]:not(img,object,embed,svg,canvas)+:before,select[height]:not(img,object,embed,svg,canvas)+:before,select[width]:not(img,object,embed,svg,canvas)+:before,source[height]:not(img,object,embed,svg,canvas)+:before,source[width]:not(img,object,embed,svg,canvas)+:before,svg[height]:not(img,object,embed,svg,canvas)+:before,svg[width]:not(img,object,embed,svg,canvas)+:before,textarea[height]:not(img,object,embed,svg,canvas)+:before,textarea[width]:not(img,object,embed,svg,canvas)+:before,track[height]:not(img,object,embed,svg,canvas)+:before,track[width]:not(img,object,embed,svg,canvas)+:before,video[height]:not(img,object,embed,svg,canvas)+:before,video[width]:not(img,object,embed,svg,canvas)+:before,wbr[height]:not(img,object,embed,svg,canvas)+:before,wbr[width]:not(img,object,embed,svg,canvas)+:before{background:#d90b0b!important;content:\"[width] & [height] are forbidden\"!important;z-index:2147483647!important}[onabort]:after,[onafterprint]:after,[onbeforeprint]:after,[onbeforeunload]:after,[onblur]:after,[oncanplay]:after,[oncanplaythrough]:after,[onchage]:after,[onclick]:after,[oncontextmenu]:after,[ondblclick]:after,[ondrag]:after,[ondragend]:after,[ondragenter]:after,[ondragleave]:after,[ondragover]:after,[ondragstart]:after,[ondrop]:after,[ondurationchange]:after,[onemptied]:after,[onended]:after,[onerror]:after,[onfocus]:after,[onformchange]:after,[onforminput]:after,[onhaschange]:after,[oninput]:after,[oninvalid]:after,[onkeydown]:after,[onkeypress]:after,[onkeyup]:after,[onload]:after,[onloadeddata]:after,[onloadedmetadata]:after,[onloadstart]:after,[onmessage]:after,[onmousedown]:after,[onmousemove]:after,[onmouseout]:after,[onmouseover]:after,[onmouseup]:after,[onmousewheel]:after,[onoffline]:after,[ononline]:after,[onpagehide]:after,[onpageshow]:after,[onpause]:after,[onplay]:after,[onplaying]:after,[onpopstate]:after,[onprogress]:after,[onratechange]:after,[onreadystatechange]:after,[onredo]:after,[onreset]:after,[onresize]:after,[onscroll]:after,[onseeked]:after,[onseeking]:after,[onselect]:after,[onstalled]:after,[onstorage]:after,[onsubmit]:after,[onsuspend]:after,[ontimeupdate]:after,[onundo]:after,[onunload]:after,[onvolumechange]:after,[onwaiting]:after{background:#d90b0b!important;content:\"JavaScript event attribute\"!important;z-index:2147483647!important}[id^=\"--\"]:after,[id^=\"-0\"]:after,[id^=\"-1\"]:after,[id^=\"-2\"]:after,[id^=\"-3\"]:after,[id^=\"-4\"]:after,[id^=\"-5\"]:after,[id^=\"-6\"]:after,[id^=\"-7\"]:after,[id^=\"-8\"]:after,[id^=\"-9\"]:after,[id^=\"0\"]:after,[id^=\"1\"]:after,[id^=\"2\"]:after,[id^=\"3\"]:after,[id^=\"4\"]:after,[id^=\"5\"]:after,[id^=\"6\"]:after,[id^=\"7\"]:after,[id^=\"8\"]:after,[id^=\"9\"]:after{background:#d90b0b!important;content:\"Invalid [id]\"!important;z-index:2147483647!important}[class^=\"--\"]:after,[class^=\"-0\"]:after,[class^=\"-1\"]:after,[class^=\"-2\"]:after,[class^=\"-3\"]:after,[class^=\"-4\"]:after,[class^=\"-5\"]:after,[class^=\"-6\"]:after,[class^=\"-7\"]:after,[class^=\"-8\"]:after,[class^=\"-9\"]:after,[class^=\"0\"]:after,[class^=\"1\"]:after,[class^=\"2\"]:after,[class^=\"3\"]:after,[class^=\"4\"]:after,[class^=\"5\"]:after,[class^=\"6\"]:after,[class^=\"7\"]:after,[class^=\"8\"]:after,[class^=\"9\"]:after{background:#d90b0b!important;content:\"Invalid [class]\"!important;z-index:2147483647!important}title:empty:after{background:#d90b0b!important;content:\"Empty <title>\"!important;z-index:2147483647!important}meta[name=viewport][content*=\"user-scalable=no\"]~link:last-of-type:before,meta[name=viewport][content*=maximum-scale]~link:last-of-type:before,meta[name=viewport][content*=minimum-scale]~link:last-of-type:before{background:#d90b0b!important;content:\"Unaccessible [meta name=viewport]\"!important;z-index:2147483647!important}meta[charset]:not([charset=utf-8],[charset=UTF-8])~link:last-of-type:before{background:#d90b0b!important;content:\"[charset] isn't utf-8\"!important;z-index:2147483647!important}head :first-child:not([charset])~link:last-of-type:before{background:#d90b0b!important;content:\"<head> must start with [charset]\"!important;z-index:2147483647!important}[dir]:not([dir=rtl],[dir=ltr],[dir=auto]):after{background:#d90b0b!important;content:\"Invalid [dir]\"!important;z-index:2147483647!important}[accesskey]:after{background:#d90b0b!important;content:\"[accesskey]\"!important;z-index:2147483647!important}[type=checkbox]:not(:only-of-type,[name])+:before,[type=radio]:not([name])+:before{background:#d90b0b!important;content:\"Missing [name]\"!important;z-index:2147483647!important}[type=radio]+:before{background:#d90b0b!important;content:\"[radio] outside any <fieldset>\"!important;z-index:2147483647!important}[role=slider]:not([aria-valuemax]):after,[role=slider]:not([aria-valuemin]):after,[role=slider]:not([aria-valuenow]):after,area[role=slider]:not([aria-valuemax])+:before,area[role=slider]:not([aria-valuemin])+:before,area[role=slider]:not([aria-valuenow])+:before,audio[role=slider]:not([aria-valuemax])+:before,audio[role=slider]:not([aria-valuemin])+:before,audio[role=slider]:not([aria-valuenow])+:before,base[role=slider]:not([aria-valuemax])+:before,base[role=slider]:not([aria-valuemin])+:before,base[role=slider]:not([aria-valuenow])+:before,br[role=slider]:not([aria-valuemax])+:before,br[role=slider]:not([aria-valuemin])+:before,br[role=slider]:not([aria-valuenow])+:before,col[role=slider]:not([aria-valuemax])+:before,col[role=slider]:not([aria-valuemin])+:before,col[role=slider]:not([aria-valuenow])+:before,command[role=slider]:not([aria-valuemax])+:before,command[role=slider]:not([aria-valuemin])+:before,command[role=slider]:not([aria-valuenow])+:before,embed[role=slider]:not([aria-valuemax])+:before,embed[role=slider]:not([aria-valuemin])+:before,embed[role=slider]:not([aria-valuenow])+:before,hr[role=slider]:not([aria-valuemax])+:before,hr[role=slider]:not([aria-valuemin])+:before,hr[role=slider]:not([aria-valuenow])+:before,iframe[role=slider]:not([aria-valuemax])+:before,iframe[role=slider]:not([aria-valuemin])+:before,iframe[role=slider]:not([aria-valuenow])+:before,img[role=slider]:not([aria-valuemax])+:before,img[role=slider]:not([aria-valuemin])+:before,img[role=slider]:not([aria-valuenow])+:before,input[role=slider]:not([aria-valuemax])+:before,input[role=slider]:not([aria-valuemin])+:before,input[role=slider]:not([aria-valuenow])+:before,keygen[role=slider]:not([aria-valuemax])+:before,keygen[role=slider]:not([aria-valuemin])+:before,keygen[role=slider]:not([aria-valuenow])+:before,link[role=slider]:not([aria-valuemax])+:before,link[role=slider]:not([aria-valuemin])+:before,link[role=slider]:not([aria-valuenow])+:before,meta[role=slider]:not([aria-valuemax])+:before,meta[role=slider]:not([aria-valuemin])+:before,meta[role=slider]:not([aria-valuenow])+:before,param[role=slider]:not([aria-valuemax])+:before,param[role=slider]:not([aria-valuemin])+:before,param[role=slider]:not([aria-valuenow])+:before,select[role=slider]:not([aria-valuemax])+:before,select[role=slider]:not([aria-valuemin])+:before,select[role=slider]:not([aria-valuenow])+:before,source[role=slider]:not([aria-valuemax])+:before,source[role=slider]:not([aria-valuemin])+:before,source[role=slider]:not([aria-valuenow])+:before,svg[role=slider]:not([aria-valuemax])+:before,svg[role=slider]:not([aria-valuemin])+:before,svg[role=slider]:not([aria-valuenow])+:before,textarea[role=slider]:not([aria-valuemax])+:before,textarea[role=slider]:not([aria-valuemin])+:before,textarea[role=slider]:not([aria-valuenow])+:before,track[role=slider]:not([aria-valuemax])+:before,track[role=slider]:not([aria-valuemin])+:before,track[role=slider]:not([aria-valuenow])+:before,video[role=slider]:not([aria-valuemax])+:before,video[role=slider]:not([aria-valuemin])+:before,video[role=slider]:not([aria-valuenow])+:before,wbr[role=slider]:not([aria-valuemax])+:before,wbr[role=slider]:not([aria-valuemin])+:before,wbr[role=slider]:not([aria-valuenow])+:before{background:#d90b0b!important;content:\"Missing attributes on [role=slider]\"!important;z-index:2147483647!important}[role=spinbutton]:not([aria-valuemax]):after,[role=spinbutton]:not([aria-valuemin]):after,[role=spinbutton]:not([aria-valuenow]):after,area[role=spinbutton]:not([aria-valuemax])+:before,area[role=spinbutton]:not([aria-valuemin])+:before,area[role=spinbutton]:not([aria-valuenow])+:before,audio[role=spinbutton]:not([aria-valuemax])+:before,audio[role=spinbutton]:not([aria-valuemin])+:before,audio[role=spinbutton]:not([aria-valuenow])+:before,base[role=spinbutton]:not([aria-valuemax])+:before,base[role=spinbutton]:not([aria-valuemin])+:before,base[role=spinbutton]:not([aria-valuenow])+:before,br[role=spinbutton]:not([aria-valuemax])+:before,br[role=spinbutton]:not([aria-valuemin])+:before,br[role=spinbutton]:not([aria-valuenow])+:before,col[role=spinbutton]:not([aria-valuemax])+:before,col[role=spinbutton]:not([aria-valuemin])+:before,col[role=spinbutton]:not([aria-valuenow])+:before,command[role=spinbutton]:not([aria-valuemax])+:before,command[role=spinbutton]:not([aria-valuemin])+:before,command[role=spinbutton]:not([aria-valuenow])+:before,embed[role=spinbutton]:not([aria-valuemax])+:before,embed[role=spinbutton]:not([aria-valuemin])+:before,embed[role=spinbutton]:not([aria-valuenow])+:before,hr[role=spinbutton]:not([aria-valuemax])+:before,hr[role=spinbutton]:not([aria-valuemin])+:before,hr[role=spinbutton]:not([aria-valuenow])+:before,iframe[role=spinbutton]:not([aria-valuemax])+:before,iframe[role=spinbutton]:not([aria-valuemin])+:before,iframe[role=spinbutton]:not([aria-valuenow])+:before,img[role=spinbutton]:not([aria-valuemax])+:before,img[role=spinbutton]:not([aria-valuemin])+:before,img[role=spinbutton]:not([aria-valuenow])+:before,input[role=spinbutton]:not([aria-valuemax])+:before,input[role=spinbutton]:not([aria-valuemin])+:before,input[role=spinbutton]:not([aria-valuenow])+:before,keygen[role=spinbutton]:not([aria-valuemax])+:before,keygen[role=spinbutton]:not([aria-valuemin])+:before,keygen[role=spinbutton]:not([aria-valuenow])+:before,link[role=spinbutton]:not([aria-valuemax])+:before,link[role=spinbutton]:not([aria-valuemin])+:before,link[role=spinbutton]:not([aria-valuenow])+:before,meta[role=spinbutton]:not([aria-valuemax])+:before,meta[role=spinbutton]:not([aria-valuemin])+:before,meta[role=spinbutton]:not([aria-valuenow])+:before,param[role=spinbutton]:not([aria-valuemax])+:before,param[role=spinbutton]:not([aria-valuemin])+:before,param[role=spinbutton]:not([aria-valuenow])+:before,select[role=spinbutton]:not([aria-valuemax])+:before,select[role=spinbutton]:not([aria-valuemin])+:before,select[role=spinbutton]:not([aria-valuenow])+:before,source[role=spinbutton]:not([aria-valuemax])+:before,source[role=spinbutton]:not([aria-valuemin])+:before,source[role=spinbutton]:not([aria-valuenow])+:before,svg[role=spinbutton]:not([aria-valuemax])+:before,svg[role=spinbutton]:not([aria-valuemin])+:before,svg[role=spinbutton]:not([aria-valuenow])+:before,textarea[role=spinbutton]:not([aria-valuemax])+:before,textarea[role=spinbutton]:not([aria-valuemin])+:before,textarea[role=spinbutton]:not([aria-valuenow])+:before,track[role=spinbutton]:not([aria-valuemax])+:before,track[role=spinbutton]:not([aria-valuemin])+:before,track[role=spinbutton]:not([aria-valuenow])+:before,video[role=spinbutton]:not([aria-valuemax])+:before,video[role=spinbutton]:not([aria-valuemin])+:before,video[role=spinbutton]:not([aria-valuenow])+:before,wbr[role=spinbutton]:not([aria-valuemax])+:before,wbr[role=spinbutton]:not([aria-valuemin])+:before,wbr[role=spinbutton]:not([aria-valuenow])+:before{background:#d90b0b!important;content:\"Missing attributes on [role=spinbutton]\"!important;z-index:2147483647!important}[role=checkbox]:not([aria-checked]):after,area[role=checkbox]:not([aria-checked])+:before,audio[role=checkbox]:not([aria-checked])+:before,base[role=checkbox]:not([aria-checked])+:before,br[role=checkbox]:not([aria-checked])+:before,col[role=checkbox]:not([aria-checked])+:before,command[role=checkbox]:not([aria-checked])+:before,embed[role=checkbox]:not([aria-checked])+:before,hr[role=checkbox]:not([aria-checked])+:before,iframe[role=checkbox]:not([aria-checked])+:before,img[role=checkbox]:not([aria-checked])+:before,input[role=checkbox]:not([aria-checked])+:before,keygen[role=checkbox]:not([aria-checked])+:before,link[role=checkbox]:not([aria-checked])+:before,meta[role=checkbox]:not([aria-checked])+:before,param[role=checkbox]:not([aria-checked])+:before,select[role=checkbox]:not([aria-checked])+:before,source[role=checkbox]:not([aria-checked])+:before,svg[role=checkbox]:not([aria-checked])+:before,textarea[role=checkbox]:not([aria-checked])+:before,track[role=checkbox]:not([aria-checked])+:before,video[role=checkbox]:not([aria-checked])+:before,wbr[role=checkbox]:not([aria-checked])+:before{background:#d90b0b!important;content:\"Missing [aria-checked]\"!important;z-index:2147483647!important}[role=combobox]:not([aria-expanded]):after,area[role=combobox]:not([aria-expanded])+:before,audio[role=combobox]:not([aria-expanded])+:before,base[role=combobox]:not([aria-expanded])+:before,br[role=combobox]:not([aria-expanded])+:before,col[role=combobox]:not([aria-expanded])+:before,command[role=combobox]:not([aria-expanded])+:before,embed[role=combobox]:not([aria-expanded])+:before,hr[role=combobox]:not([aria-expanded])+:before,iframe[role=combobox]:not([aria-expanded])+:before,img[role=combobox]:not([aria-expanded])+:before,input[role=combobox]:not([aria-expanded])+:before,keygen[role=combobox]:not([aria-expanded])+:before,link[role=combobox]:not([aria-expanded])+:before,meta[role=combobox]:not([aria-expanded])+:before,param[role=combobox]:not([aria-expanded])+:before,select[role=combobox]:not([aria-expanded])+:before,source[role=combobox]:not([aria-expanded])+:before,svg[role=combobox]:not([aria-expanded])+:before,textarea[role=combobox]:not([aria-expanded])+:before,track[role=combobox]:not([aria-expanded])+:before,video[role=combobox]:not([aria-expanded])+:before,wbr[role=combobox]:not([aria-expanded])+:before{background:#d90b0b!important;content:\"Missing [aria-expanded]\"!important;z-index:2147483647!important}[role=scrollbar]:not([aria-controls]):after,[role=scrollbar]:not([aria-orientation]):after,[role=scrollbar]:not([aria-valuemax]):after,[role=scrollbar]:not([aria-valuemin]):after,[role=scrollbar]:not([aria-valuenow]):after{background:#d90b0b!important;content:\"Missing attributes on [role=scrollbar]\"!important;z-index:2147483647!important}a a[href]:after,a audio[controls]:after,a button:after,a details:after,a embed:after,a iframe:after,a img[usemap]:after,a input[type]:not([hidden]):after,a label:after,a select:after,a textarea:after,a video[controls]:after,button a[href]:after,button audio[controls]:after,button button:after,button details:after,button embed:after,button iframe:after,button img[usemap]:after,button input[type]:not([hidden]):after,button label:after,button select:after,button textarea:after,button video[controls]:after,form form:after,label label:after,meter meter:after,progress progress:after{background:#d90b0b!important;content:\"Nested interactive elements\"!important;z-index:2147483647!important}\n/*# sourceMappingURL=a11y-en.css.map */`;\nexport const htmlbox = `!function(){var t,o,e,i,r,d,n,l=null,s={};function a(t,o,e){t.attachEvent?(t[\"e\"+o+e]=e,t[o+e]=function(){t[\"e\"+o+e](window.event)},t.attachEvent(\"on\"+o,t[o+e])):t.addEventListener(o,e,!1)}function b(t,o,e){t.detachEvent?(t.detachEvent(\"on\"+o,t[o+e]),t[o+e]=null):t.removeEventListener(o,e,!1)}function p(t,o){return parseFloat(t[o])}function g(t){c()}function x(){clearTimeout(l),l=null}function h(){x(),n&&(n.box=n.element.getBoundingClientRect(),v(n))}function c(){d.tooltip.style.display=\"none\",d.margin.horizontal.removeAttribute(\"style\"),d.margin.vertical.removeAttribute(\"style\"),d.margin.inner.style.display=\"none\",d.border.horizontal.removeAttribute(\"style\"),d.border.vertical.removeAttribute(\"style\"),d.border.inner.style.display=\"none\",d.padding.horizontal.removeAttribute(\"style\"),d.padding.vertical.removeAttribute(\"style\"),d.padding.inner.style.display=\"none\",d.box.horizontal.removeAttribute(\"style\"),d.box.vertical.removeAttribute(\"style\"),d.box.inner.style.display=\"none\"}function v(t){var o=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,e=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,i='<span class=\"t\">'+t.tagName.toLowerCase()+\"</span>\",r='<span class=\"i\">'+t.selectors.id.toLowerCase()+\"</span>\",n='<span class=\"c\">'+t.selectors.className.toLowerCase()+\"</span>\",l='<span class=\"d\"> | '+Math.round(100*t.box.width)/100+\" x \"+Math.round(100*t.box.height)/100+\"</span>\";d.tooltip.style.display=\"block\",d.tooltip.innerHTML=i+r+n+l;var s=d.tooltip.getBoundingClientRect(),a=t.box.top-30;d.tooltip.classList.remove(\"top\"),a<0&&(d.tooltip.classList.add(\"top\"),(a=t.box.bottom+6)+30>o&&(a=6));var b=t.box.left+2;d.tooltip.classList.remove(\"right\"),b<0?b=2:b+s.width>e&&(d.tooltip.classList.add(\"right\"),b=e-s.width-2),d.tooltip.style.top=a+\"px\",d.tooltip.style.left=b+\"px\",d.margin.horizontal.style.top=t.box.top-t.margin.top+\"px\",d.margin.horizontal.style.height=t.box.height+t.margin.top+t.margin.bottom+\"px\",d.margin.vertical.style.left=t.box.left-t.margin.left+\"px\",d.margin.vertical.style.width=t.box.width+t.margin.left+t.margin.right+\"px\",d.margin.inner.style.display=\"block\",d.margin.inner.style.top=t.box.top-t.margin.top+\"px\",d.margin.inner.style.left=t.box.left-t.margin.left+\"px\",d.margin.inner.style.height=t.box.height+t.margin.top+t.margin.bottom+\"px\",d.margin.inner.style.width=t.box.width+t.margin.left+t.margin.right+\"px\",d.border.horizontal.style.top=t.box.top+\"px\",d.border.horizontal.style.height=t.box.height+\"px\",d.border.vertical.style.left=t.box.left+\"px\",d.border.vertical.style.width=t.box.width+\"px\",d.border.inner.style.display=\"block\",d.border.inner.style.top=t.box.top+\"px\",d.border.inner.style.left=t.box.left+\"px\",d.border.inner.style.height=t.box.height+\"px\",d.border.inner.style.width=t.box.width+\"px\",d.padding.horizontal.style.top=t.box.top+t.border.top+\"px\",d.padding.horizontal.style.height=t.box.height-t.border.top-t.border.bottom+\"px\",d.padding.vertical.style.left=t.box.left+t.border.left+\"px\",d.padding.vertical.style.width=t.box.width-t.border.left-t.border.right+\"px\",d.padding.inner.style.display=\"block\",d.padding.inner.style.top=t.box.top+t.border.top+\"px\",d.padding.inner.style.left=t.box.left+t.border.left+\"px\",d.padding.inner.style.height=t.box.height-t.border.top-t.border.bottom+\"px\",d.padding.inner.style.width=t.box.width-t.border.left-t.border.right+\"px\",d.box.horizontal.style.top=t.box.top+t.border.top+t.padding.top+\"px\",d.box.horizontal.style.height=t.box.height-t.border.top-t.border.bottom-t.padding.top-t.padding.bottom+\"px\",d.box.vertical.style.left=t.box.left+t.border.left+t.padding.left+\"px\",d.box.vertical.style.width=t.box.width-t.border.left-t.border.right-t.padding.left-t.padding.right+\"px\",d.box.inner.style.display=\"block\",d.box.inner.style.top=t.box.top+t.border.top+t.padding.top+\"px\",d.box.inner.style.left=t.box.left+t.border.left+t.padding.left+\"px\",d.box.inner.style.height=t.box.height-t.border.top-t.border.bottom-t.padding.top-t.padding.bottom+\"px\",d.box.inner.style.width=t.box.width-t.border.left-t.border.right-t.padding.left-t.padding.right+\"px\"}!function t(){for(var o=document.getElementsByTagName(\"script\"),e=o[o.length-1].src.replace(/^[^\\\\?]+\\\\??/,\"\").split(\"&\"),i=0;i<e.length;i++){var r=e[i].split(\"=\");s[decodeURIComponent(r[0])]=!(r.length>1)||decodeURIComponent(r[1])}}(),t='.boxvis > div > div{pointer-events:none;position:fixed;z-index:2147483637;top:-10px;bottom:-10px;left:-10px;right:-10px}.boxvis:not(.noln) > div > div{border-width:1px;border-style:dashed}.boxvis > .mg > div{border-color:#e67700}.boxvis > .bd > div{border-color:#dcdc40}.boxvis > .pd > div{border-color:#00bb20}.boxvis > .bx > div{border-color:#0000e6}.boxvis > div > .o{z-index:2147483638;border:none;display:none}.boxvis:not(.nobg) > .mg > .o{background-color:rgba(255,153,0,0.125)}.boxvis:not(.nobg) > .pd > .o{background-color:rgba(0,140,64,0.125)}.boxvis:not(.nobg) > .bd > .o{background-color:rgba(255,255,0,0.125)}.boxvis:not(.nobg) > .bx > .o{background-color:rgba(0,100,255,0.35)}.boxvis > .i{box-shadow:0 0 4px -1px rgba(255,255,255,1);pointer-events:none;position:fixed;z-index:2147483638;background-color:#000;font-size:12px;padding:3px 8px 5px 10px;border-radius:4px;white-space:nowrap;display:none}.boxvis > .i:before{content:\"\";position:absolute;top:100%;left:10px;border:solid 6px transparent;border-top-color:#000}.boxvis > .i.top:before{top:-12px;border:solid 6px transparent;border-top-color:transparent;border-bottom-color:#000}.boxvis > .i.right:before{left:auto;right:10px}.boxvis > .i > .t{color:#FF74FF;font-weight:700}.boxvis > .i > .i{color:#FFB952}.boxvis > .i > .c{color:#75CFFF}.boxvis > .i > .d{font-size:10px;margin-left:3px;color:#CCC}',o=document.head||document.getElementsByTagName(\"head\")[0],(e=document.createElement(\"style\")).type=\"text/css\",e.styleSheet?e.styleSheet.cssText=t:e.appendChild(document.createTextNode(t)),o.appendChild(e),(i=document.createElement(\"div\")).className=\"boxvis\"+(s.noln?\" noln\":\"\")+(s.nobg?\" nobg\":\"\"),document.body.appendChild(i),i.innerHTML='<div class=\"mg\"><div class=\"h\"></div><div class=\"v\"></div><div class=\"o\"></div></div><div class=\"bd\"><div class=\"h\"></div><div class=\"v\"></div><div class=\"o\"></div></div><div class=\"pd\"><div class=\"h\"></div><div class=\"v\"></div><div class=\"o\"></div></div><div class=\"bx\"><div class=\"h\"></div><div class=\"v\"></div><div class=\"o\"></div></div><div class=\"i\"></div>',d={margin:{horizontal:(r=i.childNodes)[0].childNodes[0],vertical:r[0].childNodes[1],inner:r[0].childNodes[2]},border:{horizontal:r[1].childNodes[0],vertical:r[1].childNodes[1],inner:r[1].childNodes[2]},padding:{horizontal:r[2].childNodes[0],vertical:r[2].childNodes[1],inner:r[2].childNodes[2]},box:{horizontal:r[3].childNodes[0],vertical:r[3].childNodes[1],inner:r[3].childNodes[2]},tooltip:r[4]},a(document.body,\"mouseover\",function t(o){x();var e,i,r=(e=o.target,i=window.getComputedStyle(e),{element:e,tagName:e.tagName,selectors:{id:e.id?\"#\"+e.id:\"\",className:e.className?\".\"+e.className.replace(/\\\\s/g,\".\"):\"\"},box:e.getBoundingClientRect(),margin:{top:p(i,\"margin-top\"),right:p(i,\"margin-right\"),bottom:p(i,\"margin-bottom\"),left:p(i,\"margin-left\")},border:{top:p(i,\"border-top-width\"),right:p(i,\"border-right-width\"),bottom:p(i,\"border-bottom-width\"),left:p(i,\"border-left-width\")},padding:{top:p(i,\"padding-top\"),right:p(i,\"padding-right\"),bottom:p(i,\"padding-bottom\"),left:p(i,\"padding-left\")}});n=r,v(r)}),a(window,\"scroll\",function t(o){l||c(),l=setTimeout(h,250)}),a(document.body,\"mouseout\",g)}();`;\n//\n// export const guides = `!function(t,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define(e):(t=t||self).Guides=e()}(this,function(){\"use strict\";var z=function(t,e){return(z=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])})(t,e)};function n(t,e){if(\"function\"!=typeof e&&null!==e)throw new TypeError(\"Class extends value \"+String(e)+\" is not a constructor or null\");function n(){this.constructor=t}z(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}var s=function(){return(s=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var i in e=arguments[n])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)};function h(e,n){return function(t){t&&(e[n]=t)}}function F(n,r){return function(t){var e=t.prototype;n.forEach(function(t){r(e,t)})}}var Y=\"function\",A=\"object\",X=\"string\",G=\"undefined\",B=[{open:\"(\",close:\")\"},{open:'\"',close:'\"'},{open:\"'\",close:\"'\"},{open:'\\\\\\\\\"',close:'\\\\\\\\\"'},{open:\"\\\\\\\\'\",close:\"\\\\\\\\'\"}],j=1e-7,i={cm:function(t){return 96*t/2.54},mm:function(t){return 96*t/254},in:function(t){return 96*t},pt:function(t){return 96*t/72},pc:function(t){return 96*t/6},\"%\":function(t,e){return t*e/100},vw:function(t,e){return t/100*(e=void 0===e?window.innerWidth:e)},vh:function(t,e){return t/100*(e=void 0===e?window.innerHeight:e)},vmax:function(t,e){return t/100*(e=void 0===e?Math.max(window.innerWidth,window.innerHeight):e)},vmin:function(t,e){return t/100*(e=void 0===e?Math.min(window.innerWidth,window.innerHeight):e)}};function o(t){return t&&typeof t===A}function I(t){return Array.isArray(t)}function m(t){return typeof t===X}function Z(t){return typeof t===Y}function L(t,e,n,r,i){if(_(t,e,n))return n;for(var o,s=t,a=e,u=r,c=i,l=n+1;l<u;++l){var f=function(t){var e,n,r=a[t].trim();return r!==s.close||_(s,a,t)?(e=t,-1===(e=(n=E(c,function(t){return t.open===r}))?L(n,a,t,u,c):e)?(o=t,\"break\"):void(o=t=e)):{value:t}}(l);if(l=o,\"object\"==typeof f)return f.value;if(\"break\"===f)break}return-1}function _(t,e,n){return t.ignore?(e=e.slice(Math.max(n-3,0),n+3).join(\"\"),new RegExp(t.ignore).exec(e)):null}function y(o,t){var t=m(t)?{separator:t}:t,e=t.separator,s=void 0===e?\",\":e,a=t.isSeparateFirst,u=t.isSeparateOnlyOpenClose,e=t.isSeparateOpenClose,c=void 0===e?u:e,e=t.openCloseCharacters,l=void 0===e?B:e,t=l.map(function(t){var e=t.open,t=t.close;return e===t?e:e+\"|\"+t}).join(\"|\"),e=new RegExp(\"(\\\\\\\\s*\"+s+\"\\\\\\\\s*|\"+t+\"|\\\\\\\\s+)\",\"g\"),f=o.split(e).filter(function(t){return t&&\"undefined\"!==t}),d=f.length,h=[],p=[];function v(){return p.length&&(h.push(p.join(\"\")),p=[])}for(var g,n=function(t){var e=f[t].trim(),n=t,r=E(l,function(t){return t.open===e}),i=E(l,function(t){return t.close===e});if(r){if(-1!==(n=L(r,f,t,d,l))&&c)return v()&&a||(h.push(f.slice(t,n+1).join(\"\")),t=n,a)?(g=t,\"break\"):(g=t,\"continue\")}else{if(i&&!_(i,f,t))return(r=function(){for(var t=0,e=0,n=arguments.length;e<n;e++)t+=arguments[e].length;for(var r=Array(t),i=0,e=0;e<n;e++)for(var o=arguments[e],s=0,a=o.length;s<a;s++,i++)r[i]=o[s];return r}(l)).splice(l.indexOf(i),1),{value:y(o,{separator:s,isSeparateFirst:a,isSeparateOnlyOpenClose:u,isSeparateOpenClose:c,openCloseCharacters:r})};if(i=e,!((\"\"!==(r=s)&&\" \"!=r||\"\"!==i&&\" \"!=i)&&i!==r||u))return v(),a?(g=t,\"break\"):(g=t,\"continue\")}p.push(f.slice(t,(n=-1===n?d-1:n)+1).join(\"\")),g=t=n},r=0;r<d;++r){var i=n(r),r=g;if(\"object\"==typeof i)return i.value;if(\"break\"===i)break}return p.length&&h.push(p.join(\"\")),h}function u(t){return y(t,\",\")}function $(t){var e,n,t=/^([^\\\\d|e|\\\\-|\\\\+]*)((?:\\\\d|\\\\.|-|e-|e\\\\+)+)(\\\\S*)$/g.exec(t);return t?(e=t[1],n=t[2],{prefix:e,unit:t[3],value:parseFloat(n)}):{prefix:\"\",unit:\"\",value:NaN}}function x(t,r){return void 0===r&&(r=\"-\"),t.replace(/([a-z])([A-Z])/g,function(t,e,n){return\"\"+e+r+n.toLowerCase()})}function b(){return Date.now?Date.now():(new Date).getTime()}function W(t,e,n){void 0===n&&(n=-1);for(var r=t.length,i=0;i<r;++i)if(e(t[i],i,t))return i;return n}function rt(t,e){e=function(t,e,n){void 0===n&&(n=-1);for(var r=t.length-1;0<=r;--r)if(e(t[r],r,t))return r;return n}(t,e);-1<e&&t[e]}function E(t,e,n){e=W(t,e);return-1<e?t[e]:n}function l(t){return Object.keys(t)}function nt(t,e){var t=$(t),n=t.value,t=t.unit;if(o(e)){var r=e[t];if(r){if(Z(r))return r(n);if(i[t])return i[t](n,r)}}else if(\"%\"===t)return n*e/100;return i[t]?i[t](n):n}function N(t,e){var n;return e?(n=1/e,Math.round(t/e)/n):t}function S(t,e,n,r){t.addEventListener(e,n,r)}function w(t,e,n,r){t.removeEventListener(e,n,r)}function a(t){return(null==(t=null==t?void 0:t.ownerDocument)?void 0:t.defaultView)||window}var U=function(){function t(){this.keys=[],this.values=[]}var e=t.prototype;return e.get=function(t){return this.values[this.keys.indexOf(t)]},e.set=function(t,e){var n=this.keys,r=this.values,i=n.indexOf(t),i=-1===i?n.length:i;n[i]=t,r[i]=e},t}(),q=function(){function t(){this.object={}}var e=t.prototype;return e.get=function(t){return this.object[t]},e.set=function(t,e){this.object[t]=e},t}(),H=\"function\"==typeof Map,V=function(){function t(){}var e=t.prototype;return e.connect=function(t,e){this.prev=t,this.next=e,t&&(t.next=this),e&&(e.prev=this)},e.disconnect=function(){var t=this.prev,e=this.next;t&&(t.next=e),e&&(e.prev=t)},e.getIndex=function(){for(var t=this,e=-1;t;)t=t.prev,++e;return e},t}();var J=function(){function t(t,e,n,r,i,o,s,a){this.prevList=t,this.list=e,this.added=n,this.removed=r,this.changed=i,this.maintained=o,this.changedBeforeAdded=s,this.fixed=a}var e=t.prototype;return Object.defineProperty(e,\"ordered\",{get:function(){return this.cacheOrdered||this.caculateOrdered(),this.cacheOrdered},enumerable:!0,configurable:!0}),Object.defineProperty(e,\"pureChanged\",{get:function(){return this.cachePureChanged||this.caculateOrdered(),this.cachePureChanged},enumerable:!0,configurable:!0}),e.caculateOrdered=function(){t=this.changedBeforeAdded,n=this.fixed,i=[],o=[],t.forEach(function(t){var e=t[0],t=t[1],n=new V;o[t]=i[e]=n}),i.forEach(function(t,e){t.connect(i[e-1])});var n,i,o,t=t.filter(function(t,e){return!n[e]}).map(function(t,e){var n,r=t[0],t=t[1];return r===t?[0,0]:(t=o[t-1],n=(r=i[r]).getIndex(),r.disconnect(),t?r.connect(t,t.next):r.connect(void 0,i[0]),[n,r.getIndex()])}),s=this.changed,a=[];this.cacheOrdered=t.filter(function(t,e){var n=t[0],t=t[1],e=s[e],r=e[0],e=e[1];if(n!==t)return a.push([r,e]),!0}),this.cachePureChanged=a},t}();function K(t,e,n){var r=H?Map:n?q:U,n=n||function(t){return t},i=[],o=[],s=[],a=t.map(n),n=e.map(n),u=new r,c=new r,l=[],f=[],d={},h=[],p=0,v=0;return a.forEach(function(t,e){u.set(t,e)}),n.forEach(function(t,e){c.set(t,e)}),a.forEach(function(t,e){t=c.get(t);void 0===t?(++v,o.push(e)):d[t]=v}),n.forEach(function(t,e){t=u.get(t);void 0===t?(i.push(e),++p):(s.push([t,e]),v=d[e]||0,l.push([t-v,e-p]),f.push(e===t),t!==e&&h.push([t,e]))}),o.reverse(),new J(t,e,i,o,h,s,l,f)}var Q=function(t,e){return(Q=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])})(t,e)};function r(t,e){if(\"function\"!=typeof e&&null!==e)throw new TypeError(\"Class extends value \"+String(e)+\" is not a constructor or null\");function n(){this.constructor=t}Q(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}var g=function(){return(g=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var i in e=arguments[n])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)};function tt(t,e){var n={};for(i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(n[i]=t[i]);if(null!=t&&\"function\"==typeof Object.getOwnPropertySymbols)for(var r=0,i=Object.getOwnPropertySymbols(t);r<i.length;r++)e.indexOf(i[r])<0&&Object.prototype.propertyIsEnumerable.call(t,i[r])&&(n[i[r]]=t[i[r]]);return n}function et(t,e,n){if(n||2===arguments.length)for(var r,i=0,o=e.length;i<o;i++)!r&&i in e||((r=r||Array.prototype.slice.call(e,0,i))[i]=e[i]);return t.concat(r||Array.prototype.slice.call(e))}function it(t){var e=0;return t.map(function(t){return null==t?\"$compat\".concat(++e):\"\".concat(t)})}function ot(t,e){if(e)for(var n in e)typeof t[n]===G&&(t[n]=e[n]);return t}function c(t,e){if(t!==e){for(var n in t)if(!(n in e))return!0;for(var n in e)if(t[n]!==e[n])return!0}return!1}function st(t){var e=t.className,t=tt(t,[\"className\"]);return null!=e&&(t.class=e),delete t.style,delete t.children,t}function at(t){var e,n={},r={};for(e in t)0===e.indexOf(\"on\")?r[e]=t[e]:n[e]=t[e];return[n,r]}function ut(t){var e;return t?(e=t.b)instanceof Node?e:ut(t.c):null}function ct(t){var e=t.parentNode;e&&e.removeChild(t)}function lt(t){t.forEach(function(t){t()})}function ft(){return this.constructor(this.props,this.context)}var dt=0,t=function(){function t(t,e,n,r,i,o,s){void 0===s&&(s={}),this.t=t,this.d=e,this.k=n,this.i=r,this.c=i,this.ref=o,this.ps=s,this.typ=\"prov\",this._ps=[],this._cs={},this._hyd=null,this._sel=!1}var e=t.prototype;return e.s=function(){return!0},e.u=function(e,n,t,r,i){var o,s=this,a=s.d,u=function e(t){var n=[];return t.forEach(function(t){n=n.concat(I(t)?e(t):t)}),n}(l(o=n).map(function(t){return o[t]}).filter(function(t){return t.$_req}).map(function(t){return t.$_subs})),c=E(u,function(t){return t.d===a});if(s.b&&!m(t)&&!i&&!s.s(t.props,r)&&!c)return u.reduce(function(t,e){var n=e.d;return t[0]?t[0].d===n&&t.push(e):a<n&&t.push(e),t},[]).forEach(function(t){O(t,t._ps,[t.o],e,n,!0)}),!1;s.o=t,s.ss(r);i=s.ps;return m(t)||(s.ps=t.props,s.ref=t.ref),pt(this),s.r(e,n,s.b?i:{},r),!0},e.md=function(){this.rr()},e.ss=function(){},e.ud=function(){this.rr()},e.rr=function(){var t=this.ref,e=this.fr;t&&t(e?e.current:this.b)},t}();function ht(){return Object.__CROACT_CURRENT_INSTNACE__}function pt(t){Object.__CROACT_CURRENT_INSTNACE__=t,dt=0}var vt=function(){function t(t,e){this.props=t=void 0===t?{}:t,this.context=e,this.state={},this.$_timer=0,this.$_state={},this.$_subs=[],this.$_cs={}}var e=t.prototype;return e.render=function(){return null},e.shouldComponentUpdate=function(t,e){return this.props!==t||this.state!==e},e.setState=function(t,e,n){var r=this;r.$_timer||(r.$_state={}),clearTimeout(r.$_timer),r.$_timer=0,r.$_state=g(g({},r.$_state),t),n?r.$_setState(e,n):r.$_timer=window.setTimeout(function(){r.$_timer=0,r.$_setState(e,n)})},e.forceUpdate=function(t){this.setState({},t,!0)},e.componentDidMount=function(){},e.componentDidUpdate=function(t,e){},e.componentWillUnmount=function(){},e.$_setState=function(t,e){var n=[],r=this.$_p;O(r.c,[r],[r.o],n,r._cs,g(g({},this.state),this.$_state),e)&&(t&&n.push(t),lt(n),pt(null))},t}(),gt=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.shouldComponentUpdate=function(t,e){return c(this.props,t)||c(this.state,e)},e}(vt);function mt(t){function e(t){e.current=t}return e.current=t,e}var _t=function(a){function t(t,e,n,r,i,o,s){e=a.call(this,t,e,n,r,i,o,ot(s=void 0===s?{}:s,t.defaultProps))||this;return e.typ=\"comp\",e._usefs=[],e._uefs=[],e._defs=[],e}r(t,a);var e=t.prototype;return e.s=function(t,e){var n=this.b;return!1!==n.shouldComponentUpdate(ot(t,this.t.defaultProps),e||n.state)},e.r=function(t,e,n){var r,i,o,s,a=this,u=a.t,c=(a.ps=ot(a.ps,a.t.defaultProps),a.ps),l=!a.b,f=u.contextType,d=a.b,h=null==f?void 0:f.get(a),p=(a._cs=e,l?(r=c,i=h,o=a,null!=(v=null==(u=u)?void 0:u.prototype)&&v.render?s=new u(r,i):((s=new vt(r,i)).constructor=u)._fr?(o.fr=mt(),s.render=function(){return this.constructor(this.props,o.fr)}):s.render=ft,s.$_p=o,d=s,a.b=d):(d.props=c,d.context=h),d.state),v=(a._usefs=[],a._uefs=[],d.render()),u=(0===(null==(i=null==(r=null==v?void 0:v.props)?void 0:r.children)?void 0:i.length)&&(v.props.children=a.ps.children),g(g({},e),d.$_cs));O(a,a._ps,v?[v]:[],t,u),l?a._uefs.push(function(){null!=f&&f.register(a),d.componentDidMount()}):a._uefs.push(function(){d.componentDidUpdate(n,p)}),t.push(function(){a._usefs.forEach(function(t){t()}),l?a.md():a.ud(),a._defs=a._uefs.map(function(t){return t()})})},e.ss=function(t){var e=this.b;e&&t&&(e.state=t)},e.un=function(){var t,e=this;e._ps.forEach(function(t){t.un()}),null!=(t=e.t.contextType)&&t.unregister(e),clearTimeout(e.b.$_timer),e._defs.forEach(function(t){t&&t()}),e.b.componentWillUnmount()},t}(t);function yt(n,r){var i=l(n),e=l(r),t=K(i,e,function(t){return t}),o={},s={},a={};return t.added.forEach(function(t){t=e[t];o[t]=r[t]}),t.removed.forEach(function(t){t=i[t];s[t]=n[t]}),t.maintained.forEach(function(t){var t=t[0],t=i[t],e=[n[t],r[t]];n[t]!==r[t]&&(a[t]=e)}),{added:o,removed:s,changed:a}}var bt=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.typ=\"elem\",t._es={},t._svg=!1,t}r(t,e);var n=t.prototype;return n.e=function(r,t){var i=this,e=i._es,n=i.b,o=r.replace(/^on/g,\"\").toLowerCase();t?(w(n,o,e[r]),delete e[r]):(e[r]=function(t){var e,n;null!=(n=(e=i.ps)[r])&&n.call(e,t)},S(n,o,e[r]))},n.s=function(t){return c(this.ps,t)},n.r=function(t,e,n){var r,i,o,s=this,a=!s.b,u=s.ps,c=(a&&(c=!1,c=!(!s._svg&&\"svg\"!==s.t)||(f=ut(s.c))&&f.ownerSVGElement,s._svg=c,f=null==(f=s._hyd)?void 0:f.splice(0,1)[0],l=s.t,f?s._hyd=[].slice.call(f.children||[]):f=c?document.createElementNS(\"http://www.w3.org/2000/svg\",l):document.createElement(l),s.b=f),O(s,s._ps,u.children,t,e),s.b),l=at(n),f=l[0],e=l[1],l=at(u),d=l[0],l=l[1],h=c,p=(f=yt(st(f),st(d))).added,d=f.removed,v=f.changed;for(r in p)h.setAttribute(r,p[r]);for(i in v)h.setAttribute(i,v[i][1]);for(o in d)h.removeAttribute(o);var g,m,d=e,_=s,e=(d=yt(e,e=l)).added;for(g in d.removed)_.e(g,!0);for(m in e)_.e(m);var y,d=n.style||{},e=u.style||{},n=c,b=n.style,E=(n=yt(d,e)).added,d=n.removed,S=n.changed;for(y in E){var w=x(y,\"-\");b.setProperty(w,E[y])}for(y in S){var C=x(y,\"-\");b.setProperty(C,S[y][1])}for(y in d){var P=x(y,\"-\");b.removeProperty(P)}return t.push(function(){a?s.md():s.ud()}),!0},n.un=function(){var t,e=this,n=e._es,r=e.b;for(t in n)w(r,t,n[t]);e._ps.forEach(function(t){t.un()}),e._es={},e._sel||ct(r)},t}(t);function v(t){return!t||t instanceof Node?t:(t=t.$_p._ps).length?v(t[0].b):null}function C(t,e){for(var n=[],r=2;r<arguments.length;r++)n[r-2]=arguments[r];var e=e||{},i=e.key,o=e.ref,e=tt(e,[\"key\",\"ref\"]);return{type:t,key:i,ref:o,props:g(g({},e),{children:n.reduce(function(t,e){return t.concat(e)},[]).filter(function(t){return null!=t&&!1!==t})})}}var Et=function(n){function t(t,e){e=n.call(this,\"container\",e=void 0===e?0:e,\"container\",0,null)||this;return e.typ=\"container\",e.b=t,e}r(t,n);var e=t.prototype;return e.r=function(){return!0},e.un=function(){},t}(t),St=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.typ=\"text\",t}r(t,e);var n=t.prototype;return n.r=function(t){var e,n=this,r=!n.b;return r&&(e=null==(e=n._hyd)?void 0:e.splice(0,1)[0],n.b=e||document.createTextNode(n.t.replace(\"text_\",\"\"))),t.push(function(){r?n.md():n.ud()}),!0},n.un=function(){ct(this.b)},t}(t);function wt(t,e,n,r){var i,o=r.d+1;return m(t)||\"number\"==typeof t?new St(\"text_\".concat(t),o,e,n,r,null,{}):new(\"string\"==typeof(i=t.type)?bt:_t)(i,o,e,n,r,t.ref,t.props)}function O(n,r,i,o,s,a,u){c=n,l=r,d=(f=i).map(function(t){return m(t)?null:t.key}),(t=K(it(l.map(function(t){return t.k})),it(d),function(t){return t})).removed.forEach(function(t){l.splice(t,1)[0].un()}),t.ordered.forEach(function(t){var e=t[0],t=t[1],e=l.splice(e,1)[0],e=(l.splice(t,0,e),v(e.b)),t=v(l[t+1]&&l[t+1].b);e&&e.parentNode.insertBefore(e,t)}),t.added.forEach(function(t){l.splice(t,0,wt(f[t],d[t],t,c))}),e=t.maintained.filter(function(t){t[0];var t=t[1],e=f[t],n=l[t];return(m(e)?\"text_\".concat(e):e.type)!==n.t?(n.un(),l.splice(t,1,wt(e,d[t],t,c)),!0):(n.i=t,!1)});var c,l,f,d,t=et(et([],t.added,!0),e.map(function(t){return t[0],t[1]}),!0),h=n._hyd,e=r.filter(function(t,e){return t._hyd=h,t.u(o,s,i[e],a,u)}),p=(\"container\"===n.typ&&n._sel&&r.forEach(function(t){t=function t(e){if(e)return e.b&&e.b instanceof Node?e:(e=e._ps).length?t(e[0]):null}(t);t&&(t._sel=!0)}),n._hyd=null,ut(n));return p&&t.reverse().forEach(function(t){var t=r[t],e=v(t.b);e&&p!==e&&!e.parentNode&&(t=function(t,e){for(var n=t._ps,r=n.length,i=e.i+1;i<r;++i){var o=v(n[i].b);if(o)return o}return null}(n,t),p.insertBefore(e,t))}),0<e.length}function Ct(t,e,n){var r,i,o,s;return!n&&t&&((n=new Et(e.parentElement))._hyd=[e],n._sel=!0),t=t,e=e,void 0===i&&(i={}),o=!!(r=void 0===(r=n)?e.__CROACT__:r),s=[],O(r=r||new Et(e),r._ps,t?[t]:[],s,i,void 0,void 0),lt(s),pt(null),o||(e.__CROACT__=r),n}function Pt(t){var e=ht(),e=e._hs||(e._hs=[]),n=dt,r=e[n];if(dt=n+1,r){if(!c(r.deps,t.deps))return r.updated=!1,r;e[n]=t}else e.push(t);return t.value=t.func(),t.updated=!0,t}function xt(t){return Pt({func:function(){return mt(t)},deps:[]}).value}function Ot(t,e,n){var r=ht(),i=Pt({func:function(){return t},deps:e}),e=n?r._usefs:r._uefs;i.updated?e.push(function(){return i.effect&&i.effect(),i.effect=t(),i.effect}):e.push(function(){return i.effect})}var Dt=function(t,e){return(Dt=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])})(t,e)};var kt=function(e){var t=i,n=e;if(\"function\"!=typeof n&&null!==n)throw new TypeError(\"Class extends value \"+String(n)+\" is not a constructor or null\");function r(){this.constructor=t}function i(){var t=null!==e&&e.apply(this,arguments)||this;return t.state={scrollPos:0},t.width=0,t.height=0,t._zoom=0,t._rulerScale=0,t._observer=null,t._checkResize=function(){t.resize()},t}Dt(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r);n=i.prototype;return n.render=function(){var t=this.props;return this._zoom=t.zoom,C(\"canvas\",{ref:h(this,\"canvasElement\"),style:this.props.style})},n.componentDidMount=function(){var t=this.props,e=(this.state.scrollPos=t.defaultScrollPos||0,this.canvasElement),n=e.getContext(\"2d\",{alpha:!0});this.canvasContext=n,t.useResizeObserver?(this._observer=new ResizeObserver(this._checkResize),this._observer.observe(e,{box:\"border-box\"})):this.resize()},n.componentDidUpdate=function(){this.resize()},n.componentWillUnmount=function(){var t;this.state.scrollPos=0,null!=(t=this._observer)&&t.disconnect()},n.getScrollPos=function(){return this.state.scrollPos},n.scroll=function(t,e){this.draw({scrollPos:t,zoom:e})},n.resize=function(t){var e=this.canvasElement,n=this.props,r=n.width,i=n.height,n=n.scrollPos,o=this._getRulerScale();this.width=r||e.offsetWidth,this.height=i||e.offsetHeight,e.width=this.width*o,e.height=this.height*o,this.draw({scrollPos:n,zoom:t})},n.draw=function(t){var e=this.props,n=(t=void 0===t?{}:t).zoom,c=void 0===n?this._zoom:n,n=t.scrollPos,l=void 0===n?this.state.scrollPos:n,n=t.marks,n=void 0===n?e.marks:n,r=t.selectedRanges,r=void 0===r?e.selectedRanges:r,i=t.segment,o=void 0===i?e.segment||10:i,i=t.unit,f=void 0===i?e.unit:i;this._zoom=c;var t=e.type,i=e.backgroundColor,N=e.lineColor,Y=e.textColor,A=e.textBackgroundColor,d=e.direction,s=e.negativeRuler,X=void 0===s||s,a=e.textFormat,s=e.range,h=void 0===s?[-1/0,1/0]:s,s=e.rangeBackgroundColor,G=e.selectedBackgroundColor,u=e.lineWidth,u=void 0===u?1:u,B=e.selectedRangesText,p=e.selectedRangesTextColor,I=void 0===p?\"#44aaff\":p,p=e.selectedRangesTextOffset,Z=void 0===p?[0,0]:p,p=e.markColor,p=void 0===p?\"#ff5\":p,v=this._getRulerScale(),g=this.width,m=this.height,_=(this.state.scrollPos=l,this.canvasContext),y=\"horizontal\"===t,L=!1!==X,t=e.font||\"10px sans-serif\",b=e.textAlign||\"left\",$=e.textOffset||[0,0],E=y?m:g,S=nt(\"\".concat(e.mainLineSize||\"100%\"),E),W=nt(\"\".concat(e.longLineSize||10),E),U=nt(\"\".concat(e.shortLineSize||7),E),w=e.lineOffset||[0,0];switch(\"transparent\"===i?_.clearRect(0,0,g*v,m*v):(_.rect(0,0,g*v,m*v),_.fillStyle=i,_.fill()),_.save(),_.scale(v,v),_.strokeStyle=N,_.lineWidth=u,_.font=t,_.fillStyle=Y,_.textAlign=b,d){case\"start\":_.textBaseline=\"top\";break;case\"center\":_.textBaseline=\"middle\";break;case\"end\":_.textBaseline=\"bottom\"}_.translate(.5,0),_.beginPath();for(var C=y?g:m,P=c*f,x=Math.floor(l*c/P),q=Math.ceil((l*c+C)/P)-x,H=Math.max([\"left\",\"center\",\"right\"].indexOf(b)-1,-1),O=y?m:g,D=[],k=0;k<=q;++k){var T=(k+x)*f,V=\"\".concat(T),J=(a&&(V=a(T)),_.measureText(V).width);D.push({color:Y,offset:$,backgroundColor:A,value:T,text:V,textSize:J})}\"transparent\"!==G&&null!=r&&r.length&&r.forEach(function(t){var e=Math.max(t[0],h[0],X?-1/0:0),n=Math.min(t[1],h[1]),r=(e-l)*c,n=(n-e)*c;B&&t.forEach(function(t){var e=\"\".concat(t),n=(a&&(e=a(t)),_.measureText(e).width),r=t*c,i=r+n;rt(D,function(t,e){var n=t.value,t=t.textSize,n=n*c;n<=i&&r<=n+t&&D.splice(e,1)}),D.push({value:t,color:I,offset:Z,text:e,textSize:n})}),n<=0||(_.save(),_.fillStyle=G,y?_.fillRect(r,0,n,O):_.fillRect(0,r,O,n),_.restore())}),\"transparent\"!==s&&h[0]!==-1/0&&h[1]!==1/0&&(e=(h[0]-l)*c,i=(h[1]-h[0])*c,_.save(),_.fillStyle=s,y?_.fillRect(e,0,i,O):_.fillRect(0,e,O,i),_.restore());for(k=0;k<=q;++k){T=k+x;if(L||!(T<0))for(var K=T*f,Q=(K-l)*c,M=0;M<o;++M){var R=Q+M/o*P,z=K+M/o*f;if(!(R<0||C<=R||z<h[0]||z>h[1])){var F=0===M?S:M%2==0?W:U,j=0;switch(d){case\"start\":j=0;break;case\"center\":j=O/2-F/2;break;case\"end\":j=O-F}var z=y?[R+w[0],j+w[1]]:[j+w[0],R+w[1]],R=z[0],z=z[1],tt=y?[R,z+F]:[R+F,z],et=tt[0],tt=tt[1];_.moveTo(R+w[0],z+w[1]),_.lineTo(et+w[0],tt+w[1])}}}_.stroke(),_.beginPath(),_.strokeStyle=p,_.lineWidth=1,(n||[]).forEach(function(t){var e,n,r=(-l+t)*c;r<0||C<=r||t<h[0]||t>h[1]||(r=(t=y?[r+w[0],w[1]]:[w[0],r+w[1]])[0],t=t[1],e=(n=y?[r,t+E]:[r+E,t])[0],n=n[1],_.moveTo(r+w[0],t+w[1]),_.lineTo(e+w[0],n+w[1]))}),_.stroke(),D.forEach(function(t){var e=t.value,n=t.offset,r=t.backgroundColor,i=t.color,o=t.text,s=t.textSize;if(L||!(e<0)){t=(e-l)*c;if(!(t<-P||C+f*c<=t||e<h[0]||e>h[1])){var a=0;switch(d){case\"start\":a=17;break;case\"center\":a=O/2;break;case\"end\":a=O-17}e=y?[t+-3*H,a]:[a,t+3*H],t=e[0],e=e[1];if(r){var u=0;switch(b){case\"left\":u=0;break;case\"center\":u=-s/2;break;case\"right\":u=-s}_.save(),_.fillStyle=r,y?_.fillRect(t+n[0]+u,0,s,S):(_.translate(0,e+n[1]),_.rotate(-Math.PI/2),_.fillRect(u,0,s,S)),_.restore()}_.save(),_.fillStyle=i,y?_.fillText(o,t+n[0],e+n[1]):(_.translate(t+n[0],e+n[1]),_.rotate(-Math.PI/2),_.fillText(o,0,0)),_.restore()}}}),_.restore()},n._getRulerScale=function(){var t,e,n=this.props.defaultPixelScale||2;return this._rulerScale||(!(e=1<window.devicePixelRatio)&&window.matchMedia&&(e=(t=window.matchMedia(\"only screen and (min--moz-device-pixel-ratio: 1.3), only screen and (-o-min-device-pixel-ratio: 2.6/2), only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen  and (min-device-pixel-ratio: 1.3), only screen and (min-resolution: 1.3dppx)\"))&&t.matches),this._rulerScale=e?3:n),this._rulerScale},i.defaultProps={type:\"horizontal\",zoom:1,width:0,height:0,unit:50,negativeRuler:!0,mainLineSize:\"100%\",longLineSize:10,shortLineSize:7,segment:10,direction:\"end\",style:{width:\"100%\",height:\"100%\"},backgroundColor:\"#333333\",font:\"10px sans-serif\",textColor:\"#ffffff\",textBackgroundColor:\"transparent\",lineColor:\"#777777\",range:[-1/0,1/0],rangeBackgroundColor:\"transparent\",lineWidth:1,selectedBackgroundColor:\"#555555\",defaultScrollPos:0,markColor:\"#f55\",marks:[]},i}(gt),Tt=[\"type\",\"width\",\"height\",\"unit\",\"zoom\",\"direction\",\"textAlign\",\"font\",\"segment\",\"mainLineSize\",\"longLineSize\",\"shortLineSize\",\"lineOffset\",\"textOffset\",\"negativeRuler\",\"range\",\"scrollPos\",\"defaultScrollPos\",\"style\",\"backgroundColor\",\"rangeBackgroundColor\",\"lineColor\",\"textColor\",\"textBackgroundColor\",\"textFormat\",\"warpSelf\",\"selectedBackgroundColor\",\"selectedRanges\",\"defaultPixelScale\",\"useResizeObserver\",\"selectedRangesText\",\"selectedRangesTextColor\",\"selectedRangesTextOffset\",\"marks\",\"markColor\"],Mt=function(){return(Mt=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var i in e=arguments[n])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)};var t=function(){function t(){this._events={}}var e=t.prototype;return e.on=function(t,e){if(o(t))for(var n in t)this.on(n,t[n]);else this._addEvent(t,e,{});return this},e.off=function(t,e){if(t)if(o(t))for(var n in t)this.off(n);else{var r,i;e?(r=this._events[t])&&-1<(i=W(r,function(t){return t.listener===e}))&&r.splice(i,1):this._events[t]=[]}else this._events={};return this},e.once=function(e,t){var n=this;return t&&this._addEvent(e,t,{once:!0}),new Promise(function(t){n._addEvent(e,t,{once:!0})})},e.emit=function(e,n){var t,r=this,i=(void 0===n&&(n={}),this._events[e]);return!e||!i||(t=!1,n.eventType=e,n.stop=function(){t=!0},n.currentTarget=this,function(){for(var t=0,e=0,n=arguments.length;e<n;e++)t+=arguments[e].length;for(var r=Array(t),i=0,e=0;e<n;e++)for(var o=arguments[e],s=0,a=o.length;s<a;s++,i++)r[i]=o[s];return r}(i).forEach(function(t){t.listener(n),t.once&&r.off(e,t.listener)}),!t)},e.trigger=function(t,e){return this.emit(t,e=void 0===e?{}:e)},e._addEvent=function(t,e,n){var r=this._events;r[t]=r[t]||[],r[t].push(Mt({listener:e},n))},t}(),Rt=function(t,e){return(Rt=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])})(t,e)};var f=function(){return(f=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var i in e=arguments[n])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)};function zt(t){t=t.container;return t===document.body?[t.scrollLeft||document.documentElement.scrollLeft,t.scrollTop||document.documentElement.scrollTop]:[t.scrollLeft,t.scrollTop]}function Ft(t){return t?m(t)?document.querySelector(t):Z(t)?t():t instanceof Element?t:\"current\"in t?t.current:\"value\"in t?t.value:void 0:null}var jt=function(e){function t(){this.constructor=n}var n;function r(){var t=null!==e&&e.apply(this,arguments)||this;return t._startRect=null,t._startPos=[],t._prevTime=0,t._timer=0,t._prevScrollPos=[0,0],t._isWait=!1,t._flag=!1,t}Rt(n=r,i=e),n.prototype=null===i?Object.create(i):(t.prototype=i.prototype,new t);var i=r.prototype;return i.dragStart=function(t,e){var n,r,i,o,s=Ft(e.container);s?(o=i=r=n=0,o=s===document.body?(i=window.innerWidth,window.innerHeight):(n=(s=s.getBoundingClientRect()).top,r=s.left,i=s.width,s.height),this._flag=!0,this._startPos=[t.clientX,t.clientY],this._startRect={top:n,left:r,width:i,height:o},this._prevScrollPos=this._getScrollPosition([0,0],e)):this._flag=!1},i.drag=function(t,e){var n,r,i,o,s,a;if(clearTimeout(this._timer),this._flag)return n=t.clientX,r=t.clientY,i=e.threshold,o=this._startRect,s=this._startPos,a=[0,0],o.top>r-(i=void 0===i?0:i)?(s[1]>o.top||r<s[1])&&(a[1]=-1):o.top+o.height<r+i&&(s[1]<o.top+o.height||r>s[1])&&(a[1]=1),o.left>n-i?(s[0]>o.left||n<s[0])&&(a[0]=-1):o.left+o.width<n+i&&(s[0]<o.left+o.width||n>s[0])&&(a[0]=1),!(!a[0]&&!a[1])&&this._continueDrag(f(f({},e),{direction:a,inputEvent:t,isDrag:!0}))},i.checkScroll=function(t){var e,n,r,i,o,s,a,u=this;return!this._isWait&&(s=void 0===(s=t.prevScrollPos)?this._prevScrollPos:s,a=t.direction,e=void 0===(e=t.throttleTime)?0:e,n=t.inputEvent,r=t.isDrag,o=(i=this._getScrollPosition(a||[0,0],t))[0]-s[0],s=i[1]-s[1],a=a||[o?Math.abs(o)/o:0,s?Math.abs(s)/s:0],this._prevScrollPos=i,!(!o&&!s)&&(this.trigger(\"move\",{offsetX:a[0]?o:0,offsetY:a[1]?s:0,inputEvent:n}),e&&r&&(clearTimeout(this._timer),this._timer=window.setTimeout(function(){u._continueDrag(t)},e)),!0))},i.dragEnd=function(){this._flag=!1,clearTimeout(this._timer)},i._getScrollPosition=function(t,e){var n=e.container,e=e.getScrollPosition;return(void 0===e?zt:e)({container:Ft(n),direction:t})},i._continueDrag=function(t){var e=this,n=t.container,r=t.direction,i=t.throttleTime,o=t.useScroll,s=t.isDrag,a=t.inputEvent;if(!(!this._flag||s&&this._isWait)){var u=b(),i=Math.max(i+this._prevTime-u,0);if(0<i)return clearTimeout(this._timer),this._timer=window.setTimeout(function(){e._continueDrag(t)},i),!1;this._prevTime=u;i=this._getScrollPosition(r,t),u=(this._prevScrollPos=i,s&&(this._isWait=!0),{container:Ft(n),direction:r,inputEvent:a});return null!=(s=t.requestScroll)&&s.call(t,u),this.trigger(\"scroll\",u),this._isWait=!1,o||this.checkScroll(f(f({},t),{prevScrollPos:i,direction:r,inputEvent:a}))}},r}(t),Nt=function(t,e){return(Nt=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])})(t,e)};var P=function(){return(P=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var i in e=arguments[n])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)};function Yt(t){return 180*(e=[t[0].clientX,t[0].clientY],t=[t[1].clientX,t[1].clientY],n=t[0]-e[0],t=t[1]-e[1],(0<=(e=Math.atan2(t,n))?e:e+2*Math.PI)/Math.PI);var e,n}function D(t){if(!t)return[];if(t.touches){for(var e=t.touches,n=Math.min(e.length,2),r=[],i=0;i<n;++i)r.push(Gt(e[i]));return r}return[Gt(t)]}function At(t,e,n){var r=n.length,t=d(t,r),i=t.clientX,o=t.clientY,s=t.originalClientX,t=t.originalClientY,e=d(e,r),a=e.clientX,e=e.clientY,n=d(n,r);return{clientX:s,clientY:t,deltaX:i-a,deltaY:o-e,distX:i-n.clientX,distY:o-n.clientY}}function Xt(t){return Math.sqrt(Math.pow(t[0].clientX-t[1].clientX,2)+Math.pow(t[0].clientY-t[1].clientY,2))}function Gt(t){return{clientX:t.clientX,clientY:t.clientY}}function d(t,e){void 0===e&&(e=t.length);for(var n={clientX:0,clientY:0,originalClientX:0,originalClientY:0},r=0;r<e;++r){var i=t[r];n.originalClientX+=\"originalClientX\"in i?i.originalClientX:i.clientX,n.originalClientY+=\"originalClientY\"in i?i.originalClientY:i.clientY,n.clientX+=i.clientX,n.clientY+=i.clientY}return e?{clientX:n.clientX/e,clientY:n.clientY/e,originalClientX:n.originalClientX/e,originalClientY:n.originalClientY/e}:n}var Bt=function(){function t(t){this.prevClients=[],this.startClients=[],this.movement=0,this.length=0,this.startClients=t,this.prevClients=t,this.length=t.length}return t.prototype.getAngle=function(t){return Yt(t=void 0===t?this.prevClients:t)},t.prototype.getRotation=function(t){return Yt(t=void 0===t?this.prevClients:t)-Yt(this.startClients)},t.prototype.getPosition=function(t,e){var n=At((t=void 0===t?this.prevClients:t)||this.prevClients,this.prevClients,this.startClients),r=n.deltaX,i=n.deltaY;return this.movement+=Math.sqrt(r*r+i*i),this.prevClients=t,n},t.prototype.getPositions=function(n){void 0===n&&(n=this.prevClients);var r=this.prevClients;return this.startClients.map(function(t,e){return At([n[e]],[r[e]],[t])})},t.prototype.getMovement=function(t){var e,n,r=this.movement;return t?(t=d(t,this.length),e=d(this.prevClients,this.length),n=t.clientX-e.clientX,t=t.clientY-e.clientY,Math.sqrt(n*n+t*t)+r):r},t.prototype.getDistance=function(t){return Xt(t=void 0===t?this.prevClients:t)},t.prototype.getScale=function(t){return Xt(t=void 0===t?this.prevClients:t)/Xt(this.startClients)},t.prototype.move=function(e,n){this.startClients.forEach(function(t){t.clientX-=e,t.clientY-=n}),this.prevClients.forEach(function(t){t.clientX-=e,t.clientY-=n})},t}(),It=[\"textarea\",\"input\"],Zt=function(o){function t(){this.constructor=e}var e,n;function r(t,e){void 0===e&&(e={});var n,g=o.call(this)||this,t=(g.options={},g.flag=!1,g.pinchFlag=!1,g.data={},g.isDrag=!1,g.isPinch=!1,g.isMouse=!1,g.isTouch=!1,g.clientStores=[],g.targets=[],g.prevTime=0,g.doubleFlag=!1,g._dragFlag=!1,g._isTrusted=!1,g._isMouseEvent=!1,g._isSecondaryButton=!1,g._preventMouseEvent=!1,g._prevInputEvent=null,g._isDragAPI=!1,g._isIdle=!0,g._window=window,g.onDragStart=function(t,e){if(void 0===e&&(e=!0),g.flag||!1!==t.cancelable){var n=-1<=t.type.indexOf(\"drag\");if(!g.flag||!n){g._isDragAPI=!0;var n=g.options,r=n.container,i=n.pinchOutside,o=n.preventWheelClick,s=n.preventRightClick,a=n.preventDefault,u=n.checkInput,c=n.dragFocusedInput,l=n.preventClickEventOnDragStart,f=n.preventClickEventOnDrag,n=n.preventClickEventByCondition,d=g.isTouch,h=!g.flag;if(g._isSecondaryButton=3===t.which||2===t.button,o&&(2===t.which||1===t.button)||s&&(3===t.which||2===t.button))return g.stop(),!1;if(h){o=g._window.document.activeElement,s=t.target;if(s){var p=s.tagName.toLowerCase(),p=-1<It.indexOf(p),v=s.isContentEditable;if(p||v){if(u||!c&&o===s)return!1;if(o&&(o===s||v&&o.isContentEditable&&o.contains(s))){if(!c)return!1;s.blur()}}else(a||\"touchstart\"===t.type)&&o&&(p=o.tagName.toLowerCase(),(o.isContentEditable||-1<It.indexOf(p))&&o.blur());(l||f||n)&&S(g._window,\"click\",g._onClick,!0)}g.clientStores=[new Bt(D(t))],g._isIdle=!1,g.flag=!0,g.isDrag=!1,g._isTrusted=e,g._dragFlag=!0,g._prevInputEvent=t,g.data={},g.doubleFlag=b()-g.prevTime<200,g._isMouseEvent=(u=t)&&(-1<u.type.indexOf(\"mouse\")||\"button\"in u),!g._isMouseEvent&&g._preventMouseEvent&&(g._preventMouseEvent=!1),!1===(g._preventMouseEvent||g.emit(\"dragStart\",P(P({data:g.data,datas:g.data,inputEvent:t,isMouseEvent:g._isMouseEvent,isSecondaryButton:g._isSecondaryButton,isTrusted:e,isDouble:g.doubleFlag},g.getCurrentStore().getPosition()),{preventDefault:function(){t.preventDefault()},preventDrag:function(){g._dragFlag=!1}})))&&g.stop(),g._isMouseEvent&&g.flag&&a&&t.preventDefault()}if(!g.flag)return!1;v=0;h?(g._attchDragEvent(),d&&i&&(v=setTimeout(function(){S(r,\"touchstart\",g.onDragStart,{passive:!1})}))):d&&i&&w(r,\"touchstart\",g.onDragStart),g.flag&&((c=t).touches&&2<=c.touches.length)&&(clearTimeout(v),h&&t.touches.length!==t.changedTouches.length||g.pinchFlag||g.onPinchStart(t))}}},g.onDrag=function(t,e){if(g.flag){var n=g.options.preventDefault,n=(!g._isMouseEvent&&n&&t.preventDefault(),D(g._prevInputEvent=t)),r=g.moveClients(n,t,!1);if(g._dragFlag){if(g.pinchFlag||r.deltaX||r.deltaY)if(!1===(g._preventMouseEvent||g.emit(\"drag\",P(P({},r),{isScroll:!!e,inputEvent:t}))))return void g.stop();g.pinchFlag&&g.onPinch(t,n)}g.getCurrentStore().getPosition(n,!0)}},g.onDragEnd=function(t){var e,n,r,i,o,s;g.flag&&(s=(r=g.options).pinchOutside,e=r.container,o=r.preventClickEventOnDrag,i=r.preventClickEventOnDragStart,r=r.preventClickEventByCondition,n=g.isDrag,(o||i||r)&&requestAnimationFrame(function(){g._allowClickEvent()}),r||i||!o||n||g._allowClickEvent(),g.isTouch&&s&&w(e,\"touchstart\",g.onDragStart),g.pinchFlag&&g.onPinchEnd(t),0!==(r=null!=t&&t.touches?D(t):[]).length&&g.options.keepDragging?g._addStore(new Bt(r)):g.flag=!1,i=g._getPosition(),o=b(),s=!n&&g.doubleFlag,g._prevInputEvent=null,g.prevTime=n||s?0:o,g.flag||(g._dettachDragEvent(),g._preventMouseEvent||g.emit(\"dragEnd\",P({data:g.data,datas:g.data,isDouble:s,isDrag:n,isClick:!n,isMouseEvent:g._isMouseEvent,isSecondaryButton:g._isSecondaryButton,inputEvent:t,isTrusted:g._isTrusted},i)),g.clientStores=[],g._isMouseEvent||(g._preventMouseEvent=!0,requestAnimationFrame(function(){requestAnimationFrame(function(){g._preventMouseEvent=!1})})),g._isIdle=!0))},g.onBlur=function(){g.onDragEnd()},g._allowClickEvent=function(){w(g._window,\"click\",g._onClick,!0)},g._onClick=function(t){g._allowClickEvent(),g._preventMouseEvent=!1;var e=g.options.preventClickEventByCondition;null!=e&&e(t)||(t.stopPropagation(),t.preventDefault())},g._onContextMenu=function(t){g.options.preventRightClick?g.onDragEnd(t):t.preventDefault()},g._passCallback=function(){},[].concat(t)),r=t[0],r=(g._window=!r||\"document\"in r?window:a(r),g.options=P({checkInput:!1,container:!r||\"document\"in r?r:a(r),preventRightClick:!0,preventWheelClick:!0,preventClickEventOnDragStart:!1,preventClickEventOnDrag:!1,preventClickEventByCondition:null,preventDefault:!0,checkWindowBlur:!1,keepDragging:!1,pinchThreshold:0,events:[\"touch\",\"mouse\"]},e),g.options),e=r.container,i=r.events,r=r.checkWindowBlur;return g.isTouch=-1<i.indexOf(\"touch\"),g.isMouse=-1<i.indexOf(\"mouse\"),g.targets=t,g.isMouse&&(t.forEach(function(t){S(t,\"mousedown\",g.onDragStart),S(t,\"mousemove\",g._passCallback)}),S(e,\"contextmenu\",g._onContextMenu)),r&&S(a(),\"blur\",g.onBlur),g.isTouch&&(n={passive:!1},t.forEach(function(t){S(t,\"touchstart\",g.onDragStart,n),S(t,\"touchmove\",g._passCallback,n)})),g}return Nt(e=r,n=o),e.prototype=null===n?Object.create(n):(t.prototype=n.prototype,new t),r.prototype.stop=function(){this.isDrag=!1,this.data={},this.clientStores=[],this.pinchFlag=!1,this.doubleFlag=!1,this.prevTime=0,this.flag=!1,this._isIdle=!0,this._allowClickEvent(),this._dettachDragEvent(),this._isDragAPI=!1},r.prototype.getMovement=function(t){return this.getCurrentStore().getMovement(t)+this.clientStores.slice(1).reduce(function(t,e){return t+e.movement},0)},r.prototype.isDragging=function(){return this.isDrag},r.prototype.isIdle=function(){return this._isIdle},r.prototype.isFlag=function(){return this.flag},r.prototype.isPinchFlag=function(){return this.pinchFlag},r.prototype.isDoubleFlag=function(){return this.doubleFlag},r.prototype.isPinching=function(){return this.isPinch},r.prototype.scrollBy=function(t,e,n,r){void 0===r&&(r=!0),this.flag&&(this.clientStores[0].move(t,e),r&&this.onDrag(n,!0))},r.prototype.move=function(t,e){var n=t[0],r=t[1],t=this.getCurrentStore().prevClients;return this.moveClients(t.map(function(t){var e=t.clientX,t=t.clientY;return{clientX:e+n,clientY:t+r,originalClientX:e,originalClientY:t}}),e,!0)},r.prototype.triggerDragStart=function(t){this.onDragStart(t,!1)},r.prototype.setEventData=function(t){var e,n=this.data;for(e in t)n[e]=t[e];return this},r.prototype.setEventDatas=function(t){return this.setEventData(t)},r.prototype.getCurrentEvent=function(t){return void 0===t&&(t=this._prevInputEvent),P(P({data:this.data,datas:this.data},this._getPosition()),{movement:this.getMovement(),isDrag:this.isDrag,isPinch:this.isPinch,isScroll:!1,inputEvent:t})},r.prototype.getEventData=function(){return this.data},r.prototype.getEventDatas=function(){return this.data},r.prototype.unset=function(){var e=this,t=this.targets,n=this.options.container;this.off(),w(this._window,\"blur\",this.onBlur),this.isMouse&&(t.forEach(function(t){w(t,\"mousedown\",e.onDragStart)}),w(n,\"contextmenu\",this._onContextMenu)),this.isTouch&&(t.forEach(function(t){w(t,\"touchstart\",e.onDragStart)}),w(n,\"touchstart\",this.onDragStart)),this._prevInputEvent=null,this._allowClickEvent(),this._dettachDragEvent()},r.prototype.onPinchStart=function(t){var e=this,n=this.options.pinchThreshold;this.isDrag&&this.getMovement()>n||(n=new Bt(D(t)),this.pinchFlag=!0,this._addStore(n),!1===this.emit(\"pinchStart\",P(P({data:this.data,datas:this.data,angle:n.getAngle(),touches:this.getCurrentStore().getPositions()},n.getPosition()),{inputEvent:t,isTrusted:this._isTrusted,preventDefault:function(){t.preventDefault()},preventDrag:function(){e._dragFlag=!1}}))&&(this.pinchFlag=!1))},r.prototype.onPinch=function(t,e){var n;!this.flag||!this.pinchFlag||e.length<2||(n=this.getCurrentStore(),this.isPinch=!0,this.emit(\"pinch\",P(P({data:this.data,datas:this.data,movement:this.getMovement(e),angle:n.getAngle(e),rotation:n.getRotation(e),touches:n.getPositions(e),scale:n.getScale(e),distance:n.getDistance(e)},n.getPosition(e)),{inputEvent:t,isTrusted:this._isTrusted})))},r.prototype.onPinchEnd=function(t){var e,n;this.pinchFlag&&(e=this.isPinch,this.isPinch=!1,this.pinchFlag=!1,n=this.getCurrentStore(),this.emit(\"pinchEnd\",P(P({data:this.data,datas:this.data,isPinch:e,touches:n.getPositions()},n.getPosition()),{inputEvent:t})))},r.prototype.getCurrentStore=function(){return this.clientStores[0]},r.prototype.moveClients=function(t,e,n){var n=this._getPosition(t,n),r=this.isDrag,i=((n.deltaX||n.deltaY)&&(this.isDrag=!0),!1);return!r&&this.isDrag&&(i=!0),P(P({data:this.data,datas:this.data},n),{movement:this.getMovement(t),isDrag:this.isDrag,isPinch:this.isPinch,isScroll:!1,isMouseEvent:this._isMouseEvent,isSecondaryButton:this._isSecondaryButton,inputEvent:e,isTrusted:this._isTrusted,isFirstDrag:i})},r.prototype._addStore=function(t){this.clientStores.splice(0,0,t)},r.prototype._getPosition=function(t,e){var t=this.getCurrentStore().getPosition(t,e),e=this.clientStores.slice(1).reduce(function(t,e){e=e.getPosition();return t.distX+=e.distX,t.distY+=e.distY,t},t),n=e.distX,e=e.distY;return P(P({},t),{distX:n,distY:e})},r.prototype._attchDragEvent=function(){var t=this.options.container,e={passive:!1};this._isDragAPI&&(S(t,\"dragover\",this.onDrag),S(t,\"dragend\",this.onDragEnd)),this.isMouse&&(S(t,\"mousemove\",this.onDrag),S(t,\"mouseup\",this.onDragEnd)),this.isTouch&&(S(t,\"touchmove\",this.onDrag,e),S(t,\"touchend\",this.onDragEnd,e),S(t,\"touchcancel\",this.onDragEnd,e))},r.prototype._dettachDragEvent=function(){var t=this.options.container;this._isDragAPI&&(w(t,\"dragover\",this.onDrag),w(t,\"dragend\",this.onDragEnd)),this.isMouse&&(w(t,\"mousemove\",this.onDrag),w(t,\"mouseup\",this.onDragEnd)),this.isTouch&&(w(t,\"touchstart\",this.onDragStart),w(t,\"touchmove\",this.onDrag),w(t,\"touchend\",this.onDragEnd),w(t,\"touchcancel\",this.onDragEnd))},r}(t);var Lt=function(t){for(var e=5381,n=t.length;n;)e=33*e^t.charCodeAt(--n);return e>>>0};function $t(t,e,n,r,i){var o,s=document.createElement(\"style\"),e=(s.setAttribute(\"type\",\"text/css\"),s.setAttribute(\"data-styled-id\",t),s.setAttribute(\"data-styled-count\",\"1\"),n.nonce&&s.setAttribute(\"nonce\",n.nonce),s.innerHTML=(o=t,t=e,n.original?t:t.replace(/([^};{\\\\s}][^};{]*|^\\\\s*){/gm,function(t,e){e=e.trim();return(e?u(e):[\"\"]).map(function(t){t=t.trim();return 0===t.indexOf(\"@\")?t:-1<t.indexOf(\":global\")?t.replace(/\\\\:global/g,\"\"):-1<t.indexOf(\":host\")?\"\".concat(t.replace(/\\\\:host/g,\".\".concat(o))):t?\".\".concat(o,\" \").concat(t):\".\".concat(o)}).join(\", \")+\" {\"})),r.ownerDocument||document);return(i||e.head||e.body).appendChild(s),s}function Wt(o){var s=\"rCS\"+Lt(o).toString(36);return{className:s,inject:function(t,e){void 0===e&&(e={});var n,r=function(t){if(t&&t.getRootNode){t=t.getRootNode();if(11===t.nodeType)return t}}(t),i=(r||t.ownerDocument||document).querySelector('style[data-styled-id=\"'.concat(s,'\"]'));return i?(n=parseFloat(i.getAttribute(\"data-styled-count\"))||0,i.setAttribute(\"data-styled-count\",\"\".concat(n+1))):i=$t(s,o,e,t,r),{destroy:function(){var t,e=parseFloat(i.getAttribute(\"data-styled-count\"))||0;e<=1?(i.remove?i.remove():null!=(t=i.parentNode)&&t.removeChild(i),i=null):i.setAttribute(\"data-styled-count\",\"\".concat(e-1))}}}}}var Ut=function(){return(Ut=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var i in e=arguments[n])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)};function p(t,e,n){void 0===n&&(n=Math.sqrt(t.length));var r=[],i=t.length/n,o=e.length/i;if(!i)return e;if(!o)return t;for(var s=0;s<n;++s)for(var a=0;a<o;++a)for(var u=r[a*n+s]=0;u<i;++u)r[a*n+s]+=t[u*n+s]*e[a*i+u];return r}function qt(t,e){return p(t,[1,0,0,0,0,Math.cos(e),Math.sin(e),0,0,-Math.sin(e),Math.cos(e),0,0,0,0,1],4)}function Ht(t,e){return p(t,[Math.cos(e),0,-Math.sin(e),0,0,1,0,0,Math.sin(e),0,Math.cos(e),0,0,0,0,1],4)}function Vt(t,e){return p(t,(t=e,e=4,n=Math.cos(t),t=Math.sin(t),(r=Qt(e))[0]=n,r[1]=t,r[e]=-t,r[e+1]=n,r));var n,r}function k(t,e){var n=e[0],r=e[1],e=e[2];return p(t,[void 0===n?1:n,0,0,0,0,void 0===r?1:r,0,0,0,0,void 0===e?1:e,0,0,0,0,1],4)}function Jt(t,e){var n=e[0],r=e[1],e=e[2];return p(t,[1,0,0,0,0,1,0,0,0,0,1,0,void 0===n?0:n,void 0===r?0:r,void 0===e?0:e,1],4)}function Kt(t,e){return p(t,e,4)}function Qt(t){for(var e=t*t,n=[],r=0;r<e;++r)n[r]=r%(t+1)?0:1;return n}function te(){return[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}function ee(t){return t=(I(t=t)?t:function(t){return y(t,\"\")}(t)).map(function(t){var e,n,r,t=!(t=/([^(]*)\\\\(([\\\\s\\\\S]*)\\\\)([\\\\s\\\\S]*)/g.exec(t=t))||t.length<4?{}:{prefix:t[1],value:t[2],suffix:t[3]},i=t.prefix,t=t.value,o=null,s=i,a=\"\";return\"translate\"===i||\"translateX\"===i||\"translate3d\"===i?(o=Jt,a=[(r=u(t).map(function(t){return parseFloat(t)}))[0],void 0===(e=r[1])?0:e,void 0===(e=r[2])?0:e]):\"translateY\"===i?(o=Jt,a=[0,parseFloat(t),0]):\"translateZ\"===i?(o=Jt,a=[0,0,parseFloat(t)]):\"scale\"===i||\"scale3d\"===i?(o=k,a=[n=(r=u(t).map(function(t){return parseFloat(t)}))[0],void 0===(e=r[1])?n:e,void 0===(e=r[2])?1:e]):\"scaleX\"===i?(o=k,a=[n=parseFloat(t),1,1]):\"scaleY\"===i?(o=k,a=[1,parseFloat(t),1]):\"scaleZ\"===i?(o=k,a=[1,1,parseFloat(t)]):\"rotate\"===i||\"rotateZ\"===i||\"rotateX\"===i||\"rotateY\"===i?(e=(r=$(t)).unit,n=r.value,\"rotate\"===i||\"rotateZ\"===i?(s=\"rotateZ\",o=Vt):\"rotateX\"===i?o=qt:\"rotateY\"===i&&(o=Ht),a=\"rad\"===e?n:n*Math.PI/180):\"matrix3d\"===i?(o=Kt,a=u(t).map(function(t){return parseFloat(t)})):\"matrix\"===i?(o=Kt,a=[(r=u(t).map(function(t){return parseFloat(t)}))[0],r[1],0,0,r[2],r[3],0,0,0,0,1,0,r[4],r[5],0,1]):s=\"\",{name:i,functionName:s,value:t,matrixFunction:o,functionValue:a}}),n=te(),t.forEach(function(t){var e=t.matrixFunction,t=t.functionValue;e&&(n=e(n,t))}),n;var n}function ne(t,e){n=t,e=[e[0],e[1]||0,e[2]||0,1],r=(n=p(t,e,t=void 0===(t=4)?e.length:t))[t-1];var n,r,e=n.map(function(t){return t/r}),t=e[3]||1;return[e[0]/t,e[1]/t,e[2]/t]}function re(t,e){void 0===e&&(e=document.body);for(var n=t,r=te();n;){r=Kt(ee(getComputedStyle(n).transform),r);if(n===e)break;n=n.parentElement}return(r=function(t,e){void 0===e&&(e=Math.sqrt(t.length));for(var n=t.slice(),r=Qt(e),i=0;i<e;++i){var o=e*i+i;if(!N(n[o],j))for(var s=i+1;s<e;++s)if(n[e*i+s]){g=v=p=h=d=f=l=c=u=a=void 0;for(var a=n,u=r,c=i,l=s,f=e,d=0;d<f;++d){var h=c+d*f,p=l+d*f,v=a[h],g=u[h];a[h]=a[p],a[p]=v,u[h]=u[p],u[p]=g}break}if(!N(n[o],j))return[];w=S=E=b=y=_=m=void 0;for(var m=n,_=r,y=i,b=e,E=n[o],S=0;S<b;++S){var w=y+S*b;m[w]/=E,_[w]/=E}for(s=0;s<e;++s){var C=s,P=n[s+i*e];if(N(P,j)&&i!==s){F=z=R=M=T=k=D=O=x=void 0;for(var x=n,O=r,D=C,k=i,T=e,M=-P,R=0;R<T;++R){var z=D+R*T,F=k+R*T;x[z]+=x[F]*M,O[z]+=O[F]*M}}}}return r}(r,4))[12]=0,r[13]=0,r[14]=0,r}var ie=function(t,e){return(ie=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])})(t,e)};var T=function(){return(T=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var i in e=arguments[n])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)};function M(t,e,n){if(n||2===arguments.length)for(var r,i=0,o=e.length;i<o;i++)!r&&i in e||((r=r||Array.prototype.slice.call(e,0,i))[i]=e[i]);return t.concat(r||Array.prototype.slice.call(e))}function R(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];return function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];return t.map(function(t){return t.split(\" \").map(function(t){return t?\"\"+e+t:\"\"}).join(\" \")}).join(\" \")}.apply(void 0,M([\"scena-guides-\"],t,!1))}var oe,se,ae,ue,ce,le=R(\"guide\",\"adder\"),fe=R(\"guides\"),de=R(\"guide\"),he=R(\"dragging\"),pe=R(\"display-drag\"),e=(oe=\"scena-guides-\",'\\\\n{\\\\n    position: relative;\\\\n    width: 100%;\\\\n    height: 100%;\\\\n}\\\\ncanvas {\\\\n    position: relative;\\\\n}\\\\n.guide-origin {\\\\n    position: absolute;\\\\n    width: 1px;\\\\n    height: 1px;\\\\n    top: 0;\\\\n    left: 0;\\\\n    opacity: 0;\\\\n}\\\\n.guides {\\\\n    position: absolute;\\\\n    bottom: 0;\\\\n    right: 0;\\\\n    will-change: transform;\\\\n    z-index: 2000;\\\\n}\\\\n.guide-pos {\\\\n    position: absolute;\\\\n    font-weight: bold;\\\\n    font-size: 12px;\\\\n    color: #f33;\\\\n}\\\\n.horizontal .guide-pos {\\\\n    bottom: 100%;\\\\n    left: 50%;\\\\n    transform: translate(-50%);\\\\n}\\\\n.vertical .guide-pos {\\\\n    left: calc(100% + 2px);\\\\n    top: 50%;\\\\n    transform: translateY(-50%);\\\\n}\\\\n.display-drag {\\\\n    position: absolute;\\\\n    will-change: transform;\\\\n    z-index: 2000;\\\\n    font-weight: bold;\\\\n    font-size: 12px;\\\\n    display: none;\\\\n    left: 20px;\\\\n    top: -20px;\\\\n    color: #f33;\\\\n}\\\\n:host.horizontal .guides {\\\\n    width: 100%;\\\\n    height: 0;\\\\n}\\\\n:host.vertical .guides {\\\\n    height: 100%;\\\\n    width: 0;\\\\n}\\\\n:host.horizontal canvas {\\\\n    cursor: ns-resize;\\\\n}\\\\n:host.vertical canvas {\\\\n    cursor: ew-resize;\\\\n}\\\\n.guide {\\\\n    position: absolute;\\\\n    background: #f33;\\\\n    z-index: 2;\\\\n}\\\\n.guide.dragging:before {\\\\n    position: absolute;\\\\n    content: \"\";\\\\n    width: 100%;\\\\n    height: 100%;\\\\n    top: 50%;\\\\n    left: 50%;\\\\n    transform: translate(-50%, -50%);\\\\n}\\\\n:host.horizontal .guide {\\\\n    width: 100%;\\\\n    height: 1px;\\\\n    cursor: row-resize;\\\\n}\\\\n:host.vertical .guide {\\\\n    width: 1px;\\\\n    height: 100%;\\\\n    cursor: col-resize;\\\\n}\\\\n.mobile :host.horizontal .guide {\\\\n    transform: scale(1, 2);\\\\n}\\\\n.mobile :host.vertical .guide {\\\\n    transform: scale(2, 1);\\\\n}\\\\n:host.horizontal .guide:before {\\\\n    height: 20px;\\\\n}\\\\n:host.vertical .guide:before {\\\\n    width: 20px;\\\\n}\\\\n.adder {\\\\n    display: none;\\\\n}\\\\n.adder.dragging {\\\\n    display: block;\\\\n}\\\\n'.replace(/([^}{]*){/gm,function(t,e){return e.replace(/\\\\.([^{,\\\\s\\\\d.]+)/g,\".\"+oe+\"$1\")+\"{\"})),ve=M([\"className\",\"rulerStyle\",\"snapThreshold\",\"snaps\",\"displayDragPos\",\"cspNonce\",\"dragPosFormat\",\"defaultGuides\",\"showGuides\",\"scrollOptions\",\"guideStyle\",\"guidesOffset\",\"digit\",\"defaultGuidesPos\",\"dragGuideStyle\",\"displayGuidePos\",\"guidePosFormat\",\"guidePosStyle\",\"lockGuides\",\"guidesZoom\"],Tt,!0),ge=(se=\"div\",ae=Wt(e=e),ue=ae.className,(e=function(e,t){var n,r,i=e.className,i=void 0===i?\"\":i,o=(e.cspNonce,function(t,e){var n={};for(i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(n[i]=t[i]);if(null!=t&&\"function\"==typeof Object.getOwnPropertySymbols)for(var r=0,i=Object.getOwnPropertySymbols(t);r<i.length;r++)e.indexOf(i[r])<0&&Object.prototype.propertyIsEnumerable.call(t,i[r])&&(n[i[r]]=t[i[r]]);return n}(e,[\"className\",\"cspNonce\"])),s=xt();return n=t,r=function(){return s.current},Ot(function(){null!=n&&n(r())},[],!0),Ot(function(){var t=ae.inject(s.current,{nonce:e.cspNonce});return function(){t.destroy()}},[]),C(se,Ut({ref:s,\"data-styled-id\":ue,className:\"\".concat(i,\" \").concat(ue)},o))})._fr=!0,e),me=function(e){var t=i,n=e;if(\"function\"!=typeof n&&null!==n)throw new TypeError(\"Class extends value \"+String(n)+\" is not a constructor or null\");function r(){this.constructor=t}function i(t){var g=e.call(this,t)||this;return g.state={guides:[]},g.scrollPos=0,g.managerRef=mt(),g.guideElements=[],g._isFirstMove=!1,g._zoom=1,g._guidesZoom=1,g._observer=null,g.onDragStart=function(t){var e=t.datas,n=t.inputEvent;g._isFirstMove=!0,g.movePos(t),g.props.onDragStart(T(T({},t),{dragElement:e.target})),g.gesto.isFlag()&&(n.stopPropagation(),n.preventDefault(),g._startDragScroll(t))},g._onDrag=function(t){g._isFirstMove&&(g._isFirstMove=!1,n=t.datas.target,e=he,n.classList?n.classList.add(e):n.className+=\" \"+e);var e,n=g.movePos(t);if(g.props.onDrag(T(T({},t),{dragElement:t.datas.target})),g.gesto.isFlag())return g._dragScroll(t),n;g._endDragScroll(t)},g.onDragEnd=function(t){var e=t.datas,n=t.isDouble,r=t.distX,i=t.distY,o=g.movePos(t),s=g.state.guides,a=g.props,u=a.onChangeGuides,c=a.displayDragPos,l=a.digit,f=a.lockGuides,a=a.guidesOffset,d=g._guidesZoom,o=parseFloat((o/d).toFixed(l||0)),d=g.scrollPos-(a||0);if(c&&(g.displayElement.style.cssText+=\"display: none;\"),l=e.target,a=he,l.classList?l.classList.remove(a):(a=new RegExp(\"(\\\\\\\\s|^)\"+a+\"(\\\\\\\\s|$)\"),l.className=l.className.replace(a,\" \")),g.props.onDragEnd(T(T({},t),{dragElement:e.target})),g._endDragScroll(t),e.fromRuler)g._isFirstMove&&g.props.onClickRuler(T(T({},t),{pos:0})),d<=o&&s.indexOf(o)<0&&g.setState({guides:M(M([],s,!0),[o],!1)},function(){u({guides:g.state.guides,distX:r,distY:i,index:s.length,isAdd:!0,isRemove:!1,isChange:!1})});else{var h=parseFloat(e.target.getAttribute(\"data-index\")),p=!1,v=!1,c=(s=M([],s,!0)).indexOf(o);if(n||o<d||-1<c&&c!==h){if(f&&(!0===f||-1<f.indexOf(\"remove\")))return;s.splice(h,1),p=!0}else{if(-1<c)return;if(f&&(!0===f||-1<f.indexOf(\"change\")))return;s[h]=o,v=!0}g.setState({guides:s},function(){var t=g.state.guides;u({distX:r,distY:i,guides:t,isAdd:!1,index:h,isChange:v,isRemove:p})})}},g._onCheck=function(){g.resize()},g.state.guides=t.defaultGuides||[],g.scrollPos=t.defaultGuidesPos||0,g}ie(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r);n=i.prototype;return n.render=function(){var t=this.props,e=t.className,n=t.type,r=t.zoom,i=t.guidesZoom,o=t.style,s=t.rulerStyle,a=t.displayDragPos,u=t.cspNonce,c=t.dragGuideStyle,t=t.guidePosStyle,t=void 0===t?{}:t,l=this.props,f=this.getTranslateName(),d={};return Tt.forEach(function(t){\"style\"!==t&&\"warpSelf\"!==t&&\"useResizeObserver\"!==t&&(d[t]=l[t])}),this._zoom=r,this._guidesZoom=i||r,C(ge,{ref:this.managerRef,cspNonce:u,className:\"\".concat(R(\"manager\",n),\" \").concat(e),style:o},C(\"div\",{className:R(\"guide-origin\"),ref:h(this,\"originElement\")}),C(kt,T({ref:h(this,\"ruler\"),style:s},d)),C(\"div\",{className:fe,ref:h(this,\"guidesElement\"),style:{transform:\"\".concat(f,\"(\").concat(-this.scrollPos*this._guidesZoom,\"px)\")}},a&&C(\"div\",{className:pe,ref:h(this,\"displayElement\"),style:t||{}}),C(\"div\",{className:le,ref:h(this,\"adderElement\"),style:c}),this.renderGuides()))},n.drawRuler=function(t){this.ruler.draw(t)},n.renderGuides=function(){var s=this,t=this.props,a=t.type,e=t.showGuides,u=t.guideStyle,c=t.displayGuidePos,n=t.guidePosStyle,l=void 0===n?{}:n,f=t.guidesOffset,d=this._guidesZoom,h=this.getTranslateName(),n=this.state.guides,p=t.guidePosFormat||t.dragPosFormat||function(t){return t};if(this.guideElements=[],e)return n.map(function(t,e){var n,r,i,o=t+(f||0);return C(\"div\",{className:R(\"guide\",a),ref:(n=s,r=\"guideElements\",function(t){t&&(n[r][i]=t)}),key:i=e,\"data-index\":e,\"data-pos\":t,style:T(T({},u),{transform:\"\".concat(h,\"(\").concat(o*d,\"px) translateZ(0px)\")})},c&&C(\"div\",{className:R(\"guide-pos\"),style:l||{}},p(t)))})},n.componentDidMount=function(){var l=this;this.gesto=new Zt(this.managerRef.current,{container:document.body}).on(\"dragStart\",function(t){var e=l.props,n=e.type,e=e.lockGuides,r=l._guidesZoom;if(!0===e)t.stop();else{var i=t.inputEvent.target,o=t.datas,s=l.ruler.canvasElement,a=l.guidesElement,n=\"horizontal\"===n,u=l.originElement.getBoundingClientRect(),c=re(l.managerRef.current),u=ne(c,[t.clientX-u.left,t.clientY-u.top]),a=(u[0]-=a.offsetLeft,u[1]-=a.offsetTop,u[n?1:0]+=l.scrollPos*r,o.offsetPos=u,o.matrix=c,e&&-1<e.indexOf(\"add\")),n=e&&-1<e.indexOf(\"remove\"),r=e&&-1<e.indexOf(\"change\");if(i===s){if(a)return void t.stop();o.fromRuler=!0,o.target=l.adderElement}else{if(u=de,(c=i).classList?!c.classList.contains(u):!c.className.match(new RegExp(\"(\\\\\\\\s|^)\"+u+\"(\\\\\\\\s|$)\")))return t.stop(),!1;if(n&&r)return void t.stop();o.target=i}l.onDragStart(t)}}).on(\"drag\",this._onDrag).on(\"dragEnd\",this.onDragEnd),this.props.useResizeObserver?(this._observer=new ResizeObserver(this._onCheck),this._observer.observe(this.guidesElement,{box:\"border-box\"}),this._observer.observe(this.getRulerElement(),{box:\"border-box\"})):this._onCheck()},n.componentWillUnmount=function(){var t;this.gesto.unset(),null!=(t=this._observer)&&t.disconnect()},n.componentDidUpdate=function(t){var e=this.props.defaultGuides;t.defaultGuides!==e&&this.setState({guides:e||[]})},n.loadGuides=function(t){this.setState({guides:t})},n.getGuides=function(){return this.state.guides},n.scrollGuides=function(n,r){void 0===r&&(r=this._guidesZoom),this._setZoom({guidesZoom:r});var i=this.getTranslateName(),t=this.guidesElement,o=(this.scrollPos=n,t.style.transform=\"\".concat(i,\"(\").concat(-n*r,\"px)\"),this.state.guides),s=this.props.guidesOffset||0;this.guideElements.forEach(function(t,e){t&&(e=o[e]+(s||0),t.style.transform=\"\".concat(i,\"(\").concat(e*r,\"px) translateZ(0px)\"),t.style.display=-n+e<0?\"none\":\"block\")})},n.zoomTo=function(t,e){void 0===e&&(e=t),this.scroll(this.getRulerScrollPos(),t),this.scrollGuides(this.getGuideScrollPos(),e)},n.getElement=function(){return this.managerRef.current},n.getRulerElement=function(){return this.ruler.canvasElement},n.getGuideScrollPos=function(){return this.scrollPos},n.getRulerScrollPos=function(){return this.ruler.getScrollPos()},n.scroll=function(t,e){void 0===e&&(e=this._zoom),this._setZoom({zoom:e}),this.ruler.scroll(t,e)},n.resize=function(t){void 0===t&&(t=this._zoom),this._setZoom({zoom:t}),this.ruler.resize(t)},n.movePos=function(t){var e=t.datas,n=t.distX,t=t.distY,r=this.props,i=r.type,o=r.snaps,s=r.snapThreshold,a=r.displayDragPos,u=r.digit,c=r.guidesOffset||0,l=this._guidesZoom,r=r.dragPosFormat||function(t){return t},f=\"horizontal\"===i,n=ne(e.matrix,[n,t]),t=e.offsetPos,d=n[0]+t[0],n=n[1]+t[1],t=c*l,f=Math.round(f?n:d)-c,h=parseFloat((f/l).toFixed(u||0)),u=o.slice().sort(function(t,e){return Math.abs(h-t)-Math.abs(h-e)});return u.length&&Math.abs(u[0]*l-f)<s&&(f=(h=u[0])*l),e.fromRuler&&this._isFirstMove||(a&&(this.displayElement.style.cssText+=\"display: block;transform: translate(-50%, -50%) \"+\"translate(\".concat((\"horizontal\"===i?[d,f+t]:[f+t,n]).map(function(t){return\"\".concat(t,\"px\")}).join(\", \"),\")\"),this.displayElement.innerHTML=\"\".concat(r(h))),(o=e.target).setAttribute(\"data-pos\",h),o.style.transform=\"\".concat(this.getTranslateName(),\"(\").concat(f+c*l,\"px)\")),f},n.getTranslateName=function(){return\"horizontal\"===this.props.type?\"translateY\":\"translateX\"},n._startDragScroll=function(t){var e,n,i=this,r=this.props.scrollOptions;r&&(e=t.datas,n=new jt,(e.dragScroll=n).on(\"scroll\",function(t){var e,n,r=t.container,t=t.direction;null!=(n=(e=i.props).onRequestScroll)&&n.call(e,{container:r,direction:t})}).on(\"move\",function(t){var e=t.offsetX,n=t.offsetY,t=t.inputEvent;i.gesto.scrollBy(e,n,t.inputEvent,!0)}),n.dragStart(t,{container:r.container}))},n._dragScroll=function(t){var e=this.props.scrollOptions;e&&t.datas.dragScroll.drag(t,e)},n._endDragScroll=function(t){var e;null!=(e=t.datas.dragScroll)&&e.dragEnd(),t.datas.dragScroll=null},n._setZoom=function(t){var e=t.zoom,t=t.guidesZoom;this.props.zoom;!!this.props.guidesZoom?t&&(this._guidesZoom=t):(t&&(this._zoom=t,this._guidesZoom=t),e&&(this._guidesZoom=e)),e&&(this._zoom=e)},i.defaultProps={className:\"\",type:\"horizontal\",zoom:1,guidesZoom:0,style:{},snapThreshold:5,snaps:[],digit:0,onClickRuler:function(){},onChangeGuides:function(){},onRequestScroll:function(){},onDragStart:function(){},onDrag:function(){},onDragEnd:function(){},displayDragPos:!1,dragPosFormat:function(t){return t},defaultGuides:[],lockGuides:!1,showGuides:!0,guideStyle:{},dragGuideStyle:{},guidePosStyle:{},defaultGuidesPos:0},i}(gt),_e=ve,ye=[\"getGuides\",\"loadGuides\",\"scroll\",\"scrollGuides\",\"resize\",\"getElement\",\"getRulerElement\",\"forceUpdate\",\"getRulerScrollPos\",\"getGuideScrollPos\",\"zoomTo\",\"drawRuler\"],be=[\"changeGuides\",\"requestScroll\",\"dragStart\",\"drag\",\"dragEnd\",\"clickRuler\"],Ee=function(e){function t(t){t=e.call(this,t)||this;return t.state={},t.state=t.props,t}return n(t,e),t.prototype.render=function(){return C(me,s({ref:h(this,\"guides\")},this.state))},t}(vt),Se=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e}(function(o){function t(t,e){void 0===e&&(e={});var n,r=o.call(this)||this,i=(r.containerProvider=null,r.selfElement=null,r._warp=!1,{});return be.forEach(function(e){i[\"on \".concat(e).replace(/[\\\\s-_]+([^\\\\s-_])/g,function(t,e){return e.toUpperCase()})]=function(t){return r.trigger(e,t)}}),e.warpSelf?(delete e.warpSelf,r._warp=!0,n=t):(n=document.createElement(\"div\"),t.appendChild(n)),r.containerProvider=Ct(C(Ee,s({ref:h(r,\"innerGuides\")},i,e)),n),r}n(t,o);var e=t.prototype;return e.setState=function(t,e){this.innerGuides.setState(t,e)},e.forceUpdate=function(t){this.innerGuides.forceUpdate(t)},e.destroy=function(){var t,e=this.selfElement;Ct(null,e,this.containerProvider),this._warp||null!=(t=null==e?void 0:e.parentElement)&&t.removeChild(e),this.selfElement=null,this.innerGuides=null},e.getInnerGuides=function(){return this.innerGuides.guides},function(t,e,n,r){var i,o=arguments.length,s=o<3?e:null===r?r=Object.getOwnPropertyDescriptor(e,n):r;if(\"object\"==typeof Reflect&&\"function\"==typeof Reflect.decorate)s=Reflect.decorate(t,e,n,r);else for(var a=t.length-1;0<=a;a--)(i=t[a])&&(s=(o<3?i(s):3<o?i(e,n,s):i(e,n))||s);return 3<o&&s&&Object.defineProperty(e,n,s),s}([F(ye,function(t,r){t[r]||(t[r]=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];var n=this.getInnerGuides();if(n&&n[r])return n[r].apply(n,t)})}),F(_e,function(t,n){Object.defineProperty(t,n,{get:function(){return this.getInnerGuides().props[n]},set:function(t){var e;this.innerGuides.setState(((e={})[n]=t,e))},enumerable:!0,configurable:!0})})],t)}(t)),we={__proto__:null,default:Se,PROPERTIES:_e,METHODS:ye,EVENTS:be};for(ce in we)Se[ce]=we[ce];return Se});`;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/common.ts",
    "content": "interface ContextMenuMetadata {\n  id: string;\n  label: string;\n}\n\nexport const CONTEXT_MENUS: {[key: string]: ContextMenuMetadata} = {\n  INSPECT_ELEMENT: {id: 'INSPECT_ELEMENT', label: 'Inspect Element'},\n  OPEN_CONSOLE: {id: 'OPEN_CONSOLE', label: 'Open Console'},\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/index.tsx",
    "content": "import cx from 'classnames';\nimport {PREVIEW_LAYOUTS} from 'common/constants';\nimport {Device as IDevice} from 'common/deviceList';\nimport {\n  InspectElementArgs,\n  OpenDevtoolsArgs,\n  OpenDevtoolsResult,\n  ToggleInspectorArgs,\n  ToggleInspectorResult,\n} from 'main/devtools';\nimport {ReloadArgs} from 'main/menu';\nimport {\n  DisableDefaultWindowOpenHandlerArgs,\n  DisableDefaultWindowOpenHandlerResult,\n} from 'main/native-functions';\nimport {CONTEXT_MENUS} from 'main/webview-context-menu/common';\nimport {DeleteStorageArgs, DeleteStorageResult} from 'main/webview-storage-manager';\nimport {useCallback, useEffect, useRef, useState} from 'react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport Spinner from 'renderer/components/Spinner';\nimport {ADDRESS_BAR_EVENTS} from 'renderer/components/ToolBar/AddressBar';\nimport {webViewPubSub} from 'renderer/lib/pubsub';\nimport {\n  selectDevtoolsWebviewId,\n  selectDockPosition,\n  selectIsDevtoolsOpen,\n  setDevtoolsClose,\n  setDevtoolsOpen,\n} from 'renderer/store/features/devtools';\nimport {\n  selectAddress,\n  selectIsInspecting,\n  selectLayout,\n  selectRotate,\n  selectZoomFactor,\n  setAddress,\n  setIsInspecting,\n  setLayout,\n  setPageTitle,\n} from 'renderer/store/features/renderer';\nimport type {RootState} from '../../../store';\nimport {selectDesignOverlay, type ViewResolution} from '../../../store/features/design-overlay';\nimport {\n  Coordinates,\n  RulersState,\n  selectRuler,\n  selectRulerEnabled,\n  setRuler,\n} from '../../../store/features/ruler';\nimport {selectDarkMode} from '../../../store/features/ui';\nimport useKeyboardShortcut, {\n  SHORTCUT_CHANNEL,\n} from '../../KeyboardShortcutsManager/useKeyboardShortcut';\nimport {NAVIGATION_EVENTS} from '../../ToolBar/NavigationControls';\nimport GuideGrid, {DefaultGuide} from '../Guides';\nimport DesignOverlay from './DesignOverlay';\nimport Toolbar from './Toolbar';\nimport {appendHistory} from './utils';\n\ninterface Props {\n  device: IDevice;\n  isPrimary: boolean;\n  setIndividualDevice: (device: IDevice) => void;\n}\n\ninterface ErrorState {\n  code: number;\n  description: string;\n}\n\nconst Device = ({isPrimary, device, setIndividualDevice}: Props) => {\n  const [singleRotated, setSingleRotated] = useState<boolean>(false);\n  const [loading, setLoading] = useState<boolean>(false);\n  const [error, setError] = useState<ErrorState | null>(null);\n  const [screenshotInProgress, setScreenshotInProgress] = useState<boolean>(false);\n  const address = useSelector(selectAddress);\n  const zoomfactor = useSelector(selectZoomFactor);\n  const isInspecting = useSelector(selectIsInspecting);\n  const rotateDevices = useSelector(selectRotate);\n  const isDevtoolsOpen = useSelector(selectIsDevtoolsOpen);\n  const devtoolsOpenForWebviewId = useSelector(selectDevtoolsWebviewId);\n  const layout = useSelector(selectLayout);\n  const rulerEnabled = useSelector(selectRulerEnabled);\n  const getRuler = useSelector(selectRuler);\n  const dispatch = useDispatch();\n  const dockPosition = useSelector(selectDockPosition);\n  const darkMode = useSelector(selectDarkMode);\n  const ref = useRef<Electron.WebviewTag>(null);\n  const isNavigatingFromAddressBar = useRef<boolean>(false);\n  const [webviewReady, setWebviewReady] = useState<boolean>(false);\n\n  useEffect(() => {\n    if (ref.current && isPrimary) {\n      try {\n        const currentUrl = ref.current.getURL();\n        if (address !== currentUrl) {\n          isNavigatingFromAddressBar.current = true;\n          ref.current.loadURL(address);\n        }\n      } catch (err) {\n        // eslint-disable-next-line no-console\n        console.error('Error loading URL', err);\n      }\n    }\n  }, [address, isPrimary]);\n\n  useEffect(() => {\n    const webview = ref.current;\n    if (!webview) return undefined;\n    const onDomReady = () => setWebviewReady(true);\n    webview.addEventListener('dom-ready', onDomReady);\n    return () => {\n      webview.removeEventListener('dom-ready', onDomReady);\n      setWebviewReady(false);\n    };\n  }, [ref]);\n\n  const isIndividualLayout = layout === PREVIEW_LAYOUTS.INDIVIDUAL;\n\n  let {height, width} = device;\n\n  // Check if device rotation is enabled (only mobile-capable devices can be rotated)\n  const isDeviceRotationEnabled = device.isMobileCapable && (rotateDevices || singleRotated);\n\n  // Apply rotation: both global and individual rotation only affect mobile-capable devices\n  if (isDeviceRotationEnabled) {\n    const temp = width;\n    width = height;\n    height = temp;\n  }\n\n  const resolution: ViewResolution = `${width}x${height}`;\n  const designOverlay = useSelector((state: RootState) => selectDesignOverlay(state)(resolution));\n\n  const [coordinates, setCoordinates] = useState<Coordinates>({\n    deltaX: 0,\n    deltaY: 0,\n    scrollX: 0,\n    scrollY: 0,\n    innerWidth: width * 2,\n    innerHeight: height * 2,\n  });\n\n  const registerNavigationHandlers = useCallback(() => {\n    webViewPubSub.subscribe(NAVIGATION_EVENTS.RELOAD, () => {\n      if (ref.current) {\n        ref.current.reload();\n      }\n    });\n    if (isPrimary) {\n      webViewPubSub.subscribe(NAVIGATION_EVENTS.BACK, () => {\n        if (ref.current) {\n          ref.current.goBack();\n        }\n      });\n\n      webViewPubSub.subscribe(NAVIGATION_EVENTS.FORWARD, () => {\n        if (ref.current) {\n          ref.current.goForward();\n        }\n      });\n\n      webViewPubSub.subscribe(ADDRESS_BAR_EVENTS.DELETE_STORAGE, async () => {\n        if (!ref.current) {\n          return;\n        }\n        const webview = ref.current as Electron.WebviewTag;\n        await window.electron.ipcRenderer.invoke<DeleteStorageArgs, DeleteStorageResult>(\n          'delete-storage',\n          {webContentsId: webview.getWebContentsId()}\n        );\n      });\n\n      webViewPubSub.subscribe(ADDRESS_BAR_EVENTS.DELETE_COOKIES, async () => {\n        if (!ref.current) {\n          return;\n        }\n        const webview = ref.current as Electron.WebviewTag;\n        await window.electron.ipcRenderer.invoke<DeleteStorageArgs, DeleteStorageResult>(\n          'delete-storage',\n          {\n            webContentsId: webview.getWebContentsId(),\n            storages: ['cookies'],\n          }\n        );\n      });\n\n      webViewPubSub.subscribe(ADDRESS_BAR_EVENTS.DELETE_CACHE, async () => {\n        if (!ref.current) {\n          return;\n        }\n        const webview = ref.current as Electron.WebviewTag;\n        await window.electron.ipcRenderer.invoke<DeleteStorageArgs, DeleteStorageResult>(\n          'delete-storage',\n          {\n            webContentsId: webview.getWebContentsId(),\n            storages: ['network-cache'],\n          }\n        );\n      });\n    }\n  }, [isPrimary]);\n\n  const toggleRuler = useCallback(() => {\n    if (!ref.current) {\n      return;\n    }\n    const webview = ref.current as Electron.WebviewTag;\n    if (webview == null) {\n      return;\n    }\n    const ruler: RulersState | undefined = getRuler(resolution);\n    if (ruler) {\n      dispatch(\n        setRuler({\n          resolution,\n          rulerState: {\n            isRulerEnabled: !ruler.isRulerEnabled,\n            rulerCoordinates: ruler.rulerCoordinates,\n          },\n        })\n      );\n    } else {\n      dispatch(\n        setRuler({\n          resolution,\n          rulerState: {\n            isRulerEnabled: true,\n            rulerCoordinates: coordinates,\n          },\n        })\n      );\n    }\n  }, [dispatch, getRuler, coordinates, resolution]);\n\n  useKeyboardShortcut(SHORTCUT_CHANNEL.TOGGLE_RULERS, toggleRuler);\n\n  const openDevTools = useCallback(async () => {\n    if (!ref.current) {\n      return;\n    }\n    const webview = ref.current as Electron.WebviewTag;\n\n    if (webview == null) {\n      return;\n    }\n    await window.electron.ipcRenderer.invoke<OpenDevtoolsArgs, OpenDevtoolsResult>(\n      'open-devtools',\n      {\n        webviewId: webview.getWebContentsId(),\n        dockPosition,\n      }\n    );\n    dispatch(setDevtoolsOpen(webview.getWebContentsId()));\n  }, [dispatch, dockPosition]);\n\n  const inspectElement = useCallback(\n    async (deviceX: number, deviceY: number) => {\n      if (!ref.current) {\n        return;\n      }\n      const webview = ref.current as Electron.WebviewTag;\n      if (webview == null) {\n        return;\n      }\n\n      if (devtoolsOpenForWebviewId !== webview.getWebContentsId()) {\n        if (isDevtoolsOpen) {\n          dispatch(setDevtoolsClose());\n          await window.electron.ipcRenderer.invoke('close-devtools');\n        }\n        await openDevTools();\n      }\n      const {x: webViewX, y: webViewY} = webview.getBoundingClientRect();\n      webview.inspectElement(\n        Math.round(webViewX + deviceX * zoomfactor),\n        Math.round(webViewY + deviceY * zoomfactor)\n      );\n    },\n    [dispatch, devtoolsOpenForWebviewId, isDevtoolsOpen, openDevTools, zoomfactor]\n  );\n\n  const onRotateHandler = (state: boolean) => setSingleRotated(state);\n\n  const onIndividualLayoutHandler = (selectedDevice: IDevice) => {\n    if (!isIndividualLayout) {\n      dispatch(setLayout(PREVIEW_LAYOUTS.INDIVIDUAL));\n      setIndividualDevice(selectedDevice);\n    } else {\n      dispatch(setLayout(PREVIEW_LAYOUTS.COLUMN));\n    }\n  };\n\n  useEffect(() => {\n    if (!ref.current) {\n      return;\n    }\n    const webview = ref.current as Electron.WebviewTag;\n    const handlerRemovers: (() => void)[] = [];\n\n    const didNavigateHandler = (e: Electron.DidNavigateEvent | Electron.DidNavigateInPageEvent) => {\n      // Only DidNavigateInPageEvent has isMainFrame\n      if ('isMainFrame' in e && e.isMainFrame === false) return;\n      // Only update Redux on the primary device and only if this navigation wasn't initiated by AddressBar\n      if (isPrimary && !isNavigatingFromAddressBar.current) {\n        dispatch(setAddress(e.url));\n      } else if (isPrimary) {\n        isNavigatingFromAddressBar.current = false; // Reset the flag\n      }\n\n      if (isPrimary) {\n        appendHistory(webview.getURL(), webview.getTitle());\n      }\n    };\n    webview.addEventListener('did-navigate', didNavigateHandler);\n    webview.addEventListener('did-navigate-in-page', didNavigateHandler);\n    handlerRemovers.push(() => {\n      webview.removeEventListener('did-navigate', didNavigateHandler);\n      webview.removeEventListener('did-navigate-in-page', didNavigateHandler);\n    });\n\n    const ipcMessageHandler = (e: Electron.IpcMessageEvent) => {\n      if (e.channel === 'pass-scroll-data') {\n        setCoordinates({\n          deltaX: e.args[0].coordinates.x,\n          deltaY: e.args[0].coordinates.y,\n          scrollX: e.args[0].coordinates.scrollX,\n          scrollY: e.args[0].coordinates.scrollY,\n          innerHeight: e.args[0].innerHeight,\n          innerWidth: e.args[0].innerWidth,\n        });\n      }\n      if (e.channel === 'context-menu-command') {\n        const {command, arg} = e.args[0];\n        switch (command) {\n          case CONTEXT_MENUS.OPEN_CONSOLE.id:\n            openDevTools();\n            break;\n          case CONTEXT_MENUS.INSPECT_ELEMENT.id: {\n            const {\n              contextMenuMeta: {x, y},\n            } = arg;\n            inspectElement(x, y);\n            break;\n          }\n          default:\n            // eslint-disable-next-line no-console\n            console.log('Unhandled context menu command', command);\n        }\n      }\n    };\n    webview.addEventListener('ipc-message', ipcMessageHandler);\n    handlerRemovers.push(() => {\n      webview.removeEventListener('ipc-message', ipcMessageHandler);\n    });\n\n    const didStartLoadingHandler = () => {\n      setLoading(true);\n      setError(null);\n    };\n    webview.addEventListener('did-start-loading', didStartLoadingHandler);\n    handlerRemovers.push(() => {\n      webview.removeEventListener('did-start-loading', didStartLoadingHandler);\n    });\n\n    const didStopLoadingHandler = () => {\n      setLoading(false);\n    };\n\n    webview.addEventListener('did-stop-loading', didStopLoadingHandler);\n    handlerRemovers.push(() => {\n      webview.removeEventListener('did-stop-loading', didStopLoadingHandler);\n    });\n\n    const didFailLoadHandler = ({\n      errorCode,\n      errorDescription,\n      isMainFrame,\n    }: Electron.DidFailLoadEvent) => {\n      if (errorCode === -3) {\n        // Aborted error, can be ignored\n        return;\n      }\n\n      // Only show error overlay for main frame errors\n      // Iframe errors (like CSP violations) should only go to console\n      if (!isMainFrame) {\n        // eslint-disable-next-line no-console\n        console.warn('iframe error:', errorCode, errorDescription);\n        return;\n      }\n\n      setError({\n        code: errorCode,\n        description: errorDescription,\n      });\n    };\n    webview.addEventListener('did-fail-load', didFailLoadHandler);\n    handlerRemovers.push(() => {\n      webview.removeEventListener('did-fail-load', didFailLoadHandler);\n    });\n\n    if (!isPrimary) {\n      setTimeout(() => {\n        webview.addEventListener('dom-ready', () => {\n          window.electron.ipcRenderer.invoke<\n            DisableDefaultWindowOpenHandlerArgs,\n            DisableDefaultWindowOpenHandlerResult\n          >('disable-default-window-open-handler', {\n            webContentsId: webview.getWebContentsId(),\n          });\n        });\n      }, 2000);\n    }\n\n    registerNavigationHandlers();\n\n    // eslint-disable-next-line consistent-return\n    return () => {\n      handlerRemovers.forEach((handlerRemover) => {\n        handlerRemover();\n      });\n    };\n  }, [ref, dispatch, registerNavigationHandlers, isPrimary, inspectElement, openDevTools, address]);\n\n  useEffect(() => {\n    // Reload keyboard shortcuts effect\n    if (!ref.current) {\n      return undefined;\n    }\n    const webview = ref.current as Electron.WebviewTag;\n\n    const reloadHandler = (args: ReloadArgs) => {\n      const {ignoreCache} = args;\n      if (ignoreCache === true) {\n        webview.reloadIgnoringCache();\n      } else {\n        webview.reload();\n      }\n    };\n\n    window.electron.ipcRenderer.on<ReloadArgs>('reload', reloadHandler);\n\n    return () => {\n      window.electron.ipcRenderer.removeListener('reload', reloadHandler);\n    };\n  }, [ref]);\n\n  useEffect(() => {\n    if (!ref.current || !webviewReady) {\n      return undefined;\n    }\n    const webview = ref.current as Electron.WebviewTag;\n    const inspectElementHandler = async (_args: unknown) => {\n      const args: InspectElementArgs = _args as InspectElementArgs;\n      if (webview.getWebContentsId() !== args.webviewId) {\n        return;\n      }\n      dispatch(setIsInspecting(false));\n      const {\n        coords: {x: deviceX, y: deviceY},\n      } = args;\n      inspectElement(deviceX, deviceY);\n    };\n\n    window.electron.ipcRenderer.on('inspect-element', inspectElementHandler);\n\n    return () => {\n      try {\n        window.electron.ipcRenderer.removeAllListeners('inspect-element');\n      } catch (e) {\n        // eslint-disable-next-line no-console\n        console.error('Error while removing ipc listener', e);\n      }\n    };\n  }, [\n    ref,\n    dispatch,\n    isDevtoolsOpen,\n    devtoolsOpenForWebviewId,\n    openDevTools,\n    zoomfactor,\n    inspectElement,\n    webviewReady,\n  ]);\n\n  useEffect(() => {\n    if (!ref.current || !webviewReady || isInspecting === undefined) {\n      return;\n    }\n    const webview = ref.current as Electron.WebviewTag;\n    (async () => {\n      await window.electron.ipcRenderer.invoke<ToggleInspectorArgs, ToggleInspectorResult>(\n        isInspecting ? 'enable-inspector-overlay' : 'disable-inspector-overlay',\n        {\n          webviewId: webview.getWebContentsId(),\n        }\n      );\n    })();\n  }, [isInspecting, webviewReady]);\n\n  useEffect(() => {\n    if (!ref.current || !device.isMobileCapable) {\n      return;\n    }\n\n    const webview = ref.current;\n    webview.addEventListener('dom-ready', () => {\n      webview.insertCSS(`\n               ::-webkit-scrollbar {\n              display: none;\n              } `);\n    });\n\n    // eslint-disable-next-line consistent-return\n    return () => {\n      webview.removeEventListener('dom-ready', () => {});\n    };\n  }, [device.isMobileCapable]);\n\n  useEffect(() => {\n    const webview = ref.current;\n\n    if (isPrimary && webview) {\n      webview.addEventListener('dom-ready', () => {\n        const pageTitle = webview.getTitle();\n        dispatch(setPageTitle(pageTitle));\n      });\n    }\n\n    // eslint-disable-next-line consistent-return\n    return () => {\n      webview?.removeEventListener('dom-ready', () => {});\n    };\n  }, [dispatch, isPrimary]);\n\n  const scaledHeight = height * zoomfactor;\n  const scaledWidth = width * zoomfactor;\n\n  const isRestrictedMinimumDeviceSize =\n    device.width < 400 && zoomfactor < 0.6 && !isDeviceRotationEnabled;\n\n  return (\n    <div\n      className={cx('h-fit', {\n        'w-52': isRestrictedMinimumDeviceSize,\n      })}\n    >\n      <div className=\"flex justify-between\">\n        <span>\n          {device.name}\n          <span className=\"ml-[2px] text-xs opacity-60\">\n            {width}x{height}\n          </span>\n        </span>\n        {loading ? <Spinner spinnerHeight={24} /> : null}\n      </div>\n      <Toolbar\n        webview={ref.current}\n        device={device}\n        setScreenshotInProgress={setScreenshotInProgress}\n        openDevTools={openDevTools}\n        toggleRuler={toggleRuler}\n        onRotate={onRotateHandler}\n        onIndividualLayoutHandler={onIndividualLayoutHandler}\n        isIndividualLayout={isIndividualLayout}\n        isDeviceRotationEnabled={isDeviceRotationEnabled}\n      />\n      <div className=\"flex gap-4\">\n        <div\n          style={{\n            height: rulerEnabled(`${width}x${height}`) ? scaledHeight + 30 : scaledHeight,\n            width: rulerEnabled(`${width}x${height}`) ? scaledWidth + 30 : scaledWidth,\n          }}\n          className=\"relative origin-top-left overflow-hidden bg-white\"\n        >\n          <GuideGrid\n            scaledHeight={scaledHeight}\n            scaledWidth={scaledWidth}\n            height={height}\n            width={width}\n            coordinates={coordinates}\n            zoomFactor={zoomfactor}\n            night={darkMode}\n            enabled={rulerEnabled(`${width}x${height}`)}\n            defaultGuides={window.electron.store\n              .get('userPreferences.guides')\n              .flatMap((x: unknown) => x as DefaultGuide[])\n              .filter((x: DefaultGuide) => {\n                return x.resolution === `${width}x${height}`;\n              })}\n          />\n          <div className=\"bg-white\">\n            <webview\n              id={device.name}\n              src={address}\n              style={{\n                height,\n                width,\n                display: 'inline-flex',\n                transform: `scale(${zoomfactor})`,\n                marginLeft: rulerEnabled(`${width}x${height}`) ? '30px' : 0,\n                marginTop: rulerEnabled(`${width}x${height}`) ? '30px' : 0,\n              }}\n              ref={ref}\n              className=\"origin-top-left\"\n              /* eslint-disable-next-line react/no-unknown-property */\n              preload={`file://${window.responsively.webviewPreloadPath}`}\n              data-scale-factor={zoomfactor}\n              /* eslint-disable-next-line react/no-unknown-property */\n              allowpopups={isPrimary ? true : undefined}\n              /* eslint-disable-next-line react/no-unknown-property */\n              useragent={device.userAgent}\n            />\n          </div>\n\n          {designOverlay?.enabled &&\n            designOverlay.image &&\n            designOverlay.position === 'overlay' && (\n              <DesignOverlay\n                resolution={resolution}\n                scaledWidth={scaledWidth}\n                scaledHeight={scaledHeight}\n                zoomFactor={zoomfactor}\n                coordinates={coordinates}\n                position={designOverlay.position}\n                rulerMargin={rulerEnabled(`${width}x${height}`) ? 30 : 0}\n                width={width}\n                height={height}\n              />\n            )}\n\n          {screenshotInProgress ? (\n            <div\n              className=\"absolute left-0 top-0 flex h-full w-full items-center justify-center bg-slate-600 bg-opacity-95\"\n              style={{height: scaledHeight, width: scaledWidth}}\n            >\n              <Spinner spinnerHeight={30} />\n            </div>\n          ) : null}\n          {error != null ? (\n            <div\n              className=\"absolute left-0 top-0 flex h-full w-full items-center justify-center bg-slate-600 bg-opacity-95\"\n              style={{height: scaledHeight, width: scaledWidth}}\n            >\n              <div className=\"text-center text-sm text-white\">\n                <div className=\"text-base font-bold\">ERROR: {error.code}</div>\n                <div className=\"text-sm\">{error.description}</div>\n              </div>\n            </div>\n          ) : null}\n        </div>\n\n        {designOverlay?.enabled && designOverlay.image && designOverlay.position === 'side' && (\n          <DesignOverlay\n            resolution={resolution}\n            scaledWidth={scaledWidth}\n            scaledHeight={scaledHeight}\n            zoomFactor={zoomfactor}\n            coordinates={coordinates}\n            position={designOverlay.position}\n            rulerMargin={rulerEnabled(`${width}x${height}`) ? 30 : 0}\n            width={width}\n            height={height}\n          />\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default Device;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Device/utils.ts",
    "content": "import {HistoryItem} from 'renderer/components/ToolBar/AddressBar/SuggestionList';\n\n// eslint-disable-next-line import/prefer-default-export\nexport const appendHistory = (url: string, title: string) => {\n  if (url === `${title}/`) {\n    return;\n  }\n  const history: HistoryItem[] = window.electron.store.get('history');\n  window.electron.store.set(\n    'history',\n    [\n      {url, title, lastVisited: new Date().getTime()},\n      ...history.filter(({url: _url}) => url !== _url),\n    ].slice(0, 100)\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/DevtoolsResizer/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {DOCK_POSITION} from 'common/constants';\nimport {OpenDevtoolsArgs, OpenDevtoolsResult} from 'main/devtools';\nimport {Resizable, Size} from 're-resizable';\nimport {useCallback, useEffect, useRef, useState} from 'react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport {\n  DockPosition,\n  selectDevtoolsWebviewId,\n  selectDockPosition,\n  setDockPosition,\n  setDevtoolsClose,\n} from 'renderer/store/features/devtools';\nimport {selectIsInspecting, setIsInspecting} from 'renderer/store/features/renderer';\n\ntype SizeValue = number | string;\n\ninterface DockConfig {\n  resizeDirections: {[key: string]: boolean};\n  defaultSize: {\n    width: SizeValue;\n    height: SizeValue;\n  };\n  toggle: {\n    icon: string;\n    position: DockPosition;\n  };\n}\n\nconst BottomDockConfig: DockConfig = {\n  resizeDirections: {top: true},\n  defaultSize: {width: '100vw', height: 500},\n  toggle: {\n    icon: 'mdi:dock-right',\n    position: DOCK_POSITION.RIGHT,\n  },\n};\n\nconst RightDockConfig: DockConfig = {\n  resizeDirections: {left: true},\n  defaultSize: {width: 500, height: '100vh'},\n  toggle: {\n    icon: 'mdi:dock-bottom',\n    position: DOCK_POSITION.BOTTOM,\n  },\n};\n\nconst DevtoolsResizer = () => {\n  const dispatch = useDispatch();\n  const dockPosition = useSelector(selectDockPosition);\n  const webviewId = useSelector(selectDevtoolsWebviewId);\n  const isInspecting = useSelector(selectIsInspecting);\n\n  let config = BottomDockConfig;\n  if (dockPosition === DOCK_POSITION.RIGHT) {\n    config = RightDockConfig;\n  }\n  const [width, setWidth] = useState<SizeValue>(config.defaultSize.width);\n  const [height, setHeight] = useState<SizeValue>(config.defaultSize.height);\n  const [sizeBeforeResize, setSizeBeforeResize] = useState<Size>({\n    width: 0,\n    height: 0,\n  });\n  const contentRef = useRef<HTMLDivElement>(null);\n\n  const sendBounds = useCallback(() => {\n    if (!contentRef.current) return;\n    const rect = contentRef.current.getBoundingClientRect();\n    window.electron.ipcRenderer.invoke('resize-devtools', {\n      bounds: {\n        x: Math.round(rect.x),\n        y: Math.round(rect.y),\n        width: Math.round(rect.width),\n        height: Math.round(rect.height),\n      },\n    });\n  }, []);\n\n  useEffect(() => {\n    setHeight(config.defaultSize.height);\n    setWidth(config.defaultSize.width);\n  }, [config]);\n\n  useEffect(() => {\n    sendBounds();\n  }, [width, height, dockPosition, sendBounds]);\n\n  useEffect(() => {\n    const timer = setTimeout(sendBounds, 50);\n    window.addEventListener('resize', sendBounds);\n    return () => {\n      clearTimeout(timer);\n      window.removeEventListener('resize', sendBounds);\n    };\n  }, [sendBounds]);\n\n  return (\n    <div className=\"border-[#d0d0d0] bg-[#f3f3f3] text-[#555]\">\n      <Resizable\n        className=\"border\"\n        key={dockPosition}\n        size={{width, height}}\n        onResizeStart={() => setSizeBeforeResize({width, height})}\n        onResizeStop={() => setSizeBeforeResize({width: 0, height: 0})}\n        onResize={(_, __, ___, d) => {\n          setWidth((sizeBeforeResize.width as number) + d.width);\n          setHeight((sizeBeforeResize.height as number) + d.height);\n        }}\n        enable={config.resizeDirections}\n      >\n        <div className=\"flex h-full w-full flex-col\">\n          <div className=\"flex justify-between border-b-[1px]\">\n            <div>\n              <Button\n                onClick={() => dispatch(setIsInspecting(!isInspecting))}\n                isActive={isInspecting}\n                disableHoverEffects\n              >\n                <Icon icon=\"lucide:inspect\" />\n              </Button>\n            </div>\n            <div className=\"flex\">\n              <Button\n                onClick={() => {\n                  dispatch(setDockPosition(config.toggle.position));\n                }}\n                disableHoverEffects\n              >\n                <Icon icon={config.toggle.icon} />\n              </Button>\n              <Button\n                onClick={() => {\n                  window.electron.ipcRenderer.invoke('close-devtools');\n                  dispatch(setDockPosition(DOCK_POSITION.UNDOCKED));\n                  dispatch(setDevtoolsClose());\n                  setTimeout(() => {\n                    window.electron.ipcRenderer.invoke<OpenDevtoolsArgs, OpenDevtoolsResult>(\n                      'open-devtools',\n                      {\n                        webviewId,\n                        dockPosition: DOCK_POSITION.UNDOCKED,\n                      }\n                    );\n                  });\n                }}\n                disableHoverEffects\n              >\n                <Icon icon=\"mdi:dock-window\" />\n              </Button>\n              <Button\n                onClick={() => {\n                  window.electron.ipcRenderer.invoke('close-devtools');\n                  dispatch(setDevtoolsClose());\n                }}\n                disableHoverEffects\n              >\n                <Icon icon=\"ic:round-close\" />\n              </Button>\n            </div>\n          </div>\n          <div ref={contentRef} className=\"flex-grow\" />\n        </div>\n      </Resizable>\n    </div>\n  );\n};\n\nexport default DevtoolsResizer;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Guides/guide.css",
    "content": ".box {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 30px;\n  height: 30px;\n  box-sizing: border-box;\n  background: transparent;\n  z-index: 21;\n}\n\n.box:before,\n.box:after {\n  position: absolute;\n  content: '';\n  /*background: rgb(55, 65, 81);*/\n}\n\n.box:before {\n  width: 1px;\n  height: 100%;\n  left: 100%;\n}\n\n.box:after {\n  height: 1px;\n  width: 100%;\n  top: 100%;\n}\n\n.scena-guides-horizontal .scena-guides-guide-pos {\n  left: 48% !important;\n}\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/Guides/index.tsx",
    "content": "import * as React from 'react';\nimport Guides from '@scena/react-guides';\nimport {LegacyRef, useEffect, useRef, useMemo} from 'react';\nimport {Coordinates} from '../../../store/features/ruler';\nimport './guide.css';\n\nexport type DefaultGuide = {\n  resolution: string;\n  positions: number[];\n  is_vertical: boolean;\n};\n\ninterface Props {\n  scaledHeight: number;\n  height: number;\n  width: number;\n  scaledWidth: number;\n  coordinates: Coordinates;\n  zoomFactor: number;\n  night: boolean;\n  enabled: boolean;\n  defaultGuides: DefaultGuide[];\n}\n\nconst GuideGrid = ({\n  scaledHeight,\n  scaledWidth,\n  height,\n  width,\n  coordinates,\n  zoomFactor,\n  night,\n  enabled,\n  defaultGuides,\n}: Props) => {\n  const horizontalGuidesRef = useRef<Guides>();\n  const verticalGuidesRef = useRef<Guides>();\n  const defaultsHor = useMemo(() => {\n    return defaultGuides\n      .filter((x: DefaultGuide) => !x.is_vertical)\n      .flatMap((x: DefaultGuide) => x.positions);\n  }, [defaultGuides]);\n  const defaultsVer = useMemo(\n    () => defaultGuides.filter((x: DefaultGuide) => x.is_vertical).flatMap((x) => x.positions),\n    [defaultGuides]\n  );\n\n  useEffect(() => {\n    const addjustedInnerHeight = coordinates.innerHeight;\n    const scrollX =\n      horizontalGuidesRef &&\n      horizontalGuidesRef?.current &&\n      horizontalGuidesRef?.current?.getRulerScrollPos() + coordinates.deltaX;\n\n    const scrollY =\n      verticalGuidesRef &&\n      verticalGuidesRef?.current &&\n      verticalGuidesRef?.current?.getRulerScrollPos() + coordinates.deltaY;\n\n    let scrollPosY: number;\n    if (Number(scrollY) > 0) {\n      scrollPosY =\n        addjustedInnerHeight - Number(scrollY) <= 0\n          ? addjustedInnerHeight\n          : scrollY || addjustedInnerHeight;\n    } else {\n      scrollPosY = 0;\n    }\n\n    let scrollPosX: number;\n    if (Number(scrollX) > 0) {\n      scrollPosX =\n        Number(scrollX) > coordinates.innerWidth - Number(scrollX)\n          ? coordinates.innerWidth\n          : scrollX || coordinates.innerWidth;\n    } else {\n      scrollPosX = 0;\n    }\n    if (\n      coordinates.innerHeight >= scaledHeight * zoomFactor &&\n      coordinates.innerHeight - height > scrollPosY\n    ) {\n      verticalGuidesRef.current?.scroll(scrollPosY);\n      horizontalGuidesRef.current?.scroll(scrollPosX);\n      verticalGuidesRef.current?.scrollGuides(scrollPosX);\n      horizontalGuidesRef.current?.scrollGuides(scrollPosY);\n    }\n  });\n\n  return (\n    <>\n      {enabled ? (\n        <>\n          <div\n            style={{\n              position: 'absolute',\n              top: 0,\n              left: 0,\n              pointerEvents: 'none',\n              width: `${scaledWidth + 30}px`,\n              height: `${scaledHeight + 30}px`,\n              zIndex: 1,\n              overflow: 'hidden',\n            }}\n          >\n            <div className=\"box bg-slate-200 dark:bg-slate-800\" />\n            <Guides\n              ref={horizontalGuidesRef as LegacyRef<Guides>}\n              type=\"horizontal\"\n              backgroundColor=\"transparent\"\n              className=\"bg-slate-200 dark:bg-slate-800\"\n              textColor={night ? 'rgb(209, 213, 219)' : 'rgb(55, 65, 81)'}\n              mainLineSize=\"40%\"\n              longLineSize=\"5\"\n              shortLineSize=\"1\"\n              lineColor={night ? '#fefefe' : '#777777'}\n              zoom={zoomFactor}\n              style={{\n                height: '30px',\n                left: '30px',\n                width: scaledWidth * 2,\n                pointerEvents: 'auto',\n              }}\n              displayDragPos\n              displayGuidePos\n              useResizeObserver\n              defaultGuides={defaultsHor.length > 0 ? defaultsHor : undefined}\n              onChangeGuides={({guides}) => {\n                window.electron.store.set('userPreferences.guides', [\n                  ...window.electron.store\n                    .get('userPreferences.guides')\n                    .filter((x: DefaultGuide) => {\n                      if (x.resolution !== `${width}x${height}`) {\n                        return true;\n                      }\n                      return x.is_vertical;\n                    }),\n                  {\n                    resolution: `${width}x${height}`,\n                    is_vertical: false,\n                    positions: guides.filter((x) => x > 0),\n                  },\n                ]);\n              }}\n            />\n            <Guides\n              ref={verticalGuidesRef as LegacyRef<Guides>}\n              type=\"vertical\"\n              backgroundColor=\"transparent\"\n              className=\"bg-slate-200 dark:bg-slate-800\"\n              textColor={night ? 'rgb(209, 213, 219)' : 'rgb(55, 65, 81)'}\n              mainLineSize=\"40%\"\n              longLineSize=\"5\"\n              shortLineSize=\"1\"\n              lineColor={night ? '#fefefe' : '#777777'}\n              zoom={zoomFactor}\n              style={{\n                width: '30px',\n                top: '0px',\n                pointerEvents: 'auto',\n              }}\n              displayDragPos\n              displayGuidePos\n              useResizeObserver\n              defaultGuides={defaultsVer.length > 0 ? defaultsVer : undefined}\n              onChangeGuides={({guides}) => {\n                window.electron.store.set('userPreferences.guides', [\n                  ...window.electron.store\n                    .get('userPreferences.guides')\n                    .filter((x: DefaultGuide) => {\n                      if (x.resolution !== `${width}x${height}`) {\n                        return true;\n                      }\n                      return !x.is_vertical;\n                    }),\n                  {\n                    resolution: `${width}x${height}`,\n                    is_vertical: true,\n                    positions: guides.filter((x) => x > 0),\n                  },\n                ]);\n              }}\n            />\n          </div>\n        </>\n      ) : (\n        <></>\n      )}\n    </>\n  );\n};\n\nexport default GuideGrid;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/IndividualLayoutToolBar/index.tsx",
    "content": "import {useEffect, useState} from 'react';\nimport {useDispatch} from 'react-redux';\nimport {Tab, Tabs, TabList} from 'react-tabs';\nimport {Icon} from '@iconify/react';\nimport cx from 'classnames';\nimport {setLayout} from 'renderer/store/features/renderer';\nimport {PREVIEW_LAYOUTS} from 'common/constants';\nimport {Device as IDevice} from 'common/deviceList';\nimport './styles.css';\n\ninterface Props {\n  individualDevice: IDevice;\n  setIndividualDevice: (device: IDevice) => void;\n  devices: IDevice[];\n}\n\nconst IndividualLayoutToolbar = ({individualDevice, setIndividualDevice, devices}: Props) => {\n  const dispatch = useDispatch();\n  const [activeTab, setActiveTab] = useState(0);\n\n  const onTabClick = (newTabIndex: number) => {\n    setActiveTab(newTabIndex);\n    setIndividualDevice(devices[newTabIndex]);\n  };\n\n  const isActive = (idx: number) => activeTab === idx;\n  const handleCloseBtn = () => dispatch(setLayout(PREVIEW_LAYOUTS.COLUMN));\n\n  useEffect(() => {\n    const activeTabIndex = devices.findIndex((device) => device.id === individualDevice.id);\n    setActiveTab(activeTabIndex);\n  }, [individualDevice, devices]);\n\n  return (\n    <div className=\"my-4 ml-12 mr-4 flex justify-between\">\n      <Tabs\n        onSelect={onTabClick}\n        selectedIndex={activeTab}\n        className={cx('react-tabs flex flex-1')}\n      >\n        <TabList\n          className={cx(\n            'custom-scrollbar flex flex-1  justify-center gap-1 overflow-x-auto border-b border-slate-400/60 dark:border-white'\n          )}\n        >\n          {devices.map((device, idx) => (\n            <Tab\n              className={cx(\n                'border-1 bottom-auto flex flex-shrink-0 cursor-pointer items-center rounded-t-md border-gray-300 px-4',\n                {\n                  'bg-slate-400/60': isActive(idx),\n                  'dark:bg-slate-100/90': isActive(idx),\n                  'text-light-normal': isActive(idx),\n                }\n              )}\n              key={device.id}\n            >\n              {device.name}\n            </Tab>\n          ))}\n        </TabList>\n      </Tabs>\n      <div className=\"relative top-1 ml-6\">\n        <Icon\n          icon=\"carbon:close-outline\"\n          className=\"cursor-pointer\"\n          height={20}\n          onClick={handleCloseBtn}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default IndividualLayoutToolbar;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/IndividualLayoutToolBar/styles.css",
    "content": ".custom-scrollbar::-webkit-scrollbar,\n.custom-scrollbar::-webkit-scrollbar:hover,\n.dark .custom-scrollbar::-webkit-scrollbar,\n.dark .custom-scrollbar::-webkit-scrollbar:hover {\n  width: 0.25rem;\n  height: 0.25rem;\n  border-radius: 6px;\n}\n\n.custom-scrollbar::-webkit-scrollbar {\n  background-color: rgba(255, 255, 255, 0.9);\n}\n\n.custom-scrollbar::-webkit-scrollbar-thumb,\n.custom-scrollbar::-webkit-scrollbar-thumb:hover {\n  background-color: rgba(148, 163, 184, 0.7);\n}\n\n.dark .custom-scrollbar::-webkit-scrollbar {\n  background-color: rgba(148, 163, 184, 0.6);\n}\n\n.dark .custom-scrollbar::-webkit-scrollbar-thumb,\n.dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {\n  background-color: rgba(241, 245, 249, 0.8);\n}\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Previewer/index.tsx",
    "content": "import {useSelector} from 'react-redux';\nimport cx from 'classnames';\nimport {selectActiveSuite} from 'renderer/store/features/device-manager';\nimport {DOCK_POSITION, PREVIEW_LAYOUTS} from 'common/constants';\nimport {selectDockPosition, selectIsDevtoolsOpen} from 'renderer/store/features/devtools';\nimport {getDevicesMap, Device as IDevice} from 'common/deviceList';\nimport {useState} from 'react';\nimport {selectLayout} from 'renderer/store/features/renderer';\nimport Masonry from 'react-masonry-component';\nimport Device from './Device';\nimport DevtoolsResizer from './DevtoolsResizer';\nimport IndividualLayoutToolbar from './IndividualLayoutToolBar';\n\ninterface MasonryProps {\n  options?: {\n    transitionDuration: number;\n    itemSelector?: string;\n    gutter?: number;\n    fitWidth?: boolean;\n    horizontalOrder?: boolean;\n  };\n  className?: string;\n  elementType?: string;\n  children: React.ReactNode;\n}\n\nconst TypedMasonry: React.FC<MasonryProps> = Masonry as any;\n\nconst Previewer = () => {\n  const activeSuite = useSelector(selectActiveSuite);\n  const devices = activeSuite.devices.map((id) => getDevicesMap()[id]);\n  const dockPosition = useSelector(selectDockPosition);\n  const isDevtoolsOpen = useSelector(selectIsDevtoolsOpen);\n  const layout = useSelector(selectLayout);\n  const [individualDevice, setIndividualDevice] = useState<IDevice>(devices[0]);\n  const isIndividualLayout = layout === PREVIEW_LAYOUTS.INDIVIDUAL;\n  const isMasonryLayout = layout === PREVIEW_LAYOUTS.MASONRY; // New state for Masonry layout\n\n  const masonryOptions = {\n    columnWidth: 275,\n    gutter: 0,\n    fitWidth: true,\n    transitionDuration: 0,\n  };\n\n  return (\n    <div className=\"h-full\">\n      {isIndividualLayout && (\n        <IndividualLayoutToolbar\n          individualDevice={individualDevice}\n          setIndividualDevice={setIndividualDevice}\n          devices={devices}\n        />\n      )}\n      <div\n        className={cx('flex h-full', {\n          'flex-col': dockPosition === DOCK_POSITION.BOTTOM,\n          'flex-row': dockPosition === DOCK_POSITION.RIGHT,\n          'justify-between': !isIndividualLayout,\n          'justify-center': isIndividualLayout,\n        })}\n      >\n        <div className=\"flex flex-grow overflow-hidden\">\n          <div className=\"w-full flex-grow overflow-y-auto\" style={{height: '100%'}}>\n            {isMasonryLayout ? (\n              <TypedMasonry options={masonryOptions} className=\"w-full gap-4 p-2\">\n                {devices.map((device) => (\n                  <div key={device.id} className=\"device-item p-4\">\n                    <Device\n                      device={device}\n                      isPrimary={device.id === devices[0].id}\n                      setIndividualDevice={setIndividualDevice}\n                    />\n                  </div>\n                ))}\n              </TypedMasonry>\n            ) : (\n              <div\n                className={cx('flex h-full gap-4 overflow-auto p-4', {\n                  'flex-wrap': layout === PREVIEW_LAYOUTS.FLEX,\n                  'justify-center': isIndividualLayout,\n                })}\n              >\n                {isIndividualLayout ? (\n                  <Device\n                    key={individualDevice.id}\n                    device={individualDevice}\n                    isPrimary\n                    setIndividualDevice={setIndividualDevice}\n                  />\n                ) : (\n                  devices.map((device, idx) => (\n                    <Device\n                      key={device.id}\n                      device={device}\n                      isPrimary={idx === 0}\n                      setIndividualDevice={setIndividualDevice}\n                    />\n                  ))\n                )}\n              </div>\n            )}\n          </div>\n        </div>\n        {isDevtoolsOpen && dockPosition !== DOCK_POSITION.UNDOCKED ? <DevtoolsResizer /> : null}\n      </div>\n    </div>\n  );\n};\n\nexport default Previewer;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ReleaseNotes/index.tsx",
    "content": "import {IPC_MAIN_CHANNELS} from 'common/constants';\nimport cx from 'classnames';\nimport {AppMetaResponse} from 'main/app-meta';\nimport {useCallback, useEffect, useState} from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport Button from '../Button';\nimport Modal from '../Modal';\n\nexport const isReleaseNotesUnseen = async () => {\n  const appMeta = await window.electron.ipcRenderer.invoke<object, AppMetaResponse>(\n    IPC_MAIN_CHANNELS.APP_META,\n    {}\n  );\n  const seenVersions = window.electron.store.get('seenReleaseNotes');\n  if (seenVersions && seenVersions.includes(appMeta.appVersion)) {\n    return false;\n  }\n  if (!seenVersions || seenVersions.length === 0) {\n    // First time user, so we don't show release notes\n    window.electron.store.set('seenReleaseNotes', [appMeta.appVersion]);\n    return false;\n  }\n  return true;\n};\n\nexport const ReleaseNotes = () => {\n  const [version, setVersion] = useState<string>('');\n  const [content, setContent] = useState<string | undefined>();\n  const [isOpen, setIsOpen] = useState<boolean>(false);\n\n  useEffect(() => {\n    (async () => {\n      if (!isReleaseNotesUnseen()) {\n        return;\n      }\n      await new Promise((resolve) => setTimeout(resolve, 1000));\n      const appMeta = await window.electron.ipcRenderer.invoke<object, AppMetaResponse>(\n        IPC_MAIN_CHANNELS.APP_META,\n        {}\n      );\n      setVersion(appMeta.appVersion);\n      const release = await fetch(\n        `https://api.github.com/repos/responsively-org/responsively-app/releases/tags/v${appMeta.appVersion}`\n      ).then((res) => res.json());\n      try {\n        setContent(\n          release.body\n            .replace(`What's Changed`, ``)\n            .replaceAll(/(https[^\\n ]*)/g, `[$1]($1)`)\n            .replaceAll(/\\*\\*Full.*/g, ``)\n            .replaceAll(/@(\\w+)/g, '[@$1](https://github.com/$1)')\n            .trim()\n        );\n        setIsOpen(true);\n      } catch (error) {\n        // eslint-disable-next-line no-console\n        console.error('Error while fetching release notes', error);\n      }\n    })();\n  }, []);\n\n  const closeAndMarkAsRead = useCallback(() => {\n    window.electron.store.set('seenReleaseNotes', [\n      ...window.electron.store.get('seenReleaseNotes'),\n      version,\n    ]);\n    setIsOpen(false);\n  }, [version]);\n\n  if (content === undefined) {\n    return null;\n  }\n\n  return (\n    <Modal\n      isOpen={isOpen}\n      onClose={closeAndMarkAsRead}\n      title={\n        <span className=\"pl-2\">\n          What&apos;s New in <span className=\"font-bold\">v{version}</span> &nbsp;&nbsp;🎉\n        </span>\n      }\n    >\n      <>\n        <div className=\"prose dark:prose-invert lg:prose-xl\">\n          <ReactMarkdown\n            components={{\n              a: ({node, className, children, ...props}) => {\n                return (\n                  // eslint-disable-next-line jsx-a11y/interactive-supports-focus, jsx-a11y/click-events-have-key-events\n                  <a\n                    // eslint-disable-next-line react/jsx-props-no-spreading\n                    {...props}\n                    onClick={(e) => {\n                      if (!(e.target instanceof HTMLAnchorElement)) {\n                        return;\n                      }\n                      e.preventDefault();\n                      window.electron.ipcRenderer.sendMessage(IPC_MAIN_CHANNELS.OPEN_EXTERNAL, {\n                        url: e.target.href,\n                      });\n                    }}\n                    role=\"link\"\n                    className={cx('outline-none')}\n                  >\n                    {children}\n                  </a>\n                );\n              },\n            }}\n          >\n            {content}\n          </ReactMarkdown>\n        </div>\n        <div className=\"mt-10 flex justify-between gap-12\">\n          <div className=\"flex gap-1\">\n            <Button\n              onClick={() => {\n                window.electron.ipcRenderer.sendMessage(IPC_MAIN_CHANNELS.OPEN_EXTERNAL, {\n                  url: `https://github.com/responsively-org/responsively-app/releases/tag/v${version}`,\n                });\n                closeAndMarkAsRead();\n              }}\n              tabIndex={0}\n              isActionButton\n              isTextButton\n            >\n              Release Notes\n            </Button>\n            <Button onClick={closeAndMarkAsRead} isActionButton isTextButton>\n              Close\n            </Button>\n          </div>\n\n          <Button\n            onClick={() => {\n              window.electron.ipcRenderer.sendMessage(IPC_MAIN_CHANNELS.OPEN_EXTERNAL, {\n                url: 'https://responsively.app/sponsor?utm_source=app&utm_medium=release_notes&utm_campaign=sponsor',\n              });\n            }}\n            isPrimary\n            isTextButton\n          >\n            Support Responsively App\n          </Button>\n        </div>\n      </>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Select/index.tsx",
    "content": "import {useId} from 'react';\n\ninterface Props {\n  label: string;\n}\n\nconst Select = ({\n  label,\n  ...props\n}: Props &\n  React.DetailedHTMLProps<React.SelectHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>) => {\n  const id = useId();\n  return (\n    <div className=\"flex flex-col gap-1\">\n      <label htmlFor={id}>{label}</label>\n      <select\n        id=\"device-capabilities\"\n        className=\"rounded-sm bg-white p-1 px-1 dark:bg-slate-900\"\n        /* eslint-disable-next-line react/jsx-props-no-spreading */\n        {...props}\n      >\n        {props.children}\n      </select>\n    </div>\n  );\n};\n\nexport default Select;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Spinner/index.tsx",
    "content": "import {Icon} from '@iconify/react';\n\ninterface Props {\n  spinnerHeight?: number;\n}\n\nconst Spinner = ({spinnerHeight = undefined}: Props) => {\n  return (\n    <span className=\"animate-spin\">\n      <Icon icon=\"ei:spinner-3\" height={spinnerHeight} />\n    </span>\n  );\n};\n\nexport default Spinner;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Sponsorship/index.tsx",
    "content": "import {useEffect, useMemo, useState} from 'react';\n\nimport {IPC_MAIN_CHANNELS} from 'common/constants';\n\nimport Modal from '../Modal';\nimport Icon from '../../assets/img/logo.png';\nimport Button from '../Button';\n\nconst CONTENT_COPY = [\n  {\n    id: 'level-up',\n    title: 'Level Up: Support Us as a Sponsor!',\n    content1: `Join us on an incredible journey! By becoming a sponsor, you'll fuel Responsively App's progress, ensuring a smooth and amazing experience for all users.`,\n    content2: `Support us today and let's embark on this exciting adventure together!`,\n  },\n  {\n    id: 'elevate-your-impact',\n    title: 'Elevate Your Impact: Sponsor Responsively App!',\n    content1: `Take part in an extraordinary mission! By sponsoring Responsively App, you'll empower our growth and enhance the user experience for everyone.`,\n    content2: `Make a difference today and let's forge ahead on this thrilling journey!`,\n  },\n  {\n    id: 'unlock-the-future',\n    title: 'Unlock the Future: Sponsor Responsively App!',\n    content1: `Join us in unlocking limitless possibilities! By sponsoring Responsively App, you'll drive our progress and unlock an exceptional user experience.`,\n    content2: `Support our vision now and let's embark on an exhilarating ride together!`,\n  },\n  {\n    id: 'ignite-innovation',\n    title: 'Ignite Innovation: Become a Sponsor of Responsively App!',\n    content1: `Be a catalyst for innovation! By sponsoring Responsively App, you'll ignite our growth and fuel an extraordinary user experience.`,\n    content2: `Support our pursuit of excellence today and let's embark on an inspiring journey together!`,\n  },\n  {\n    id: 'power-the-revolution',\n    title: 'Power the Revolution: Sponsor Responsively App!',\n    content1: `Join the revolution and power our mission! By becoming a sponsor, you'll drive our progress and revolutionize the user experience.`,\n    content2: `Support us today and let's embark on a groundbreaking adventure together!`,\n  },\n  {\n    id: 'supercharge-success',\n    title: 'Supercharge Success: Sponsor Responsively App!',\n    content1: `Supercharge your impact! By sponsoring Responsively App, you'll fuel our success and empower an exceptional user experience.`,\n    content2: `Support our mission now and let's embark on an exhilarating journey together!`,\n  },\n];\n\nexport const Sponsorship = () => {\n  const [isOpen, setIsOpen] = useState(false);\n\n  useEffect(() => {\n    const lastShownMs = window.electron.store.get('sponsorship.lastShown');\n    const now = Date.now();\n    if (lastShownMs === undefined || now - lastShownMs > 1000 * 60 * 60 * 24 * 7) {\n      setIsOpen(true);\n    }\n  }, []);\n\n  const onClose = () => {\n    window.electron.store.set('sponsorship.lastShown', Date.now());\n    setIsOpen(false);\n  };\n\n  // Choose a random content copy\n  const contentCopy = useMemo(\n    () => CONTENT_COPY[Math.floor(Math.random() * CONTENT_COPY.length)],\n    []\n  );\n\n  return (\n    <Modal\n      title={\n        <div className=\"flex flex-col items-center gap-2 border-b border-slate-500 pb-2\">\n          <div className=\"flex w-full justify-center\">\n            <img src={Icon} alt=\"Logo\" width={64} />\n          </div>\n          <div>{contentCopy.title}</div>\n        </div>\n      }\n      isOpen={isOpen}\n      onClose={onClose}\n    >\n      <div className=\"max-w-lg\">\n        <p className=\"pb-4 text-center\">\n          {contentCopy.content1}\n          <br />\n          <br />\n          {contentCopy.content2}\n        </p>\n        <div className=\"mt-4 flex justify-center\">\n          <Button\n            onClick={() => {\n              window.electron.ipcRenderer.sendMessage(IPC_MAIN_CHANNELS.OPEN_EXTERNAL, {\n                url: `https://responsively.app/sponsor?utm_source=app&utm_medium=app-banner&utm_campaign=sponsor&utm_term=${contentCopy.id}`,\n              });\n              onClose();\n            }}\n            isTextButton\n            isPrimary\n          >\n            Become a Sponsor\n          </Button>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/Toggle/index.tsx",
    "content": "interface Props {\n  isOn: boolean;\n  onChange?: React.ChangeEventHandler<HTMLInputElement>;\n}\n\nconst Toggle = ({isOn, onChange}: Props) => {\n  return (\n    // eslint-disable-next-line jsx-a11y/label-has-associated-control\n    <label className=\"relative inline-flex cursor-pointer items-center\">\n      <input\n        type=\"checkbox\"\n        checked={isOn}\n        id=\"small-toggle\"\n        className=\"peer sr-only\"\n        onChange={onChange}\n      />\n      <div className=\"peer h-5 w-9 rounded-full bg-gray-300 after:absolute after:left-[2px] after:top-[2px] after:h-4 after:w-4 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-green-600 peer-checked:after:translate-x-full peer-checked:after:border-white dark:border-gray-600 dark:bg-gray-700\" />\n    </label>\n  );\n};\n\nexport default Toggle;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/AddressBar/AuthModal.tsx",
    "content": "import {IPC_MAIN_CHANNELS} from 'common/constants';\nimport {AuthInfo} from 'electron';\nimport {AuthResponseArgs} from 'main/http-basic-auth';\nimport {useEffect, useState} from 'react';\nimport Button from 'renderer/components/Button';\nimport Input from 'renderer/components/Input';\nimport Modal from 'renderer/components/Modal';\n\ninterface Props {\n  isOpen: boolean;\n  onClose: () => void;\n  authInfo: AuthInfo | null;\n}\n\nconst AuthModal = ({isOpen, onClose, authInfo}: Props) => {\n  const [username, setUsername] = useState<string>('');\n  const [password, setPassword] = useState<string>('');\n\n  useEffect(() => {\n    if (!isOpen) {\n      setUsername('');\n      setPassword('');\n    }\n  }, [isOpen]);\n\n  const onSubmit = (proceed: boolean) => {\n    if (authInfo == null) {\n      return;\n    }\n    window.electron.ipcRenderer.sendMessage<AuthResponseArgs>(IPC_MAIN_CHANNELS.AUTH_RESPONSE, {\n      authInfo,\n      username: proceed ? username : '',\n      password: proceed ? password : '',\n    });\n    onClose();\n  };\n\n  return (\n    <Modal isOpen={isOpen} onClose={onClose} title=\"Http Authentication\">\n      <div className=\"flex flex-col gap-4\">\n        <p>\n          Authentication request for <span className=\"font-bold\">{authInfo?.host}</span>\n        </p>\n        <div className=\"flex w-[420px] flex-col gap-2\">\n          <Input label=\"Username\" value={username} onChange={(e) => setUsername(e.target.value)} />\n          <Input\n            label=\"Password\"\n            type=\"password\"\n            value={password}\n            onChange={(e) => setPassword(e.target.value)}\n          />\n        </div>\n\n        <div className=\"flex flex-row justify-end gap-2\">\n          <Button className=\"px-2\" onClick={() => onSubmit(false)}>\n            Cancel\n          </Button>\n          <Button className=\"px-2\" onClick={() => onSubmit(true)} isActive>\n            Proceed\n          </Button>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default AuthModal;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/AddressBar/BookmarkButton.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useState, useMemo} from 'react';\nimport {useDetectClickOutside} from 'react-detect-click-outside';\nimport {useDispatch, useSelector} from 'react-redux';\nimport cx from 'classnames';\nimport Button from 'renderer/components/Button';\n\nimport {IBookmarks, addBookmark, selectBookmarks} from 'renderer/store/features/bookmarks';\nimport useKeyboardShortcut, {\n  SHORTCUT_CHANNEL,\n} from 'renderer/components/KeyboardShortcutsManager/useKeyboardShortcut';\nimport BookmarkFlyout from '../Menu/Flyout/Bookmark/ViewAllBookmarks/BookmarkFlyout';\n\ninterface Props {\n  currentAddress: string;\n  pageTitle: string;\n}\n\nconst BookmarkButton = ({currentAddress, pageTitle}: Props) => {\n  const [openFlyout, setOpenFlyout] = useState<boolean>(false);\n  const dispatch = useDispatch();\n  const ref = useDetectClickOutside({\n    onTriggered: () => {\n      if (!openFlyout) return;\n      if (openFlyout) setOpenFlyout(false);\n    },\n  });\n\n  const initbookmark = {\n    id: '',\n    name: pageTitle,\n    address: currentAddress,\n  };\n\n  const bookmarks = useSelector(selectBookmarks);\n  const bookmarkFound = useMemo(\n    () => bookmarks.find((bm: IBookmarks) => bm.address === currentAddress),\n    [currentAddress, bookmarks]\n  );\n\n  const isPageBookmarked = !!bookmarkFound;\n\n  const handleFlyout = () => {\n    setOpenFlyout(!openFlyout);\n  };\n\n  const handleKeyboardShortcut = () => {\n    handleFlyout();\n    dispatch(addBookmark(bookmarkFound || initbookmark));\n  };\n\n  useKeyboardShortcut(SHORTCUT_CHANNEL.BOOKMARK, handleKeyboardShortcut);\n\n  return (\n    <div ref={ref}>\n      <div>\n        <Button\n          className={cx('rounded-full', {\n            'text-blue-500': isPageBookmarked,\n          })}\n          onClick={handleFlyout}\n          title={`${!isPageBookmarked ? 'Add' : 'Remove'} bookmark`}\n        >\n          <Icon icon={`ic:baseline-star${!isPageBookmarked ? '-border' : ''}`} />\n        </Button>\n      </div>\n\n      <div className=\"absolute right-[0px] top-[40px]\">\n        {openFlyout && (\n          <BookmarkFlyout bookmark={bookmarkFound || initbookmark} setOpenFlyout={setOpenFlyout} />\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default BookmarkButton;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/AddressBar/SitePermissions/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useState, useEffect, useRef} from 'react';\nimport {IPC_MAIN_CHANNELS, SitePermission} from 'common/constants';\nimport {webViewPubSub} from 'renderer/lib/pubsub';\nimport {NAVIGATION_EVENTS} from 'renderer/components/ToolBar/NavigationControls';\n\nconst PERMISSION_STATES = {\n  GRANTED: 'GRANTED',\n  DENIED: 'DENIED',\n  PROMPT: 'PROMPT',\n  UNKNOWN: 'UNKNOWN',\n} as const;\n\ninterface SitePermissionsDropdownProps {\n  currentAddress: string;\n  isVisible: boolean;\n  onClose: () => void;\n}\n\ninterface PermissionToggleProps {\n  permission: SitePermission;\n  onToggle: (type: string, state: string) => void;\n}\n\nconst PermissionToggle = ({permission, onToggle}: PermissionToggleProps) => {\n  const getStateDisplay = (state: string) => {\n    switch (state) {\n      case PERMISSION_STATES.GRANTED:\n        return {\n          text: 'Allow',\n          color: 'text-green-600 dark:text-green-400',\n          icon: 'mdi:check-circle',\n        };\n      case PERMISSION_STATES.DENIED:\n        return {\n          text: 'Block',\n          color: 'text-red-600 dark:text-red-400',\n          icon: 'mdi:block-helper',\n        };\n      default:\n        return {\n          text: 'Ask',\n          color: 'text-gray-600 dark:text-gray-400',\n          icon: 'mdi:help-circle',\n        };\n    }\n  };\n\n  const cycleState = () => {\n    const states = [PERMISSION_STATES.UNKNOWN, PERMISSION_STATES.GRANTED, PERMISSION_STATES.DENIED];\n    const currentIndex = states.findIndex((state) => state === permission.state);\n    const nextIndex = (currentIndex + 1) % states.length;\n    onToggle(permission.type, states[nextIndex]);\n  };\n\n  const display = getStateDisplay(permission.state);\n\n  return (\n    <div className=\"flex items-center justify-between rounded px-2 py-2 hover:bg-gray-100 dark:hover:bg-slate-700\">\n      <div className=\"flex items-center gap-2\">\n        <Icon icon={permission.icon} className=\"text-sm\" />\n        <span className=\"text-xs\">{permission.displayName}</span>\n      </div>\n      <button\n        type=\"button\"\n        onClick={cycleState}\n        className={`flex items-center gap-1 rounded px-2 py-0.5 text-xs font-medium transition-colors hover:bg-gray-200 dark:hover:bg-slate-600 ${display.color}`}\n      >\n        <Icon icon={display.icon} className=\"text-xs\" />\n        {display.text}\n      </button>\n    </div>\n  );\n};\n\nconst SitePermissionsDropdown = ({\n  currentAddress,\n  isVisible,\n  onClose,\n}: SitePermissionsDropdownProps) => {\n  const [sitePermissions, setSitePermissions] = useState<SitePermission[]>([]);\n  const [origin, setOrigin] = useState<string>('');\n  const [showRefreshNotification, setShowRefreshNotification] = useState(false);\n  const [isRefreshing, setIsRefreshing] = useState(false);\n  const dropdownRef = useRef<HTMLDivElement>(null);\n\n  // Close dropdown when clicking outside\n  useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n        onClose();\n      }\n    };\n\n    if (isVisible) {\n      document.addEventListener('mousedown', handleClickOutside);\n    }\n\n    return () => {\n      document.removeEventListener('mousedown', handleClickOutside);\n    };\n  }, [isVisible, onClose]);\n\n  const refreshPage = async () => {\n    if (isRefreshing) return;\n\n    setIsRefreshing(true);\n    try {\n      // Use the webview pubsub system to reload all webviews\n      webViewPubSub.publish(NAVIGATION_EVENTS.RELOAD);\n\n      // Hide refresh notification after a brief delay\n      setTimeout(() => {\n        setShowRefreshNotification(false);\n        setIsRefreshing(false);\n      }, 2000);\n    } catch (error) {\n      // eslint-disable-next-line no-console\n      console.error('Failed to refresh page:', error);\n      setIsRefreshing(false);\n    }\n  };\n\n  useEffect(() => {\n    const getOriginFromUrl = (url: string) => {\n      try {\n        if (url.startsWith('file://')) return 'Local File';\n        const urlObj = new URL(url);\n        return urlObj.origin;\n      } catch {\n        return 'Invalid URL';\n      }\n    };\n\n    const currentOrigin = getOriginFromUrl(currentAddress);\n    setOrigin(currentOrigin);\n\n    const loadSitePermissions = async () => {\n      if (currentOrigin && currentOrigin !== 'Local File' && currentOrigin !== 'Invalid URL') {\n        try {\n          const permissions = (await window.electron.ipcRenderer.invoke(\n            IPC_MAIN_CHANNELS.GET_SITE_PERMISSIONS,\n            currentOrigin\n          )) as SitePermission[];\n          setSitePermissions(permissions);\n        } catch (error) {\n          // eslint-disable-next-line no-console\n          console.error('Failed to load site permissions:', error);\n          setSitePermissions([]);\n        }\n      } else {\n        setSitePermissions([]);\n      }\n    };\n\n    if (isVisible) {\n      loadSitePermissions();\n    }\n  }, [currentAddress, isVisible]);\n\n  // Listen for permission updates from the main process\n  useEffect(() => {\n    const handlePermissionUpdate = (args: {origin: string; type: string; state: string}) => {\n      // Only update if it's for the current origin\n      if (args.origin === origin) {\n        setSitePermissions((prev) =>\n          prev.map((p) =>\n            p.type === args.type ? {...p, state: args.state as SitePermission['state']} : p\n          )\n        );\n\n        // Show refresh notification when permission changes\n        setShowRefreshNotification(true);\n      }\n    };\n\n    window.electron.ipcRenderer.on(IPC_MAIN_CHANNELS.PERMISSION_UPDATED, handlePermissionUpdate);\n\n    return () => {\n      window.electron.ipcRenderer.removeListener(\n        IPC_MAIN_CHANNELS.PERMISSION_UPDATED,\n        handlePermissionUpdate\n      );\n    };\n  }, [origin]);\n\n  const handlePermissionToggle = async (type: string, state: string) => {\n    try {\n      await window.electron.ipcRenderer.invoke(IPC_MAIN_CHANNELS.UPDATE_SITE_PERMISSION, {\n        origin,\n        type,\n        state,\n      });\n    } catch (error) {\n      // eslint-disable-next-line no-console\n      console.error('Failed to update permission:', error);\n    }\n  };\n\n  const handleClearAllPermissions = async () => {\n    try {\n      await window.electron.ipcRenderer.invoke(IPC_MAIN_CHANNELS.CLEAR_SITE_PERMISSIONS, origin);\n    } catch (error) {\n      // eslint-disable-next-line no-console\n      console.error('Failed to clear permissions:', error);\n    }\n  };\n\n  const hasActivePermissions = sitePermissions.some(\n    (p) => p.state === PERMISSION_STATES.GRANTED || p.state === PERMISSION_STATES.DENIED\n  );\n\n  if (!isVisible) return null;\n\n  if (!origin || origin === 'Local File' || origin === 'Invalid URL') {\n    return (\n      <div\n        ref={dropdownRef}\n        className=\"w-80 rounded-md bg-white p-4 shadow-lg ring-1 ring-slate-500 !ring-opacity-40 dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40\"\n      >\n        <div className=\"flex items-center gap-2 text-gray-500\">\n          <Icon icon=\"mdi:shield-lock\" />\n          <span className=\"text-sm\">Site permissions not available for this page</span>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div\n      ref={dropdownRef}\n      className=\"w-80 rounded-md bg-white shadow-lg ring-1 ring-slate-500 !ring-opacity-40 dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40\"\n    >\n      {/* Header */}\n      <div className=\"border-b border-gray-200 px-4 py-3 dark:border-slate-700\">\n        <div className=\"flex items-center justify-between\">\n          <div className=\"flex items-center gap-2\">\n            <Icon icon=\"mdi:shield-lock\" className=\"text-lg\" />\n            <span className=\"text-sm font-medium\">Site Permissions</span>\n          </div>\n          <button\n            type=\"button\"\n            onClick={onClose}\n            className=\"rounded p-1 hover:bg-gray-100 dark:hover:bg-slate-700\"\n          >\n            <Icon icon=\"mdi:close\" className=\"text-sm\" />\n          </button>\n        </div>\n        <div className=\"mt-1 truncate text-xs text-gray-500\" title={origin}>\n          {origin.replace(/^https?:\\/\\//, '')}\n        </div>\n      </div>\n\n      {/* Refresh Notification */}\n      {showRefreshNotification && (\n        <div className=\"mx-4 mb-2 mt-3 rounded border border-blue-200 bg-blue-50 px-3 py-2 dark:border-blue-700 dark:bg-blue-900\">\n          <div className=\"flex items-center justify-between\">\n            <div className=\"flex items-center gap-2 text-sm text-blue-700 dark:text-blue-300\">\n              <Icon\n                icon={isRefreshing ? 'mdi:loading' : 'mdi:information'}\n                className={isRefreshing ? 'animate-spin' : ''}\n              />\n              <span className=\"text-xs\">\n                {isRefreshing\n                  ? 'Refreshing page...'\n                  : 'Permission updated. Refresh to apply changes.'}\n              </span>\n            </div>\n            {!isRefreshing && (\n              <div className=\"flex gap-1\">\n                <button\n                  type=\"button\"\n                  onClick={refreshPage}\n                  className=\"rounded bg-blue-600 px-2 py-1 text-xs text-white transition-colors hover:bg-blue-700\"\n                >\n                  Refresh\n                </button>\n                <button\n                  type=\"button\"\n                  onClick={() => setShowRefreshNotification(false)}\n                  className=\"rounded bg-gray-500 px-2 py-1 text-xs text-white transition-colors hover:bg-gray-600\"\n                >\n                  ×\n                </button>\n              </div>\n            )}\n          </div>\n        </div>\n      )}\n\n      {/* Permissions List */}\n      <div className=\"px-4 py-3\">\n        <div className=\"mb-3 flex items-center justify-between\">\n          <span className=\"text-xs font-medium text-gray-700 dark:text-gray-300\">\n            Permissions for this site\n          </span>\n          {hasActivePermissions && (\n            <button\n              type=\"button\"\n              onClick={handleClearAllPermissions}\n              className=\"text-xs text-red-600 hover:underline dark:text-red-400\"\n              title=\"Reset all permissions to default\"\n            >\n              Reset All\n            </button>\n          )}\n        </div>\n\n        <div className=\"max-h-64 space-y-1 overflow-y-auto\">\n          {sitePermissions.map((permission) => (\n            <PermissionToggle\n              key={permission.type}\n              permission={permission}\n              onToggle={handlePermissionToggle}\n            />\n          ))}\n        </div>\n\n        <div className=\"mt-3 text-xs text-gray-500 dark:text-gray-400\">\n          Changes take effect immediately. Reload the page if needed.\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default SitePermissionsDropdown;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/AddressBar/SuggestionList.tsx",
    "content": "import {useCallback, useEffect, useMemo, useState} from 'react';\nimport cx from 'classnames';\n\nexport interface HistoryItem {\n  title: string;\n  url: string;\n  lastVisited: number;\n}\n\ninterface Props {\n  match: string;\n  onEnter: (url?: string) => void;\n}\n\nconst SuggestionList = ({match, onEnter}: Props) => {\n  const [activeIndex, setActiveIndex] = useState<number>(0);\n  const [history] = useState<HistoryItem[]>(window.electron.store.get('history'));\n\n  const suggestions = useMemo(() => {\n    return history\n      .filter((item) => {\n        return `${item.title}-${item.url}`.toLowerCase().includes(match.toLowerCase());\n      })\n      .slice(0, 10);\n  }, [match, history]);\n\n  const keyDownHandler = useCallback(\n    (e: KeyboardEvent) => {\n      if (e.key === 'Enter') {\n        onEnter(suggestions[activeIndex] != null ? suggestions[activeIndex].url : undefined);\n        return;\n      }\n      if (e.key === 'ArrowUp') {\n        if (activeIndex === 0) {\n          return;\n        }\n        setActiveIndex(activeIndex - 1);\n      }\n      if (e.key === 'ArrowDown') {\n        if (activeIndex === suggestions.length - 1) {\n          return;\n        }\n        setActiveIndex(activeIndex + 1);\n      }\n    },\n    [activeIndex, suggestions, onEnter]\n  );\n\n  useEffect(() => {\n    document.addEventListener('keydown', keyDownHandler);\n    return () => {\n      document.removeEventListener('keydown', keyDownHandler);\n    };\n  }, [keyDownHandler]);\n\n  return (\n    <div className=\"absolute z-20 flex w-full flex-col items-start rounded-b-lg bg-white pb-2  shadow-lg dark:bg-slate-900\">\n      {suggestions.map(({title, url}, idx) => (\n        <button\n          onClickCapture={() => {\n            onEnter(url);\n          }}\n          className={cx(\n            'pointer-events-auto flex w-full items-center gap-2 py-1 pl-2 pr-8 hover:bg-slate-200 dark:hover:bg-slate-700',\n            {'bg-slate-200 dark:bg-slate-700': activeIndex === idx}\n          )}\n          type=\"button\"\n          key={url}\n        >\n          <span>\n            <img\n              src={`https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url}&size=64`}\n              className=\"w-4 rounded-md\"\n              alt=\"favicon\"\n            />\n          </span>\n          <span className=\"flex flex-row gap-1 overflow-hidden\">\n            <span className=\"overflow-hidden text-ellipsis whitespace-nowrap\">{title}</span>-\n            <span className=\"max-w-2/5 overflow-hidden text-ellipsis whitespace-nowrap text-blue-500\">\n              {url}\n            </span>\n          </span>\n        </button>\n      ))}\n    </div>\n  );\n};\n\nexport default SuggestionList;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/AddressBar/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport cx from 'classnames';\nimport {IPC_MAIN_CHANNELS, OpenUrlArgs} from 'common/constants';\nimport {AuthRequestArgs} from 'main/http-basic-auth';\nimport {PermissionRequestArg} from 'main/web-permissions/PermissionsManager';\nimport {DragEvent, KeyboardEventHandler, useCallback, useEffect, useRef, useState} from 'react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport {webViewPubSub} from 'renderer/lib/pubsub';\nimport {selectAddress, selectPageTitle, setAddress} from 'renderer/store/features/renderer';\nimport useKeyboardShortcut, {\n  SHORTCUT_CHANNEL,\n} from 'renderer/components/KeyboardShortcutsManager/useKeyboardShortcut';\nimport AuthModal from './AuthModal';\nimport SuggestionList from './SuggestionList';\nimport Bookmark from './BookmarkButton';\nimport SitePermissionsDropdown from './SitePermissions';\n\nexport const ADDRESS_BAR_EVENTS = {\n  DELETE_COOKIES: 'DELETE_COOKIES',\n  DELETE_STORAGE: 'DELETE_STORAGE',\n  DELETE_CACHE: 'DELETE_CACHE',\n};\n\nconst AddressBar = () => {\n  const inputRef = useRef<HTMLInputElement>(null);\n  const [typedAddress, setTypedAddress] = useState<string>('');\n  const [isSuggesting, setIsSuggesting] = useState<boolean>(false);\n  const [isDragOver, setIsDragOver] = useState<boolean>(false);\n  const [homepage, setHomepage] = useState<string>(window.electron.store.get('homepage'));\n  const [isFocused, setIsFocused] = useState(false);\n  const [deleteStorageLoading, setDeleteStorageLoading] = useState<boolean>(false);\n  const [deleteCookiesLoading, setDeleteCookiesLoading] = useState<boolean>(false);\n  const [deleteCacheLoading, setDeleteCacheLoading] = useState<boolean>(false);\n  const [permissionRequest, setPermissionRequest] = useState<PermissionRequestArg | null>(null);\n  const [authRequest, setAuthRequest] = useState<AuthRequestArgs | null>(null);\n  const [showSitePermissions, setShowSitePermissions] = useState(false);\n  const address = useSelector(selectAddress);\n  const pageTitle = useSelector(selectPageTitle);\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    if (address === typedAddress) {\n      return;\n    }\n    setTypedAddress(address);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [address]);\n\n  const dispatchAddress = useCallback(\n    (url?: string) => {\n      let newAddress = url ?? typedAddress;\n      if (newAddress.indexOf('://') === -1) {\n        let protocol = 'https://';\n        if (typedAddress.indexOf('localhost') !== -1 || typedAddress.indexOf('127.0.0.1') !== -1) {\n          protocol = 'http://';\n        }\n        newAddress = protocol + typedAddress;\n      }\n      if (url && url !== typedAddress) setTypedAddress(url);\n      dispatch(setAddress(newAddress));\n    },\n    [dispatch, typedAddress]\n  );\n\n  useEffect(() => {\n    window.electron.ipcRenderer.on<PermissionRequestArg>(\n      IPC_MAIN_CHANNELS.PERMISSION_REQUEST,\n      (args) => {\n        setPermissionRequest(args);\n      }\n    );\n\n    window.electron.ipcRenderer.on<AuthRequestArgs>(IPC_MAIN_CHANNELS.AUTH_REQUEST, (args) => {\n      setAuthRequest(args);\n    });\n    window.electron.ipcRenderer.on<OpenUrlArgs>(IPC_MAIN_CHANNELS.OPEN_URL, (args) => {\n      dispatchAddress(args.url);\n    });\n\n    return () => {\n      window.electron.ipcRenderer.removeAllListeners(IPC_MAIN_CHANNELS.PERMISSION_REQUEST);\n      window.electron.ipcRenderer.removeAllListeners(IPC_MAIN_CHANNELS.AUTH_REQUEST);\n      window.electron.ipcRenderer.removeAllListeners(IPC_MAIN_CHANNELS.OPEN_URL);\n    };\n  }, [dispatchAddress]);\n\n  useEffect(() => {\n    if (homepage !== window.electron.store.get('homepage')) {\n      window.electron.store.set('homepage', homepage);\n    }\n  }, [homepage]);\n\n  const permissionReqClickHandler = (allow: boolean) => {\n    if (!permissionRequest) {\n      return;\n    }\n    window.electron.ipcRenderer.invoke(IPC_MAIN_CHANNELS.PERMISSION_RESPONSE, {\n      permissionRequest,\n      allow,\n    });\n    setPermissionRequest(null);\n  };\n\n  const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (\n    e: React.KeyboardEvent<HTMLInputElement>\n  ) => {\n    if (!isSuggesting && !['Escape', 'Enter'].includes(e.key)) {\n      setIsSuggesting(true);\n    } else if (e.key === 'Escape' && isSuggesting) {\n      setIsSuggesting(false);\n    } else if (e.key === 'Enter' && !isSuggesting) {\n      dispatchAddress(typedAddress);\n    }\n  };\n\n  const onEnter = (url?: string) => {\n    dispatchAddress(url);\n    inputRef.current?.blur();\n  };\n\n  const handleDragEnter = (e: DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(true);\n  };\n\n  const handleDragExit = (e: DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(false);\n  };\n\n  const handleDrop = (e: DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(false);\n    const draggedText = e.dataTransfer.getData('text/plain');\n    try {\n      const draggedUrl = new URL(draggedText);\n      if (draggedUrl.protocol === 'http:' || draggedUrl.protocol === 'https:') {\n        dispatchAddress(draggedUrl.href);\n      } else {\n        throw new Error('Invalid URL');\n      }\n    } catch (err) {\n      // eslint-disable-next-line no-console\n      console.error('Invalid URL', err);\n    }\n  };\n\n  const deleteCookies = async () => {\n    setDeleteCookiesLoading(true);\n    await webViewPubSub.publish(ADDRESS_BAR_EVENTS.DELETE_COOKIES);\n    setDeleteCookiesLoading(false);\n  };\n\n  const deleteStorage = async () => {\n    setDeleteStorageLoading(true);\n    await webViewPubSub.publish(ADDRESS_BAR_EVENTS.DELETE_STORAGE);\n    setDeleteStorageLoading(false);\n  };\n\n  const deleteCache = async () => {\n    setDeleteCacheLoading(true);\n    await webViewPubSub.publish(ADDRESS_BAR_EVENTS.DELETE_CACHE);\n    setDeleteCacheLoading(false);\n  };\n\n  const deleteAll = () => {\n    deleteCache();\n    deleteStorage();\n    deleteCookies();\n  };\n\n  const handleEditUrl = () => {\n    if (inputRef.current) {\n      inputRef.current.focus();\n      inputRef.current.select();\n    }\n  };\n\n  const isHomepage = address === homepage;\n\n  useKeyboardShortcut(SHORTCUT_CHANNEL.DELETE_CACHE, deleteCache);\n  useKeyboardShortcut(SHORTCUT_CHANNEL.DELETE_STORAGE, deleteStorage);\n  useKeyboardShortcut(SHORTCUT_CHANNEL.DELETE_COOKIES, deleteCookies);\n  useKeyboardShortcut(SHORTCUT_CHANNEL.DELETE_ALL, deleteAll);\n  useKeyboardShortcut(SHORTCUT_CHANNEL.EDIT_URL, handleEditUrl);\n\n  return (\n    <>\n      <div\n        onDragEnter={handleDragEnter}\n        onDragLeave={handleDragExit}\n        onDrop={handleDrop}\n        className=\"relative z-10 w-full flex-grow\"\n      >\n        <div className=\"absolute inset-y-0 left-2 flex items-center\">\n          <button\n            type=\"button\"\n            onClick={() => setShowSitePermissions(!showSitePermissions)}\n            className=\"rounded-full p-1 transition-colors hover:bg-gray-200 dark:hover:bg-slate-700\"\n            title=\"Site permissions\"\n          >\n            <Icon icon=\"mdi:web\" className=\"text-gray-500\" />\n          </button>\n        </div>\n\n        <div className=\"absolute left-2 top-full z-50\">\n          <SitePermissionsDropdown\n            currentAddress={address}\n            isVisible={showSitePermissions}\n            onClose={() => setShowSitePermissions(false)}\n          />\n        </div>\n\n        {permissionRequest != null ? (\n          <div className=\"absolute left-2 top-12 z-40 flex w-96 flex-col gap-8 rounded bg-white p-6 shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40\">\n            <span>\n              {permissionRequest.requestingOrigin} requests permission for: <br />\n              <span className=\"flex justify-center font-bold capitalize\">\n                {permissionRequest.permission}\n              </span>\n            </span>\n            <div className=\"flex justify-end\">\n              <div className=\"flex w-1/2 justify-around\">\n                <Button\n                  onClick={() => {\n                    permissionReqClickHandler(false);\n                  }}\n                  isActionButton\n                >\n                  Block\n                </Button>\n                <Button\n                  onClick={() => {\n                    permissionReqClickHandler(true);\n                  }}\n                  isActionButton\n                >\n                  Allow\n                </Button>\n              </div>\n            </div>\n          </div>\n        ) : null}\n        <input\n          ref={inputRef}\n          type=\"text\"\n          className={cx(\n            'w-full text-ellipsis rounded-full px-2 py-1 pl-8 pr-40 dark:bg-slate-900',\n            {\n              'rounded-bl-none rounded-br-none rounded-tl-lg rounded-tr-lg outline-none':\n                isSuggesting,\n            }\n          )}\n          value={typedAddress}\n          onChange={(e) => setTypedAddress(e.target.value)}\n          onKeyDown={handleKeyDown}\n          onBlur={() => {\n            setIsFocused(false);\n            setTimeout(() => {\n              setIsSuggesting(false);\n            }, 100);\n          }}\n          onSelect={(e) => {\n            if (e.target === inputRef.current && !isFocused) {\n              inputRef.current?.select();\n              setIsFocused(true);\n            }\n          }}\n        />\n        <div\n          className={`${\n            isDragOver ? 'opacity-100' : 'opacity-0'\n          } pointer-events-none absolute left-0 top-0 z-10 flex h-full w-full border-spacing-1 items-center justify-center gap-2 rounded-full border-2 border-dashed border-[#37b598] bg-[#92e2ce] duration-100 dark:bg-[#86e0ca] dark:text-slate-900`}\n        >\n          <Icon icon=\"mdi:plus\" />\n          <p className=\"text-sm font-semibold\">Drop URL Here</p>\n        </div>\n        <div className=\"absolute inset-y-0 right-0 mr-2 flex items-center\">\n          <Button\n            className=\"rounded-full\"\n            onClick={deleteStorage}\n            isLoading={deleteStorageLoading}\n            title=\"Delete Storage\"\n          >\n            <Icon icon=\"mdi:database-remove-outline\" />\n          </Button>\n          <Button\n            className=\"rounded-full\"\n            onClick={deleteCookies}\n            isLoading={deleteCookiesLoading}\n            title=\"Delete Cookies\"\n          >\n            <Icon icon=\"mdi:cookie-remove-outline\" />\n          </Button>\n          <Button\n            className=\"rounded-full\"\n            onClick={deleteCache}\n            isLoading={deleteCacheLoading}\n            title=\"Clear Cache\"\n          >\n            <Icon icon=\"mdi:wifi-remove\" />\n          </Button>\n          <Button\n            className={cx('rounded-full', {\n              'text-blue-500': isHomepage,\n            })}\n            onClick={() => setHomepage(address)}\n            title=\"Homepage\"\n          >\n            <Icon icon={isHomepage ? 'mdi:home' : 'mdi:home-outline'} />\n          </Button>\n          <Bookmark pageTitle={pageTitle} currentAddress={address} />\n        </div>\n        {isSuggesting ? <SuggestionList match={typedAddress} onEnter={onEnter} /> : null}\n      </div>\n      <AuthModal\n        isOpen={authRequest != null}\n        onClose={() => setAuthRequest(null)}\n        authInfo={authRequest}\n      />\n    </>\n  );\n};\n\nexport default AddressBar;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/ColorBlindnessControls/index.tsx",
    "content": "import {useEffect, useState} from 'react';\nimport {VisionSimulationDropDown} from 'renderer/components/VisionSimulationDropDown';\nimport {webViewPubSub} from 'renderer/lib/pubsub';\n\nexport const COLOR_BLINDNESS_CHANNEL = 'color-blindness';\n\nexport const ColorBlindnessControls = () => {\n  const [simulationName, setSimulationName] = useState<string | undefined>(undefined);\n\n  useEffect(() => {\n    webViewPubSub.publish(COLOR_BLINDNESS_CHANNEL, {simulationName});\n  }, [simulationName]);\n\n  return <VisionSimulationDropDown simulationName={simulationName} onChange={setSimulationName} />;\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/ColorSchemeToggle/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {SetNativeThemeArgs, SetNativeThemeResult} from 'main/native-functions';\nimport {useState} from 'react';\nimport Button from 'renderer/components/Button';\n\nconst ColorSchemeToggle = () => {\n  const [isDarkColorScheme, setIsDarkColorScheme] = useState<boolean>(false);\n\n  return (\n    <Button\n      onClick={() => {\n        window.electron.ipcRenderer.invoke<SetNativeThemeArgs, SetNativeThemeResult>(\n          'set-native-theme',\n          {\n            theme: isDarkColorScheme ? 'light' : 'dark',\n          }\n        );\n        setIsDarkColorScheme(!isDarkColorScheme);\n      }}\n      subtle\n      title=\"Device theme color toggle\"\n    >\n      <span className=\"relative\">\n        <Icon icon=\"iconoir:empty-page\" />\n        <Icon\n          icon={isDarkColorScheme ? 'carbon:moon' : 'carbon:sun'}\n          className=\"absolute inset-0 m-auto\"\n          fontSize={10}\n        />\n      </span>\n    </Button>\n  );\n};\n\nexport default ColorSchemeToggle;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/AllowInSecureSSL/index.tsx",
    "content": "import {useState} from 'react';\nimport Toggle from 'renderer/components/Toggle';\n\nconst AllowInSecureSSL = () => {\n  const [allowed, setAllowed] = useState<boolean>(\n    window.electron.store.get('userPreferences.allowInsecureSSLConnections')\n  );\n\n  return (\n    <div className=\"flex flex-row items-center justify-start px-4\">\n      <span className=\"w-1/2\">Allow Insecure SSL</span>\n      <div className=\"flex items-center gap-2 border-l px-4 dark:border-slate-400\">\n        <Toggle\n          isOn={allowed}\n          onChange={(value) => {\n            setAllowed(value.target.checked);\n            window.electron.store.set(\n              'userPreferences.allowInsecureSSLConnections',\n              value.target.checked\n            );\n          }}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default AllowInSecureSSL;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Bookmark/ViewAllBookmarks/BookmarkFlyout.tsx",
    "content": "import {useEffect, useState} from 'react';\nimport {useDispatch} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport {IBookmarks, addBookmark, removeBookmark} from 'renderer/store/features/bookmarks';\nimport Input from 'renderer/components/Input';\n\ninterface Props {\n  bookmark: IBookmarks;\n  setOpenFlyout: (bool: boolean) => void;\n}\n\nconst BookmarkFlyout = ({bookmark, setOpenFlyout}: Props) => {\n  const [currentBookmark, setCurrentBookmark] = useState<IBookmarks>(bookmark);\n  const dispatch = useDispatch();\n\n  const handleButton = (e: React.MouseEvent) => {\n    const target = e.target as HTMLButtonElement;\n    const buttonType = target.id;\n\n    if (buttonType === 'add') dispatch(addBookmark(currentBookmark));\n    else dispatch(removeBookmark(currentBookmark));\n\n    setOpenFlyout(false);\n  };\n\n  const handleChange = (e: React.ChangeEvent) => {\n    const target = e.target as HTMLButtonElement;\n    const inputType = target.id;\n    const inputValue = target.value;\n\n    setCurrentBookmark((prevBookmark) => ({\n      ...prevBookmark,\n      [inputType]: inputValue,\n    }));\n  };\n\n  useEffect(() => {\n    setCurrentBookmark(bookmark);\n  }, [bookmark]);\n\n  return (\n    <>\n      <div className=\"z-50 flex w-80 flex-col gap-4 rounded bg-white px-6 py-4 text-sm shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40\">\n        <Input\n          type=\"text\"\n          className=\"rounded-sm bg-slate-200 p-1 px-1 dark:bg-slate-700\"\n          id=\"name\"\n          name=\"name\"\n          label=\"Bookmark Name\"\n          value={currentBookmark.name}\n          onChange={handleChange}\n        />\n        <Input\n          type=\"text\"\n          className=\"rounded-sm bg-slate-200 p-1 px-1 dark:bg-slate-700\"\n          id=\"address\"\n          name=\"address\"\n          label=\"Address\"\n          value={currentBookmark.address}\n          onChange={handleChange}\n        />\n        <div className=\"mb-1 mt-4 flex justify-center\">\n          <Button onClick={handleButton} id=\"remove\" className=\"mr-6 px-6\">\n            Remove\n          </Button>\n          <Button onClick={handleButton} id=\"add\" className=\"px-8\" isActionButton>\n            Save\n          </Button>\n        </div>\n      </div>\n    </>\n  );\n};\n\nexport default BookmarkFlyout;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Bookmark/ViewAllBookmarks/BookmarkListButton.tsx",
    "content": "import cx from 'classnames';\nimport Button from 'renderer/components/Button';\nimport {IBookmarks} from 'renderer/store/features/bookmarks';\nimport {Icon} from '@iconify/react';\nimport {useState} from 'react';\n\nexport interface Props {\n  bookmark: IBookmarks;\n  handleBookmarkClick: (address: string) => void;\n  setCurrentBookmark: (bookmark: IBookmarks) => void;\n  setOpenFlyout: (bool: boolean) => void;\n}\n\nconst BookmarkListButton = ({\n  bookmark,\n  handleBookmarkClick,\n  setCurrentBookmark,\n  setOpenFlyout,\n}: Props) => {\n  const [isHovered, setIsHovered] = useState(false);\n\n  return (\n    <div\n      className=\"flex h-[40px] w-60 justify-between hover:bg-slate-400 dark:hover:bg-slate-600 \"\n      onMouseEnter={() => setIsHovered(true)}\n      onMouseLeave={() => setIsHovered(false)}\n      key={bookmark.id}\n    >\n      <button\n        type=\"button\"\n        className=\"cursor-default truncate pl-3\"\n        onClick={() => handleBookmarkClick(bookmark.address)}\n      >\n        {bookmark.name}\n      </button>\n      <button\n        type=\"button\"\n        className={cx('ml-1 flex shrink-0 items-center justify-center px-2', {\n          invisible: !isHovered,\n          visible: isHovered,\n        })}\n        onClick={() => {\n          setCurrentBookmark(bookmark);\n          setOpenFlyout(true);\n        }}\n      >\n        <Icon icon=\"ic:sharp-edit\" />\n      </button>\n    </div>\n  );\n};\n\nexport default BookmarkListButton;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Bookmark/ViewAllBookmarks/index.tsx",
    "content": "import {useDispatch} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport {IBookmarks} from 'renderer/store/features/bookmarks';\nimport {setAddress} from 'renderer/store/features/renderer';\nimport {useState} from 'react';\nimport BookmarkListButton from './BookmarkListButton';\nimport BookmarkFlyout from './BookmarkFlyout';\n\nexport interface Props {\n  bookmarks: IBookmarks[];\n  handleBookmarkFlyout: () => void;\n}\n\nconst ViewAllBookmarks = ({bookmarks, handleBookmarkFlyout}: Props) => {\n  const [currentBookmark, setCurrentBookmark] = useState<IBookmarks>({\n    id: '',\n    name: '',\n    address: '',\n  });\n  const [openFlyout, setOpenFlyout] = useState(false);\n  const dispatch = useDispatch();\n\n  const areBookmarksPresent = bookmarks.length > 0;\n\n  const handleBookmarkClick = (address: string) => {\n    dispatch(setAddress(address));\n    handleBookmarkFlyout();\n  };\n\n  return (\n    <div>\n      <div className=\"absolute right-[316px] top-0 z-50 flex max-h-[60vh] min-h-min flex-col overflow-x-auto overflow-y-auto rounded border bg-slate-100 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40\">\n        {bookmarks.map((bookmark) => {\n          return (\n            <div key={bookmark.id}>\n              <BookmarkListButton\n                bookmark={bookmark}\n                handleBookmarkClick={handleBookmarkClick}\n                setCurrentBookmark={setCurrentBookmark}\n                setOpenFlyout={setOpenFlyout}\n              />\n            </div>\n          );\n        })}\n        {!areBookmarksPresent && (\n          <Button className=\"w-60 py-2\" disabled disableHoverEffects>\n            No bookmarks found{' '}\n          </Button>\n        )}\n      </div>\n      <div className=\"absolute right-[560px]\">\n        {openFlyout && <BookmarkFlyout bookmark={currentBookmark} setOpenFlyout={setOpenFlyout} />}\n      </div>\n    </div>\n  );\n};\n\nexport default ViewAllBookmarks;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Bookmark/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useEffect, useState} from 'react';\nimport Button from 'renderer/components/Button';\nimport {closeMenuFlyout, selectMenuFlyout} from 'renderer/store/features/ui';\nimport {useDispatch, useSelector} from 'react-redux';\nimport {selectBookmarks} from 'renderer/store/features/bookmarks';\nimport ViewAllBookmarks from './ViewAllBookmarks';\n\nconst Bookmark = () => {\n  const [isOpen, setIsOpen] = useState<boolean>(false);\n  const dispatch = useDispatch();\n  const menuFlyout = useSelector(selectMenuFlyout);\n  const bookmarks = useSelector(selectBookmarks);\n\n  const handleBookmarkFlyout = () => {\n    setIsOpen(!isOpen);\n    dispatch(closeMenuFlyout(!isOpen));\n  };\n\n  useEffect(() => {\n    if (!menuFlyout) setIsOpen(false);\n  }, [menuFlyout]);\n\n  return (\n    <div\n      className=\"relative\"\n      onMouseEnter={() => setIsOpen(true)}\n      onMouseLeave={() => setIsOpen(false)}\n    >\n      <div>\n        <div className=\"relative right-2 w-80 dark:border-slate-400\">\n          <Button className=\"flex w-full items-center justify-between pl-6\" isActive={isOpen}>\n            <span>Bookmarks</span>\n            <Icon\n              className=\"mr-3 -rotate-90 transform\"\n              icon=\"ic:baseline-arrow-drop-down\"\n              height={20}\n            />\n          </Button>\n        </div>\n      </div>\n      {isOpen && (\n        <ViewAllBookmarks bookmarks={bookmarks} handleBookmarkFlyout={handleBookmarkFlyout} />\n      )}\n    </div>\n  );\n};\n\nexport default Bookmark;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/ClearHistory/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport Button from 'renderer/components/Button';\n\nconst ClearHistory = () => {\n  return (\n    <div className=\"flex flex-row items-center justify-start px-4\">\n      <span className=\"w-1/2\">Clear History</span>\n      <div className=\"flex items-center gap-2 border-l px-4 dark:border-slate-400\">\n        <Button\n          subtle\n          onClick={() => {\n            window.electron.store.set('history', []);\n          }}\n        >\n          <Icon icon=\"carbon:trash-can\" />\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nexport default ClearHistory;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Devtools/index.tsx",
    "content": "import {DOCK_POSITION} from 'common/constants';\nimport {useDispatch, useSelector} from 'react-redux';\nimport {selectDockPosition, setDockPosition} from 'renderer/store/features/devtools';\nimport Toggle from 'renderer/components/Toggle';\n\nconst Devtools = () => {\n  const dockPosition = useSelector(selectDockPosition);\n  const dispatch = useDispatch();\n\n  return (\n    <div className=\"flex flex-row items-center justify-start px-4\">\n      <span className=\"w-1/2\">Dock Devtools</span>\n      <div className=\"flex items-center gap-2 border-l px-4 dark:border-slate-400\">\n        <Toggle\n          isOn={dockPosition !== DOCK_POSITION.UNDOCKED}\n          onChange={(value) => {\n            if (value.target.checked) {\n              dispatch(setDockPosition(DOCK_POSITION.BOTTOM));\n            } else {\n              dispatch(setDockPosition(DOCK_POSITION.UNDOCKED));\n            }\n          }}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default Devtools;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/PreviewLayout/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {PREVIEW_LAYOUTS, PreviewLayout} from 'common/constants';\nimport {useDispatch, useSelector} from 'react-redux';\nimport {ButtonGroup} from 'renderer/components/ButtonGroup';\nimport useKeyboardShortcut, {\n  SHORTCUT_CHANNEL,\n} from 'renderer/components/KeyboardShortcutsManager/useKeyboardShortcut';\nimport {selectLayout, setLayout} from 'renderer/store/features/renderer';\n\nconst PreviewLayoutSelector = () => {\n  const layout = useSelector(selectLayout);\n  const dispatch = useDispatch();\n\n  const handleLayout = (newLayout: PreviewLayout) => {\n    dispatch(setLayout(newLayout));\n  };\n\n  const toggleNextLayout = () => {\n    const layouts = Object.values(PREVIEW_LAYOUTS);\n    const currentIndex = layouts.findIndex((l) => l === layout);\n    const nextIndex = (currentIndex + 1) % layouts.length;\n    dispatch(setLayout(layouts[nextIndex]));\n  };\n\n  useKeyboardShortcut(SHORTCUT_CHANNEL.PREVIEW_LAYOUT, toggleNextLayout);\n\n  return (\n    <div className=\"flex flex-row items-center justify-start px-4\">\n      <span className=\"w-1/2\">Preview Layout</span>\n      <div className=\"flex w-fit items-center gap-3 px-5 \">\n        <ButtonGroup\n          buttons={[\n            {\n              content: (\n                <div className=\"flex flex-col items-center text-xs\">\n                  {' '}\n                  <Icon icon=\"radix-icons:layout\" /> Column\n                </div>\n              ),\n              srContent: 'Horizontal Layout',\n              onClick: () => handleLayout(PREVIEW_LAYOUTS.COLUMN),\n              isActive: layout === PREVIEW_LAYOUTS.COLUMN,\n            },\n            {\n              content: (\n                <div className=\"flex min-w-12 flex-col items-center text-xs\">\n                  {' '}\n                  <Icon icon=\"lucide:layout-dashboard\" /> Flex\n                </div>\n              ),\n              srContent: 'Flex Layout',\n              onClick: () => handleLayout(PREVIEW_LAYOUTS.FLEX),\n              isActive: layout === PREVIEW_LAYOUTS.FLEX,\n            },\n            {\n              content: (\n                <div className=\"flex flex-col items-center text-xs\">\n                  {' '}\n                  <Icon icon=\"bx:bx-grid-alt\" /> Masonry\n                </div>\n              ),\n              srContent: 'Masonry Layout',\n              onClick: () => handleLayout(PREVIEW_LAYOUTS.MASONRY),\n              isActive: layout === PREVIEW_LAYOUTS.MASONRY,\n            },\n          ]}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default PreviewLayoutSelector;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Settings/SettingsContent.test.tsx",
    "content": "import * as React from 'react';\n\nimport {render, fireEvent} from '@testing-library/react';\n\nimport {SettingsContent} from './SettingsContent';\n\nconst mockOnClose = vi.fn();\n\ndescribe('SettingsContentHeader', () => {\n  const renderComponent = () => render(<SettingsContent onClose={mockOnClose} />);\n\n  it('Accept-Language is saved to store', () => {\n    const {getByTestId} = renderComponent();\n\n    const acceptLanguageInput = getByTestId('settings-accept_language-input');\n    const screenshotLocationInput = getByTestId('settings-screenshot_location-input');\n    const saveButton = getByTestId('settings-save-button');\n\n    fireEvent.change(acceptLanguageInput, {target: {value: 'cz-Cz'}});\n    fireEvent.change(screenshotLocationInput, {\n      target: {value: './path/location'},\n    });\n    fireEvent.click(saveButton);\n\n    expect(window.electron.store.set).toHaveBeenNthCalledWith(\n      1,\n      'userPreferences.screenshot.saveLocation',\n      './path/location'\n    );\n    expect(window.electron.store.set).toHaveBeenNthCalledWith(\n      2,\n      'userPreferences.webRequestHeaderAcceptLanguage',\n      'cz-Cz'\n    );\n\n    expect(mockOnClose).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Settings/SettingsContent.tsx",
    "content": "import {useId, useState} from 'react';\n\nimport Button from 'renderer/components/Button';\nimport {SettingsContentHeaders} from './SettingsContentHeaders';\n\ninterface Props {\n  onClose: () => void;\n}\n\nexport const SettingsContent = ({onClose}: Props) => {\n  const id = useId();\n  const [screenshotSaveLocation, setScreenshotSaveLocation] = useState<string>(\n    window.electron.store.get('userPreferences.screenshot.saveLocation')\n  );\n  const [webRequestHeaderAcceptLanguage, setWebRequestHeaderAcceptLanguage] = useState<string>(\n    window.electron.store.get('userPreferences.webRequestHeaderAcceptLanguage')\n  );\n\n  const onSave = () => {\n    if (screenshotSaveLocation === '' || screenshotSaveLocation == null) {\n      // eslint-disable-next-line no-alert\n      alert('Please enter a valid location.');\n      return;\n    }\n\n    window.electron.store.set('userPreferences.screenshot.saveLocation', screenshotSaveLocation);\n\n    window.electron.store.set(\n      'userPreferences.webRequestHeaderAcceptLanguage',\n      webRequestHeaderAcceptLanguage\n    );\n\n    onClose();\n  };\n\n  return (\n    <div className=\"w-[75vw] max-w-3xl\">\n      <h2>Screenshots</h2>\n      <div className=\"my-4 flex flex-col space-y-4 text-sm\">\n        <div className=\"flex flex-col space-y-2\">\n          <label htmlFor={id} className=\"flex flex-col\">\n            Location\n            <input\n              data-testid=\"settings-screenshot_location-input\"\n              type=\"text\"\n              id={id}\n              className=\"mt-2 rounded-md border border-gray-300 px-4 py-2 text-base focus-visible:outline-gray-400 dark:border-gray-500 dark:bg-slate-900\"\n              value={screenshotSaveLocation}\n              onChange={(e) => setScreenshotSaveLocation(e.target.value)}\n            />\n          </label>\n          <p className=\"text-sm text-gray-500 dark:text-gray-400\">\n            The location where screenshots will be saved.\n          </p>\n        </div>\n      </div>\n\n      <SettingsContentHeaders\n        acceptLanguage={webRequestHeaderAcceptLanguage}\n        setAcceptLanguage={setWebRequestHeaderAcceptLanguage}\n      />\n\n      <Button\n        data-testid=\"settings-save-button\"\n        className=\"mt-6 px-5 py-1\"\n        onClick={onSave}\n        isPrimary\n        isTextButton\n      >\n        Save\n      </Button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Settings/SettingsContentHeaders.tsx",
    "content": "import {FC, useId} from 'react';\n\ninterface ISettingsContentHeaders {\n  acceptLanguage: string;\n  setAcceptLanguage: (arg0: string) => void;\n}\n\nexport const SettingsContentHeaders: FC<ISettingsContentHeaders> = ({\n  acceptLanguage = '',\n  setAcceptLanguage,\n}) => {\n  const id = useId();\n\n  return (\n    <>\n      <h2>Request Headers</h2>\n      <div className=\"my-4 flex flex-col space-y-4 text-sm\">\n        <div className=\"flex flex-col space-y-2\">\n          <label htmlFor={id} className=\"flex flex-col\">\n            Accept-Language\n            <input\n              data-testid=\"settings-accept_language-input\"\n              type=\"text\"\n              id={id}\n              placeholder=\"example: en-US\"\n              className=\"mt-2 rounded-md border border-gray-300 px-4 py-2 text-base focus-visible:outline-gray-400 dark:border-gray-500 dark:bg-slate-900\"\n              value={acceptLanguage}\n              onChange={(e) => setAcceptLanguage(e.target.value)}\n            />\n          </label>\n          <p className=\"text-sm text-gray-500 dark:text-gray-400\">\n            HTTP request Accept-Language parameter (default: language from OS locale settings)\n          </p>\n        </div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Settings/index.tsx",
    "content": "import {useState} from 'react';\nimport Button from 'renderer/components/Button';\nimport Modal from 'renderer/components/Modal';\nimport {SettingsContent} from './SettingsContent';\n\ninterface Props {\n  closeFlyout: () => void;\n}\n\nexport const Settings = ({closeFlyout}: Props) => {\n  const [isOpen, setIsOpen] = useState(false);\n\n  const onClose = () => setIsOpen(false);\n\n  return (\n    <div className=\"relative right-2 w-80\">\n      <Button\n        className=\"right-2 m-0 flex w-80 !justify-start pl-6\"\n        onClick={() => {\n          setIsOpen(true);\n          closeFlyout();\n        }}\n      >\n        Settings\n      </Button>\n      <Modal isOpen={isOpen} onClose={onClose} title=\"Settings\">\n        <SettingsContent onClose={onClose} />\n      </Modal>\n    </div>\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/UITheme/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport useKeyboardShortcut, {\n  SHORTCUT_CHANNEL,\n} from 'renderer/components/KeyboardShortcutsManager/useKeyboardShortcut';\nimport {selectDarkMode, setDarkMode} from 'renderer/store/features/ui';\n\nconst UITheme = () => {\n  const darkMode = useSelector(selectDarkMode);\n  const dispatch = useDispatch();\n\n  const handleTheme = () => dispatch(setDarkMode(!darkMode));\n\n  useKeyboardShortcut(SHORTCUT_CHANNEL.THEME, handleTheme);\n\n  return (\n    <div className=\"flex flex-row items-center justify-start px-4\">\n      <span className=\"w-1/2\">UI Theme</span>\n      <div className=\"flex items-center gap-2 border-l px-4 dark:border-slate-400\">\n        <Button onClick={handleTheme} subtle>\n          <Icon icon={darkMode ? 'carbon:moon' : 'carbon:sun'} />\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nexport default UITheme;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/Zoom.tsx",
    "content": "import {useCallback} from 'react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport Button from 'renderer/components/Button';\nimport useKeyboardShortcut, {\n  SHORTCUT_CHANNEL,\n} from 'renderer/components/KeyboardShortcutsManager/useKeyboardShortcut';\nimport {selectZoomFactor, zoomIn, zoomOut} from 'renderer/store/features/renderer';\n\ninterface ZoomButtonProps {\n  children: React.ReactNode;\n  onClick: () => void;\n}\n\nconst ZoomButton = ({children, onClick}: ZoomButtonProps) => {\n  return (\n    <Button className=\"p-0 px-2\" onClick={onClick} subtle>\n      {children}\n    </Button>\n  );\n};\n\nconst Zoom = () => {\n  const zoomfactor = useSelector(selectZoomFactor);\n  const dispatch = useDispatch();\n\n  const onZoomIn = useCallback(() => {\n    dispatch(zoomIn());\n  }, [dispatch]);\n\n  const onZoomOut = useCallback(() => {\n    dispatch(zoomOut());\n  }, [dispatch]);\n\n  useKeyboardShortcut(SHORTCUT_CHANNEL.ZOOM_IN, onZoomIn);\n  useKeyboardShortcut(SHORTCUT_CHANNEL.ZOOM_OUT, onZoomOut);\n\n  return (\n    <div className=\"flex flex-row items-center justify-start px-4\">\n      <span className=\"w-1/2\">Zoom</span>\n      <div className=\"flex w-fit items-center gap-2 border-l px-4 dark:border-slate-400\">\n        <ZoomButton onClick={onZoomOut}>-</ZoomButton>\n        <span className=\"w-10 text-center\">{Math.ceil(zoomfactor * 100)}%</span>\n        <ZoomButton onClick={onZoomIn}>+</ZoomButton>\n      </div>\n    </div>\n  );\n};\n\nexport default Zoom;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/Flyout/index.tsx",
    "content": "import Notifications from 'renderer/components/Notifications/Notifications';\nimport {Divider} from 'renderer/components/Divider';\nimport Devtools from './Devtools';\nimport UITheme from './UITheme';\nimport Zoom from './Zoom';\nimport AllowInSecureSSL from './AllowInSecureSSL';\nimport ClearHistory from './ClearHistory';\nimport PreviewLayout from './PreviewLayout';\nimport Bookmark from './Bookmark';\nimport {Settings} from './Settings';\n\ninterface Props {\n  closeFlyout: () => void;\n}\n\nconst MenuFlyout = ({closeFlyout}: Props) => {\n  return (\n    <div className=\"absolute right-[4px] top-[26px] z-50 flex w-80 flex-col gap-2 rounded bg-slate-100 p-2 pb-0 text-sm shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40\">\n      <Zoom />\n      <UITheme />\n      <Devtools />\n      <AllowInSecureSSL />\n      <ClearHistory />\n      <Divider />\n\n      <PreviewLayout />\n\n      <Divider />\n\n      <div>\n        <Bookmark />\n        <Settings closeFlyout={closeFlyout} />\n      </div>\n      <Divider />\n      <Notifications />\n    </div>\n  );\n};\n\nexport default MenuFlyout;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Menu/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useDetectClickOutside} from 'react-detect-click-outside';\nimport Button from 'renderer/components/Button';\nimport {useDispatch, useSelector} from 'react-redux';\nimport {closeMenuFlyout, selectMenuFlyout} from 'renderer/store/features/ui';\nimport {selectNotifications} from 'renderer/store/features/renderer';\nimport useLocalStorage from 'renderer/components/useLocalStorage/useLocalStorage';\nimport NotificationsBubble from 'renderer/components/Notifications/NotificationsBubble';\nimport MenuFlyout from './Flyout';\n\nconst Menu = () => {\n  const dispatch = useDispatch();\n  const isMenuFlyoutOpen = useSelector(selectMenuFlyout);\n  const notifications = useSelector(selectNotifications);\n\n  const [hasNewNotifications, setHasNewNotifications] = useLocalStorage(\n    'hasNewNotifications',\n    true\n  );\n\n  const ref = useDetectClickOutside({\n    onTriggered: () => {\n      if (!isMenuFlyoutOpen) {\n        return;\n      }\n      dispatch(closeMenuFlyout(false));\n    },\n  });\n\n  const handleFlyout = () => {\n    dispatch(closeMenuFlyout(!isMenuFlyoutOpen));\n    setHasNewNotifications(false);\n  };\n\n  const onClose = () => {\n    dispatch(closeMenuFlyout(false));\n  };\n\n  return (\n    <div className=\"relative mr-2 flex items-center\" ref={ref}>\n      <Button onClick={handleFlyout} isActive={isMenuFlyoutOpen}>\n        <Icon icon=\"carbon:overflow-menu-vertical\" />\n        {notifications && notifications?.length > 0 && Boolean(hasNewNotifications) && (\n          <NotificationsBubble />\n        )}\n      </Button>\n      <div style={{visibility: isMenuFlyoutOpen ? 'visible' : 'hidden'}}>\n        <MenuFlyout closeFlyout={onClose} />\n      </div>\n    </div>\n  );\n};\n\nexport default Menu;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/NavigationControls.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {webViewPubSub} from 'renderer/lib/pubsub';\nimport Button from '../Button';\nimport useKeyboardShortcut, {\n  SHORTCUT_CHANNEL,\n  ShortcutChannel,\n} from '../KeyboardShortcutsManager/useKeyboardShortcut';\n\nexport const NAVIGATION_EVENTS = {\n  BACK: 'back',\n  FORWARD: 'forward',\n  RELOAD: 'reload',\n};\n\ninterface NavigationItemProps {\n  label: string;\n  icon: string;\n  action: () => void;\n}\n\nconst NavigationButton = ({label, icon, action}: NavigationItemProps) => {\n  const shortcutName: ShortcutChannel = label.toUpperCase() as ShortcutChannel;\n  useKeyboardShortcut(SHORTCUT_CHANNEL[shortcutName], action);\n  return (\n    <Button className=\"!rounded-full px-2 py-1\" onClick={action} title={label}>\n      <Icon icon={icon} />\n    </Button>\n  );\n};\n\nconst ITEMS: NavigationItemProps[] = [\n  {\n    label: 'Back',\n    icon: 'ic:round-arrow-back',\n    action: () => {\n      webViewPubSub.publish(NAVIGATION_EVENTS.BACK);\n    },\n  },\n  {\n    label: 'Forward',\n    icon: 'ic:round-arrow-forward',\n    action: () => {\n      webViewPubSub.publish(NAVIGATION_EVENTS.FORWARD);\n    },\n  },\n  {\n    label: 'Refresh',\n    icon: 'ic:round-refresh',\n    action: () => {\n      webViewPubSub.publish(NAVIGATION_EVENTS.RELOAD);\n    },\n  },\n];\n\nconst NavigationControls = () => {\n  return (\n    <div className=\"flex\">\n      {ITEMS.map((item) => (\n        // eslint-disable-next-line react/jsx-props-no-spreading\n        <NavigationButton {...item} key={item.label} />\n      ))}\n    </div>\n  );\n};\n\nexport default NavigationControls;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/PreviewSuiteSelector/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useDispatch, useSelector} from 'react-redux';\nimport {DropDown} from 'renderer/components/DropDown';\nimport {\n  selectActiveSuite,\n  selectSuites,\n  setActiveSuite,\n} from 'renderer/store/features/device-manager';\nimport {APP_VIEWS, setAppView} from 'renderer/store/features/ui';\n\nexport const PreviewSuiteSelector = () => {\n  const dispatch = useDispatch();\n  const suites = useSelector(selectSuites);\n  const activeSuite = useSelector(selectActiveSuite);\n  return (\n    <DropDown\n      label={<Icon icon=\"heroicons:swatch\" fontSize={18} />}\n      options={[\n        ...suites.map((suite) => ({\n          label: (\n            <div className=\"flex w-full items-center justify-between gap-12 whitespace-nowrap\">\n              <span>{suite.name}</span>\n              {suite.id === activeSuite.id ? <Icon icon=\"mdi:check\" /> : null}\n            </div>\n          ),\n          onClick: () => dispatch(setActiveSuite(suite.id)),\n        })),\n        {\n          type: 'separator',\n        },\n        {\n          label: (\n            <div className=\"flex w-full flex-shrink-0 items-center justify-between gap-12 whitespace-nowrap\">\n              <span>Manage Suites</span>\n            </div>\n          ),\n          onClick: () => {\n            dispatch(setAppView(APP_VIEWS.DEVICE_MANAGER));\n          },\n        },\n      ]}\n    />\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Shortcuts/ShortcutsModal/ShortcutButton.tsx",
    "content": "interface Props {\n  text: string[];\n}\n\nconst ShortcutButton = ({text}: Props) => {\n  const btnText = text[0].split('+');\n  const btnTextLength = btnText.length - 1;\n\n  const formatText = (value: string) => {\n    if (value === 'mod') {\n      if (navigator?.userAgent?.includes('Windows')) {\n        return `Ctrl`;\n      }\n      return `⌘`;\n    }\n\n    if (value.length === 1) return value.toUpperCase();\n    return value;\n  };\n  return (\n    <div>\n      {btnText.map((value, i) => (\n        <span key={value}>\n          <span className=\"rounded border border-gray-200 bg-gray-100 px-[6px] py-[2px] text-xs font-semibold text-gray-800 dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100\">\n            {formatText(value)}\n          </span>\n          {i < btnTextLength && <span className=\"px-1\">+</span>}\n        </span>\n      ))}\n    </div>\n  );\n};\n\nexport default ShortcutButton;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Shortcuts/ShortcutsModal/ShortcutName.tsx",
    "content": "interface Props {\n  text: string;\n}\n\nconst ShortcutName = ({text}: Props) => {\n  const formattedText = text.replace('_', ' ').toLowerCase();\n  return <div className=\"capitalize\">{formattedText}</div>;\n};\n\nexport default ShortcutName;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Shortcuts/ShortcutsModal/index.tsx",
    "content": "import {SHORTCUT_KEYS} from 'renderer/components/KeyboardShortcutsManager/constants';\nimport Modal from 'renderer/components/Modal';\nimport Button from 'renderer/components/Button';\nimport ShortcutName from './ShortcutName';\nimport ShortcutButton from './ShortcutButton';\n\ninterface Props {\n  isOpen: boolean;\n  onClose: () => void;\n}\n\nexport const shortcutsList = [\n  {\n    id: 0,\n    name: 'General Shortcuts',\n    shortcuts: Object.entries(SHORTCUT_KEYS).splice(0, 7),\n  },\n  {\n    id: 1,\n    name: 'Previewer Shorcuts',\n    shortcuts: Object.entries(SHORTCUT_KEYS).splice(7),\n  },\n];\n\nconst ShortcutsModal = ({isOpen, onClose}: Props) => {\n  return (\n    <>\n      <Modal isOpen={isOpen} onClose={onClose}>\n        <div className=\"flex w-[380px] flex-col gap-4 px-2\">\n          {Object.values(shortcutsList).map((category) => (\n            <div key={category.id}>\n              <h3 className=\"mb-3 border-b border-slate-600 pb-1 text-lg\">{category.name}</h3>\n              {category.shortcuts.map((value) => (\n                <div className=\"my-2.5 flex justify-between\" key={value[0]}>\n                  <ShortcutName text={value[0]} />\n                  <ShortcutButton text={value[1]} />\n                </div>\n              ))}\n            </div>\n          ))}\n          <div className=\"mb-2 flex flex-row justify-end gap-2\">\n            <Button className=\"px-2\" onClick={onClose}>\n              Close\n            </Button>\n          </div>\n        </div>\n      </Modal>\n    </>\n  );\n};\n\nexport default ShortcutsModal;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/Shortcuts/index.tsx",
    "content": "import {Icon} from '@iconify/react';\nimport {useState} from 'react';\nimport Button from 'renderer/components/Button';\nimport ShortcutsModal from './ShortcutsModal';\n\nconst Shortcuts = () => {\n  const [isOpen, setIsOpen] = useState<boolean>(false);\n  const handleClose = () => setIsOpen(!isOpen);\n\n  return (\n    <>\n      <Button onClick={handleClose} isActive={isOpen} title=\"View Shortcuts\">\n        <span className=\"relative\">\n          <Icon icon=\"iconoir:apple-shortcuts\" />\n        </span>\n      </Button>\n      <ShortcutsModal isOpen={isOpen} onClose={handleClose} />\n    </>\n  );\n};\n\nexport default Shortcuts;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/ToolBar/index.tsx",
    "content": "import {useDispatch, useSelector} from 'react-redux';\nimport {\n  selectIsCapturingScreenshot,\n  selectIsInspecting,\n  selectRotate,\n  setIsCapturingScreenshot,\n  setIsInspecting,\n  setRotate,\n  setNotifications,\n} from 'renderer/store/features/renderer';\nimport {Icon} from '@iconify/react';\nimport {ScreenshotAllArgs} from 'main/screenshot';\nimport {selectActiveSuite} from 'renderer/store/features/device-manager';\nimport WebPage from 'main/screenshot/webpage';\nimport {getDevicesMap} from 'common/deviceList';\nimport {updateWebViewHeightAndScale} from 'common/webViewUtils';\nimport {APP_VIEWS, setAppView} from 'renderer/store/features/ui';\nimport NavigationControls from './NavigationControls';\nimport Menu from './Menu';\nimport Button from '../Button';\nimport AddressBar from './AddressBar';\nimport ColorSchemeToggle from './ColorSchemeToggle';\nimport ModalLoader from '../ModalLoader';\nimport {PreviewSuiteSelector} from './PreviewSuiteSelector';\nimport useKeyboardShortcut, {\n  SHORTCUT_CHANNEL,\n} from '../KeyboardShortcutsManager/useKeyboardShortcut';\nimport Shortcuts from './Shortcuts';\nimport {ColorBlindnessControls} from './ColorBlindnessControls';\n\nconst Divider = () => <div className=\"h-6 w-px bg-gray-300 dark:bg-gray-700\" />;\n\nconst ToolBar = () => {\n  const rotateDevices = useSelector(selectRotate);\n  const isInspecting = useSelector(selectIsInspecting);\n  const isCapturingScreenshot = useSelector(selectIsCapturingScreenshot);\n  const activeSuite = useSelector(selectActiveSuite);\n  const dispatch = useDispatch();\n\n  function handleInspectShortcut() {\n    dispatch(setIsInspecting(!isInspecting));\n  }\n\n  const screenshotCaptureHandler = async () => {\n    if (isCapturingScreenshot) {\n      return;\n    }\n\n    dispatch(setIsCapturingScreenshot(true));\n    const webViews: NodeListOf<Electron.WebviewTag> = document.querySelectorAll('webView');\n    const screens: Array<ScreenshotAllArgs> = [];\n    const devices = activeSuite.devices.map((d) => getDevicesMap()[d]);\n    webViews.forEach(async (webview) => {\n      const device = devices.find((d) => d.name === webview.id);\n      const webPage = new WebPage(webview as unknown as Electron.WebContents);\n      const pageHeight = await webPage.getPageHeight();\n      const previousHeight = webview.style.height;\n      const previousTransform = webview.style.transform;\n      updateWebViewHeightAndScale(webview, pageHeight);\n      if (device != null) {\n        screens.push({\n          webContentsId: webview.getWebContentsId(),\n          device,\n          previousHeight,\n          previousTransform,\n          pageHeight,\n        });\n      }\n    });\n    await new Promise((resolve) => setTimeout(resolve, 1000));\n    await window.electron.ipcRenderer.invoke<Array<ScreenshotAllArgs>, any>(\n      'screenshot:All',\n      screens\n    );\n\n    // reset webviews to original size\n    webViews.forEach((webview) => {\n      const screent = screens.find((s) => s.device.name === webview.id);\n      if (screent != null) {\n        webview.style.height = screent.previousHeight;\n        webview.style.transform = screent.previousTransform;\n      }\n    });\n\n    dispatch(setIsCapturingScreenshot(false));\n  };\n\n  const handleClose = () => {\n    // Do nothing. Prevent Dialog from closing.\n  };\n\n  const handleRotate = () => {\n    dispatch(setRotate(!rotateDevices));\n  };\n\n  useKeyboardShortcut(SHORTCUT_CHANNEL.ROTATE_ALL, handleRotate);\n  useKeyboardShortcut(SHORTCUT_CHANNEL.SCREENSHOT_ALL, screenshotCaptureHandler);\n  useKeyboardShortcut(SHORTCUT_CHANNEL.INSPECT_ELEMENTS, handleInspectShortcut);\n\n  return (\n    <div className=\"flex items-center justify-between gap-2\">\n      <NavigationControls />\n      <AddressBar />\n      <Button onClick={handleRotate} isActive={rotateDevices} title=\"Rotate Devices\">\n        <Icon icon={rotateDevices ? 'mdi:phone-rotate-portrait' : 'mdi:phone-rotate-landscape'} />\n      </Button>\n      <Button\n        onClick={() => dispatch(setIsInspecting(!isInspecting))}\n        isActive={isInspecting}\n        title=\"Inspect Elements\"\n      >\n        <Icon icon=\"lucide:inspect\" />\n      </Button>\n      <Button\n        onClick={screenshotCaptureHandler}\n        isActive={isCapturingScreenshot}\n        title=\"Screenshot All WebViews\"\n      >\n        <Icon icon=\"lucide:camera\" />\n      </Button>\n      <ColorSchemeToggle />\n      <Shortcuts />\n      <ColorBlindnessControls />\n      <Divider />\n      <PreviewSuiteSelector />\n      <Button\n        onClick={() => {\n          dispatch(setAppView(APP_VIEWS.DEVICE_MANAGER));\n        }}\n      >\n        <Icon icon=\"lucide:plus\" width={16} />\n      </Button>\n      <Menu />\n      <ModalLoader isOpen={isCapturingScreenshot} onClose={handleClose} title=\"Screenshot\" />\n    </div>\n  );\n};\n\nexport default ToolBar;\n"
  },
  {
    "path": "desktop-app/src/renderer/components/VisionSimulationDropDown/index.tsx",
    "content": "import cx from 'classnames';\nimport {Icon} from '@iconify/react';\nimport {DropDown} from '../DropDown';\n\nconst MenuItemLabel = ({label, isActive}: {label: string; isActive: boolean}) => {\n  return (\n    <div className=\"flex w-full flex-shrink-0 items-center justify-normal gap-1 whitespace-nowrap\">\n      <Icon\n        icon=\"ic:round-check\"\n        className={cx('opacity-0', {\n          'opacity-100': isActive,\n        })}\n      />\n      <span\n        className={cx('capitalize', {\n          'font-semibold text-black dark:text-white': isActive,\n        })}\n      >\n        {label}\n      </span>\n    </div>\n  );\n};\n\nconst MenuItemHeader = ({label}: {label: string}) => {\n  return (\n    <div className=\"relative flex w-full min-w-44 items-center justify-between gap-1 whitespace-nowrap\">\n      <div className=\"absolute inset-0 flex items-center\" aria-hidden=\"true\">\n        <div className=\"w-full border-t border-gray-300 dark:border-gray-600\" />\n      </div>\n      <span className=\"mxl-1 z-10 flex-shrink-0 bg-slate-100 pr-2 dark:bg-slate-900\">{label}</span>\n    </div>\n  );\n};\n\nexport const SIMULATIONS = {\n  DEUTERANOPIA: 'deuteranopia',\n  DEUTERANOMALY: 'deuteranomaly',\n  PROTANOPIA: 'protanopia',\n  PROTANOMALY: 'protanomaly',\n  TRITANOPIA: 'tritanopia',\n  TRITANOMALY: 'tritanomaly',\n  ACHROMATOMALY: 'achromatomaly',\n  ACHROMATOPSIA: 'achromatopsia',\n  CATARACT: 'cataract',\n  FAR: 'farsightedness',\n  GLAUCOME: 'glaucoma',\n  SOLARIZE: 'solarize',\n  COLOR_CONTRAST_LOSS: 'color-contrast-loss',\n};\n\nexport const RED_GREEN = [\n  SIMULATIONS.DEUTERANOPIA,\n  SIMULATIONS.DEUTERANOMALY,\n  SIMULATIONS.PROTANOPIA,\n  SIMULATIONS.PROTANOMALY,\n];\nexport const BLUE_YELLOW = [SIMULATIONS.TRITANOPIA, SIMULATIONS.TRITANOMALY];\nexport const FULL = [SIMULATIONS.ACHROMATOMALY, SIMULATIONS.ACHROMATOPSIA];\nexport const VISUAL_IMPAIRMENTS = [\n  SIMULATIONS.CATARACT,\n  SIMULATIONS.FAR,\n  SIMULATIONS.GLAUCOME,\n  SIMULATIONS.COLOR_CONTRAST_LOSS,\n];\nexport const SUNLIGHT = [SIMULATIONS.SOLARIZE];\n\ninterface Props {\n  simulationName: string | undefined;\n  onChange: (name: string | undefined) => void;\n}\n\nexport const VisionSimulationDropDown = ({simulationName, onChange}: Props) => {\n  return (\n    <DropDown\n      className={cx('rounded-lg text-xs', {\n        'bg-slate-400/60': simulationName != null,\n      })}\n      label={<Icon icon=\"bx:low-vision\" fontSize={18} />}\n      options={[\n        {\n          label: <MenuItemHeader label=\"No deficiency\" />,\n          onClick: null,\n        },\n        {\n          label: <MenuItemLabel label=\"Disable tool\" isActive={simulationName === undefined} />,\n          onClick: () => {\n            onChange(undefined);\n          },\n        },\n        {\n          label: <MenuItemHeader label=\"Red-green deficiency\" />,\n          onClick: null,\n        },\n        ...RED_GREEN.map((x: string) => {\n          return {\n            label: <MenuItemLabel label={x} isActive={simulationName === x.toLowerCase()} />,\n            onClick: () => {\n              onChange(x.toLowerCase());\n            },\n          };\n        }),\n        {\n          label: <MenuItemHeader label=\"Blue-yellow deficiency\" />,\n          onClick: null,\n        },\n        ...BLUE_YELLOW.map((x: string) => {\n          return {\n            label: <MenuItemLabel label={x} isActive={simulationName === x.toLowerCase()} />,\n            onClick: () => {\n              onChange(x.toLowerCase());\n            },\n          };\n        }),\n        {\n          label: <MenuItemHeader label=\"Full color deficiency\" />,\n          onClick: null,\n        },\n        ...FULL.map((x: string) => {\n          return {\n            label: <MenuItemLabel label={x} isActive={simulationName === x.toLowerCase()} />,\n            onClick: () => {\n              onChange(x.toLowerCase());\n            },\n          };\n        }),\n        {\n          label: <MenuItemHeader label=\"Visual impairment\" />,\n          onClick: null,\n        },\n        ...VISUAL_IMPAIRMENTS.map((x: string) => {\n          return {\n            label: <MenuItemLabel label={x} isActive={simulationName === x.toLowerCase()} />,\n            onClick: () => {\n              onChange(x.toLowerCase());\n            },\n          };\n        }),\n        {\n          label: <MenuItemHeader label=\"Temporary impairment\" />,\n          onClick: null,\n        },\n        ...SUNLIGHT.map((x: string) => {\n          return {\n            label: <MenuItemLabel label={x} isActive={simulationName === x.toLowerCase()} />,\n            onClick: () => {\n              onChange(x.toLowerCase());\n            },\n          };\n        }),\n      ]}\n    />\n  );\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/components/useLocalStorage/useLocalStorage.tsx",
    "content": "import {useState, useEffect} from 'react';\n\nfunction useLocalStorage<T>(key: string, initialValue?: T) {\n  const [storedValue, setStoredValue] = useState<T | undefined>(() => {\n    try {\n      const item = window.localStorage.getItem(key);\n      return item ? (JSON.parse(item) as T) : undefined;\n    } catch (error) {\n      console.error('Error reading from localStorage', error);\n      return undefined;\n    }\n  });\n\n  useEffect(() => {\n    if (storedValue === undefined && initialValue !== undefined) {\n      setStoredValue(initialValue);\n      window.localStorage.setItem(key, JSON.stringify(initialValue));\n    }\n  }, [initialValue, storedValue, key]);\n\n  const setValue = (value: T | ((val: T | undefined) => T)) => {\n    try {\n      const valueToStore = value instanceof Function ? value(storedValue) : value;\n      setStoredValue(valueToStore);\n      window.localStorage.setItem(key, JSON.stringify(valueToStore));\n    } catch (error) {\n      console.error('Error setting localStorage', error);\n    }\n  };\n\n  const removeValue = () => {\n    try {\n      window.localStorage.removeItem(key);\n      setStoredValue(undefined);\n    } catch (error) {\n      console.error('Error removing from localStorage', error);\n    }\n  };\n\n  return [storedValue, setValue, removeValue] as const;\n}\n\nexport default useLocalStorage;\n"
  },
  {
    "path": "desktop-app/src/renderer/context/ThemeProvider/index.tsx",
    "content": "import React, {useEffect} from 'react';\nimport {useSelector} from 'react-redux';\nimport {selectDarkMode} from 'renderer/store/features/ui';\n\nconst ThemeProvider = ({children}: {children: React.ReactNode}) => {\n  const darkMode = useSelector(selectDarkMode);\n\n  useEffect(() => {\n    const body = document.querySelector('body');\n    'bg-slate-200 text-light-normal dark:bg-slate-800 dark:text-dark-normal'\n      .split(' ')\n      .forEach((className) => {\n        body?.classList.add(className);\n      });\n    if (darkMode) {\n      document.documentElement.classList.add('dark');\n    } else {\n      document.documentElement.classList.remove('dark');\n    }\n  }, [darkMode]);\n\n  return <div className=\"min-w-screen min-h-screen\">{children}</div>;\n};\n\nexport default ThemeProvider;\n"
  },
  {
    "path": "desktop-app/src/renderer/index.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"script-src 'self' 'unsafe-inline'\"\n    />\n    <title>Responsively App</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "desktop-app/src/renderer/index.tsx",
    "content": "import {IPC_MAIN_CHANNELS} from 'common/constants';\nimport {createRoot} from 'react-dom/client';\nimport App from './AppContent';\n\nconst container = document.getElementById('root')!;\nconst root = createRoot(container);\n\nwindow.electron.ipcRenderer\n  .invoke(IPC_MAIN_CHANNELS.APP_META, [])\n  .then((arg: any) => {\n    window.responsively = {webviewPreloadPath: arg.webviewPreloadPath};\n    return root.render(<App />);\n  })\n  .catch((err) => {\n    // eslint-disable-next-line no-console\n    console.error(err);\n  });\n"
  },
  {
    "path": "desktop-app/src/renderer/lib/pubsub/index.test.ts",
    "content": "import Bluebird from 'bluebird';\nimport PubSub from '.';\n\ndescribe('PubSub', () => {\n  it('should invoke subscribed callback', async () => {\n    const pubsub = new PubSub();\n    let invokedTest = false;\n    pubsub.subscribe('test', () => {\n      invokedTest = true;\n    });\n    await pubsub.publish('test');\n    expect(invokedTest).toBe(true);\n  });\n\n  it('should handler async handlers', async () => {\n    const pubsub = new PubSub();\n    pubsub.subscribe('test', async () => {\n      await Bluebird.delay(1000);\n      return 'handler1';\n    });\n    pubsub.subscribe('test', async () => {\n      await Bluebird.delay(2000);\n      return 'handler2';\n    });\n\n    const results = await pubsub.publish('test');\n    expect(results).toEqual([\n      {result: 'handler1', error: null},\n      {result: 'handler2', error: null},\n    ]);\n  });\n\n  it('should sends args to the handler', async () => {\n    const pubsub = new PubSub();\n    pubsub.subscribe('test', (arg: number) => {\n      return `test${arg}`;\n    });\n    const results = await pubsub.publish('test', 10);\n    expect(results).toHaveLength(1);\n    expect(results[0].result).toBe('test10');\n  });\n\n  it('should return error from the handler', async () => {\n    const pubsub = new PubSub();\n    pubsub.subscribe('test', () => {\n      throw new Error('test');\n    });\n    const results = await pubsub.publish('test');\n    expect(results).toHaveLength(1);\n    expect(results[0].result).toBeNull();\n    expect(results[0].error).not.toBeNull();\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/lib/pubsub/index.ts",
    "content": "import Bluebird from 'bluebird';\n\ninterface HandlerResult {\n  result: any;\n  error: any;\n}\n\ntype Handler = ((...args: any) => void) | ((...args: any) => Promise<any>);\n\nclass PubSub {\n  registry: {[key: string]: Handler[]};\n\n  constructor() {\n    this.registry = {};\n  }\n\n  subscribe = (topic: string, callback: Handler): void => {\n    if (!this.registry[topic]) {\n      this.registry[topic] = [];\n    }\n    this.registry[topic].push(callback);\n  };\n\n  unsubscribe = (topic: string, callback: Handler): void => {\n    if (!this.registry[topic]) {\n      return;\n    }\n    const index = this.registry[topic].indexOf(callback);\n    if (index > -1) {\n      this.registry[topic].splice(index, 1);\n    }\n  };\n\n  publish = async (topic: string, ...args: any[]): Promise<HandlerResult[]> => {\n    if (!this.registry[topic]) {\n      return [];\n    }\n\n    return Bluebird.map(this.registry[topic], async (callback: Handler) => {\n      try {\n        return {result: await callback(...args), error: null};\n      } catch (err) {\n        return {result: null, error: err};\n      }\n    });\n  };\n}\n\nexport const webViewPubSub = new PubSub();\n\nexport default PubSub;\n"
  },
  {
    "path": "desktop-app/src/renderer/preload.d.ts",
    "content": "import type {Channels} from 'common/constants';\n\ndeclare global {\n  interface Window {\n    electron: {\n      ipcRenderer: {\n        sendMessage<T>(channel: Channels, ...args: T[]): void;\n        on<T>(channel: string, func: (...args: T[]) => void): (() => void) | undefined;\n        once<T>(channel: string, func: (...args: T[]) => void): void;\n        invoke<T, P>(channel: string, ...args: T[]): Promise<P>;\n        removeListener<T>(channel: string, func: (...args: T[]) => void): void;\n        removeAllListeners(channel: string): void;\n      };\n      store: {\n        /* eslint-disable @typescript-eslint/no-explicit-any */\n        get: (key: string) => any;\n        set: (key: string, val: any) => void;\n      };\n    };\n    responsively: {\n      webviewPreloadPath: string;\n    };\n  }\n}\n\nexport {};\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/bookmarks/index.ts",
    "content": "import {createSlice} from '@reduxjs/toolkit';\nimport type {PayloadAction} from '@reduxjs/toolkit';\nimport {v4 as uuidv4} from 'uuid';\nimport type {RootState} from '../..';\n\nexport interface IBookmarks {\n  id?: string;\n  name: string;\n  address: string;\n}\nexport interface BookmarksState {\n  bookmarks: IBookmarks[];\n}\n\nconst initialState: BookmarksState = {\n  bookmarks: window.electron.store.get('bookmarks'),\n};\n\nexport const bookmarksSlice = createSlice({\n  name: 'bookmarks',\n  initialState,\n  reducers: {\n    addBookmark: (state, action: PayloadAction<IBookmarks>) => {\n      const bookmarks: IBookmarks[] = window.electron.store.get('bookmarks');\n      if (action.payload.id) {\n        const index = bookmarks.findIndex((bookmark) => bookmark.id === action.payload.id);\n        bookmarks[index] = action.payload;\n      } else {\n        const updatedPayload = {\n          ...action.payload,\n          id: uuidv4(),\n        };\n        bookmarks.push(updatedPayload);\n      }\n      state.bookmarks = bookmarks;\n      window.electron.store.set('bookmarks', bookmarks);\n    },\n    removeBookmark: (state, action) => {\n      const bookmarks = window.electron.store.get('bookmarks');\n      const bookmarkIndex = state.bookmarks.findIndex(\n        (bookmark) => bookmark.id === action.payload.id\n      );\n      if (bookmarkIndex === -1) {\n        return;\n      }\n      bookmarks.splice(bookmarkIndex, 1);\n      state.bookmarks = bookmarks;\n      window.electron.store.set('bookmarks', bookmarks);\n    },\n  },\n});\n\n// Action creators are generated for each case reducer function\nexport const {addBookmark, removeBookmark} = bookmarksSlice.actions;\n\nexport const selectBookmarks = (state: RootState) => state.bookmarks.bookmarks;\n\nexport default bookmarksSlice.reducer;\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/design-overlay/index.test.ts",
    "content": "import {RootState} from 'renderer/store';\nimport {configureStore} from '@reduxjs/toolkit';\nimport {type Mock} from 'vitest';\nimport designOverlayReducer, {\n  setDesignOverlay,\n  removeDesignOverlay,\n  selectDesignOverlay,\n  selectDesignOverlayEnabled,\n  type DesignOverlayState,\n} from './index';\n\nconst mockStore = {\n  get: vi.fn(),\n  set: vi.fn(),\n};\n\nbeforeEach(() => {\n  vi.clearAllMocks();\n  (window.electron.store.get as Mock) = mockStore.get;\n  (window.electron.store.set as Mock) = mockStore.set;\n  mockStore.get.mockReturnValue({});\n});\n\ndescribe('designOverlaySlice', () => {\n  const createStore = () =>\n    configureStore({\n      reducer: {\n        designOverlay: designOverlayReducer,\n      },\n    });\n\n  const mockOverlayState: DesignOverlayState = {\n    image:\n      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',\n    opacity: 50,\n    position: 'overlay',\n    enabled: true,\n  };\n\n  const resolution = '390x844';\n\n  describe('setDesignOverlay', () => {\n    it('should add a new overlay to the state', () => {\n      const store = createStore();\n      store.dispatch(\n        setDesignOverlay({\n          resolution,\n          overlayState: mockOverlayState,\n        })\n      );\n\n      const state = store.getState();\n      expect(state.designOverlay[resolution]).toEqual(mockOverlayState);\n    });\n\n    it('should persist overlay to electron store', () => {\n      const store = createStore();\n      store.dispatch(\n        setDesignOverlay({\n          resolution,\n          overlayState: mockOverlayState,\n        })\n      );\n\n      expect(mockStore.set).toHaveBeenCalledWith(\n        'userPreferences.designOverlays',\n        expect.objectContaining({\n          [resolution]: mockOverlayState,\n        })\n      );\n    });\n\n    it('should update existing overlay', () => {\n      const store = createStore();\n      const updatedState = {...mockOverlayState, opacity: 75};\n\n      store.dispatch(\n        setDesignOverlay({\n          resolution,\n          overlayState: mockOverlayState,\n        })\n      );\n\n      store.dispatch(\n        setDesignOverlay({\n          resolution,\n          overlayState: updatedState,\n        })\n      );\n\n      const state = store.getState();\n      expect(state.designOverlay[resolution].opacity).toBe(75);\n    });\n  });\n\n  describe('removeDesignOverlay', () => {\n    it('should remove overlay from state', () => {\n      const store = createStore();\n      store.dispatch(\n        setDesignOverlay({\n          resolution,\n          overlayState: mockOverlayState,\n        })\n      );\n\n      store.dispatch(removeDesignOverlay({resolution}));\n\n      const state = store.getState();\n      expect(state.designOverlay[resolution]).toBeUndefined();\n    });\n\n    it('should remove overlay from electron store', () => {\n      const store = createStore();\n      mockStore.get.mockReturnValue({\n        [resolution]: mockOverlayState,\n      });\n\n      store.dispatch(removeDesignOverlay({resolution}));\n\n      expect(mockStore.set).toHaveBeenCalledWith('userPreferences.designOverlays', {});\n    });\n  });\n\n  describe('selectDesignOverlay', () => {\n    it('should return overlay for existing resolution', () => {\n      const store = createStore();\n      store.dispatch(\n        setDesignOverlay({\n          resolution,\n          overlayState: mockOverlayState,\n        })\n      );\n\n      const state = store.getState() as RootState;\n      const selector = selectDesignOverlay(state);\n      const overlay = selector(resolution);\n\n      expect(overlay).toEqual(mockOverlayState);\n    });\n\n    it('should return undefined for non-existent resolution', () => {\n      const store = createStore();\n      const state = store.getState() as RootState;\n      const selector = selectDesignOverlay(state);\n      const overlay = selector('999x999');\n\n      expect(overlay).toBeUndefined();\n    });\n  });\n\n  describe('selectDesignOverlayEnabled', () => {\n    it('should return true when overlay is enabled', () => {\n      const store = createStore();\n      store.dispatch(\n        setDesignOverlay({\n          resolution,\n          overlayState: mockOverlayState,\n        })\n      );\n\n      const state = store.getState() as RootState;\n      const selector = selectDesignOverlayEnabled(state);\n      const enabled = selector(resolution);\n\n      expect(enabled).toBe(true);\n    });\n\n    it('should return false when overlay is disabled', () => {\n      const store = createStore();\n      store.dispatch(\n        setDesignOverlay({\n          resolution,\n          overlayState: {...mockOverlayState, enabled: false},\n        })\n      );\n\n      const state = store.getState() as RootState;\n      const selector = selectDesignOverlayEnabled(state);\n      const enabled = selector(resolution);\n\n      expect(enabled).toBe(false);\n    });\n\n    it('should return false when overlay does not exist', () => {\n      const store = createStore();\n      const state = store.getState() as RootState;\n      const selector = selectDesignOverlayEnabled(state);\n      const enabled = selector('999x999');\n\n      expect(enabled).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/design-overlay/index.ts",
    "content": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport type {RootState} from '../..';\n\nexport type DesignOverlayPosition = 'overlay' | 'side';\n\nexport interface DesignOverlayState {\n  image: string;\n  opacity: number;\n  position: DesignOverlayPosition;\n  enabled: boolean;\n  fileName?: string;\n}\n\nexport type ViewResolution = string;\n\nconst loadPersistedOverlays = (): {\n  [key: ViewResolution]: DesignOverlayState;\n} => {\n  try {\n    const overlays = window.electron.store.get('userPreferences.designOverlays') || {};\n    return overlays as {[key: ViewResolution]: DesignOverlayState};\n  } catch {\n    return {};\n  }\n};\n\nconst initialState: {[key: ViewResolution]: DesignOverlayState} = loadPersistedOverlays();\n\nexport const designOverlaySlice = createSlice({\n  name: 'designOverlay',\n  initialState,\n  reducers: {\n    setDesignOverlay: (\n      state,\n      action: PayloadAction<{\n        overlayState: DesignOverlayState;\n        resolution: ViewResolution;\n      }>\n    ) => {\n      state[action.payload.resolution] = action.payload.overlayState;\n\n      const overlays = window.electron.store.get('userPreferences.designOverlays') || {};\n      overlays[action.payload.resolution] = action.payload.overlayState;\n      window.electron.store.set('userPreferences.designOverlays', overlays);\n    },\n    removeDesignOverlay: (\n      state,\n      action: PayloadAction<{\n        resolution: ViewResolution;\n      }>\n    ) => {\n      delete state[action.payload.resolution];\n\n      const overlays = window.electron.store.get('userPreferences.designOverlays') || {};\n      delete overlays[action.payload.resolution];\n      window.electron.store.set('userPreferences.designOverlays', overlays);\n    },\n  },\n});\n\nexport const {setDesignOverlay, removeDesignOverlay} = designOverlaySlice.actions;\n\nexport const selectDesignOverlay =\n  (state: RootState) =>\n  (resolution: ViewResolution | undefined): DesignOverlayState | undefined => {\n    if (resolution && state.designOverlay[resolution]) {\n      return state.designOverlay[resolution];\n    }\n    return undefined;\n  };\n\nexport const selectDesignOverlayEnabled =\n  (state: RootState) => (resolution: ViewResolution | undefined) => {\n    const overlay = selectDesignOverlay(state)(resolution);\n    return overlay?.enabled ?? false;\n  };\n\nexport default designOverlaySlice.reducer;\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/device-manager/index.ts",
    "content": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport {getDevicesMap, Device} from 'common/deviceList';\nimport type {RootState} from '../..';\nimport {sanitizeSuites} from './utils';\n\nconst activeDeviceIds: string[] = window.electron.store.get('deviceManager.activeDevices');\n\nconst DEFAULT_DEVICES: Device[] = activeDeviceIds.map((id) => getDevicesMap()[id]);\n\nexport interface PreviewSuite {\n  id: string;\n  name: string;\n  devices: string[];\n}\n\nexport type PreviewSuites = PreviewSuite[];\n\nexport interface DeviceManagerState {\n  devices: Device[];\n  activeSuite: string;\n  suites: PreviewSuites;\n}\n\nsanitizeSuites();\n\nconst initialState: DeviceManagerState = {\n  devices: DEFAULT_DEVICES,\n  activeSuite: 'default',\n  suites: window.electron.store.get('deviceManager.previewSuites'),\n};\n\nexport const deviceManagerSlice = createSlice({\n  name: 'deviceManager',\n  initialState,\n  reducers: {\n    setDevices: (state, action: PayloadAction<Device[]>) => {\n      state.devices = action.payload;\n      window.electron.store.set(\n        'deviceManager.activeDevices',\n        action.payload.map((device) => device.id)\n      );\n    },\n    setSuiteDevices: (state, action: PayloadAction<{suite: string; devices: string[]}>) => {\n      const {suite, devices} = action.payload;\n      const suites: PreviewSuites = window.electron.store.get('deviceManager.previewSuites');\n      const suiteIndex = suites.findIndex((s) => s.id === suite);\n      if (suiteIndex === -1) {\n        return;\n      }\n      suites[suiteIndex].devices = devices;\n      state.suites = suites;\n      window.electron.store.set('deviceManager.previewSuites', suites);\n    },\n    setActiveSuite(state, action: PayloadAction<string>) {\n      state.activeSuite = action.payload;\n    },\n    addSuite(state, action: PayloadAction<PreviewSuite>) {\n      const suites = window.electron.store.get('deviceManager.previewSuites');\n      suites.push(action.payload);\n      state.suites = suites;\n      state.activeSuite = action.payload.id;\n      window.electron.store.set('deviceManager.previewSuites', suites);\n    },\n    addSuites(state, action: PayloadAction<PreviewSuite[]>) {\n      const existingSuites = window.electron.store.get('deviceManager.previewSuites');\n\n      const suitesMap = new Map();\n      action.payload.forEach((suite) => suitesMap.set(suite.name, suite));\n\n      existingSuites.forEach((suite: PreviewSuite) => {\n        if (!suitesMap.has(suite.name)) {\n          suitesMap.set(suite.name, suite);\n        }\n      });\n\n      const mergedSuites = Array.from(suitesMap.values());\n\n      state.suites = mergedSuites;\n      state.activeSuite = action.payload[0].id;\n      window.electron.store.set('deviceManager.previewSuites', mergedSuites);\n    },\n    deleteSuite(state, action: PayloadAction<string>) {\n      const suites: PreviewSuite[] = window.electron.store.get('deviceManager.previewSuites');\n      const suiteIndex = suites.findIndex((s) => s.id === action.payload);\n      if (suiteIndex === -1) {\n        return;\n      }\n      suites.splice(suiteIndex, 1);\n      state.suites = suites;\n      state.activeSuite = suites[0].id;\n      window.electron.store.set('deviceManager.previewSuites', suites);\n    },\n    deleteAllSuites(state) {\n      const defaultSuites = {\n        id: 'default',\n        name: 'Default',\n        devices: ['10008', '10013', '10015'],\n      };\n      const suites: PreviewSuite[] = window.electron.store.get('deviceManager.previewSuites');\n      window.electron.store.set('deviceManager.previewSuites', [defaultSuites]);\n      state.suites = [defaultSuites];\n    },\n  },\n});\n\n// Action creators are generated for each case reducer function\nexport const {\n  setDevices,\n  setSuiteDevices,\n  setActiveSuite,\n  addSuite,\n  addSuites,\n  deleteSuite,\n  deleteAllSuites,\n} = deviceManagerSlice.actions;\n\nexport const selectSuites = (state: RootState) => state.deviceManager.suites;\n\nexport const selectActiveSuite = (state: RootState): PreviewSuite => {\n  const {activeSuite, suites} = state.deviceManager;\n  return suites.find((suite) => suite.id === activeSuite) ?? suites[0];\n};\n\nexport default deviceManagerSlice.reducer;\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/device-manager/utils.ts",
    "content": "import {getDevicesMap} from 'common/deviceList';\nimport type {PreviewSuites} from '.';\n\nexport const sanitizeSuites = () => {\n  const existingSuites: PreviewSuites = window.electron.store.get('deviceManager.previewSuites');\n  if (existingSuites == null || existingSuites.length === 0) {\n    window.electron.store.set('deviceManager.previewSuites', [\n      {\n        id: 'default',\n        name: 'Default',\n        devices: ['10008', '10013', '10015'],\n      },\n    ]);\n\n    return;\n  }\n\n  let dirty = false;\n\n  existingSuites.forEach((suite) => {\n    const availableDevices = suite.devices.filter((id) => getDevicesMap()[id] != null);\n    if (availableDevices.length !== suite.devices.length) {\n      suite.devices = availableDevices;\n      dirty = true;\n    }\n  });\n\n  if (dirty) {\n    window.electron.store.set('deviceManager.previewSuites', existingSuites);\n  }\n};\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/devtools/index.ts",
    "content": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport {DOCK_POSITION} from 'common/constants';\nimport type {RootState} from '../..';\n\nconst defaultBounds = {x: 0, y: 0, width: 0, height: 0};\n\nexport type DockPosition = (typeof DOCK_POSITION)[keyof typeof DOCK_POSITION];\n\nexport interface DevtoolsState {\n  bounds: {\n    x: number;\n    y: number;\n    width: number;\n    height: number;\n  };\n  isOpen: boolean;\n  dockPosition: DockPosition;\n  webViewId: number;\n}\n\nconst initialState: DevtoolsState = {\n  bounds: defaultBounds,\n  isOpen: false,\n  dockPosition: window.electron.store.get('devtools.dockPosition'),\n  webViewId: -1,\n};\n\nexport const devtoolsSlice = createSlice({\n  name: 'devtools',\n  initialState,\n  reducers: {\n    setBounds: (state, action: PayloadAction<DevtoolsState['bounds']>) => {\n      state.bounds = action.payload;\n    },\n    setDevtoolsOpen: (state, action: PayloadAction<number>) => {\n      if (state.dockPosition === DOCK_POSITION.UNDOCKED) {\n        return;\n      }\n      state.isOpen = true;\n      state.webViewId = action.payload;\n    },\n    setDevtoolsClose: (state) => {\n      state.isOpen = false;\n      state.webViewId = -1;\n    },\n    setDockPosition: (state, action: PayloadAction<DockPosition>) => {\n      window.electron.store.set('devtools.dockPosition', action.payload);\n      state.dockPosition = action.payload;\n    },\n  },\n});\n\n// Action creators are generated for each case reducer function\nexport const {setBounds, setDevtoolsOpen, setDevtoolsClose, setDockPosition} =\n  devtoolsSlice.actions;\n\nexport const selectIsDevtoolsOpen = (state: RootState) => state.devtools.isOpen;\n\nexport const selectDevtoolsWebviewId = (state: RootState) => state.devtools.webViewId;\n\nexport const selectDockPosition = (state: RootState) => state.devtools.dockPosition;\n\nexport default devtoolsSlice.reducer;\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/renderer/index.ts",
    "content": "import {createSlice} from '@reduxjs/toolkit';\nimport type {PayloadAction} from '@reduxjs/toolkit';\nimport {IPC_MAIN_CHANNELS, Notification, PREVIEW_LAYOUTS, PreviewLayout} from 'common/constants';\nimport type {RootState} from '../..';\n\nexport interface RendererState {\n  address: string;\n  pageTitle: string;\n  individualZoomFactor: number;\n  zoomFactor: number;\n  rotate: boolean;\n  isInspecting: boolean | undefined;\n  layout: PreviewLayout;\n  isCapturingScreenshot: boolean;\n  notifications: Notification[] | null;\n}\n\nconst zoomSteps = [0.25, 0.33, 0.5, 0.55, 0.67, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2];\n\nconst urlFromQueryParam = () => {\n  const params = new URLSearchParams(window.location.search);\n  const url = params.get('urlToOpen');\n  if (url !== 'undefined') {\n    return url;\n  }\n  return undefined;\n};\n\nconst initialState: RendererState = {\n  address: urlFromQueryParam() ?? window.electron.store.get('homepage'),\n  pageTitle: '',\n  individualZoomFactor: zoomSteps[window.electron.store.get('renderer.individualZoomStepIndex')],\n  zoomFactor: zoomSteps[window.electron.store.get('renderer.zoomStepIndex')],\n  rotate: false,\n  isInspecting: undefined,\n  layout: window.electron.store.get('ui.previewLayout'),\n  isCapturingScreenshot: false,\n  notifications: null,\n};\n\nexport const updateFileWatcher = (newURL: string) => {\n  if (newURL.startsWith('file://') && (newURL.endsWith('.html') || newURL.endsWith('.htm')))\n    window.electron.ipcRenderer.sendMessage(IPC_MAIN_CHANNELS.START_WATCHING_FILE, {\n      path: newURL,\n    });\n  else window.electron.ipcRenderer.sendMessage(IPC_MAIN_CHANNELS.STOP_WATCHER);\n};\n\nexport const rendererSlice = createSlice({\n  name: 'renderer',\n  initialState,\n  reducers: {\n    setAddress: (state, action: PayloadAction<string>) => {\n      if (action.payload !== state.address) {\n        updateFileWatcher(action.payload);\n        state.address = action.payload;\n      }\n    },\n    setPageTitle: (state, action: PayloadAction<string>) => {\n      if (action.payload !== state.pageTitle) {\n        state.pageTitle = action.payload;\n      }\n    },\n    zoomIn: (state) => {\n      const index =\n        state.layout === PREVIEW_LAYOUTS.INDIVIDUAL\n          ? zoomSteps.indexOf(state.individualZoomFactor)\n          : zoomSteps.indexOf(state.zoomFactor);\n\n      if (index < zoomSteps.length - 1) {\n        if (state.layout === PREVIEW_LAYOUTS.INDIVIDUAL) {\n          const newIndex = index + 1;\n          state.individualZoomFactor = zoomSteps[newIndex];\n          window.electron.store.set('renderer.individualZoomStepIndex', newIndex);\n        } else {\n          const newIndex = index + 1;\n          state.zoomFactor = zoomSteps[newIndex];\n          window.electron.store.set('renderer.zoomStepIndex', newIndex);\n        }\n      }\n    },\n    zoomOut: (state) => {\n      const index =\n        state.layout === PREVIEW_LAYOUTS.INDIVIDUAL\n          ? zoomSteps.indexOf(state.individualZoomFactor)\n          : zoomSteps.indexOf(state.zoomFactor);\n      if (index > 0) {\n        if (state.layout === PREVIEW_LAYOUTS.INDIVIDUAL) {\n          const newIndex = index - 1;\n          state.individualZoomFactor = zoomSteps[newIndex];\n          window.electron.store.set('renderer.individualZoomStepIndex', newIndex);\n        } else {\n          const newIndex = index - 1;\n          state.zoomFactor = zoomSteps[newIndex];\n          window.electron.store.set('renderer.zoomStepIndex', newIndex);\n        }\n      }\n    },\n    setRotate: (state, action: PayloadAction<boolean>) => {\n      state.rotate = action.payload;\n    },\n    setIsInspecting: (state, action: PayloadAction<boolean>) => {\n      state.isInspecting = action.payload;\n    },\n    setLayout: (state, action: PayloadAction<PreviewLayout>) => {\n      state.layout = action.payload;\n      window.electron.store.set('ui.previewLayout', action.payload);\n    },\n    setIsCapturingScreenshot: (state, action: PayloadAction<boolean>) => {\n      state.isCapturingScreenshot = action.payload;\n    },\n    setNotifications: (state, action: PayloadAction<Notification>) => {\n      const notifications = state.notifications || [];\n      const index = notifications.findIndex(\n        (notification: Notification) => notification.id === action.payload.id\n      );\n\n      if (index === -1) {\n        state.notifications = [...notifications, action.payload];\n      }\n    },\n  },\n});\n\n// Action creators are generated for each case reducer function\nexport const {\n  setAddress,\n  zoomIn,\n  zoomOut,\n  setRotate,\n  setIsInspecting,\n  setLayout,\n  setIsCapturingScreenshot,\n  setPageTitle,\n  setNotifications,\n} = rendererSlice.actions;\n\n// Use different zoom factor based on state's current layout\nexport const selectZoomFactor = (state: RootState) => {\n  if (state.renderer.layout === PREVIEW_LAYOUTS.INDIVIDUAL) {\n    return state.renderer.individualZoomFactor;\n  }\n  return state.renderer.zoomFactor;\n};\n\nexport const selectAddress = (state: RootState) => state.renderer.address;\nexport const selectPageTitle = (state: RootState) => state.renderer.pageTitle;\nexport const selectRotate = (state: RootState) => state.renderer.rotate;\nexport const selectIsInspecting = (state: RootState) => state.renderer.isInspecting;\nexport const selectLayout = (state: RootState) => state.renderer.layout;\nexport const selectIsCapturingScreenshot = (state: RootState) =>\n  state.renderer.isCapturingScreenshot;\nexport const selectNotifications = (state: RootState) => state.renderer.notifications;\n\nexport default rendererSlice.reducer;\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/ruler/index.ts",
    "content": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport type {RootState} from '../..';\n\nexport interface Coordinates {\n  deltaX: number;\n  deltaY: number;\n  scrollX?: number;\n  scrollY?: number;\n  innerHeight: number;\n  innerWidth: number;\n}\n\nexport type RulersState = {\n  isRulerEnabled: boolean;\n  rulerCoordinates: Coordinates;\n};\n\nexport type ViewResolution = string;\n\nconst initialState: {[key: ViewResolution]: RulersState} = {};\n\nexport const rulerSlice = createSlice({\n  name: 'rulers',\n  initialState,\n  reducers: {\n    setRuler: (\n      state,\n      action: PayloadAction<{\n        rulerState: RulersState;\n        resolution: ViewResolution;\n      }>\n    ) => {\n      state[action.payload.resolution] = action.payload.rulerState;\n    },\n  },\n});\n\n// Action creators are generated for each case reducer function\nexport const {setRuler} = rulerSlice.actions;\n\nexport const selectRuler =\n  (state: RootState) =>\n  (resolution: ViewResolution | undefined): RulersState | undefined => {\n    if (resolution && state.rulers[resolution]) {\n      return state.rulers[resolution];\n    }\n    return undefined;\n  };\n\nexport const selectRulerEnabled =\n  (state: RootState) => (resolution: ViewResolution | undefined) => {\n    if (resolution && state.rulers[resolution]) {\n      return state.rulers[resolution].isRulerEnabled;\n    }\n    return false;\n  };\n\nexport default rulerSlice.reducer;\n"
  },
  {
    "path": "desktop-app/src/renderer/store/features/ui/index.ts",
    "content": "import {createSlice} from '@reduxjs/toolkit';\nimport type {PayloadAction} from '@reduxjs/toolkit';\nimport type {RootState} from '../..';\n\nexport const APP_VIEWS = {\n  BROWSER: 'BROWSER',\n  DEVICE_MANAGER: 'DEVICE_MANAGER',\n} as const;\n\nexport type AppView = (typeof APP_VIEWS)[keyof typeof APP_VIEWS];\n\nexport interface UIState {\n  darkMode: boolean;\n  appView: AppView;\n  menuFlyout: boolean;\n}\n\nconst initialState: UIState = {\n  darkMode: window.electron.store.get('ui.darkMode'),\n  appView: APP_VIEWS.BROWSER,\n  menuFlyout: false,\n};\n\nexport const uiSlice = createSlice({\n  name: 'ui',\n  initialState,\n  reducers: {\n    setDarkMode: (state, action: PayloadAction<boolean>) => {\n      state.darkMode = action.payload;\n      window.electron.store.set('ui.darkMode', action.payload);\n    },\n    setAppView: (state, action: PayloadAction<AppView>) => {\n      state.appView = action.payload;\n    },\n    closeMenuFlyout: (state, action: PayloadAction<boolean>) => {\n      state.menuFlyout = action.payload;\n    },\n  },\n});\n\n// Action creators are generated for each case reducer function\nexport const {setDarkMode, setAppView, closeMenuFlyout} = uiSlice.actions;\n\nexport const selectDarkMode = (state: RootState) => state.ui.darkMode;\nexport const selectAppView = (state: RootState) => state.ui.appView;\nexport const selectMenuFlyout = (state: RootState) => state.ui.menuFlyout;\n\nexport default uiSlice.reducer;\n"
  },
  {
    "path": "desktop-app/src/renderer/store/index.ts",
    "content": "import {configureStore} from '@reduxjs/toolkit';\n\nimport deviceManagerReducer from './features/device-manager';\nimport devtoolsReducer from './features/devtools';\nimport rendererReducer from './features/renderer';\nimport rulersReducer from './features/ruler';\nimport uiReducer from './features/ui';\nimport bookmarkReducer from './features/bookmarks';\nimport designOverlayReducer from './features/design-overlay';\n\nexport const store = configureStore({\n  reducer: {\n    renderer: rendererReducer,\n    ui: uiReducer,\n    deviceManager: deviceManagerReducer,\n    devtools: devtoolsReducer,\n    bookmarks: bookmarkReducer,\n    rulers: rulersReducer,\n    designOverlay: designOverlayReducer,\n  },\n});\n\n// Infer the `RootState` and `AppDispatch` types from the store itself\nexport type RootState = ReturnType<typeof store.getState>;\n// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}\nexport type AppDispatch = typeof store.dispatch;\n"
  },
  {
    "path": "desktop-app/src/store/index.ts",
    "content": "import path from 'path';\nimport {homedir} from 'os';\nimport {DOCK_POSITION, PREVIEW_LAYOUTS} from '../common/constants';\nimport {migrations} from './migrations';\n\nconst Store = require('electron-store');\n\nconst schema = {\n  ui: {\n    type: 'object',\n    properties: {\n      darkMode: {\n        type: 'boolean',\n        default: true,\n      },\n      previewlayout: {\n        enum: Object.values(PREVIEW_LAYOUTS),\n        default: PREVIEW_LAYOUTS.FLEX,\n      },\n    },\n  },\n  renderer: {\n    type: 'object',\n    properties: {\n      individualZoomStepIndex: {\n        type: 'number',\n        default: 8,\n      },\n      zoomStepIndex: {\n        type: 'number',\n        default: 3,\n      },\n    },\n    default: {},\n  },\n  devtools: {\n    type: 'object',\n    properties: {\n      dockPosition: {\n        type: 'string',\n        enum: Object.keys(DOCK_POSITION),\n        default: DOCK_POSITION.BOTTOM,\n      },\n    },\n  },\n  deviceManager: {\n    type: 'object',\n    properties: {\n      // TODO: remove this in a future version of v1.2.0\n      activeDevices: {\n        type: 'array',\n        items: {\n          type: 'string',\n        },\n        default: ['10008', '10013', '10015'],\n      },\n      previewSuites: {\n        type: 'array',\n        items: {\n          type: 'object',\n          properties: {\n            id: {\n              type: 'string',\n            },\n            name: {\n              type: 'string',\n            },\n            devices: {\n              type: 'array',\n              items: {\n                type: 'string',\n              },\n            },\n          },\n        },\n        default: [\n          {\n            id: 'default',\n            name: 'Default',\n            devices: ['10008', '10013', '10015'],\n          },\n        ],\n      },\n      customDevices: {\n        type: 'array',\n        items: {\n          type: 'object',\n          properties: {\n            id: {\n              type: 'string',\n            },\n            name: {\n              type: 'string',\n            },\n            width: {\n              type: 'number',\n            },\n            height: {\n              type: 'number',\n            },\n            userAgent: {\n              type: 'string',\n            },\n            type: {\n              type: 'string',\n            },\n            dpr: {\n              type: 'number',\n            },\n            isTouchCapable: {\n              type: 'boolean',\n            },\n            isMobileCapable: {\n              type: 'boolean',\n            },\n            capabilities: {\n              type: 'array',\n              items: {\n                type: 'string',\n              },\n              default: [],\n            },\n            isCustom: {\n              type: 'boolean',\n              default: true,\n            },\n          },\n        },\n        default: [],\n      },\n    },\n    default: {},\n  },\n  userPreferences: {\n    type: 'object',\n    properties: {\n      webRequestHeaderAcceptLanguage: {\n        type: 'string',\n        default: '',\n      },\n      allowInsecureSSLConnections: {\n        type: 'boolean',\n        default: false,\n      },\n      guides: {\n        type: 'array',\n        items: {\n          type: 'object',\n          properties: {\n            positions: {\n              type: 'array',\n              items: {\n                type: 'number',\n              },\n            },\n            is_vertical: {\n              type: 'boolean',\n            },\n            resolution: {\n              type: 'string',\n            },\n          },\n          default: {},\n        },\n        default: [],\n      },\n      screenshot: {\n        type: 'object',\n        properties: {\n          saveLocation: {\n            type: 'string',\n            default: path.join(homedir(), `Desktop/Responsively-Screenshots`),\n          },\n        },\n        default: {},\n      },\n      designOverlays: {\n        type: 'object',\n        additionalProperties: {\n          type: 'object',\n          properties: {\n            image: {type: 'string'},\n            opacity: {type: 'number'},\n            position: {type: 'string', enum: ['overlay', 'side']},\n            enabled: {type: 'boolean'},\n            fileName: {type: 'string'},\n          },\n        },\n        default: {},\n      },\n    },\n    default: {},\n  },\n  webPermissions: {\n    type: 'array',\n    items: {\n      type: 'object',\n      properties: {\n        origin: {\n          type: 'string',\n        },\n        permissions: {\n          type: 'array',\n          items: {\n            type: 'object',\n            properties: {\n              type: {\n                type: 'string',\n              },\n              status: {\n                type: 'string',\n                enum: ['GRANTED', 'DENIED'],\n              },\n            },\n          },\n        },\n      },\n    },\n    default: [],\n  },\n  history: {\n    type: 'array',\n    items: {\n      type: 'object',\n      properties: {\n        url: {\n          type: 'string',\n        },\n        title: {\n          type: 'string',\n        },\n        lastVisited: {\n          type: 'number',\n        },\n      },\n    },\n    default: [],\n  },\n  homepage: {\n    type: 'string',\n    default: 'https://www.google.com/',\n  },\n  seenReleaseNotes: {\n    type: 'array',\n    items: {\n      type: 'string',\n    },\n    default: [],\n  },\n  bookmarks: {\n    type: 'array',\n    items: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string',\n        },\n        name: {\n          type: 'string',\n        },\n        address: {\n          type: 'string',\n        },\n      },\n    },\n    default: [],\n  },\n  sponsorship: {\n    type: 'object',\n    properties: {\n      lastShown: {\n        type: 'number',\n      },\n    },\n    default: {},\n  },\n} as const;\n\nconst store = new Store({\n  schema,\n  watch: true,\n  migrations,\n});\n\nexport default store;\n"
  },
  {
    "path": "desktop-app/src/store/migrations.ts",
    "content": "import Store from 'electron-store';\nimport {randomUUID} from 'crypto';\n\nimport {PreviewSuites} from '../renderer/store/features/device-manager';\n\nimport {defaultDevices, Device} from '../common/deviceList';\n\nconst defaultActiveDevices = ['10008', '10013', '10015'];\n\nexport const migrations = {\n  '1.2.0': (store: Store) => {\n    try {\n      // eslint-disable-next-line no-console\n      console.log('Migrating for 1.2.0', store.get('deviceManager'));\n\n      // Migrate custom devices\n      const previousCustomDevices: Device[] = store.get('deviceManager.customDevices') as Device[];\n      const newCustomDevices: Device[] = previousCustomDevices.map((device) => {\n        const newDevice = {\n          ...device,\n          id: randomUUID(),\n        };\n        return newDevice;\n      });\n      store.set('deviceManager.customDevices', newCustomDevices);\n\n      // Migrate active devices to suites\n      const previousActiveDevices: string[] = store.get('deviceManager.activeDevices') as string[];\n\n      const newActiveDevices: string[] = previousActiveDevices\n        .map((name) => {\n          return defaultDevices.find((device) => device.name === name)?.id;\n        })\n        .filter(Boolean) as string[];\n\n      if (\n        newActiveDevices.length === 3 &&\n        newActiveDevices.every((id) => defaultActiveDevices.includes(id))\n      ) {\n        // default devices so no need to migrate\n        console.log('No need to migrate');\n        return;\n      }\n\n      store.set('deviceManager.previewSuites', [\n        {\n          id: 'default',\n          name: 'Default',\n          devices: newActiveDevices.length > 0 ? newActiveDevices : defaultActiveDevices,\n        },\n      ]);\n    } catch (e) {\n      // eslint-disable-next-line no-console\n      console.log('Migration failed', e);\n      store.set('deviceManager.previewSuites', [\n        {\n          id: 'default',\n          name: 'Default',\n          devices: defaultActiveDevices,\n        },\n      ]);\n      return;\n    }\n    // eslint-disable-next-line no-console\n    console.log('Migration successful', store.get('deviceManager'));\n  },\n  '1.2.1': (store: Store) => {\n    const suites = store.get('deviceManager.previewSuites') as PreviewSuites | undefined;\n    if (suites == null || suites.length > 0) {\n      return;\n    }\n    store.set('deviceManager.previewSuites', [\n      {\n        id: 'default',\n        name: 'Default',\n        devices: defaultActiveDevices,\n      },\n    ]);\n  },\n  '1.14.0': (store: Store) => {\n    // Migrate dpi to dpr in custom devices\n    try {\n      const previousCustomDevices: any[] = store.get('deviceManager.customDevices') as any[];\n      const newCustomDevices: Device[] = previousCustomDevices.map((device) => {\n        const newDevice = {\n          ...device,\n          dpr: device.dpi !== undefined ? device.dpi : device.dpr,\n        };\n        delete newDevice.dpi;\n        return newDevice as Device;\n      });\n      store.set('deviceManager.customDevices', newCustomDevices);\n      console.log('Migration for 1.14.0 successful', store.get('deviceManager.customDevices'));\n    } catch (e) {\n      console.log('Migration for 1.14.0 failed', e);\n    }\n  },\n};\n"
  },
  {
    "path": "desktop-app/tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\n\nconst defaultTheme = require('tailwindcss/defaultTheme');\nconst colors = require('tailwindcss/colors');\nconst typography = require('@tailwindcss/typography');\n\nmodule.exports = {\n  content: ['./src/renderer/**/*.tsx'],\n  darkMode: 'class',\n  theme: {\n    extend: {\n      colors: {\n        dark: {\n          normal: colors.gray['300'],\n        },\n        light: {\n          normal: colors.gray['700'],\n        },\n      },\n      fontFamily: {\n        sans: ['Lato', ...defaultTheme.fontFamily.sans],\n      },\n      maxHeight: (theme) => ({\n        ...theme('spacing'),\n      }),\n      maxWidth: (theme) => ({\n        ...theme('spacing'),\n      }),\n      minHeight: (theme) => ({\n        ...theme('spacing'),\n      }),\n      minWidth: (theme) => ({\n        ...theme('spacing'),\n      }),\n    },\n  },\n  plugins: [typography],\n};\n"
  },
  {
    "path": "desktop-app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"incremental\": true,\n    \"target\": \"es2021\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"dom\", \"es2021\"],\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"sourceMap\": true,\n    \"baseUrl\": \"./src\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"resolveJsonModule\": true,\n    \"allowJs\": true,\n    \"outDir\": \".erb/dll\",\n    \"skipLibCheck\": true,\n    \"types\": [\"vitest/globals\"]\n  },\n  \"exclude\": [\"test\", \"release/build\", \"release/app/dist\", \".erb/dll\"]\n}"
  },
  {
    "path": "desktop-app/vitest.config.ts",
    "content": "import path from 'path';\nimport {defineConfig} from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: 'jsdom',\n    setupFiles: ['./setupTests.ts'],\n    css: {modules: {classNameStrategy: 'non-scoped'}},\n    exclude: ['**/node_modules/**', '**/dist/**', '.erb/**', 'release/**'],\n  },\n  resolve: {\n    alias: {\n      renderer: path.resolve(__dirname, 'src/renderer'),\n      common: path.resolve(__dirname, 'src/common'),\n      main: path.resolve(__dirname, 'src/main'),\n      store: path.resolve(__dirname, 'src/store'),\n    },\n  },\n});\n"
  },
  {
    "path": "desktop-app-legacy/.dockerignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n.eslintcache\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# OSX\n.DS_Store\n\n# flow-typed\nflow-typed/npm/*\n!flow-typed/npm/module_vx.x.x.js\n\n# App packaged\nrelease\napp/main.prod.js\napp/main.prod.js.map\napp/renderer.prod.js\napp/renderer.prod.js.map\napp/style.css\napp/style.css.map\ndist\ndll\nmain.js\nmain.js.map\n\n.idea\nnpm-debug.log.*\n.*.dockerfile"
  },
  {
    "path": "desktop-app-legacy/.editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "desktop-app-legacy/.eslintignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n.eslintcache\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# OSX\n.DS_Store\n\n# flow-typed\nflow-typed/npm/*\n!flow-typed/npm/module_vx.x.x.js\n\n# App packaged\nrelease\napp/main.prod.js\napp/main.prod.js.map\napp/renderer.prod.js\napp/renderer.prod.js.map\napp/style.css\napp/style.css.map\ndist\ndll\nmain.js\nmain.js.map\nlib\n\n.idea\nnpm-debug.log.*\n__snapshots__\n\n# Package.json\npackage.json\n.travis.yml\n"
  },
  {
    "path": "desktop-app-legacy/.eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"parserOptions\": {\n    \"sourceType\": \"module\",\n    \"allowImportExportEverywhere\": true\n  },\n  \"extends\": [\"airbnb\", \"prettier\", \"prettier/flowtype\", \"prettier/react\"],\n  \"env\": {\n    \"browser\": true,\n    \"node\": true\n  },\n  \"rules\": {\n    \"arrow-parens\": [\"off\"],\n    \"array-callback-return\": \"off\",\n    \"class-methods-use-this\": \"off\",\n    \"compat/compat\": \"error\",\n    \"consistent-return\": \"off\",\n    \"comma-dangle\": \"off\",\n    \"flowtype/boolean-style\": [\"error\", \"boolean\"],\n    \"flowtype/define-flow-type\": \"warn\",\n    \"flowtype/delimiter-dangle\": \"off\",\n    \"flowtype/generic-spacing\": [\"error\", \"never\"],\n    \"flowtype/no-primitive-constructor-types\": \"off\",\n    \"flowtype/no-weak-types\": \"off\",\n    \"flowtype/object-type-delimiter\": [\"error\", \"comma\"],\n    \"flowtype/require-parameter-type\": \"off\",\n    \"flowtype/require-return-type\": \"off\",\n    \"flowtype/require-valid-file-annotation\": \"off\",\n    \"flowtype/semi\": [\"error\", \"always\"],\n    \"flowtype/space-after-type-colon\": [\"error\", \"always\"],\n    \"flowtype/space-before-generic-bracket\": [\"error\", \"never\"],\n    \"flowtype/space-before-type-colon\": [\"error\", \"never\"],\n    \"flowtype/union-intersection-spacing\": [\"error\", \"always\"],\n    \"flowtype/use-flow-type\": \"error\",\n    \"flowtype/valid-syntax\": \"error\",\n    \"generator-star-spacing\": \"off\",\n    \"global-require\": \"off\",\n    \"import/order\": \"off\",\n    \"import/extensions\": \"off\",\n    \"no-cond-assign\": \"off\",\n    \"no-await-in-loop\": \"off\",\n    \"import/first\": \"off\",\n    \"import/no-duplicates\": \"off\",\n    \"import/no-unresolved\": \"off\",\n    \"import/no-cycle\": \"off\",\n    \"import/no-extraneous-dependencies\": \"off\",\n    \"jsx-a11y/anchor-is-valid\": \"off\",\n    \"jsx-a11y/click-events-have-key-events\": \"off\",\n    \"jsx-a11y/no-static-element-interactions\": \"off\",\n    \"jsx-a11y/alt-text\": \"off\",\n    \"jsx-a11y/accessible-emoji\": \"off\",\n    \"lines-between-class-members\": \"off\",\n    \"no-case-declarations\": \"off\",\n    \"no-console\": \"off\",\n    \"no-constant-condition\": \"off\",\n    \"no-continue\": \"off\",\n    \"no-empty\": \"off\",\n    \"no-undef\": \"off\",\n    \"no-unused-vars\": \"off\",\n    \"no-use-before-define\": \"off\",\n    \"no-underscore-dangle\": \"off\",\n    \"no-multi-assign\": \"off\",\n    \"no-nested-ternary\": \"off\",\n    \"no-param-reassign\": \"off\",\n    \"no-plusplus\": \"off\",\n    \"no-restricted-syntax\": \"off\",\n    \"no-return-assign\": \"off\",\n    \"no-shadow\": \"off\",\n    \"no-sequences\": \"off\",\n    \"no-unreachable\": \"off\",\n    \"import/no-named-as-default\": \"off\",\n    \"import/prefer-default-export\": \"off\",\n    \"prefer-destructuring\": \"off\",\n    \"promise/param-names\": \"off\",\n    \"promise/always-return\": \"off\",\n    \"promise/catch-or-return\": \"off\",\n    \"promise/no-native\": \"off\",\n    \"react/destructuring-assignment\": \"off\",\n    \"react/jsx-boolean-value\": \"off\",\n    \"react/jsx-no-duplicate-props\": \"off\",\n    \"react/no-access-state-in-setstate\": \"off\",\n    \"react/no-array-index-key\": \"off\",\n    \"react/no-unused-state\": \"off\",\n    \"react/prop-types\": \"off\",\n    \"react/sort-comp\": [\n      \"off\",\n      {\n        \"order\": [\n          \"type-annotations\",\n          \"static-methods\",\n          \"lifecycle\",\n          \"everything-else\",\n          \"render\"\n        ]\n      }\n    ],\n    \"react/style-prop-object\": \"off\",\n    \"react/jsx-no-bind\": \"off\",\n    \"react/jsx-filename-extension\": [\"error\", {\"extensions\": [\".js\", \".jsx\"]}],\n    \"react/prefer-stateless-function\": \"off\"\n  },\n  \"plugins\": [\"flowtype\", \"import\", \"promise\", \"compat\", \"react\"],\n  \"settings\": {\n    \"import/resolver\": {\n      \"webpack\": {\n        \"config\": \"configs/webpack.config.eslint.js\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/.flowconfig",
    "content": "[ignore]\n<PROJECT_ROOT>/app/main.prod.js\n<PROJECT_ROOT>/app/main.prod.js.map\n<PROJECT_ROOT>/app/dist/.*\n<PROJECT_ROOT>/resources/.*\n<PROJECT_ROOT>/node_modules/webpack-cli\n<PROJECT_ROOT>/release/.*\n<PROJECT_ROOT>/dll/.*\n<PROJECT_ROOT>/release/.*\n<PROJECT_ROOT>/git/.*\n\n[include]\n\n[libs]\n\n[options]\nesproposal.class_static_fields=enable\nesproposal.class_instance_fields=enable\nesproposal.export_star_as=enable\nmodule.name_mapper.extension='css' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'\nmodule.name_mapper.extension='styl' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'\nmodule.name_mapper.extension='scss' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'\nmodule.name_mapper.extension='png' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'\nmodule.name_mapper.extension='jpg' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'\nsuppress_comment=\\\\(.\\\\|\\n\\\\)*\\\\$FlowFixMe\nsuppress_comment=\\\\(.\\\\|\\n\\\\)*\\\\$FlowIssue\n"
  },
  {
    "path": "desktop-app-legacy/.gitattributes",
    "content": "* text eol=lf\n*.png binary\n*.jpg binary\n*.jpeg binary\n*.ico binary\n*.icns binary\n"
  },
  {
    "path": "desktop-app-legacy/.github/ISSUE_TEMPLATE.md",
    "content": "<!--- Provide a general summary of the issue in the Title above -->\n\n## Prerequisites\n\n- [ ] Using yarn\n- [ ] Using an up-to-date master branch\n- [ ] Using latest version of devtools. See [wiki for howto update](https://github.com/electron-react-boilerplate/electron-react-boilerplate/wiki/DevTools)\n- [ ] Link to stacktrace in a Gist (for bugs)\n- [ ] For issue in production release, devtools output of `DEBUG_PROD=true yarn build && yarn start`\n- [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400)\n\n## Expected Behavior\n\n<!--- If you're describing a bug, tell us what should happen -->\n<!--- If you're suggesting a change/improvement, tell us how it should work -->\n\n## Current Behavior\n\n<!--- If describing a bug, tell us what happens instead of the expected behavior -->\n<!--- If suggesting a change/improvement, explain the difference from current behavior -->\n\n## Possible Solution\n\n<!--- Not obligatory, but suggest a fix/reason for the bug, -->\n<!--- or ideas how to implement the addition or change -->\n\n## Steps to Reproduce (for bugs)\n\n<!--- Provide a link to a live example, or an unambiguous set of steps to -->\n<!--- reproduce this bug. Include code to reproduce, if relevant -->\n\n1.\n\n2.\n\n3.\n\n4.\n\n## Context\n\n<!--- How has this issue affected you? What are you trying to accomplish? -->\n<!--- Did you make any changes to the boilerplate after cloning it? -->\n<!--- Providing context helps us come up with a solution that is most useful in the real world -->\n\n## Your Environment\n\n<!--- Include as many relevant details about the environment you experienced the bug in -->\n\n- Node version :\n- Version or Branch used :\n- Operating System and version :\n- Link to your project :\n"
  },
  {
    "path": "desktop-app-legacy/.github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pr\n  - discussion\n  - e2e\n  - enhancement\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": "desktop-app-legacy/.gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n.eslintcache\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# OSX\n.DS_Store\n\n# flow-typed\nflow-typed/npm/*\n!flow-typed/npm/module_vx.x.x.js\n\n# App packaged\nrelease\napp/main.prod.js\napp/main.prod.js.map\napp/renderer.prod.js\napp/renderer.prod.js.map\napp/style.css\napp/style.css.map\n/dist\napp/dist\ndll\nmain.js\nmain.js.map\n\n.idea\nnpm-debug.log.*\npackage-lock.json\n\n# local configs\nsetCreds.sh\nembedded.provisionprofile\n"
  },
  {
    "path": "desktop-app-legacy/.nvmrc",
    "content": "12.13.0\n"
  },
  {
    "path": "desktop-app-legacy/.prettierignore",
    "content": "build/entitlements.mac.plist\n"
  },
  {
    "path": "desktop-app-legacy/.prettierrc",
    "content": "{\n  \"overrides\": [\n    {\n      \"files\": [\".prettierrc\", \".babelrc\", \".eslintrc\", \".stylelintrc\"],\n      \"options\": {\n        \"parser\": \"json\"\n      }\n    }\n  ],\n  \"singleQuote\": true,\n  \"bracketSpacing\": false,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": "desktop-app-legacy/.stylelintrc",
    "content": "{\n  \"extends\": [\"stylelint-config-standard\", \"stylelint-config-prettier\"]\n}\n"
  },
  {
    "path": "desktop-app-legacy/.testcafe-electron-rc",
    "content": "{\n  \"mainWindowUrl\": \"./app/app.html\",\n  \"appPath\": \".\"\n}\n"
  },
  {
    "path": "desktop-app-legacy/.travis.yml",
    "content": "matrix:\n  allow_failures:\n    - os: windows\n  include:\n    - os: osx\n      language: node_js\n      node_js:\n        - 12.13.0\n      env:\n        - ELECTRON_CACHE=$HOME/.cache/electron\n        - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder\n\n    - os: linux\n      language: node_js\n      node_js:\n        - node\n      addons:\n        apt:\n          sources:\n            - ubuntu-toolchain-r-test\n          packages:\n            - gcc-multilib\n            - g++-8\n            - g++-multilib\n            - icnsutils\n            - graphicsmagick\n            - xz-utils\n            - xorriso\n            - rpm\n\n    - os: windows\n      language: node_js\n      node_js:\n        - node\n      env:\n        - ELECTRON_CACHE=$HOME/.cache/electron\n        - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder\n\nbefore_cache:\n  - rm -rf $HOME/.cache/electron-builder/wine\n\ncache:\n  yarn: true\n  directories:\n    - node_modules\n    - $(npm config get prefix)/lib/node_modules\n    - flow-typed\n    - $HOME/.cache/electron\n    - $HOME/.cache/electron-builder\n\nbefore_install:\n  - if [[ \"$TRAVIS_OS_NAME\" == \"linux\" ]]; then export CXX=\"g++-8\"; fi\n\ninstall:\n  - yarn --ignore-engines\n  # On Linux, initialize \"virtual display\". See before_script\n  - |\n    if [ \"$TRAVIS_OS_NAME\" == \"linux\" ]; then\n      /sbin/start-stop-daemon \\\n      --start \\\n      --quiet \\\n      --pidfile /tmp/custom_xvfb_99.pid \\\n      --make-pidfile \\\n      --background \\\n      --exec /usr/bin/Xvfb \\\n      -- :99 -ac -screen 0 1280x1024x16\n    else\n      :\n    fi\n\nbefore_script:\n  # On Linux, create a \"virtual display\". This allows browsers to work properly\n  - if [[ \"$TRAVIS_OS_NAME\" == \"linux\" ]]; then export DISPLAY=:99.0; fi\n  - if [[ \"$TRAVIS_OS_NAME\" == \"linux\" ]]; then sh -e /etc/init.d/xvfb start; fi\n  - if [[ \"$TRAVIS_OS_NAME\" == \"linux\" ]]; then sleep 3; fi\n\nscript:\n  - yarn package-ci\n  - yarn lint\n  - yarn flow\n  # HACK: Temporarily ignore `yarn test` on linux\n  - if [[ \"$TRAVIS_OS_NAME\" == \"osx\" ]]; then yarn test; fi\n  - yarn build-e2e\n  - yarn test-e2e\n"
  },
  {
    "path": "desktop-app-legacy/CHANGELOG.md",
    "content": "# 0.17.1 (2018.11.20)\n\n- Fix `yarn test-e2e` and testcafe for single package.json structure\n- Fixes incorrect path in `yarn start` script\n- Bumped deps\n- Bump g++ in travis\n- Change clone arguments to clone only master\n- Change babel config to target current electron version\n\nFor full change list, see https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2021\n\n# 0.17.0 (2018.10.30)\n\n- upgraded to `babel@7` (thanks to @vikr01 🎉🎉🎉)\n- migrated from [two `package.json` structure](https://www.electron.build/tutorials/two-package-structure) (thanks to @HyperSprite!)\n- initial auto update support (experimental)\n- migrate from greenkeeper to [renovate](https://renovatebot.com)\n- added issue template\n- use `babel-preset-env` to target current electron version\n- add [opencollective](https://opencollective.com/electron-react-boilerplate-594) banner message display in postinstall script (help support ERB 🙏)\n- fix failing ci issues\n\n# 0.16.0 (2018.10.3)\n\n- removed unused dependencies\n- migrate from `react-redux-router` to `connect-react-router`\n- move webpack configs to `./webpack` dir\n- use `g++` on travis when testing linux\n- migrate from `spectron` to `testcafe` for e2e tests\n- add linting support for config styles\n- changed stylelint config\n- temporarily disabled flow in appveyor to make ci pass\n- added necessary infra to publish releases from ci\n\n# 0.15.0 (2018.8.25)\n\n- Performance: cache webpack uglify results\n- Feature: add start minimized feature\n- Feature: lint and fix styles with prettier and stylelint\n- Feature: add greenkeeper support\n\n# 0.14.0 (2018.5.24)\n\n- Improved CI timings\n- Migrated README commands to yarn from npm\n- Improved vscode config\n- Updated all dependencies to latest semver\n- Fix `electron-rebuild` script bug\n- Migrated to `mini-css-extract-plugin` from `extract-text-plugin`\n- Added `optimize-css-assets-webpack-plugin`\n- Run `prettier` on json, css, scss, and more filetypes\n\n# 0.13.3 (2018.5.24)\n\n- Add git precommit hook, when git commit will use `prettier` to format git add code\n- Add format code function in `lint-fix` npm script which can use `prettier` to format project js code\n\n# 0.13.2 (2018.1.31)\n\n- Hot Module Reload (HMR) fixes\n- Bumped all dependencies to latest semver\n- Prevent error propagation of `CheckNativeDeps` script\n\n# 0.13.1 (2018.1.13)\n\n- Hot Module Reload (HMR) fixes\n- Bumped all dependencies to latest semver\n- Fixed electron-rebuild script\n- Fixed tests scripts to run on all platforms\n- Skip redux logs in console in test ENV\n\n# 0.13.0 (2018.1.6)\n\n#### Additions\n\n- Add native dependencies check on postinstall\n- Updated all dependencies to latest semver\n\n# 0.12.0 (2017.7.8)\n\n#### Misc\n\n- Removed `babel-polyfill`\n- Renamed and alphabetized npm scripts\n\n#### Breaking\n\n- Changed node dev `__dirname` and `__filename` to node built in fn's (https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/1035)\n- Renamed `app/bundle.js` to `app/renderer.prod.js` for consistency\n- Renamed `dll/vendor.js` to `dll/renderer.dev.dll.js` for consistency\n\n#### Additions\n\n- Enable node_modules cache on CI\n\n# 0.11.2 (2017.5.1)\n\nYay! Another patch release. This release mostly includes refactorings and router bug fixes. Huge thanks to @anthonyraymond!\n\n⚠️ Windows electron builds are failing because of [this issue](https://github.com/electron/electron/issues/9321). This is not an issue with the boilerplate ⚠️\n\n#### Breaking\n\n- **Renamed `./app/main.development.js` => `./app/main.{dev,prod}.js`:** [#963](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/963)\n\n#### Fixes\n\n- **Fixed reloading when not on `/` path:** [#958](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/958) [#949](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/949)\n\n#### Additions\n\n- **Added support for stylefmt:** [#960](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/960)\n\n# 0.11.1 (2017.4.23)\n\nYou can now debug the production build with devtools like so:\n\n```\nDEBUG_PROD=true npm run package\n```\n\n🎉🎉🎉\n\n#### Additions\n\n- **Added support for debugging production build:** [#fab245a](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/941/commits/fab245a077d02a09630f74270806c0c534a4ff95)\n\n#### Bug Fixes\n\n- **Fixed bug related to importing native dependencies:** [#933](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/933)\n\n#### Improvements\n\n- **Updated all deps to latest semver**\n\n# 0.11.0 (2017.4.19)\n\nHere's the most notable changes since `v0.10.0`. Its been about a year since a release has been pushed. Expect a new release to be published every 3-4 weeks.\n\n#### Breaking Changes\n\n- **Dropped support for node < 6**\n- **Refactored webpack config files**\n- **Migrate to two-package.json project structure**\n- **Updated all devDeps to latest semver**\n- **Migrated to Jest:** [#768](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/768)\n- **Migrated to `react-router@4`**\n- **Migrated to `electron-builder@4`**\n- **Migrated to `webpack@2`**\n- **Migrated to `react-hot-loader@3`**\n- **Changed default live reload server PORT to `1212` from `3000`**\n\n#### Additions\n\n- **Added support for Yarn:** [#451](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/451)\n- **Added support for Flow:** [#425](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/425)\n- **Added support for stylelint:** [#911](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/911)\n- **Added support for electron-builder:** [#876](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/876)\n- **Added optional support for SASS:** [#880](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/880)\n- **Added support for eslint-plugin-flowtype:** [#911](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/911)\n- **Added support for appveyor:** [#280](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/280)\n- **Added support for webpack dlls:** [#860](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/860)\n- **Route based code splitting:** [#884](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/884)\n- **Added support for Webpack Bundle Analyzer:** [#922](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/922)\n\n#### Improvements\n\n- **Parallelize renderer and main build processes when running `npm run build`**\n- **Dynamically generate electron app menu**\n- **Improved vscode integration:** [#856](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/856)\n\n#### Bug Fixes\n\n- **Fixed hot module replacement race condition bug:** [#917](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/917) [#920](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/920)\n\n# 0.10.0 (2016.4.18)\n\n#### Improvements\n\n- **Use Babel in main process with Webpack build:** [#201](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/201)\n- **Change targets to built-in support by webpack:** [#197](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/197)\n- **use es2015 syntax for webpack configs:** [#195](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/195)\n- **Open application when webcontent is loaded:** [#192](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/192)\n- **Upgraded dependencies**\n\n#### Bug fixed\n\n- **Fix `npm list electron-prebuilt` in package.js:** [#188](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/188)\n\n# 0.9.0 (2016.3.23)\n\n#### Improvements\n\n- **Added [redux-logger](https://github.com/fcomb/redux-logger)**\n- **Upgraded [react-router-redux](https://github.com/reactjs/react-router-redux) to v4**\n- **Upgraded dependencies**\n- **Added `npm run dev` command:** [#162](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/162)\n- **electron to v0.37.2**\n\n#### Breaking Changes\n\n- **css module as default:** [#154](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/154).\n- **set default NODE_ENV to production:** [#140](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/140)\n\n# 0.8.0 (2016.2.17)\n\n#### Bug fixed\n\n- **Fix lint errors**\n- **Fix Webpack publicPath for production builds**: [#119](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/119).\n- **package script now chooses correct OS icon extension**\n\n#### Improvements\n\n- **babel 6**\n- **Upgrade Dependencies**\n- **Enable CSS source maps**\n- **Add json-loader**: [#128](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/128).\n- **react-router 2.0 and react-router-redux 3.0**\n\n# 0.7.1 (2015.12.27)\n\n#### Bug fixed\n\n- **Fixed npm script on windows 10:** [#103](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/103).\n- **history and react-router version bump**: [#109](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/109), [#110](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/110).\n\n#### Improvements\n\n- **electron 0.36**\n\n# 0.7.0 (2015.12.16)\n\n#### Bug fixed\n\n- **Fixed process.env.NODE_ENV variable in webpack:** [#74](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/74).\n- **add missing object-assign**: [#76](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/76).\n- **packaging in npm@3:** [#77](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/77).\n- **compatibility in windows:** [#100](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/100).\n- **disable chrome debugger in production env:** [#102](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/102).\n\n#### Improvements\n\n- **redux**\n- **css-modules**\n- **upgrade to react-router 1.x**\n- **unit tests**\n- **e2e tests**\n- **travis-ci**\n- **upgrade to electron 0.35.x**\n- **use es2015**\n- **check dev engine for node and npm**\n\n# 0.6.5 (2015.11.7)\n\n#### Improvements\n\n- **Bump style-loader to 0.13**\n- **Bump css-loader to 0.22**\n\n# 0.6.4 (2015.10.27)\n\n#### Improvements\n\n- **Bump electron-debug to 0.3**\n\n# 0.6.3 (2015.10.26)\n\n#### Improvements\n\n- **Initialize ExtractTextPlugin once:** [#64](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/64).\n\n# 0.6.2 (2015.10.18)\n\n#### Bug fixed\n\n- **Babel plugins production env not be set properly:** [#57](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/57).\n\n# 0.6.1 (2015.10.17)\n\n#### Improvements\n\n- **Bump electron to v0.34.0**\n\n# 0.6.0 (2015.10.16)\n\n#### Breaking Changes\n\n- **From react-hot-loader to react-transform**\n\n# 0.5.2 (2015.10.15)\n\n#### Improvements\n\n- **Run tests with babel-register:** [#29](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/29).\n\n# 0.5.1 (2015.10.12)\n\n#### Bug fixed\n\n- **Fix #51:** use `path.join(__dirname` instead of `./`.\n\n# 0.5.0 (2015.10.11)\n\n#### Improvements\n\n- **Simplify webpack config** see [#50](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/50).\n\n#### Breaking Changes\n\n- **webpack configs**\n- **port changed:** changed default port from 2992 to 3000.\n- **npm scripts:** remove `start-dev` and `dev-server`. rename `hot-dev-server` to `hot-server`.\n\n# 0.4.3 (2015.9.22)\n\n#### Bug fixed\n\n- **Fix #45 zeromq crash:** bump version of `electron-prebuilt`.\n\n# 0.4.2 (2015.9.15)\n\n#### Bug fixed\n\n- **run start-hot breaks chrome refresh(CTRL+R) (#42)**: bump `electron-debug` to `0.2.1`\n\n# 0.4.1 (2015.9.11)\n\n#### Improvements\n\n- **use electron-prebuilt version for packaging (#33)**\n\n# 0.4.0 (2015.9.5)\n\n#### Improvements\n\n- **update dependencies**\n\n# 0.3.0 (2015.8.31)\n\n#### Improvements\n\n- **eslint-config-airbnb**\n\n# 0.2.10 (2015.8.27)\n\n#### Features\n\n- **custom placeholder icon**\n\n#### Improvements\n\n- **electron-renderer as target:** via [webpack-target-electron-renderer](https://github.com/chentsulin/webpack-target-electron-renderer)\n\n# 0.2.9 (2015.8.18)\n\n#### Bug fixed\n\n- **Fix hot-reload**\n\n# 0.2.8 (2015.8.13)\n\n#### Improvements\n\n- **bump electron-debug**\n- **babelrc**\n- **organize webpack scripts**\n\n# 0.2.7 (2015.7.9)\n\n#### Bug fixed\n\n- **defaultProps:** fix typos.\n\n# 0.2.6 (2015.7.3)\n\n#### Features\n\n- **menu**\n\n#### Bug fixed\n\n- **package.js:** include webpack build.\n\n# 0.2.5 (2015.7.1)\n\n#### Features\n\n- **NPM Script:** support multi-platform\n- **package:** `--all` option\n\n# 0.2.4 (2015.6.9)\n\n#### Bug fixed\n\n- **Eslint:** typo, [#17](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/17) and improve `.eslintrc`\n\n# 0.2.3 (2015.6.3)\n\n#### Features\n\n- **Package Version:** use latest release electron version as default\n- **Ignore Large peerDependencies**\n\n#### Bug fixed\n\n- **Npm Script:** typo, [#6](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/6)\n- **Missing css:** [#7](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/7)\n\n# 0.2.2 (2015.6.2)\n\n#### Features\n\n- **electron-debug**\n\n#### Bug fixed\n\n- **Webpack:** add `.json` and `.node` to extensions for imitating node require.\n- **Webpack:** set `node_modules` to externals for native module support.\n\n# 0.2.1 (2015.5.30)\n\n#### Bug fixed\n\n- **Webpack:** #1, change build target to `atom`.\n\n# 0.2.0 (2015.5.30)\n\n#### Features\n\n- **Ignore:** `test`, `tools`, `release` folder and devDependencies in `package.json`.\n- **Support asar**\n- **Support icon**\n\n# 0.1.0 (2015.5.27)\n\n#### Features\n\n- **Webpack:** babel, react-hot, ...\n- **Flux:** actions, api, components, containers, stores..\n- **Package:** darwin (osx), linux and win32 (windows) platform.\n"
  },
  {
    "path": "desktop-app-legacy/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-present C. T. Lin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "desktop-app-legacy/add-osx-cert.sh",
    "content": "#!/usr/bin/env sh\n\nKEY_CHAIN=build.keychain\nCERTIFICATE_P12=certificate.p12\n\n# Recreate the certificate from the secure environment variable\necho $CERTIFICATE_OSX_P12 | base64 --decode > $CERTIFICATE_P12\n\n#create a keychain\nsecurity create-keychain -p travis $KEY_CHAIN\n\n# Make the keychain the default so identities are found\nsecurity default-keychain -s $KEY_CHAIN\n\n# Unlock the keychain\nsecurity unlock-keychain -p travis $KEY_CHAIN\n\nsecurity import $CERTIFICATE_P12 -k $KEY_CHAIN -P $CERTIFICATE_OSX_PASSWORD -T /usr/bin/codesign;\n\nsecurity set-key-partition-list -S apple-tool:,apple: -s -k travis $KEY_CHAIN\n\n# remove certs\nrm -fr *.p12\n"
  },
  {
    "path": "desktop-app-legacy/app/AppContent.js",
    "content": "import React, {Fragment} from 'react';\nimport Box from '@material-ui/core/Box';\nimport Paper from '@material-ui/core/Paper';\nimport {makeStyles} from '@material-ui/core/styles';\nimport routes from './constants/routes';\nimport Browser from './containers/Browser';\nimport LeftIconsPaneContainer from './containers/LeftIconsPaneContainer';\nimport StatusBarContainer from './containers/StatusBarContainer';\nimport DevToolResizerContainer from './containers/DevToolResizerContainer';\n\nconst AppContent = () => {\n  const classes = useStyles();\n  return (\n    <Fragment>\n      <Paper elevation={0} className={classes.root}>\n        <div className={classes.contentColumn}>\n          <Browser />\n        </div>\n      </Paper>\n      <StatusBarContainer />\n      <DevToolResizerContainer />\n    </Fragment>\n  );\n};\n\nconst useStyles = makeStyles(theme => ({\n  '@global': {\n    /* The scrollbar Handle */\n    '::-webkit-scrollbar-thumb': {\n      WebkitBorderRadius: '10px',\n      borderRadius: '10px',\n      backgroundColor: theme.palette.scrollbar.main,\n      WebkitBoxShadow: 'inset 0 0 6px rgba(0, 0, 0, 0.2)',\n    },\n    /* When the scrollbar is inactive */\n    '::-webkit-scrollbar-thumb:window-inactive': {\n      backgroundColor: theme.palette.scrollbar.main,\n    },\n  },\n  root: {\n    height: '100%',\n    overflow: 'hidden',\n    margin: 0,\n    display: 'flex',\n    boxSizing: 'border-box',\n    borderRadius: 0,\n    backgroundColor: theme.palette.background.l0,\n  },\n  iconColumn: {\n    flexShrink: 0,\n    width: '50px',\n    height: '100%',\n    display: 'flex',\n    flexDirection: 'column',\n    background: '#ffffff15',\n    boxShadow: '0 3px 5px rgba(0, 0, 0, 0.35)',\n    paddingTop: '20px',\n  },\n  contentColumn: {\n    height: '100%',\n    position: 'relative',\n    display: 'flex',\n    flexDirection: 'column',\n    flexGrow: 1,\n    overflow: 'hidden',\n    flexBasis: 0,\n  },\n}));\n\nexport default AppContent;\n"
  },
  {
    "path": "desktop-app-legacy/app/actions/bookmarks.js",
    "content": "export const TOGGLE_BOOKMARK = 'TOGGLE_BOOKMARK';\nexport const EDIT_BOOKMARK = 'EDIT_BOOKMARK';\n\n// Add or Remove an URL from the bookmark list\nexport function toggleBookmarkUrl(url, pageMeta = {}) {\n  return {\n    type: TOGGLE_BOOKMARK,\n    url,\n    title: pageMeta.title,\n  };\n}\n\n// Updates bookmark title\nexport function editBookmark(bookmark, {title, url}) {\n  return {\n    type: EDIT_BOOKMARK,\n    title,\n    url,\n    bookmark,\n  };\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/actions/browser.js",
    "content": "// @flow\nimport {ipcRenderer, remote} from 'electron';\nimport pubsub from 'pubsub.js';\nimport type {Dispatch, BrowserStateType} from '../reducers/types';\nimport {\n  SCROLL_DOWN,\n  SCROLL_UP,\n  NAVIGATION_BACK,\n  NAVIGATION_FORWARD,\n  NAVIGATION_RELOAD,\n  SCREENSHOT_ALL_DEVICES,\n  FLIP_ORIENTATION_ALL_DEVICES,\n  TOGGLE_DEVICE_MUTED_STATE,\n  TOGGLE_EVENT_MIRRORING_ALL_DEVICES,\n  RELOAD_CSS,\n  DELETE_STORAGE,\n  ADDRESS_CHANGE,\n  STOP_LOADING,\n  TOGGLE_DEVICE_DESIGN_MODE_STATE,\n  PAGE_NAVIGATOR_CHANGED,\n} from '../constants/pubsubEvents';\nimport {getBounds, getDefaultDevToolsWindowSize} from '../reducers/browser';\nimport {DEVTOOLS_MODES} from '../constants/previewerLayouts';\nimport {normalizeZoomLevel} from '../utils/browserUtils';\n\nexport const NEW_ADDRESS = 'NEW_ADDRESS';\nexport const NEW_PAGE_META_FIELD = 'NEW_PAGE_META_FIELD';\nexport const NEW_DEV_TOOLS_CONFIG = 'NEW_DEV_TOOLS_CONFIG';\nexport const NEW_HOMEPAGE = 'NEW_HOMEPAGE';\nexport const NEW_ZOOM_LEVEL = 'NEW_ZOOM_LEVEL';\nexport const NEW_SCROLL_POSITION = 'NEW_SCROLL_POSITION';\nexport const NEW_NAVIGATOR_STATUS = 'NEW_NAVIGATOR_STATUS';\nexport const NEW_INSPECTOR_STATUS = 'NEW_INSPECTOR_STATUS';\nexport const NEW_CSS_EDITOR_STATUS = 'NEW_CSS_EDITOR_STATUS';\nexport const NEW_CSS_EDITOR_POSITION = 'NEW_CSS_EDITOR_POSITION';\nexport const NEW_CSS_EDITOR_CONTENT = 'NEW_CSS_EDITOR_CONTENT';\nexport const NEW_DRAWER_CONTENT = 'NEW_DRAWER_CONTENT';\nexport const NEW_PREVIEWER_CONFIG = 'NEW_PREVIEWER_CONFIG';\nexport const NEW_ACTIVE_DEVICES = 'NEW_ACTIVE_DEVICES';\nexport const NEW_CUSTOM_DEVICE = 'NEW_CUSTOM_DEVICE';\nexport const LOAD_CUSTOM_DEVICES = 'LOAD_CUSTOM_DEVICES';\nexport const DELETE_CUSTOM_DEVICE = 'DELETE_CUSTOM_DEVICE';\nexport const NEW_FILTERS = 'NEW_FILTERS';\nexport const NEW_USER_PREFERENCES = 'NEW_USER_PREFERENCES';\nexport const TOGGLE_BOOKMARK = 'TOGGLE_BOOKMARK';\nexport const NEW_WINDOW_SIZE = 'NEW_WINDOW_SIZE';\nexport const DEVICE_LOADING = 'DEVICE_LOADING';\nexport const NEW_FOCUSED_DEVICE = 'NEW_FOCUSED_DEVICE';\nexport const TOGGLE_ALL_DEVICES_MUTED = 'TOGGLE_ALL_DEVICES_MUTED';\nexport const TOGGLE_DEVICE_MUTED = 'TOGGLE_DEVICE_MUTED';\nexport const NEW_THEME = 'NEW_THEME';\nexport const TOGGLE_ALL_DEVICES_DESIGN_MODE = 'TOGGLE_ALL_DEVICES_DESIGN_MODE';\nexport const TOGGLE_DEVICE_DESIGN_MODE = 'TOGGLE_DEVICE_DESIGN_MODE';\nexport const SET_HEADER_VISIBILITY = 'SET_HEADER_VISIBILITY';\nexport const SET_LEFT_PANE_VISIBILITY = 'SET_LEFT_PANE_VISIBILITY';\nexport const SET_HOVERED_LINK = 'SET_HOVERED_LINK';\nexport const SET_STARTUP_PAGE = 'SET_STARTUP_PAGE';\nexport const UPDATE_PAGE_NAVIGATOR = 'UPDATE_PAGE_NAVIGATOR';\nexport const TOGGLE_PAGE_NAVIGATOR = 'TOGGLE_PAGE_NAVIGATOR';\n\nexport function newAddress(address) {\n  return {\n    type: NEW_ADDRESS,\n    address,\n  };\n}\n\nexport function newPageMetaField(name, value) {\n  return {\n    type: NEW_PAGE_META_FIELD,\n    name,\n    value,\n  };\n}\n\nexport function newWindowSize(size) {\n  return {\n    type: NEW_WINDOW_SIZE,\n    size,\n  };\n}\n\nexport function newDevToolsConfig(config) {\n  return {\n    type: NEW_DEV_TOOLS_CONFIG,\n    config,\n  };\n}\n\nexport function newHomepage(homepage) {\n  return {\n    type: NEW_HOMEPAGE,\n    homepage,\n  };\n}\n\nexport function newInspectorState(status) {\n  return {\n    type: NEW_INSPECTOR_STATUS,\n    status,\n  };\n}\n\nexport function newCSSEditorState(status) {\n  return {\n    type: NEW_CSS_EDITOR_STATUS,\n    status,\n  };\n}\n\nexport function newCSSEditorPosition(position) {\n  return {\n    type: NEW_CSS_EDITOR_POSITION,\n    position,\n  };\n}\n\nexport function newCSSEditorContent(content) {\n  return {\n    type: NEW_CSS_EDITOR_CONTENT,\n    content,\n  };\n}\n\nexport function newUserPreferences(userPreferences) {\n  return {\n    type: NEW_USER_PREFERENCES,\n    userPreferences,\n  };\n}\n\nexport function newZoomLevel(zoomLevel) {\n  return {\n    type: NEW_ZOOM_LEVEL,\n    zoomLevel,\n  };\n}\n\nexport function newScrollPosition(scrollPosition) {\n  return {\n    type: NEW_SCROLL_POSITION,\n    scrollPosition,\n  };\n}\n\nexport function newNavigatorStatus(navigatorStatus) {\n  return {\n    type: NEW_NAVIGATOR_STATUS,\n    navigatorStatus,\n  };\n}\n\nexport function newDrawerContent(drawer) {\n  return {\n    type: NEW_DRAWER_CONTENT,\n    drawer,\n  };\n}\n\nexport function newPreviewerConfig(previewer) {\n  return {\n    type: NEW_PREVIEWER_CONFIG,\n    previewer,\n  };\n}\n\nexport function newFocusedDevice(previewer) {\n  return {\n    type: NEW_FOCUSED_DEVICE,\n    previewer,\n  };\n}\n\nexport function newActiveDevices(devices) {\n  return {\n    type: NEW_ACTIVE_DEVICES,\n    devices,\n  };\n}\n\nexport function newCustomDevice(device) {\n  return {\n    type: NEW_CUSTOM_DEVICE,\n    device,\n  };\n}\n\nexport function loadCustomDevices(devices) {\n  return {\n    type: LOAD_CUSTOM_DEVICES,\n    devices,\n  };\n}\n\nexport function deleteCustomDevice(device) {\n  return {\n    type: DELETE_CUSTOM_DEVICE,\n    device,\n  };\n}\n\nexport function newFilters(filters) {\n  return {\n    type: NEW_FILTERS,\n    filters,\n  };\n}\n\nexport function newDeviceLoading(device) {\n  return {\n    type: DEVICE_LOADING,\n    device,\n  };\n}\n\nexport function toggleAllDevicesMuted(allDevicesMuted) {\n  return {\n    type: TOGGLE_ALL_DEVICES_MUTED,\n    allDevicesMuted,\n  };\n}\n\nexport function toggleDeviceMuted(deviceId, isMuted) {\n  return {\n    type: TOGGLE_DEVICE_MUTED,\n    deviceId,\n    isMuted,\n  };\n}\n\nexport function toggleAllDevicesDesignMode() {\n  return {\n    type: TOGGLE_ALL_DEVICES_DESIGN_MODE,\n  };\n}\n\nexport function toggleDeviceDesignMode(deviceId) {\n  return {\n    type: TOGGLE_DEVICE_DESIGN_MODE,\n    deviceId,\n  };\n}\n\nexport function onAddressChange(newURL, force) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {address},\n    } = getState();\n\n    if (newURL === address) {\n      if (force) {\n        pubsub.publish(NAVIGATION_RELOAD, [{ignoreCache: false}]);\n      }\n      return;\n    }\n\n    dispatch(newAddress(newURL));\n    pubsub.publish(ADDRESS_CHANGE, [{address: newURL, force: false}]);\n  };\n}\n\nexport function onPageMetaFieldUpdate(name, value) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    dispatch(newPageMetaField(name, value));\n  };\n}\n\nfunction isHashOnlyChange(newURL, oldURL) {\n  if (!newURL || !oldURL) {\n    return false;\n  }\n  let diff = newURL.replace(oldURL, '').trim();\n  if (diff.startsWith('/')) {\n    diff = diff.substring(1);\n  }\n\n  return diff.startsWith('#');\n}\n\nexport function onZoomChange(newLevel) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {zoomLevel},\n    } = getState();\n    const normalizedZoomLevel = normalizeZoomLevel(newLevel);\n\n    if (normalizedZoomLevel === zoomLevel) {\n      return;\n    }\n\n    dispatch(newZoomLevel(normalizedZoomLevel));\n  };\n}\n\nexport function onScrollChange({x: newX, y: newY}) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {\n        scrollPosition: {x, y},\n      },\n    } = getState();\n\n    if (newX === x && newY === y) {\n      return;\n    }\n\n    dispatch(newScrollPosition({x: newX, y: newY}));\n  };\n}\n\nexport function updateNavigatorStatus({\n  backEnabled: newBackEnabled,\n  forwardEnabled: newForwardEnabled,\n}) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {\n        navigatorStatus: {backEnabled, forwardEnabled},\n      },\n    } = getState();\n\n    if (\n      newBackEnabled === backEnabled &&\n      newForwardEnabled === forwardEnabled\n    ) {\n      return;\n    }\n\n    dispatch(\n      newNavigatorStatus({\n        backEnabled: newBackEnabled,\n        forwardEnabled: newForwardEnabled,\n      })\n    );\n  };\n}\n\nexport function openDrawerAndSetContent(_newDrawerContent) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {\n        drawer: {drawerContent, open},\n      },\n    } = getState();\n\n    if (_newDrawerContent === drawerContent && open) {\n      return;\n    }\n\n    dispatch(\n      newDrawerContent({\n        content: _newDrawerContent,\n        open: true,\n      })\n    );\n  };\n}\n\nexport function changeDrawerOpenState(newOpenState) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {drawer},\n    } = getState();\n\n    if (newOpenState === drawer.open) {\n      return;\n    }\n\n    dispatch(\n      newDrawerContent({\n        ...drawer,\n        open: newOpenState,\n      })\n    );\n  };\n}\n\nexport function setPreviewLayout(newLayout) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {previewer},\n    } = getState();\n\n    if (previewer.layout === newLayout) {\n      return;\n    }\n\n    dispatch(\n      newPreviewerConfig({\n        ...previewer,\n        layout: newLayout,\n      })\n    );\n  };\n}\n\nexport function setFocusedDevice(focusedDeviceId) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {previewer},\n    } = getState();\n\n    if (previewer.focusedDeviceId === focusedDeviceId) {\n      return;\n    }\n\n    dispatch(\n      newFocusedDevice({\n        ...previewer,\n        focusedDeviceId,\n      })\n    );\n  };\n}\n\nexport function setActiveDevices(newDevices) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {devices},\n    } = getState();\n\n    if (false) {\n      // TODO verify the devices list and return if the order of the devices didn;t change;\n      return;\n    }\n\n    dispatch(newActiveDevices(newDevices));\n  };\n}\n\nexport function addCustomDevice(newDevice) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {devices},\n    } = getState();\n\n    dispatch(newCustomDevice(newDevice));\n\n    if (newDevice.added) {\n      dispatch(newActiveDevices([...devices, newDevice]));\n    }\n  };\n}\n\nexport function deleteDevice(device) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    dispatch(deleteCustomDevice(device));\n  };\n}\n\nexport function toggleFilter(filterField, value) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {filters},\n    } = getState();\n    if (!filters[filterField]) {\n      filters[filterField] = [];\n    }\n    const index = filters[filterField].indexOf(value);\n    if (index === -1) {\n      filters[filterField].push(value);\n    } else {\n      filters[filterField].splice(index, 1);\n    }\n    dispatch(newFilters(filters));\n  };\n}\n\nexport function goToHomepage() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {homepage, address},\n    } = getState();\n\n    if (homepage === address) {\n      return;\n    }\n\n    dispatch(newAddress(homepage));\n    pubsub.publish(ADDRESS_CHANGE, [{address: homepage, force: true}]);\n  };\n}\n\nexport function gotoUrl(url) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {address},\n    } = getState();\n\n    if (url === address) {\n      return;\n    }\n\n    dispatch(newAddress(url));\n  };\n}\n\nexport function onDevToolsModeChange(newMode) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {devToolsConfig, windowSize},\n    } = getState();\n\n    const {mode, activeDevTools, open} = devToolsConfig;\n\n    if (mode === newMode) {\n      return;\n    }\n\n    let newActiveDevTools = [...activeDevTools];\n    let newOpen = open;\n\n    if (\n      newMode === DEVTOOLS_MODES.UNDOCKED ||\n      mode === DEVTOOLS_MODES.UNDOCKED\n    ) {\n      dispatch(onDevToolsClose(null, true));\n      if (newActiveDevTools.length > 0) {\n        newActiveDevTools = [newActiveDevTools[0]];\n        newOpen = true;\n      } else {\n        newActiveDevTools = [];\n        newOpen = false;\n      }\n    }\n\n    let newConfig = {\n      ...devToolsConfig,\n      activeDevTools: newActiveDevTools,\n      mode: newMode,\n      open: newOpen,\n    };\n    if (newMode !== DEVTOOLS_MODES.UNDOCKED) {\n      const size = getDefaultDevToolsWindowSize(newMode, windowSize);\n      const bounds = getBounds(newMode, size, windowSize);\n      newConfig = {\n        ...newConfig,\n        size,\n        bounds,\n      };\n    }\n\n    if (\n      newMode === DEVTOOLS_MODES.UNDOCKED ||\n      mode === DEVTOOLS_MODES.UNDOCKED\n    ) {\n      newConfig.activeDevTools.forEach(({webViewId, deviceId}) =>\n        setTimeout(() => {\n          dispatch(onDevToolsOpen(deviceId, webViewId, true));\n        })\n      );\n    } else {\n      ipcRenderer.send('resize-devtools', {bounds: newConfig.bounds});\n    }\n\n    dispatch(newDevToolsConfig(newConfig));\n  };\n}\n\nexport function onWindowResize(size) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {\n        windowSize: {width, height},\n        devToolsConfig,\n      },\n    } = getState();\n\n    if (width === size.width && height === size.height) {\n      return;\n    }\n\n    dispatch(newWindowSize(size));\n    const devToolsSize = getDefaultDevToolsWindowSize(\n      devToolsConfig.mode,\n      size\n    );\n    const newBounds = getBounds(devToolsConfig.mode, devToolsSize, size);\n    ipcRenderer.send('resize-devtools', {\n      bounds: newBounds,\n    });\n    dispatch(\n      newDevToolsConfig({\n        ...devToolsConfig,\n        size: devToolsSize,\n        bounds: newBounds,\n      })\n    );\n  };\n}\n\nexport function onDevToolsResize(size) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {devToolsConfig, windowSize},\n    } = getState();\n\n    const {size: devToolsSize, bounds, mode} = devToolsConfig;\n\n    if (\n      devToolsSize.width === size.width &&\n      devToolsSize.height === size.height\n    ) {\n      return;\n    }\n    const newBounds = getBounds(mode, size, windowSize);\n    ipcRenderer.send('resize-devtools', {\n      bounds: newBounds,\n    });\n\n    dispatch(newDevToolsConfig({...devToolsConfig, size, bounds: newBounds}));\n  };\n}\n\nexport function onDevToolsOpen(newDeviceId, newWebViewId, force) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {devToolsConfig},\n    } = getState();\n\n    const {open, activeDevTools, bounds, mode} = devToolsConfig;\n\n    if (\n      open &&\n      !!activeDevTools.find(({deviceId}) => deviceId === newDeviceId)\n    ) {\n      if (force) {\n        ipcRenderer.send('open-devtools', {\n          bounds,\n          mode,\n          webViewId: newWebViewId,\n        });\n      }\n      return;\n    }\n\n    let newActiveDevices = [...activeDevTools];\n\n    if (open && activeDevTools[0] && mode !== DEVTOOLS_MODES.UNDOCKED) {\n      activeDevTools.forEach(({webViewId}) => {\n        ipcRenderer.send('close-devtools', {webViewId});\n        newActiveDevices = newActiveDevices.filter(\n          ({webViewId: _webViewId}) => webViewId !== _webViewId\n        );\n      });\n    }\n\n    ipcRenderer.send('open-devtools', {bounds, mode, webViewId: newWebViewId});\n\n    const newData = {\n      ...devToolsConfig,\n      open: true,\n      activeDevTools: [\n        ...newActiveDevices,\n        {deviceId: newDeviceId, webViewId: newWebViewId},\n      ],\n    };\n\n    dispatch(newDevToolsConfig(newData));\n  };\n}\n\nexport function onDevToolsClose(devToolsInfo, closeAll) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {devToolsConfig},\n    } = getState();\n\n    const {open, activeDevTools} = devToolsConfig;\n\n    if (!open) {\n      return;\n    }\n\n    let devToolsToClose = [];\n\n    if (closeAll) {\n      devToolsToClose = [...activeDevTools];\n    } else {\n      devToolsToClose = [devToolsInfo];\n    }\n\n    let newActiveDevTools = [...activeDevTools];\n\n    devToolsToClose.forEach(({webViewId}) => {\n      ipcRenderer.send('close-devtools', {webViewId});\n      newActiveDevTools = newActiveDevTools.filter(\n        ({webViewId: _webViewId}) => _webViewId !== webViewId\n      );\n    });\n\n    dispatch(\n      newDevToolsConfig({\n        ...devToolsConfig,\n        open: newActiveDevTools.length > 0,\n        activeDevTools: newActiveDevTools,\n      })\n    );\n  };\n}\n\nexport function downloadPreferences(url) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    ipcRenderer.send('download-preferences', {url});\n  };\n}\n\nexport function uploadPreferences() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    remote.dialog\n      .showOpenDialog({\n        title: 'Select user configuration file',\n        buttonLabel: 'Upload File',\n        filters: [\n          {\n            name: 'Json file',\n            extensions: ['json'],\n          },\n        ],\n        properties: ['openFile'],\n      })\n      .then(async file => {\n        if (!file.canceled) {\n          const response = await (await fetch(file.filePaths[0])).json();\n          const preferencesObj = response;\n          dispatch(newActiveDevices(preferencesObj.devices));\n          dispatch(loadCustomDevices(preferencesObj.customDevices));\n          dispatch(newUserPreferences(preferencesObj.userPreferences));\n          dispatch(setTheme(preferencesObj.userPreferences.theme));\n        }\n      });\n  };\n}\n\nexport function setCurrentAddressAsHomepage() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {homepage, address},\n    } = getState();\n\n    if (homepage === address) {\n      return;\n    }\n\n    dispatch(newHomepage(address));\n  };\n}\n\nexport function onUserPreferencesChange(userPreferences) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    dispatch(newUserPreferences(userPreferences));\n  };\n}\n\nexport function triggerScrollDown() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(SCROLL_DOWN);\n  };\n}\n\nexport function toggleEventMirroringAllDevices(status: boolean) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(TOGGLE_EVENT_MIRRORING_ALL_DEVICES, [{status}]);\n  };\n}\n\nexport function screenshotAllDevices() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(SCREENSHOT_ALL_DEVICES, [{now: new Date()}]);\n  };\n}\n\nexport function flipOrientationAllDevices() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(FLIP_ORIENTATION_ALL_DEVICES);\n  };\n}\n\nexport function onAllDevicesMutedChange() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {allDevicesMuted},\n    } = getState();\n    const next = !allDevicesMuted;\n    pubsub.publish(TOGGLE_DEVICE_MUTED_STATE, [{muted: next}]);\n    dispatch(toggleAllDevicesMuted(next));\n  };\n}\n\nexport function onDeviceMutedChange(deviceId, isMuted) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    dispatch(toggleDeviceMuted(deviceId, isMuted));\n  };\n}\n\nexport function onToggleAllDeviceDesignMode() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {allDevicesInDesignMode},\n    } = getState();\n    const next = !allDevicesInDesignMode;\n    pubsub.publish(TOGGLE_DEVICE_DESIGN_MODE_STATE, [{designMode: next}]);\n    dispatch(toggleAllDevicesDesignMode());\n  };\n}\n\nexport function onToggleDeviceDesignMode(deviceId) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    dispatch(toggleDeviceDesignMode(deviceId));\n  };\n}\n\nexport function toggleInspector() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {isInspecting},\n    } = getState();\n\n    dispatch(newInspectorState(!isInspecting));\n  };\n}\n\nexport function toggleCSSEditor() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {\n        CSSEditor: {isOpen},\n      },\n    } = getState();\n\n    dispatch(newCSSEditorState(!isOpen));\n  };\n}\n\nexport function changeCSSEditorPosition(newPosition) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {\n        CSSEditor: {position},\n      },\n    } = getState();\n\n    if (position === newPosition) {\n      return;\n    }\n\n    dispatch(newCSSEditorPosition(newPosition));\n  };\n}\n\nexport function onCSSEditorContentChange(newContent) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {\n        CSSEditor: {content},\n      },\n    } = getState();\n\n    if (content === newContent) {\n      return;\n    }\n\n    dispatch(newCSSEditorContent(newContent));\n  };\n}\n\nexport function deviceLoadingChange(deviceInfo) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    dispatch(newDeviceLoading(deviceInfo));\n  };\n}\n\nexport function triggerScrollUp() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(SCROLL_UP);\n  };\n}\n\nexport function triggerNavigationBack() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(NAVIGATION_BACK);\n  };\n}\n\nexport function triggerNavigationForward() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(NAVIGATION_FORWARD);\n  };\n}\n\nexport function triggerNavigationReload(_, args) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const ignoreCache = (args || {}).ignoreCache || false;\n    pubsub.publish(NAVIGATION_RELOAD, [{ignoreCache}]);\n  };\n}\n\nexport function triggerStopLoading(_, args) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(STOP_LOADING);\n  };\n}\n\nexport function deleteCookies() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(DELETE_STORAGE, [{storages: ['cookies']}]);\n  };\n}\n\nexport function deleteStorage() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(DELETE_STORAGE, [\n      {\n        storages: [\n          'appcache',\n          'filesystem',\n          'indexdb',\n          'localstorage',\n          'shadercache',\n          'websql',\n          'serviceworkers',\n          'cachestorage',\n        ],\n      },\n    ]);\n  };\n}\n\nexport function reloadCSS() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(RELOAD_CSS);\n  };\n}\n\nexport function setTheme(theme) {\n  return {\n    type: NEW_THEME,\n    theme,\n  };\n}\n\n/**\n * Shows/Hides the top control pane.\n * @param {boolean} isVisible Shows the top pane when true and hides when false.\n */\nexport function setHeaderVisibility(isVisible: boolean) {\n  return {\n    type: SET_HEADER_VISIBILITY,\n    isVisible,\n  };\n}\n\n/**\n * Shows/Hides the left control pane.\n * @param {boolean} isVisible Shows the left control pane when true and hides when false.\n */\nexport function setLeftPaneVisibility(isVisible: boolean) {\n  return {\n    type: SET_LEFT_PANE_VISIBILITY,\n    isVisible,\n  };\n}\n\n/**\n * Sets the url being hovered in a webview. When empty, it means that no anchor element is being hovered.\n *\n * @param {string} url URL from the anchor tag that is being hovered. Pass empty string to indicate no links are being hovered.\n * @returns Redux action with type as SET_HOVERED_LINK\n */\nexport function setHoveredLink(url) {\n  return {\n    type: SET_HOVERED_LINK,\n    url,\n  };\n}\n\nexport function setStartupPage(value: 'BLANK' | 'HOME') {\n  return {\n    type: SET_STARTUP_PAGE,\n    value,\n  };\n}\n\nexport function changeStartupPage(value: 'BLANK' | 'HOME') {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    dispatch(setStartupPage(value));\n  };\n}\n\nexport function updatePageNavigator(selector, index) {\n  return {\n    type: UPDATE_PAGE_NAVIGATOR,\n    selector,\n    index,\n  };\n}\n\nexport function resetPageNavigator() {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    const {\n      browser: {\n        pageNavigator: {selector, index},\n      },\n    } = getState();\n    if (selector == null && index == null) return;\n    dispatch(updatePageNavigator(null, null));\n  };\n}\n\nexport function navigateToNextSelector(selector) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    if ((selector || '').length === 0) return;\n\n    const {\n      browser: {pageNavigator},\n    } = getState();\n\n    let index = pageNavigator.index;\n\n    if (pageNavigator.selector !== selector || index == null) index = -1;\n\n    pubsub.publish(PAGE_NAVIGATOR_CHANGED, [\n      {selector, index: (index || 0) + 1},\n    ]);\n    dispatch(updatePageNavigator(selector, (index || 0) + 1));\n  };\n}\n\nexport function navigateToPrevSelector(selector) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    if ((selector || '').length === 0) return;\n\n    const {\n      browser: {pageNavigator},\n    } = getState();\n\n    let index = pageNavigator.index;\n\n    if (pageNavigator.selector !== selector || index == null) index = 0;\n\n    pubsub.publish(PAGE_NAVIGATOR_CHANGED, [\n      {selector, index: (index || 0) - 1},\n    ]);\n    dispatch(updatePageNavigator(selector, (index || 0) - 1));\n  };\n}\n\nexport function setPageNavigatorActive(active) {\n  return {\n    type: TOGGLE_PAGE_NAVIGATOR,\n    active,\n  };\n}\n\nexport function onChangePageNavigatorActive(active) {\n  return (dispatch: Dispatch, getState: RootStateType) => {\n    pubsub.publish(TOGGLE_EVENT_MIRRORING_ALL_DEVICES, [{status: !active}]);\n    dispatch(setPageNavigatorActive(active));\n  };\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/actions/networkConfig.js",
    "content": "import pubsub from 'pubsub.js';\nimport type {Dispatch, GetState} from '../reducers/types';\nimport {\n  SET_NETWORK_TROTTLING_PROFILE,\n  CLEAR_NETWORK_CACHE,\n} from '../constants/pubsubEvents';\nimport {convertToProxyConfig, proxyRuleToString} from '../utils/proxyUtils';\nimport {ipcRenderer} from 'electron';\n\nexport const CHANGE_ACTIVE_THROTTLING_PROFILE =\n  'CHANGE_ACTIVE_THROTTLING_PROFILE';\nexport const SAVE_THROTTLING_PROFILES = 'SAVE_THROTTLING_PROFILES';\n\nexport const TOGGLE_USE_PROXY = 'TOGGLE_USE_PROXY';\nexport const CHANGE_PROXY_PROFILE = 'CHANGE_PROXY_PROFILE';\n\nexport function changeActiveThrottlingProfile(title = 'Online') {\n  return {\n    type: CHANGE_ACTIVE_THROTTLING_PROFILE,\n    title,\n  };\n}\n\nexport function saveThrottlingProfilesList(profiles) {\n  return {\n    type: SAVE_THROTTLING_PROFILES,\n    profiles,\n  };\n}\n\nexport function onActiveThrottlingProfileChanged(title) {\n  return (dispatch: Dispatch, getState: GetState) => {\n    const {\n      browser: {\n        networkConfiguration: {throttling},\n      },\n    } = getState();\n\n    const activeProfile = throttling.find(x => x.title === title);\n\n    if (activeProfile != null) {\n      pubsub.publish(SET_NETWORK_TROTTLING_PROFILE, [activeProfile]);\n      dispatch(changeActiveThrottlingProfile(title));\n    }\n  };\n}\n\nexport function onThrottlingProfilesListChanged(profiles) {\n  return (dispatch: Dispatch, getState: GetState) => {\n    dispatch(saveThrottlingProfilesList(profiles));\n    const activeProfile = profiles.find(x => x.type === 'Online');\n    pubsub.publish(SET_NETWORK_TROTTLING_PROFILE, [activeProfile]);\n  };\n}\n\nexport function onClearNetworkCache() {\n  return (dispatch: Dispatch, getState: GetState) => {\n    pubsub.publish(CLEAR_NETWORK_CACHE);\n  };\n}\n\nexport function toggleUseProxy(useProxy: boolean = false) {\n  return {\n    type: TOGGLE_USE_PROXY,\n    useProxy,\n  };\n}\n\nexport function changeProxyProfile(profile) {\n  return {\n    type: CHANGE_PROXY_PROFILE,\n    profile,\n  };\n}\n\nexport function onToggleUseProxy(useProxy: boolean = false) {\n  return (dispatch: Dispatch, getState: GetState) => {\n    const {\n      browser: {\n        networkConfiguration: {proxy},\n      },\n    } = getState();\n    ipcRenderer.send(\n      'set-proxy-profile',\n      convertToProxyConfig({...proxy, active: !!useProxy})\n    );\n    dispatch(toggleUseProxy(useProxy));\n  };\n}\n\nexport function onProxyProfileChanged(profile) {\n  return (dispatch: Dispatch, getState: GetState) => {\n    ipcRenderer.send('set-proxy-profile', convertToProxyConfig(profile));\n    dispatch(changeProxyProfile(profile));\n  };\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/actions/statusBar.js",
    "content": "// @flow\nimport {statusBarSettings} from '../settings/statusBarSettings';\n\nexport const TOGGLE_STATUS_BAR_VISIBILITY = 'TOGGLE_STATUS_BAR_VISIBILITY';\n\nexport function toggleStatusBarVisibility() {\n  const newVisibility = !statusBarSettings.getVisibility();\n  statusBarSettings.setVisibility(newVisibility);\n\n  return {\n    type: TOGGLE_STATUS_BAR_VISIBILITY,\n    visible: newVisibility,\n  };\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/app-updater.js",
    "content": "import {autoUpdater} from 'electron-updater';\nimport log from 'electron-log';\nimport {getPackageJson} from './utils/generalUtils';\nimport {shell, Notification} from 'electron';\n\nconst {EventEmitter} = require('events');\n\nconst AppUpdaterStatus = {\n  Idle: 'idle',\n  Checking: 'checking',\n  NoUpdate: 'noUpdate',\n  NewVersion: 'newVersion',\n  Downloading: 'downloading',\n  Downloaded: 'downloaded',\n};\n\nObject.freeze(AppUpdaterStatus);\n\nclass AppUpdater extends EventEmitter {\n  status: string;\n\n  timerId = null;\n\n  isPortableVersion =\n    process.env.PORTABLE_EXECUTABLE_DIR != null &&\n    process.env.PORTABLE_EXECUTABLE_DIR.length !== 0;\n\n  constructor() {\n    super();\n    log.transports.file.level = 'info';\n    autoUpdater.logger = log;\n    autoUpdater.autoDownload = !this.isPortableVersion;\n    this.status = AppUpdaterStatus.Idle;\n\n    autoUpdater.on('checking-for-update', () =>\n      this.handleStatusChange(AppUpdaterStatus.Checking, false)\n    );\n    autoUpdater.on('update-not-available', () =>\n      this.handleStatusChange(AppUpdaterStatus.NoUpdate, true)\n    );\n    autoUpdater.on('download-progress', () =>\n      this.handleStatusChange(AppUpdaterStatus.Downloading, false)\n    );\n    autoUpdater.on('update-downloaded', () =>\n      this.handleStatusChange(AppUpdaterStatus.Downloaded, true)\n    );\n  }\n\n  getCurrentStatus() {\n    return this.status;\n  }\n\n  checkForUpdatesAndNotify() {\n    const pkg = getPackageJson();\n    if (this.status === AppUpdaterStatus.Idle) {\n      if (this.isPortableVersion) {\n        return autoUpdater.checkForUpdates().then(r => {\n          if (\n            r?.updateInfo?.version != null &&\n            r.updateInfo.version !== pkg.version\n          ) {\n            this.handleStatusChange(AppUpdaterStatus.NewVersion, true);\n            if (Notification.isSupported()) {\n              const notif = new Notification({\n                title: `New version ${r.updateInfo.version} available`,\n                body:\n                  'You have an outdated version of Responsively. Click here to download the latest version',\n              });\n              notif.on('click', () => {\n                shell.openExternal(\n                  'https://github.com/responsively-org/responsively-app/releases/latest'\n                );\n              });\n              notif.show();\n            }\n          }\n          return r;\n        });\n      }\n      return autoUpdater.checkForUpdatesAndNotify();\n    }\n  }\n\n  handleStatusChange(nextStatus: string, backToIdle: boolean) {\n    clearTimeout(this.timerId);\n\n    if (this.status !== nextStatus) {\n      this.status = nextStatus;\n      this.emit('status-changed', nextStatus);\n\n      if (backToIdle) {\n        this.timerId = setTimeout(() => {\n          if (this.status !== AppUpdaterStatus.Idle) {\n            this.status = AppUpdaterStatus.Idle;\n            this.emit('status-changed', AppUpdaterStatus.Idle);\n          }\n        }, 2000);\n      }\n    }\n  }\n}\n\nconst appUpdaterInstance = new AppUpdater();\nexport {AppUpdaterStatus, appUpdaterInstance as appUpdater};\n"
  },
  {
    "path": "desktop-app-legacy/app/app.global.css",
    "content": "/*\n * @NOTE: Prepend a `~` to css file paths that are in your node_modules\n *        See https://github.com/webpack-contrib/sass-loader#imports\n */\n@import '~@fortawesome/fontawesome-free/css/all.css';\n@import '~react-toastify/dist/ReactToastify.css';\n@import '~react-tabs/style/react-tabs.css';\n\n* {\n  box-sizing: border-box;\n  -webkit-user-select: none;\n}\n\nbody {\n  position: relative;\n  color: white;\n  height: 100vh;\n  margin: 0;\n  font-family: 'Roboto', sans-serif;\n  overflow: hidden;\n}\n\nsvg {\n  pointer-events: none;\n}\n\np {\n  font-size: 24px;\n}\n\nli {\n  list-style: none;\n}\n\na {\n  color: white;\n  opacity: 0.75;\n  text-decoration: none;\n}\n\na:hover {\n  opacity: 1;\n  text-decoration: none;\n  cursor: pointer;\n}\n\n.spin::before {\n  animation: fa-spin 1s linear infinite;\n}\n\n#root {\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n}\n\n#HW_badge_cont {\n  position: absolute !important;\n  width: 100% !important;\n  height: 100% !important;\n}\n#HW_badge {\n  left: 26px !important;\n  top: 6px !important;\n}\n\n/* --start\nAdd device form styles */\n.padded-input input {\n  padding: 20px 14px;\n}\n\n.input-adornment p {\n  color: #ffffff90;\n}\n\n/* Add device form styles \n--End */\n\n/*\n{\n\t\"$schema\": \"vscode://schemas/color-theme\",\n\t\"name\": \"Dark Default Colors\",\n\t\"colors\": {\n\t\t\"editor.background\": \"#1E1E1E\",\n\t\t\"editor.foreground\": \"#D4D4D4\",\n\t\t\"editor.inactiveSelectionBackground\": \"#3A3D41\",\n\t\t\"editorIndentGuide.background\": \"#404040\",\n\t\t\"editorIndentGuide.activeBackground\": \"#707070\",\n\t\t\"editor.selectionHighlightBackground\": \"#ADD6FF26\",\n\t\t\"list.dropBackground\": \"#383B3D\",\n\t\t\"activityBarBadge.background\": \"#007ACC\",\n\t\t\"sideBarTitle.foreground\": \"#BBBBBB\",\n\t\t\"input.placeholderForeground\": \"#A6A6A6\",\n\t\t\"settings.textInputBackground\": \"#292929\",\n\t\t\"settings.numberInputBackground\": \"#292929\",\n\t\t\"menu.background\": \"#252526\",\n\t\t\"menu.foreground\": \"#CCCCCC\"\n\t}\n}\n*/\n\n/* This is to make the scrollbar consistent */\n::-webkit-scrollbar {\n  width: 10px;\n  height: 10px;\n}\n\n/* The scrollbar Track */\n::-webkit-scrollbar-track {\n  display: none;\n}\n\n/* When the box area between the horzontal scrollbar and the vertical scrollbar */\n::-webkit-scrollbar-corner {\n  background: rgba(0, 0, 0, 0);\n}\n\n.ace_mobile-menu {\n  display: none;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/app.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"default-src * data: blob: 'unsafe-inline' 'unsafe-eval';\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <meta charset=\"utf-8\" />\n    <title>Responsively</title>\n    <script src=\"../node_modules/electron-cookies/dist/electron-cookies.js\"></script>\n    <script type=\"text/javascript\">\n      // prettier-ignore\n      window.heap = window.heap || [], heap.load = function (e, t) { window.heap.appid = e, window.heap.config = t = t || {}; var r = t.forceSSL || \"https:\" === document.location.protocol, a = document.createElement(\"script\"); a.type = \"text/javascript\", a.async = !0, a.src =  \"https://cdn.heapanalytics.com/js/heap-\" + e + \".js\"; var n = document.getElementsByTagName(\"script\")[0]; n.parentNode.insertBefore(a, n); for (var o = function (e) { return function () { heap.push([e].concat(Array.prototype.slice.call(arguments, 0))) } }, p = [\"addEventProperties\", \"addUserProperties\", \"clearEventProperties\", \"identify\", \"resetIdentity\", \"removeEventProperty\", \"setEventProperties\", \"track\", \"unsetEventProperty\"], c = 0; c < p.length; c++)heap[p[c]] = o(p[c]) };\n      heap.load('3453744655');\n    </script>\n    <script>\n      (function() {\n        if (!process.env.HOT) {\n          const link = document.createElement('link');\n          link.rel = 'stylesheet';\n          link.href = './dist/style.css';\n          // HACK: Writing the script path should be done with webpack\n          document.getElementsByTagName('head')[0].appendChild(link);\n        }\n      })();\n    </script>\n  </head>\n\n  <body>\n    <div id=\"root\"></div>\n    <script>\n      {\n        const scripts = [];\n\n        // Dynamically insert the DLL script in development env in the\n        // renderer process\n        if (process.env.NODE_ENV === 'development') {\n          scripts.push('../dll/renderer.dev.dll.js');\n        }\n\n        // Dynamically insert the bundled app script in the renderer process\n        const port = process.env.PORT || 1212;\n        scripts.push(\n          process.env.HOT\n            ? 'http://localhost:' + port + '/dist/renderer.dev.js'\n            : './dist/renderer.prod.js'\n        );\n\n        document.write(\n          scripts\n            .map(script => `<script defer src=\"${script}\"><\\/script>`)\n            .join('')\n        );\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "desktop-app-legacy/app/components/AddressInput.js",
    "content": "import React from 'react';\nimport cx from 'classnames';\nimport FavIconOff from '@material-ui/icons/StarBorder';\nimport FavIconOn from '@material-ui/icons/Star';\nimport {Tooltip} from '@material-ui/core';\nimport {withTheme, withStyles, styled} from '@material-ui/core/styles';\nimport debounce from 'lodash/debounce';\nimport HomePlusIcon from './icons/HomePlus';\nimport DeleteCookieIcon from './icons/DeleteCookie';\nimport DeleteStorageIcon from './icons/DeleteStorage';\nimport StartIcon from './icons/Start';\nimport {\n  getExistingSearchResults,\n  updateExistingUrl,\n  searchUrlUtils,\n} from '../services/searchUrlSuggestions';\nimport UrlSearchResults from './UrlSearchResults';\nimport {styles as commonStyles} from './useCommonStyles';\nimport {notifyPermissionToHandleReloadOrNewAddress} from '../utils/permissionUtils';\nimport {normalize} from '../utils/urlUtils';\n\nclass AddressBar extends React.Component {\n  props: Props;\n\n  constructor(props) {\n    super(props);\n    this.state = {\n      userTypedAddress: props.address,\n      previousAddress: props.address,\n      suggestionList: [],\n      canShowSuggestions: false,\n      cursor: null,\n    };\n    this.inputRef = React.createRef();\n  }\n\n  componentDidMount() {\n    document.addEventListener('click', this._handleClickOutside);\n  }\n\n  componentWillUnmount() {\n    document.removeEventListener('click', this._handleClickOutside);\n  }\n\n  static getDerivedStateFromProps(props, state) {\n    if (props.address !== state.previousAddress) {\n      return {\n        userTypedAddress: props.address,\n        previousAddress: props.address,\n      };\n    }\n    return null;\n  }\n\n  render() {\n    const {\n      suggestionList,\n      canShowSuggestions,\n      cursor,\n      userTypedAddress,\n    } = this.state;\n    const {theme, classes, address, homepage} = this.props;\n    const addressBarSameAsHomepage = address === homepage;\n    const showSuggestions =\n      canShowSuggestions && !this._isSuggestionListEmpty();\n\n    return (\n      <div\n        className={cx(classes.addressBarContainer, {\n          [classes.showSuggestions]: showSuggestions,\n        })}\n      >\n        <input\n          ref={this.inputRef}\n          type=\"text\"\n          id=\"adress\"\n          name=\"address\"\n          className={classes.addressInput}\n          placeholder=\"https://your-website.com\"\n          value={userTypedAddress}\n          onKeyDown={this._handleKeyDown}\n          onChange={this._handleInputChange}\n        />\n        <div className={classes.optionsContainer}>\n          <div className={cx(classes.icon, classes.iconRound)}>\n            <Tooltip\n              title={\n                this.props.isBookmarked\n                  ? 'Remove from Bookmarks'\n                  : 'Add to Bookmarks'\n              }\n            >\n              <div\n                className={classes.flexAlignVerticalMiddle}\n                onClick={() => this.props.toggleBookmark(userTypedAddress)}\n              >\n                <StartIcon\n                  width={22}\n                  height={22}\n                  padding={5}\n                  strokeColor=\"currentColor\"\n                  fillColor={this.props.isBookmarked ? 'currentColor' : 'none'}\n                />\n              </div>\n            </Tooltip>\n          </div>\n          <div className={cx(classes.icon, classes.iconRound)}>\n            <Tooltip title=\"Delete Storage\">\n              <div\n                className={classes.flexAlignVerticalMiddle}\n                onClick={this.props.deleteStorage}\n              >\n                <DeleteStorageIcon\n                  height={22}\n                  width={22}\n                  color=\"currentColor\"\n                  padding={5}\n                />\n              </div>\n            </Tooltip>\n          </div>\n          <div className={cx(classes.icon, classes.iconRound)}>\n            <Tooltip title=\"Delete Cookies\">\n              <div\n                className={classes.flexAlignVerticalMiddle}\n                onClick={this.props.deleteCookies}\n              >\n                <DeleteCookieIcon\n                  height={22}\n                  width={22}\n                  color=\"currentColor\"\n                  padding={5}\n                />\n              </div>\n            </Tooltip>\n          </div>\n          <div\n            className={cx(classes.icon, classes.iconRound, {\n              [classes.iconHoverDisabled]: addressBarSameAsHomepage,\n              [classes.iconDisabled]: addressBarSameAsHomepage,\n            })}\n          >\n            <Tooltip title=\"Set as Homepage\">\n              <div\n                className={cx(commonStyles.flexAlignVerticalMiddle)}\n                onClick={this.props.setHomepage}\n              >\n                <HomePlusIcon\n                  height={22}\n                  width={22}\n                  color={\n                    addressBarSameAsHomepage\n                      ? theme.palette.text.primary\n                      : 'currentColor'\n                  }\n                  padding={5}\n                />\n              </div>\n            </Tooltip>\n          </div>\n        </div>\n        {showSuggestions ? (\n          <UrlSearchResults\n            filteredSearchResults={suggestionList}\n            cursorIndex={cursor}\n            handleUrlChange={this._onSearchedUrlClick}\n          />\n        ) : null}\n      </div>\n    );\n  }\n\n  _handleInputChange = e => {\n    const {value} = e.target;\n    if (value) {\n      this.setState({userTypedAddress: value, canShowSuggestions: true}, () => {\n        this._filterExistingUrl();\n      });\n    } else {\n      this.setState({userTypedAddress: value, suggestionList: []}, () => {\n        this._hideSuggestions();\n      });\n    }\n  };\n\n  _handleKeyDown = e => {\n    const {cursor, suggestionList} = this.state;\n    if (e.key === 'Enter') {\n      this.inputRef.current.blur();\n      this.setState(\n        {\n          suggestionList: [],\n          canShowSuggestions: false,\n          cursor: null,\n        },\n        () => {\n          this._onChange();\n        }\n      );\n    } else if (e.key === 'ArrowUp' && !this._isSuggestionListEmpty()) {\n      // if the suggestion list just opened or the first suggestion is selected set the cursor to the last suggestion\n      if (cursor === null || cursor === 0) {\n        this._openSuggestionListAndSetCursorAt(suggestionList.length - 1);\n        // if cursor is down move it up by subtracting 1\n      } else if (cursor > 0) {\n        this._handleSuggestionSelection(-1);\n      }\n    } else if (e.key === 'ArrowDown' && !this._isSuggestionListEmpty()) {\n      // if the suggestion list just opened or the last suggestion is selected set the cursor to the first suggestion\n      if (cursor === null || cursor === suggestionList.length - 1) {\n        this._openSuggestionListAndSetCursorAt(0);\n        // if cursor is up move it down by adding 1\n      } else if (cursor < suggestionList.length - 1) {\n        this._handleSuggestionSelection(1);\n      }\n    } else if (e.key === 'Escape') {\n      this._hideSuggestions();\n    }\n  };\n\n  _hideSuggestions = () => {\n    this.setState({\n      canShowSuggestions: false,\n      cursor: null,\n    });\n  };\n\n  /**\n   * Open suggestion list and set current selection at cursor position.\n   * @param {number} cursor Cursor position.\n   */\n  _openSuggestionListAndSetCursorAt = cursor => {\n    this.setState(prevState => ({\n      cursor,\n      userTypedAddress: this.state.suggestionList[cursor].url,\n      canShowSuggestions: true,\n    }));\n  };\n  /**\n   * Handles the suggestion selection on arrow key up and down.\n   * @param {number} direction Indicates the direction. 1 for down, -1 for up.\n   */\n  _handleSuggestionSelection = direction => {\n    const modifier = 1 * direction;\n    this.setState(prevState => ({\n      cursor: prevState.cursor + modifier,\n      userTypedAddress: this.state.suggestionList[prevState.cursor + modifier]\n        .url,\n      canShowSuggestions: true,\n    }));\n  };\n\n  _isSuggestionListEmpty = () => this.state.suggestionList.length === 0;\n\n  _handleClickOutside = () => {\n    this._hideSuggestions();\n  };\n\n  _onChange = () => {\n    if (!this.state.userTypedAddress || !this.props.onChange) {\n      return;\n    }\n\n    notifyPermissionToHandleReloadOrNewAddress();\n    this.props.onChange(normalize(this.state.userTypedAddress), true);\n  };\n\n  _onSearchedUrlClick = (url, index) => {\n    if (url !== this.state.previousAddress) {\n      this.props.onChange(normalize(url), true);\n    }\n\n    this.setState({\n      userTypedAddress: url,\n      suggestionList: [],\n    });\n  };\n\n  _filterExistingUrl = debounce(() => {\n    const finalResult = searchUrlUtils(this.state.userTypedAddress);\n    this.setState({suggestionList: finalResult.slice(0, MAX_SUGGESTIONS)});\n  }, 300);\n}\n\nconst MAX_SUGGESTIONS = 8;\n\nconst styles = theme => {\n  const {mode} = theme.palette;\n  return {\n    ...commonStyles(theme),\n    addressBarContainer: {\n      display: 'flex',\n      position: 'relative',\n      alignItems: 'center',\n      height: '20px',\n      width: '100%',\n      padding: '14px 10px',\n      borderRadius: '20px',\n      backgroundColor: theme.palette.background.l0,\n      color: theme.palette.text.normal,\n      border: `1px solid ${theme.palette.lightIcon.main}`,\n      outline: 'none',\n      transition: 'border 500ms ease-out',\n      '&:focus-within': {\n        color: theme.palette.text.active,\n        border: `1px solid ${theme.palette.primary.main}`,\n      },\n    },\n    showSuggestions: {\n      borderRadius: '14px 14px 0 0',\n    },\n    addressInput: {\n      height: '20px',\n      background: 'unset',\n      fontSize: '16px',\n      color: 'inherit',\n      border: 'none',\n      width: '92%',\n      margin: '0',\n      outline: 'none',\n      textOverflow: 'ellipsis',\n    },\n    optionsContainer: {\n      display: 'flex',\n      alignItems: 'center',\n      position: 'absolute',\n      right: 5,\n      color: theme.palette.text.primary,\n    },\n  };\n};\n\nexport default withStyles(styles)(withTheme(AddressBar));\n"
  },
  {
    "path": "desktop-app-legacy/app/components/AppNotification/AppNotification.js",
    "content": "import React, {useEffect, useState} from 'react';\nimport cx from 'classnames';\nimport {motion} from 'framer-motion';\nimport {shell} from 'electron';\nimport settings from 'electron-settings';\nimport {APP_NOTIFICATION} from '../../constants/settingKeys';\nimport useCommonStyles from '../useCommonStyles';\nimport styles from './styles.module.css';\nimport appMetadata from '../../services/db/appMetadata';\nimport logo from '../../../resources/logo.svg';\n\nfunction updateNotificationStatus(id, action) {\n  const notifications = settings.get(APP_NOTIFICATION) || [];\n\n  const notificationStatusObject = {\n    id,\n    action,\n  };\n  notifications.push(notificationStatusObject);\n  settings.set(APP_NOTIFICATION, notifications);\n}\n\nfunction checkIfInteracted(id) {\n  const notifications = settings.get(APP_NOTIFICATION);\n\n  let seenNotification;\n  if (notifications) {\n    seenNotification = notifications.find(\n      notification => notification.id === id\n    );\n  }\n  if (seenNotification) {\n    return true;\n  }\n  return false;\n}\n\nconst AppNotification = () => {\n  const [notificationInteracted, setNotificationInteracted] = useState(false);\n  const [data, setData] = useState(null);\n  const commonClasses = useCommonStyles();\n\n  useEffect(() => {\n    if (process.env.NODE_ENV === 'development') {\n      return;\n    }\n    (async () => {\n      try {\n        const response = await (\n          await fetch('https://responsively.app/assets/appMessages.json')\n        ).json();\n        if (!response?.notifications) {\n          return;\n        }\n        const notifications = response.notifications.sort(\n          (a, b) => a.minOpenCount - b.minOpenCount\n        );\n        const eligibleNotifications = notifications\n          .filter(({minOpenCount}) => appMetadata.getOpenCount() > minOpenCount)\n          .filter(({id}) => !checkIfInteracted(id));\n        if (eligibleNotifications.length === 0) {\n          return;\n        }\n        setData(eligibleNotifications[0]);\n      } catch (err) {\n        console.log('Error fetching appMessages.json', err);\n      }\n    })();\n  }, []);\n\n  if (!data || (!notificationInteracted && checkIfInteracted(data.id))) {\n    return null;\n  }\n\n  const {id, title, text, okText, dismissText, link} = data;\n\n  const notificationClicked = () => {\n    shell.openExternal(link);\n    updateNotificationStatus(id, 'ANSWERED');\n    setNotificationInteracted(true);\n  };\n\n  return (\n    <motion.div\n      className={styles.container}\n      initial={{x: notificationInteracted ? 0 : 500, scale: 1}}\n      animate={{x: notificationInteracted ? 500 : 0, scale: 1}}\n      transition={{\n        type: 'spring',\n        stiffness: 260,\n        damping: 20,\n        delay: notificationInteracted ? 0 : 3,\n      }}\n    >\n      <div className={commonClasses.flexContainer}>\n        <img src={logo} width={150} />\n      </div>\n      <div className={cx(styles.titleContainer, commonClasses.flexContainer)}>\n        <div\n          className={cx(commonClasses.flexContainer)}\n          onClick={notificationClicked}\n        >\n          {title}\n        </div>\n      </div>\n      <div className={commonClasses.flexContainer}>\n        <div className={styles.content} onClick={notificationClicked}>\n          {text}\n        </div>\n      </div>\n      <div className={styles.responseButtonsContainer}>\n        <div\n          className={cx('notificationDismiss', styles.responseButtons)}\n          onClick={() => {\n            updateNotificationStatus(id, 'DISMISS');\n            setNotificationInteracted(true);\n          }}\n        >\n          {dismissText}\n        </div>\n        <div\n          className={cx(\n            'notificationOK',\n            styles.responseButtons,\n            styles.okButton\n          )}\n          onClick={notificationClicked}\n        >\n          {okText}\n        </div>\n      </div>\n    </motion.div>\n  );\n};\n\nexport default AppNotification;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/AppNotification/styles.module.css",
    "content": ".container {\n  font-size: 14px;\n  position: absolute;\n  bottom: 35px;\n  right: 25px;\n  background: #7587ec;\n  border-radius: 5px;\n  word-wrap: break-word;\n  box-shadow: inset 0 0 1px #000;\n  padding: 10px 20px 20px 20px;\n  min-width: 250px;\n  max-width: 320px;\n  color: #f8f8f8;\n  z-index: 2;\n}\n\n.titleContainer {\n  cursor: pointer;\n  font-size: 16px;\n  font-weight: 500;\n  margin: 10px;\n  text-align: center;\n}\n\n.content {\n  cursor: pointer;\n  margin: 0 0 25px 0;\n  width: 70%;\n  text-align: center;\n}\n\n.responseButtons {\n  padding: 8px;\n  border: solid 1px white;\n  border-radius: 100px;\n  margin: 0 10px;\n  min-width: 70px;\n  justify-content: center;\n  display: flex;\n  cursor: pointer;\n}\n\n.responseButtons:hover {\n  background: #6679e2;\n}\n\n.okButton {\n  background: #efefef;\n  border: solid 1px #efefef;\n  color: black;\n}\n\n.okButton:hover {\n  background: white;\n  border: solid 1px white;\n  color: #1e1e1e;\n}\n\n.responseButtonsContainer {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/BookmarksBar/BookmarkEditDialog.js",
    "content": "import React, {useRef} from 'react';\nimport Button from '@material-ui/core/Button';\nimport TextField from '@material-ui/core/TextField';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\n\nexport default function BookmarkEditDialog({\n  open,\n  onClose,\n  onSubmit,\n  bookmark,\n}) {\n  const titleInput = useRef(null);\n  const urlInput = useRef(null);\n\n  const handleSubmit = function handleSubmit(e) {\n    onSubmit(\n      titleInput.current.querySelector('input').value,\n      urlInput.current.querySelector('input').value\n    );\n    onClose();\n  };\n\n  const handleKeyPress = function handleKeyPress(e) {\n    if (e.key === 'Enter') {\n      handleSubmit(e);\n    } else if (e.key === 'Escape') {\n      onClose();\n    }\n  };\n\n  return (\n    <Dialog open={open} onClose={onClose} aria-labelledby=\"form-dialog-title\">\n      <DialogTitle id=\"form-dialog-title\">Bookmark title</DialogTitle>\n      <DialogContent>\n        <TextField\n          autoFocus\n          ref={titleInput}\n          margin=\"dense\"\n          id=\"title\"\n          label=\"Title\"\n          type=\"text\"\n          onKeyPress={handleKeyPress}\n          defaultValue={bookmark.title}\n          fullWidth\n        />\n        <TextField\n          style={{marginTop: '16px'}}\n          autoFocus\n          ref={urlInput}\n          margin=\"dense\"\n          id=\"url\"\n          label=\"URL\"\n          type=\"text\"\n          onKeyPress={handleKeyPress}\n          defaultValue={bookmark.url}\n          fullWidth\n        />\n      </DialogContent>\n      <DialogActions>\n        <Button onClick={onClose}>Cancel</Button>\n        <Button onClick={handleSubmit} color=\"primary\">\n          Update\n        </Button>\n      </DialogActions>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/BookmarksBar/index.js",
    "content": "import React, {useState} from 'react';\nimport Grid from '@material-ui/core/Grid';\nimport Button from '@material-ui/core/Button';\nimport Menu from '@material-ui/core/Menu';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport {makeStyles} from '@material-ui/core/styles';\nimport BookmarkEditDialog from './BookmarkEditDialog';\nimport Globe from '../icons/Globe';\n\nconst BookmarksBar = ({\n  bookmarks,\n  onBookmarkClick,\n  onBookmarkDelete,\n  onBookmarkEdit,\n}) => {\n  const classes = useStyles();\n  return (\n    <div className={classes.bookmarks}>\n      {bookmarks.map((bookmark, k) => (\n        <BookmarkItem\n          bookmark={bookmark}\n          onClick={onBookmarkClick}\n          key={`bookmark${k}`}\n          onDelete={onBookmarkDelete}\n          onEdit={onBookmarkEdit}\n        />\n      ))}\n    </div>\n  );\n};\n\nconst useToggle = () => {\n  const [value, setValue] = useState(false);\n  return [\n    value,\n    () => {\n      setValue(true);\n    },\n    () => {\n      setValue(false);\n    },\n  ];\n};\n\nconst BookmarkItem = ({bookmark, onClick, onDelete, onEdit}) => {\n  const [anchorEl, setAnchorEl] = useState(null);\n  const [renameDialog, openRenameDialog, closeRenameDialog] = useToggle(null);\n  const classes = useStyles();\n\n  const handleContextMenu = function handleContextMenu(event) {\n    event.preventDefault();\n    setAnchorEl(event.currentTarget);\n  };\n\n  const handleClose = () => {\n    setAnchorEl(null);\n  };\n\n  const handleClick = () => {\n    onClick(bookmark);\n  };\n\n  const handleDelete = () => {\n    setAnchorEl(null);\n    onDelete(bookmark);\n  };\n\n  const handleRename = (title, url) => {\n    onEdit(bookmark, {title, url});\n    setAnchorEl(null);\n  };\n\n  const closeDialog = () => {\n    closeRenameDialog();\n    setAnchorEl(null);\n  };\n\n  return (\n    <>\n      <div\n        className={classes.bookmarkItem}\n        key={bookmark.url}\n        onClick={handleClick}\n        onContextMenu={handleContextMenu}\n      >\n        <Globe height={10} className={classes.icon} />\n        <span>{bookmark.title}</span>\n      </div>\n      <Menu\n        id=\"bookmark-menu\"\n        anchorEl={anchorEl}\n        keepMounted\n        open={Boolean(anchorEl)}\n        onClose={handleClose}\n        anchorOrigin={{\n          vertical: 'bottom',\n          horizontal: 'center',\n        }}\n        getContentAnchorEl={null}\n        onContextMenu={handleClose}\n      >\n        <MenuItem onClick={openRenameDialog}>Edit</MenuItem>\n        <MenuItem onClick={handleDelete}>Delete</MenuItem>\n      </Menu>\n      <BookmarkEditDialog\n        open={renameDialog}\n        onSubmit={handleRename}\n        onClose={closeDialog}\n        bookmark={bookmark}\n      />\n    </>\n  );\n};\n\nconst useStyles = makeStyles(theme => ({\n  bookmarks: {\n    padding: '0 10px',\n    display: 'flex',\n    marginTop: '4px',\n  },\n  bookmarkItem: {\n    color: theme.palette.text.primary,\n    fill: theme.palette.text.primary,\n    cursor: 'default',\n    padding: '2px 10px',\n    borderRadius: '15px',\n    fontSize: '13px',\n    minWidth: '100px',\n    maxWidth: '150px',\n    overflow: 'hidden',\n    textOverflow: 'ellipsis',\n    whiteSpace: 'nowrap',\n    '&:hover': {\n      backgroundColor: theme.palette.primary.main,\n      color: theme.palette.background.l0,\n      fill: theme.palette.background.l0,\n    },\n  },\n  icon: {\n    marginRight: '5px',\n  },\n}));\n\nexport default BookmarksBar;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ClearNetworkCache/index.js",
    "content": "import React from 'react';\nimport cx from 'classnames';\nimport Button from '@material-ui/core/Button';\nimport CachedIcon from '@material-ui/icons/Cached';\nimport useCommonStyles from '../useCommonStyles';\n\nfunction ClearNetworkCache(props) {\n  const commonClasses = useCommonStyles();\n\n  return (\n    <div className={commonClasses.sidebarContentSection}>\n      <div className={commonClasses.sidebarContentSectionTitleBar}>\n        <CachedIcon style={{marginRight: 5}} /> Network Cache\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <Button\n          variant=\"contained\"\n          color=\"primary\"\n          aria-label=\"clear network cache\"\n          component=\"span\"\n          onClick={() => props.onClearNetworkCache()}\n        >\n          Clear Network Cache\n        </Button>\n      </div>\n    </div>\n  );\n}\n\nexport default ClearNetworkCache;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/CreateIssue/index.js",
    "content": "import React from 'react';\nimport Button from '@material-ui/core/Button';\nimport {makeStyles} from '@material-ui/core/styles';\nimport {getEnvironmentInfo} from '../../utils/generalUtils';\n\nconst {openNewGitHubIssue} = require('electron-util');\n\nconst reportBody = state => {\n  const {\n    appVersion,\n    electronVersion,\n    chromeVersion,\n    nodeVersion,\n    v8Version,\n    osInfo,\n  } = getEnvironmentInfo();\n  return `Hi I'm reporting an app crash:\n**PLEASE WRITE HERE ANY HINT THAT HELP US TO REPRODUCE THIS**\n\n<details>\n<summary>Environment</summary>\n\n- Version: ${appVersion}\n- Electron: ${electronVersion}\n- Chrome: ${chromeVersion}\n- Node.js: ${nodeVersion}\n- V8: ${v8Version}\n- OS: ${osInfo}\n</details>\n\n<details>\n<summary>Error Info</summary>\n\n\\`\\`\\`json\n${(state.error || '').split('\\\\n ').join('\\n')}\n\\`\\`\\`\n</details>\n\n<details>\n<summary>Stack Trace</summary>\n\n\\`\\`\\`json\n${(state.errorInfo || '').split('\\\\n ').join('\\n')}\n\\`\\`\\`\n</details>\n\nHope you find the issue, regards`;\n};\n\nconst createIssue = state => {\n  openNewGitHubIssue({\n    user: 'responsively-org',\n    repo: 'responsively-app',\n    body: reportBody(state),\n    title: 'App crash report',\n  });\n};\n\nfunction CreateIssue(props) {\n  const classes = useStyles();\n  return (\n    <div className={classes.container}>\n      <p>\n        Please click on the below link to automatically create an issue on\n        GitHub.\n      </p>\n      <Button\n        variant=\"contained\"\n        color=\"primary\"\n        onClick={() => createIssue(props.state)}\n      >\n        Create issue\n      </Button>\n    </div>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    textAlign: 'center',\n    color: theme.palette.text.primary,\n  },\n}));\n\nexport default CreateIssue;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DevToolsResizer/index.js",
    "content": "import React, {useState, useEffect, useMemo} from 'react';\nimport cx from 'classnames';\nimport {ipcRenderer} from 'electron';\nimport {Resizable} from 're-resizable';\nimport {Tooltip} from '@material-ui/core';\nimport pubsub from 'pubsub.js';\nimport debounce from 'lodash/debounce';\nimport styles from './style.module.css';\nimport Cross from '../icons/Cross';\nimport DockRight from '../icons/DockRight';\nimport DockBottom from '../icons/DockBottom';\nimport InspectElementChrome from '../icons/InspectElementChrome';\nimport {DEVTOOLS_MODES} from '../../constants/previewerLayouts';\nimport CrossChrome from '../icons/CrossChrome';\nimport {OPEN_CONSOLE_FOR_DEVICE} from '../../constants/pubsubEvents';\nimport {DARK_THEME, LIGHT_THEME} from '../../constants/theme';\n\nconst getResizingDirections = mode => {\n  if (mode === DEVTOOLS_MODES.RIGHT) {\n    return {left: true};\n  }\n  return {top: true};\n};\n\nconst getResizerPosition = (mode, bounds) => {\n  if (mode === DEVTOOLS_MODES.RIGHT) {\n    return {right: 0, top: bounds.y - 30};\n  }\n  return {bottom: 0};\n};\n\nconst getToolbarPosition = (mode, bounds) => {\n  if (mode === DEVTOOLS_MODES.RIGHT) {\n    return {position: 'absolute', top: 0};\n  }\n  return {};\n};\n\nconst DevToolsResizer = ({\n  activeDevTools,\n  devices,\n  size,\n  open,\n  mode,\n  bounds,\n  isInspecting,\n  onDevToolsResize,\n  onDevToolsClose,\n  onDevToolsModeChange,\n  onWindowResize,\n  toggleInspector,\n  isDarkTheme,\n}) => {\n  useEffect(() => {\n    const handler = debounce(\n      (event, args) => {\n        const {height, width} = args;\n        onWindowResize({height, width});\n      },\n      100,\n      {maxWait: 200}\n    );\n    ipcRenderer.on('window-resize', handler);\n    return () => {\n      ipcRenderer.removeListener('window-resize', handler);\n    };\n  }, []);\n\n  const initialTheme = useMemo(() => (isDarkTheme ? DARK_THEME : LIGHT_THEME), [\n    activeDevTools[0]?.deviceId,\n  ]);\n\n  const [sizeBeforeDrag, setSizeBeforeDrag] = useState(null);\n\n  if (!open || mode === DEVTOOLS_MODES.UNDOCKED) {\n    return null;\n  }\n\n  const switchDevTools = e => {\n    pubsub.publish(OPEN_CONSOLE_FOR_DEVICE, [{deviceId: e.target.value}]);\n  };\n\n  return (\n    <div style={{position: 'absolute', ...getResizerPosition(mode, bounds)}}>\n      <Resizable\n        className={styles.resizable}\n        size={{width: size.width, height: size.height}}\n        onResizeStart={() => setSizeBeforeDrag(size)}\n        onResizeStop={() => setSizeBeforeDrag(null)}\n        onResize={debounce(\n          (e, direction, ref, d) => {\n            onDevToolsResize({\n              width: sizeBeforeDrag.width + d.width,\n              height: sizeBeforeDrag.height + d.height,\n            });\n          },\n          25,\n          {maxWait: 50}\n        )}\n        enable={getResizingDirections(mode)}\n      >\n        <div\n          className={cx(\n            styles.toolbarContainer,\n            initialTheme === DARK_THEME ? styles.darkMode : styles.lightMode\n          )}\n          style={{width: '100%', ...getToolbarPosition(mode, bounds)}}\n        >\n          <div className={styles.toolsGroup}>\n            <span className={styles.icon} onClick={toggleInspector}>\n              <InspectElementChrome\n                style={{height: 16}}\n                selected={isInspecting}\n              />\n            </span>\n            <div className={styles.inputSection}>\n              <span className={styles.labelText}>Device:</span>\n              <select\n                id=\"devices\"\n                onChange={switchDevTools}\n                className={styles.chromeSelect}\n                value={activeDevTools[0].deviceId}\n              >\n                {devices.map(device => (\n                  <option value={device.id} key={device.id}>\n                    {device.name}\n                  </option>\n                ))}\n              </select>\n            </div>\n          </div>\n          <div className={styles.toolsGroup}>\n            {mode !== DEVTOOLS_MODES.RIGHT ? (\n              <Tooltip title=\"Dock to right\" placement=\"top\">\n                <span\n                  className={styles.icon}\n                  onClick={() => onDevToolsModeChange(DEVTOOLS_MODES.RIGHT)}\n                >\n                  <DockRight height={10} />\n                </span>\n              </Tooltip>\n            ) : null}\n            {mode !== DEVTOOLS_MODES.BOTTOM ? (\n              <Tooltip title=\"Dock to bottom\" placement=\"top\">\n                <span\n                  className={styles.icon}\n                  onClick={() => onDevToolsModeChange(DEVTOOLS_MODES.BOTTOM)}\n                >\n                  <DockBottom height={10} />\n                </span>\n              </Tooltip>\n            ) : null}\n            <span\n              className={styles.icon}\n              onClick={() => onDevToolsClose(null, true)}\n            >\n              <CrossChrome width={18} color=\"inherit\" />\n            </span>\n          </div>\n        </div>\n      </Resizable>\n    </div>\n  );\n};\n\nexport default DevToolsResizer;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DevToolsResizer/style.module.css",
    "content": ".toolbarContainer {\n  height: 20px;\n  border-style: solid;\n  border-width: 1px 0;\n  display: flex;\n  justify-content: space-between;\n  z-index: 1400;\n}\n\n.toolbarContainer.darkMode {\n  color: rgb(165, 165, 165);\n  border-color: rgb(61, 61, 61);\n  background-color: #333;\n}\n\n.toolbarContainer.lightMode {\n  background-color: #f3f3f3;\n  color: #555;\n  border-color: #d0d0d0;\n}\n\n.toolsGroup {\n  height: 18px;\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  margin: 0 8px;\n}\n\n.icon {\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  cursor: pointer;\n}\n\n.icon svg {\n  fill: rgb(165, 165, 165);\n}\n\n.toolbarContainer.lightMode .icon svg {\n  fill: #555;\n}\n\n.inputSection {\n  margin: 0 20px;\n  display: flex;\n  align-items: center;\n  font-size: 12px;\n}\n\n.labelText {\n  font-size: 12px;\n  margin-right: 5px;\n}\n\n.chromeSelect {\n  -webkit-appearance: none;\n  user-select: none;\n  border: 1px solid rgb(0 0 0 / 20%);\n  border-radius: 2px;\n  color: #333;\n  font: inherit;\n  margin: 0;\n  outline: none;\n  padding-right: 20px;\n  padding-left: 6px;\n  background-image: -webkit-image-set(\n    url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAQAAACxSAwfAAAASUlEQVQY02NgIBlEMaShwCisqhITmb8x/IdB5m+JiViV/VdYsJDtJ0QR288FC/8r4LAVphCvIphCvi8EFEEUvm0iqAiskBVTDABtayfbVbR52gAAAABJRU5ErkJggg==)\n      1x,\n    url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACcAAAAQCAQAAAA/1a6rAAAAMklEQVQ4y2NgGAUMDQz/8cAGahrYQE0XNlDTyw3UDMMGakZKAzVjuYGayaZhNO8MMQAAJ0U/yRb8G2IAAAAASUVORK5CYII=)\n      2x\n  );\n  background-color: hsl(0deg 0% 98%);\n  background-position: right center;\n  background-repeat: no-repeat;\n  min-width: 80px;\n  background-size: 15px;\n}\n\n.chromeSelect:disabled {\n  opacity: 38%;\n}\n\n.chromeSelect:enabled:active,\n.chromeSelect:enabled:focus,\n.chromeSelect:enabled:hover {\n  background-color: hsl(0deg 0% 96%);\n  box-shadow: 0 1px 2px rgb(0 0 0 / 10%);\n}\n\n.chromeSelect:enabled:active {\n  background-color: #f2f2f2;\n}\n\n.chromeSelect:enabled:focus {\n  border-color: transparent;\n  box-shadow: 0 1px 2px rgb(0 0 0 / 10%), 0 0 0 2px rgb(66 133 244 / 40%);\n}\n\n.chromeSelect optgroup,\n.chromeSelect option {\n  background-color: #eee;\n  color: #222;\n}\n\n.resizable {\n  color: white;\n  z-index: 1400;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DeviceDrawer/index.js",
    "content": "import React from 'react';\nimport cx from 'classnames';\nimport Divider from '@material-ui/core/Divider';\nimport PreviewerLayoutSelector from '../PreviewerLayoutSelector';\nimport DeviceManagerContainer from '../../containers/DeviceManagerContainer';\nimport QuickFilterDevicesContainer from '../../containers/QuickFilterDevicesContainer';\nimport DevicesIcon from '../icons/Devices';\nimport DevicesOverviewContainer from '../../containers/DevicesOverviewContainer';\nimport styles from './styles.css';\n\nexport default function DeviceDrawer({\n  browser: {\n    previewer: {layout},\n  },\n  setPreviewLayout,\n}) {\n  return (\n    <div>\n      <DevicesOverviewContainer />\n      <PreviewerLayoutSelector\n        value={layout}\n        onChange={val => setPreviewLayout(val.value)}\n      />\n      <QuickFilterDevicesContainer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DeviceDrawer/styles.css",
    "content": ".label {\n  font-size: 14px;\n  margin-bottom: 5px;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DeviceManager/AddDevice/index.js",
    "content": "import React, {Fragment, useState} from 'react';\nimport cx from 'classnames';\nimport Dialog from '@material-ui/core/Dialog';\nimport {green} from '@material-ui/core/colors';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport {makeStyles, useTheme} from '@material-ui/core/styles';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport Checkbox from '@material-ui/core/Checkbox';\nimport TextField from '@material-ui/core/TextField';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport Button from '@material-ui/core/Button';\nimport Fab from '@material-ui/core/Fab';\nimport RadioGroup from '@material-ui/core/RadioGroup';\nimport Radio from '@material-ui/core/Radio';\nimport AddIcon from '@material-ui/icons/Add';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport Input from '@material-ui/core/Input';\nimport FormLabel from '@material-ui/core/FormLabel';\nimport InputLabel from '@material-ui/core/InputLabel';\nimport FormControl from '@material-ui/core/FormControl';\nimport FormGroup from '@material-ui/core/FormGroup';\nimport FormControlLabel from '@material-ui/core/FormControlLabel';\nimport {Typography, Grid, MenuItem, NativeSelect} from '@material-ui/core';\nimport RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked';\nimport RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';\nimport CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';\nimport CheckBoxIcon from '@material-ui/icons/CheckBox';\nimport Switch from 'react-switch';\nimport {\n  DEVICE_TYPE,\n  CAPABILITIES,\n  OS,\n  SOURCE,\n} from '../../../constants/devices';\n\nconst useStyles = makeStyles(theme => ({\n  fab: {\n    position: 'absolute',\n    top: theme.spacing(10),\n    right: theme.spacing(3),\n  },\n  extendedIcon: {\n    marginRight: theme.spacing(1),\n  },\n  inputField: {\n    marginRight: '1%',\n    width: '49%',\n  },\n  formControl: {\n    margin: theme.spacing(1),\n    minWidth: 120,\n  },\n  select: {\n    marginTop: theme.spacing(2),\n    maxWidth: 150,\n  },\n  radioIcon: {\n    color: theme.palette.lightIcon.main,\n  },\n  inputAdornment: {\n    color: theme.palette.lightIcon.main,\n  },\n  userAgent: {\n    fontSize: 12,\n  },\n  actionButton: {\n    padding: '10px !important',\n    borderRadius: '5px !important',\n  },\n}));\n\nexport default function AddDevice(props) {\n  const [open, setOpen] = useState(false);\n  const [name, setName] = useState('');\n  const [width, setWidth] = useState(400);\n  const [height, setHeight] = useState(600);\n  const [userAgentString, setUserAgentString] = useState('');\n  const theme = useTheme();\n  const [previewState, setPreviewState] = useState(true);\n  const [capabilities, setCapabilities] = useState({\n    [CAPABILITIES.mobile]: false,\n    [CAPABILITIES.touch]: true,\n    [CAPABILITIES.responsive]: false,\n  });\n  const [deviceType, setDeviceType] = useState(DEVICE_TYPE.phone);\n  const [os, setOS] = useState(OS.android);\n  const classes = useStyles();\n\n  const updateUserAgent = () => {\n    let userAgent =\n      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36';\n    if (os === OS.android) {\n      if (deviceType === DEVICE_TYPE.phone) {\n        userAgent =\n          'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36';\n      }\n      if (deviceType === DEVICE_TYPE.tablet) {\n        userAgent =\n          'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36';\n      }\n    }\n    if (os === OS.ios) {\n      if (deviceType === DEVICE_TYPE.phone) {\n        userAgent =\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1';\n      }\n      if (deviceType === DEVICE_TYPE.tablet) {\n        userAgent =\n          'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1';\n      }\n    }\n\n    if (os === OS.windowsPhone) {\n      userAgent =\n        'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)';\n    }\n\n    setUserAgentString(userAgent);\n  };\n\n  React.useEffect(updateUserAgent, [os, deviceType]);\n\n  const CustomRadio = props => (\n    <Radio\n      {...props}\n      color=\"primary\"\n      icon={<RadioButtonUncheckedIcon className={classes.radioIcon} />}\n      checkedIcon={<RadioButtonCheckedIcon className={classes.radioIcon} />}\n    />\n  );\n\n  const CustomCheckbox = props => (\n    <Checkbox\n      {...props}\n      color=\"primary\"\n      icon={<CheckBoxOutlineBlankIcon className={classes.radioIcon} />}\n      checkedIcon={<CheckBoxIcon className={classes.radioIcon} />}\n    />\n  );\n\n  const closeDialog = () => setOpen(false);\n\n  const saveDevice = () => {\n    props.addCustomDevice({\n      name,\n      width: parseInt(width, 10),\n      height: parseInt(height, 10),\n      added: previewState,\n      useragent: userAgentString,\n      id: `custom-${new Date().getTime()}`,\n      type: deviceType,\n      capabilities: Object.keys(capabilities).filter(val => capabilities[val]),\n      os,\n      source: SOURCE.custom,\n    });\n    closeDialog();\n    setName('');\n    setWidth(400);\n    setHeight(600);\n    setPreviewState(true);\n  };\n  return (\n    <Fragment>\n      <Fab\n        variant=\"extended\"\n        aria-label=\"add\"\n        color=\"primary\"\n        className={classes.fab}\n        onClick={() => setOpen(true)}\n      >\n        <AddIcon className={classes.extendedIcon} /> New Device\n      </Fab>\n\n      <Dialog\n        disableEnforceFocus\n        open={open}\n        onClose={closeDialog}\n        aria-labelledby=\"form-dialog-title\"\n        fullWidth\n      >\n        <DialogTitle id=\"form-dialog-title\">New Device</DialogTitle>\n        <DialogContent>\n          <DialogContentText color=\"textPrimary\">\n            Please enter the details of the new device\n          </DialogContentText>\n          <TextField\n            autoFocus\n            fullWidth\n            variant=\"outlined\"\n            margin=\"normal\"\n            label=\"Name\"\n            type=\"text\"\n            placeholder=\"Device name\"\n            InputLabelProps={{\n              shrink: true,\n            }}\n            value={name}\n            onChange={e => setName(e.target.value)}\n            className=\"padded-input\"\n          />\n          <TextField\n            className={classes.inputField}\n            variant=\"outlined\"\n            margin=\"normal\"\n            label=\"Width\"\n            type=\"number\"\n            InputProps={{\n              endAdornment: (\n                <InputAdornment position=\"end\" className=\"input-adornment\">\n                  px\n                </InputAdornment>\n              ),\n            }}\n            value={width}\n            onChange={e => setWidth(e.target.value)}\n            className=\"padded-input\"\n          />\n          <TextField\n            className={classes.inputField}\n            variant=\"outlined\"\n            margin=\"normal\"\n            label=\"Height\"\n            type=\"number\"\n            InputProps={{\n              endAdornment: (\n                <InputAdornment position=\"end\" className=\"input-adornment\">\n                  px\n                </InputAdornment>\n              ),\n            }}\n            value={height}\n            onChange={e => setHeight(e.target.value)}\n            className=\"padded-input\"\n          />\n          <FormControl\n            component=\"fieldset\"\n            className={classes.formControl}\n            fullWidth\n          >\n            <FormLabel component=\"legend\">Device Type</FormLabel>\n            <RadioGroup\n              name=\"deviceType\"\n              value={deviceType}\n              onChange={e => setDeviceType(e.target.value)}\n            >\n              <Grid container alignItems=\"center\" spacing={1}>\n                <Grid item>\n                  <FormControlLabel\n                    value={DEVICE_TYPE.phone}\n                    control={<CustomRadio />}\n                    label=\"Phone\"\n                  />\n                </Grid>\n                <Grid item>\n                  <FormControlLabel\n                    value={DEVICE_TYPE.tablet}\n                    control={<CustomRadio />}\n                    label=\"Tablet\"\n                  />\n                </Grid>\n                <Grid item>\n                  <FormControlLabel\n                    value={DEVICE_TYPE.desktop}\n                    control={<CustomRadio />}\n                    label=\"Laptop/Desktop\"\n                  />\n                </Grid>\n              </Grid>\n            </RadioGroup>\n          </FormControl>\n          <FormControl\n            component=\"fieldset\"\n            className={classes.formControl}\n            fullWidth\n          >\n            <FormLabel component=\"legend\">Operating System</FormLabel>\n            <RadioGroup\n              name=\"os\"\n              value={os}\n              onChange={e => setOS(e.target.value)}\n            >\n              <Grid container alignItems=\"center\" spacing={1}>\n                <Grid item>\n                  <FormControlLabel\n                    value={OS.android}\n                    control={<CustomRadio />}\n                    label=\"Android\"\n                  />\n                </Grid>\n                <Grid item>\n                  <FormControlLabel\n                    value={OS.ios}\n                    control={<CustomRadio />}\n                    label=\"iOS\"\n                  />\n                </Grid>\n                <Grid item>\n                  <FormControlLabel\n                    value={OS.windowsPhone}\n                    control={<CustomRadio />}\n                    label=\"Windows Phone\"\n                  />\n                </Grid>\n                <Grid item>\n                  <FormControlLabel\n                    value={OS.pc}\n                    control={<CustomRadio />}\n                    label=\"Desktop\"\n                  />\n                </Grid>\n              </Grid>\n            </RadioGroup>\n          </FormControl>\n          <FormLabel>Device capabilities</FormLabel>\n          <FormGroup>\n            <Grid container alignItems=\"center\" spacing={1}>\n              <Grid item>\n                <FormControlLabel\n                  control={\n                    <CustomCheckbox\n                      checked={capabilities[CAPABILITIES.mobile]}\n                      onChange={e =>\n                        setCapabilities({\n                          ...capabilities,\n                          [CAPABILITIES.mobile]: e.target.checked,\n                        })\n                      }\n                      value=\"Flippable\"\n                    />\n                  }\n                  label=\"Flippable\"\n                />\n              </Grid>\n              <Grid item>\n                <FormControlLabel\n                  control={\n                    <CustomCheckbox\n                      checked={capabilities[CAPABILITIES.touch]}\n                      onChange={e =>\n                        setCapabilities({\n                          ...capabilities,\n                          [CAPABILITIES.touch]: e.target.checked,\n                        })\n                      }\n                      value=\"Touchscreen\"\n                    />\n                  }\n                  label=\"Touchscreen\"\n                />\n              </Grid>\n              <Grid item>\n                <FormControlLabel\n                  control={\n                    <CustomCheckbox\n                      checked={capabilities[CAPABILITIES.responsive]}\n                      onChange={e =>\n                        setCapabilities({\n                          ...capabilities,\n                          [CAPABILITIES.responsive]: e.target.checked,\n                        })\n                      }\n                      value=\"Responsive\"\n                    />\n                  }\n                  label=\"Responsive\"\n                />\n              </Grid>\n            </Grid>\n          </FormGroup>\n          <TextField\n            fullWidth\n            variant=\"outlined\"\n            margin=\"normal\"\n            label=\"User-Agent String\"\n            placeholder=\"User-Agent String\"\n            InputLabelProps={{\n              shrink: true,\n            }}\n            InputProps={{\n              classes: {\n                input: classes.userAgent,\n              },\n            }}\n            type=\"text\"\n            value={userAgentString}\n            onChange={e => setUserAgentString(e.target.value)}\n            className={cx('padded-input')}\n          />\n\n          <Grid\n            container\n            alignItems=\"center\"\n            className=\"MuiFormControl-marginNormal\"\n          >\n            <Grid item style={{flex: 1}} className=\"MuiFormLabel-root\">\n              Activate Preview\n            </Grid>\n            <Grid\n              item\n              className={cx('MuiFormLabel-root', {\n                'Mui-focused': !previewState,\n              })}\n            >\n              {/* Off */}\n            </Grid>\n            <Grid item>\n              <Switch\n                onChange={checked => setPreviewState(checked)}\n                checked={previewState}\n                onColor={theme.palette.primary.main}\n              />\n            </Grid>\n            <Grid\n              item\n              className={cx('MuiFormLabel-root', {\n                'Mui-focused': previewState,\n              })}\n            >\n              {/* On */}\n            </Grid>\n          </Grid>\n        </DialogContent>\n        <DialogActions>\n          <Button\n            onClick={closeDialog}\n            color=\"primary\"\n            className={classes.actionButton}\n          >\n            Cancel\n          </Button>\n          <Button\n            variant=\"contained\"\n            onClick={saveDevice}\n            color=\"primary\"\n            className={classes.actionButton}\n          >\n            Add\n          </Button>\n        </DialogActions>\n      </Dialog>\n    </Fragment>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DeviceManager/DeviceItem.js",
    "content": "import React, {useState} from 'react';\nimport {Draggable} from 'react-beautiful-dnd';\nimport DragIndicator from '@material-ui/icons/DragIndicator';\nimport PhoneIcon from '@material-ui/icons/PhoneIphone';\nimport IconButton from '@material-ui/core/IconButton';\nimport {useTheme, makeStyles} from '@material-ui/core/styles';\nimport DeleteForeverIcon from '@material-ui/icons/DeleteForever';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport cx from 'classnames';\n\nimport useCommonStyles from '../useCommonStyles';\nimport {DEVICE_TYPE, SOURCE} from '../../constants/devices';\nimport {getDeviceIcon} from '../../utils/iconUtils';\nimport OSIcon from './OSIcon';\n\nfunction DeviceItem({device, index, enableCustomDeviceDeletion, deleteDevice}) {\n  const [showDeleteIcon, setShowDeleteIcon] = useState(false);\n  const theme = useTheme();\n  const commonClasses = useCommonStyles();\n  const classes = useStyles();\n\n  return (\n    <Draggable draggableId={device.id} index={index}>\n      {provided => (\n        <div\n          className={classes.deviceHolder}\n          ref={provided.innerRef}\n          {...provided.draggableProps}\n          {...provided.dragHandleProps}\n          onMouseEnter={() => setShowDeleteIcon(true)}\n          onMouseLeave={() => setShowDeleteIcon(false)}\n        >\n          <div\n            className={cx(\n              commonClasses.flexAlignVerticalMiddle,\n              commonClasses.fullWidth,\n              classes.content\n            )}\n          >\n            <DragIndicator style={{color: 'grey'}} />\n            <div\n              className={cx(\n                commonClasses.flexContainerSpaceBetween,\n                commonClasses.fullWidth\n              )}\n            >\n              <div className={commonClasses.flexAlignVerticalMiddle}>\n                {getDeviceIcon(device.type)}\n                <span className={classes.deviceName}>{device.name}</span>\n                <span className={classes.deviceDimensions}>\n                  {device.width}x{device.height}\n                </span>\n              </div>\n              <div>\n                <OSIcon os={device.os} color={theme.palette.lightIcon.main} />\n              </div>\n            </div>\n            {enableCustomDeviceDeletion &&\n            device.source === SOURCE.custom &&\n            showDeleteIcon ? (\n              <Tooltip title=\"Delete Device Profile\">\n                <IconButton\n                  className={classes.deleteIcon}\n                  onClick={() => deleteDevice(device)}\n                >\n                  <DeleteForeverIcon fontSize=\"small\" />\n                </IconButton>\n              </Tooltip>\n            ) : null}\n          </div>\n        </div>\n      )}\n    </Draggable>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  deviceHolder: {\n    display: 'flex',\n    width: '100%',\n    borderRadius: '10px',\n    height: '70px',\n    background: theme.palette.mode({\n      light: theme.palette.grey[400],\n      dark: '#2b2b2b',\n    }),\n    border: '1px solid lightgrey',\n    padding: '20px',\n    boxSizing: 'border-box',\n    marginBottom: '10px',\n  },\n  deviceName: {\n    fontSize: '16px',\n    marginRight: '5px',\n  },\n  deviceDimensions: {\n    fontSize: '10px',\n    color: theme.palette.mode({\n      light: theme.palette.grey[800],\n      dark: '#ffffff90',\n    }),\n  },\n  content: {\n    padding: '0.8em 1em 0.8em 0',\n    position: 'relative',\n  },\n  deleteIcon: {\n    position: 'absolute !important',\n    right: '-25px',\n    top: '-10px',\n  },\n}));\n\nexport default DeviceItem;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DeviceManager/DeviceList.js",
    "content": "import React, {useState, useEffect} from 'react';\nimport {Droppable} from 'react-beautiful-dnd';\nimport IconButton from '@material-ui/core/IconButton';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport SearchIcon from '@material-ui/icons/Search';\nimport TextField from '@material-ui/core/TextField';\nimport CancelIcon from '@material-ui/icons/Cancel';\nimport {makeStyles} from '@material-ui/core/styles';\nimport cx from 'classnames';\nimport DeviceItem from './DeviceItem';\n\nfunction DeviceList({\n  droppableId,\n  devices,\n  enableFiltering,\n  onFiltering,\n  enableCustomDeviceDeletion,\n  deleteDevice,\n}) {\n  const [searchOpen, setSearchOpen] = useState(false);\n  const [searchText, setSearchText] = useState('');\n  const [filteredDevices, setFilteredList] = useState(devices);\n  const classes = useStyles();\n  useEffect(() => {\n    const filteredDevices = devices.filter(device => {\n      if (!searchText) {\n        return true;\n      }\n      return (\n        device.name.toLowerCase().indexOf(searchText) > -1 ||\n        String(device.width).indexOf(searchText) > -1 ||\n        String(device.height).indexOf(searchText) > -1\n      );\n    });\n\n    setFilteredList(filteredDevices);\n    if (onFiltering) {\n      onFiltering(filteredDevices);\n    }\n  }, [searchText, devices]);\n  return (\n    <>\n      <div className={classes.searchContainer}>\n        {enableFiltering && (\n          <>\n            {!searchOpen ? (\n              <IconButton\n                className={classes.searchIcon}\n                onClick={() => setSearchOpen(true)}\n              >\n                <SearchIcon fontSize=\"default\" />\n              </IconButton>\n            ) : null}\n            {searchOpen ? (\n              <TextField\n                autoFocus\n                fullWidth\n                variant=\"outlined\"\n                placeholder=\"Search...\"\n                value={searchText}\n                onChange={e => setSearchText(e.target.value.toLowerCase())}\n                InputProps={{\n                  endAdornment: (\n                    <InputAdornment>\n                      <IconButton\n                        className={classes.searchActiveIcon}\n                        onClick={() => {\n                          setSearchOpen(false);\n                          setSearchText('');\n                        }}\n                      >\n                        <CancelIcon />\n                      </IconButton>\n                    </InputAdornment>\n                  ),\n                }}\n              />\n            ) : null}\n          </>\n        )}\n      </div>\n      <Droppable droppableId={droppableId}>\n        {provided => (\n          <div\n            ref={provided.innerRef}\n            {...provided.droppableProps}\n            className={classes.listHolder}\n          >\n            {filteredDevices.map((device, index) => (\n              <DeviceItem\n                device={device}\n                index={index}\n                key={device.id}\n                enableCustomDeviceDeletion={enableCustomDeviceDeletion}\n                deleteDevice={deleteDevice}\n              />\n            ))}\n            {provided.placeholder}\n          </div>\n        )}\n      </Droppable>\n    </>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  listHolder: {\n    padding: '20px',\n    background: theme.palette.mode({\n      light: theme.palette.background.primary,\n      dark: '#00000030',\n    }),\n    borderRadius: '10px',\n    border: '1px solid lightgrey',\n    height: '65vh',\n    width: '400px',\n    overflow: 'scroll',\n  },\n  searchContainer: {\n    height: '50px',\n    display: 'flex',\n    justifyContent: 'flex-end',\n    alignItems: 'center',\n  },\n  searchIcon: {\n    margin: '0 14px 0 !important',\n    color: 'white !important',\n  },\n  searchActiveIcon: {\n    color: 'white !important',\n  },\n}));\n\nexport default DeviceList;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DeviceManager/OSIcon.js",
    "content": "import React from 'react';\nimport MobileIcon from '@material-ui/icons/Smartphone';\nimport AndroidIcon from '@material-ui/icons/Android';\nimport DesktopIcon from '@material-ui/icons/DesktopWindows';\nimport {useTheme} from '@material-ui/core/styles';\nimport AppleIcon from '../icons/Apple';\nimport WindowsIcon from '../icons/Windows';\nimport {OS} from '../../constants/devices';\n\nfunction OSIcon({os, color}) {\n  const theme = useTheme();\n  const _color = color || theme.palette.text.primary;\n  const iconProps = {\n    style: {fontSize: 'inherit', paddingRight: 2, _color},\n    height: '1em',\n  };\n\n  switch (os) {\n    case OS.ios:\n      return <AppleIcon {...iconProps} color={_color} />;\n    case OS.android:\n      return <AndroidIcon {...iconProps} />;\n    case OS.windowsPhone:\n      return <WindowsIcon {...iconProps} height=\"0.8em\" color={_color} />;\n    case OS.pc:\n      return <DesktopIcon {...iconProps} />;\n    default:\n      return null;\n  }\n}\n\nexport default OSIcon;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DeviceManager/index.js",
    "content": "import React, {useState, Fragment, useEffect} from 'react';\nimport {makeStyles} from '@material-ui/core/styles';\nimport IconButton from '@material-ui/core/IconButton';\nimport Grid from '@material-ui/core/Grid';\nimport Button from '@material-ui/core/Button';\nimport Fab from '@material-ui/core/Fab';\nimport EditIcon from '@material-ui/icons/Edit';\nimport CloseIcon from '@material-ui/icons/Close';\nimport AddIcon from '@material-ui/icons/Add';\nimport Dialog from '@material-ui/core/Dialog';\nimport AppBar from '@material-ui/core/AppBar';\nimport Alert from '@material-ui/lab/Alert';\nimport Toolbar from '@material-ui/core/Toolbar';\nimport Typography from '@material-ui/core/Typography';\nimport {DragDropContext, Droppable, Draggable} from 'react-beautiful-dnd';\nimport LightBulbIcon from '../icons/LightBulb';\nimport DeviceList from './DeviceList';\nimport AddDeviceContainer from '../../containers/AddDeviceContainer';\nimport ErrorBoundary from '../ErrorBoundary';\nimport {recommendedMaxNumberOfDevices} from '../../utils/deviceManagerUtils';\n\nimport styles from './styles.css';\n\nfunction DeviceManager(props) {\n  const [open, setOpen] = useState(false);\n  const classes = useStyles();\n\n  const [devices, setDevices] = useState({\n    active: [],\n    inactive: [],\n    inactiveFiltered: [],\n  });\n\n  const [maxDevicesWarning, setMaxDevicesWarning] = useState(false);\n\n  useEffect(() => {\n    const activeDevices = props.browser.devices;\n    const activeDevicesById = activeDevices.reduce((acc, val) => {\n      acc[val.id] = val;\n      return acc;\n    }, {});\n\n    setMaxDevicesWarning(activeDevices.length >= recommendedMaxNumberOfDevices);\n\n    const currentInactiveDevicesById = devices.inactive.reduce((acc, val) => {\n      acc[val.id] = val;\n      return acc;\n    }, {});\n\n    const devicesById = props.browser.allDevices.reduce((acc, val) => {\n      acc[val.id] = val;\n      return acc;\n    }, {});\n\n    const inactiveDevices = [\n      ...props.browser.allDevices.filter(\n        device =>\n          !activeDevicesById[device.id] &&\n          !currentInactiveDevicesById[device.id]\n      ),\n      ...devices.inactive.filter(device => devicesById[device.id]),\n    ];\n\n    setDevices({active: activeDevices, inactive: inactiveDevices});\n  }, [props.browser.devices, props.browser.allDevices]);\n\n  const onInactiveListFiltering = inactiveFiltered => {\n    setDevices({...devices, inactiveFiltered});\n  };\n\n  const closeDialog = () => setOpen(false);\n\n  const onDragEnd = result => {\n    const {source, destination} = result;\n\n    if (!source || !destination) {\n      return;\n    }\n\n    const sourceList = devices[source.droppableId];\n    const destinationList = devices[destination.droppableId];\n\n    if (!sourceList || !destinationList) {\n      return;\n    }\n\n    const itemDragged =\n      source.droppableId === 'inactive'\n        ? devices.inactiveFiltered[source.index]\n        : sourceList[source.index];\n\n    let idx = destination.index;\n\n    if (destination.droppableId === 'inactive') {\n      idx =\n        destination.index < devices.inactiveFiltered.length\n          ? devices.inactive.findIndex(\n              d => d.id === devices.inactiveFiltered[destination.index].id\n            )\n          : devices.inactive.length;\n    }\n\n    sourceList.splice(sourceList.indexOf(itemDragged), 1);\n\n    destinationList.splice(idx, 0, itemDragged);\n\n    updateDevices(devices);\n  };\n\n  const updateDevices = devices => {\n    const active = [...devices.active];\n    const inactive = [...devices.inactive];\n    setMaxDevicesWarning(active.length >= recommendedMaxNumberOfDevices);\n    setDevices({active, inactive});\n    props.setActiveDevices(active);\n  };\n\n  return (\n    <Fragment>\n      <Button\n        variant=\"contained\"\n        color=\"primary\"\n        aria-label=\"upload picture\"\n        component=\"span\"\n        onClick={() => {\n          props.onDevToolsClose(null, true);\n          setOpen(true);\n        }}\n        className={styles.editButton}\n      >\n        Customize\n      </Button>\n      {open ? (\n        <Dialog fullScreen open={open} onClose={closeDialog}>\n          <AppBar className={classes.appBar} color=\"secondary\">\n            <Toolbar>\n              <Typography variant=\"h6\" className={classes.title}>\n                Manage Devices\n              </Typography>\n              <Button color=\"inherit\" onClick={closeDialog}>\n                close\n              </Button>\n            </Toolbar>\n          </AppBar>\n          <div className={styles.container}>\n            {maxDevicesWarning && (\n              <Alert severity=\"warning\" className={classes.maxDevicesWarning}>\n                Adding more than {recommendedMaxNumberOfDevices} devices may\n                slow down the system.\n              </Alert>\n            )}\n            <Typography variant=\"body1\" className={classes.toolTip}>\n              <span>✨</span>Drag and drop the devices across to re-order them.\n            </Typography>\n            <DragDropContext onDragEnd={onDragEnd}>\n              <Grid container className={styles.content}>\n                <Grid item className={styles.section}>\n                  <div className={styles.listTitle}>\n                    <LightBulbIcon height={30} color=\"#FFD517\" />\n                    Active Devices\n                  </div>\n                  <DeviceList droppableId=\"active\" devices={devices.active} />\n                </Grid>\n                <Grid item className={styles.section}>\n                  <div className={styles.listTitle}>\n                    <LightBulbIcon height={30} color=\"darkgrey\" />\n                    Inactive Devices\n                  </div>\n                  <DeviceList\n                    droppableId=\"inactive\"\n                    devices={devices.inactive}\n                    enableFiltering\n                    onFiltering={onInactiveListFiltering}\n                    enableCustomDeviceDeletion\n                    deleteDevice={props.deleteDevice}\n                  />\n                </Grid>\n              </Grid>\n            </DragDropContext>\n            <AddDeviceContainer />\n          </div>\n        </Dialog>\n      ) : null}\n    </Fragment>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  appBar: {\n    position: 'relative',\n  },\n  title: {\n    marginLeft: theme.spacing(2),\n    flex: 1,\n  },\n  toolTip: {\n    background: theme.palette.mode({\n      light: theme.palette.grey[400],\n      dark: '#ffffff10',\n    }),\n    padding: '10px 40px',\n    borderRadius: '5px',\n    margin: '0 auto 20px',\n    textAlign: 'center',\n    fontSize: '14px',\n    color: theme.palette.text.primary,\n    width: 'fit-content',\n  },\n  maxDevicesWarning: {\n    position: 'absolute',\n    top: '80px',\n  },\n}));\n\nexport default DeviceManager;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DeviceManager/styles.css",
    "content": ".container {\n  margin: 50px;\n  height: calc(100vh - 200px);\n}\n\n.content {\n  height: 100%;\n  justify-content: center;\n}\n\n.section {\n  position: relative;\n  padding: 0 20px;\n}\n\n.listTitle {\n  display: flex;\n  background: #7587ec;\n  padding: 20px;\n  margin: 0 auto;\n  border-radius: 10px;\n  color: white;\n  align-items: flex-end;\n  margin-bottom: 10px;\n  justify-content: center;\n}\n\n.toolTip {\n  background: #ffffff10;\n  padding: 10px 40px;\n  border-radius: 5px;\n  margin: 0 auto 20px;\n  text-align: center;\n  font-size: 14px;\n  color: #dedede;\n  width: fit-content;\n}\n\n.editButton {\n  font-size: 10px !important;\n  padding: 5px !important;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DevicesOverview/index.js",
    "content": "import React from 'react';\nimport cx from 'classnames';\nimport {useTheme} from '@material-ui/core/styles';\nimport DeviceManagerContainer from '../../containers/DeviceManagerContainer';\nimport DevicesIcon from '../icons/Devices';\nimport useCommonStyles from '../useCommonStyles';\n\nfunction DevicesOverview(props) {\n  const theme = useTheme();\n  const commonClasses = useCommonStyles();\n\n  return (\n    <div className={commonClasses.sidebarContentSection}>\n      <div className={commonClasses.sidebarContentSectionTitleBar}>\n        <DevicesIcon color={theme.palette.text.primary} width={26} margin={2} />{' '}\n        Devices\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div\n          className={cx(\n            commonClasses.flexContainer,\n            commonClasses.flexContainerSpaceBetween\n          )}\n        >\n          {props.browser.devices.length} active device\n          {props.browser.devices.length !== 1 && 's'}\n          <DeviceManagerContainer />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default DevicesOverview;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DevicesPreviewer/index.js",
    "content": "// @flow\nimport React, {Fragment, useState, useEffect, useRef, createRef} from 'react';\nimport cx from 'classnames';\nimport {Tab, Tabs, TabList} from 'react-tabs';\nimport Renderer from '../Renderer';\n\nimport {\n  HORIZONTAL_LAYOUT,\n  FLEXIGRID_LAYOUT,\n  INDIVIDUAL_LAYOUT,\n  DEVTOOLS_MODES,\n  CSS_EDITOR_MODES,\n  isHorizontallyStacked,\n} from '../../constants/previewerLayouts';\nimport {isDeviceEligible} from '../../utils/filterUtils';\nimport {getDeviceIcon} from '../../utils/iconUtils';\nimport useStyes from './useStyles';\nimport LiveCssEditor from '../LiveCssEditor';\nimport LinkHoverDisplayContainer from '../../containers/LinkHoverDisplayContainer';\nimport PageNavigatorContainer from '../../containers/PageNavigatorContainer';\n\nexport default function DevicesPreviewer(props) {\n  const {\n    browser: {\n      devices,\n      devToolsConfig,\n      address,\n      CSSEditor,\n      zoomLevel,\n      previewer: {layout},\n    },\n  } = props;\n  const [activeTab, changeTab] = useState(0);\n  const classes = useStyes();\n\n  const newActiveTab = activeTab;\n  const devicesAfterFiltering = devices\n    .map((device, index) => {\n      if (isDeviceEligible(device, props.browser.filters)) {\n        return device;\n      }\n    })\n    .filter(Boolean);\n\n  let focusedDeviceIndex = 0;\n  if (layout === INDIVIDUAL_LAYOUT) {\n    if (props.browser.previewer.focusedDeviceId) {\n      focusedDeviceIndex = (devicesAfterFiltering || []).findIndex(\n        device => device.id === props.browser.previewer.focusedDeviceId\n      );\n    }\n    if (focusedDeviceIndex === -1) {\n      focusedDeviceIndex = 0;\n    }\n\n    if (focusedDeviceIndex !== activeTab) {\n      changeTab(focusedDeviceIndex);\n    }\n  }\n\n  let focusedDeviceId;\n  if (devicesAfterFiltering.length > 0) {\n    focusedDeviceId = devicesAfterFiltering[focusedDeviceIndex].id;\n  }\n\n  const onTabClick = function onTabClick(newTabIndex) {\n    changeTab(newTabIndex);\n    props.setFocusedDevice(devicesAfterFiltering[newTabIndex].id);\n  };\n\n  const editor = CSSEditor.isOpen && (\n    <LiveCssEditor\n      boundaryClass={classes.container}\n      devToolsConfig={devToolsConfig}\n      changeCSSEditorPosition={props.changeCSSEditorPosition}\n      onCSSEditorContentChange={props.onCSSEditorContentChange}\n      {...CSSEditor}\n    />\n  );\n\n  return (\n    <div\n      className={cx(classes.container)}\n      style={{\n        flexDirection: isHorizontallyStacked(CSSEditor.position)\n          ? 'column'\n          : null,\n      }}\n    >\n      {(CSSEditor.position === CSS_EDITOR_MODES.LEFT ||\n        CSSEditor.position === CSS_EDITOR_MODES.UNDOCKED ||\n        CSSEditor.position === CSS_EDITOR_MODES.TOP) &&\n        editor}\n      <div className={cx(classes.previewer)}>\n        {layout === INDIVIDUAL_LAYOUT && (\n          <Tabs\n            className={cx('react-tabs', classes.reactTabs)}\n            onSelect={onTabClick}\n            selectedIndex={focusedDeviceIndex}\n          >\n            <TabList\n              className={cx('react-tabs__tab-list', classes.reactTabs__tabList)}\n            >\n              {devicesAfterFiltering.map(device => (\n                <Tab\n                  className={cx('react-tabs__tab', classes.reactTabs__tab)}\n                  tabId={device.id}\n                  key={device.id}\n                >\n                  {getDeviceIcon(device.type)}\n                  {device.name}\n                </Tab>\n              ))}\n            </TabList>\n          </Tabs>\n        )}\n        <div\n          className={cx(classes.devicesContainer, {\n            [classes.flexigrid]: layout === FLEXIGRID_LAYOUT,\n            [classes.horizontal]: layout === HORIZONTAL_LAYOUT,\n          })}\n        >\n          {devices.map((device, index) => (\n            <div\n              key={device.id}\n              className={cx({\n                [classes.tab]: layout === INDIVIDUAL_LAYOUT,\n                [classes.activeTab]:\n                  layout === INDIVIDUAL_LAYOUT && focusedDeviceId === device.id,\n              })}\n            >\n              <Renderer\n                hidden={!isDeviceEligible(device, props.browser.filters)}\n                device={device}\n                src={address}\n                zoomLevel={zoomLevel}\n                transmitNavigatorStatus={index === 0}\n                onDeviceMutedChange={props.onDeviceMutedChange}\n              />\n            </div>\n          ))}\n        </div>\n      </div>\n      {(CSSEditor.position === CSS_EDITOR_MODES.RIGHT ||\n        CSSEditor.position === CSS_EDITOR_MODES.BOTTOM) &&\n        editor}\n      <LinkHoverDisplayContainer />\n      <PageNavigatorContainer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DevicesPreviewer/index.test.js",
    "content": "// @flow\nimport React from 'react';\nimport {shallow} from 'enzyme';\nimport {expect} from 'chai';\n\nimport DevicesPreviewer from '.';\n\nconst testSrc = 'https://testUrl.com';\nconst testDevice1 = {\n  id: 1,\n  name: 'testDevice1',\n  width: 100,\n  height: 100,\n};\nconst testDevice2 = {\n  id: 2,\n  name: 'testDevice2',\n  width: 200,\n  height: 200,\n};\nconst testDevices = [testDevice1, testDevice2];\n\ndescribe('<DevicesPreviewer />', () => {\n  it('Renders the Renderer for all passed array of devices', () => {\n    const wrapper = shallow(\n      <DevicesPreviewer devices={testDevices} url={testSrc} />\n    );\n    expect(wrapper.find('Renderer')).to.have.lengthOf(testDevices.length);\n  });\n\n  it('Renders the Renderer for all devices passed with the given url', () => {\n    const wrapper = shallow(\n      <DevicesPreviewer devices={testDevices} url={testSrc} />\n    );\n    wrapper.find('Renderer').forEach(renderer => {\n      expect(renderer.prop('src')).to.equal(testSrc);\n    });\n  });\n\n  it('Renders the Renderer for all devices in the given order', () => {\n    const wrapper = shallow(\n      <DevicesPreviewer devices={testDevices} url={testSrc} />\n    );\n    wrapper.find('Renderer').forEach((renderer, idx) => {\n      expect(renderer.prop('device')).to.equal(testDevices[idx]);\n    });\n  });\n});\n"
  },
  {
    "path": "desktop-app-legacy/app/components/DevicesPreviewer/useStyles.js",
    "content": "import {makeStyles} from '@material-ui/core/styles';\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    display: 'flex',\n    height: 'inherit',\n  },\n  previewer: {\n    display: 'flex',\n    flex: '1',\n    height: '100%',\n    width: '100%',\n    overflow: 'auto',\n    flexDirection: 'column',\n  },\n  flexigrid: {\n    flexWrap: 'wrap',\n  },\n  horizontal: {\n    flexWrap: 'nowrap',\n  },\n  devicesContainer: {\n    display: 'flex',\n    paddingBottom: '100px',\n    paddingLeft: 5,\n  },\n  tab: {\n    display: 'none',\n    margin: 'auto',\n  },\n  activeTab: {\n    display: 'block',\n  },\n  reactTabs: {\n    position: 'sticky',\n    left: 0,\n    top: 0,\n    marginBottom: '10px',\n    zIndex: '4',\n    backgroundColor: theme.palette.background.l0,\n  },\n  reactTabs__tab: {\n    display: 'flex',\n    alignItems: 'center',\n    flexShrink: 0,\n    bottom: 'unset',\n    border: '1px solid #aaa',\n    borderRadius: '5px 5px 0 0',\n  },\n  reactTabs__tabList: {\n    display: 'flex',\n    marginBottom: 0,\n    overflow: 'auto',\n  },\n}));\n\nexport default useStyles;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/Drawer.js",
    "content": "import React from 'react';\nimport {makeStyles} from '@material-ui/core/styles';\nimport cx from 'classnames';\nimport DeviceDrawerContainer from '../containers/DeviceDrawerContainer';\nimport UserPreferencesContainer from '../containers/UserPreferencesContainer';\nimport ExtensionsManagerContainer from '../containers/ExtensionsManagerContainer';\nimport NetworkConfigurationContainer from '../containers/NetworkConfigurationContainer';\nimport useCommonStyles from './useCommonStyles';\nimport {\n  DEVICE_MANAGER,\n  USER_PREFERENCES,\n  EXTENSIONS_MANAGER,\n  NETWORK_CONFIGURATION,\n} from '../constants/DrawerContents';\nimport DoubleLeftArrowIcon from './icons/DoubleLeftArrow';\nimport LiveCssEditor from './LiveCssEditor';\n\nfunction Drawer(props) {\n  const classes = useStyles();\n  const commonClasses = useCommonStyles();\n\n  return (\n    <div\n      className={cx(classes.drawer, {[classes.drawerOpen]: props.drawer.open})}\n    >\n      <div\n        className={classes.root}\n        onClick={() => props.changeDrawerOpenState(false)}\n      >\n        <div\n          className={cx(commonClasses.flexContainer, commonClasses.icon)}\n          onClick={() => props.changeDrawerOpenState(false)}\n        >\n          <DoubleLeftArrowIcon color=\"currentColor\" height={30} />\n        </div>\n      </div>\n      <div className={classes.drawerContent}>\n        {getDrawerContent(props.drawer.content)}\n      </div>\n    </div>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  root: {\n    display: 'flex',\n    justifyContent: 'flex-end',\n    margin: '0 0 10px',\n  },\n  drawer: {\n    width: '310px',\n    overflow: 'hidden',\n    transition: 'margin-left 0.2s',\n    background: theme.palette.header.main,\n    margin: '0 0 0 -310px',\n    padding: '5px 5px 0 5px',\n  },\n  drawerOpen: {\n    marginLeft: 0,\n    marginRight: 5,\n    flexShrink: 0,\n    boxShadow: `${theme.palette.mode({\n      light: '0px 2px 4px',\n      dark: '5px 7px 5px',\n    })} 0 rgba(0, 0, 0, 0.75)`,\n  },\n  drawerContent: {\n    height: '95%',\n    overflow: 'auto',\n  },\n}));\n\nfunction getDrawerContent(type) {\n  switch (type) {\n    case DEVICE_MANAGER:\n      return <DeviceDrawerContainer />;\n    case USER_PREFERENCES:\n      return <UserPreferencesContainer />;\n    case EXTENSIONS_MANAGER:\n      return <ExtensionsManagerContainer />;\n    case NETWORK_CONFIGURATION:\n      return <NetworkConfigurationContainer />;\n    default:\n      return null;\n  }\n}\n\nexport default Drawer;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ErrorBoundary/index.js",
    "content": "import React from 'react';\nimport {withStyles} from '@material-ui/core/styles';\nimport Typography from '@material-ui/core/Typography';\nimport TextAreaWithCopyButton from '../../utils/TextAreaWithCopyButton';\nimport CreateIssue from '../CreateIssue';\nimport Logo from '../icons/Logo';\n\nclass ErrorBoundary extends React.Component {\n  static getDerivedStateFromError(error) {\n    // Update state so the next render will show the fallback UI.\n    return {\n      hasError: true,\n      error: JSON.stringify(error, Object.getOwnPropertyNames(error)),\n    };\n  }\n\n  componentDidCatch(error, errorInfo) {\n    // You can also log the error to an error reporting service\n    this.setState({\n      err: error,\n      error: JSON.stringify(error, Object.getOwnPropertyNames(error)),\n      errorInfo: JSON.stringify(\n        errorInfo,\n        Object.getOwnPropertyNames(errorInfo)\n      ),\n    });\n  }\n\n  constructor(props) {\n    super(props);\n    this.state = {hasError: false};\n  }\n\n  render() {\n    const {classes} = this.props;\n    if (this.state.hasError) {\n      // You can render any custom fallback UI\n      return (\n        <div className={classes.errorBoundaryContainer}>\n          <h1 className={classes.title}>\n            <Logo height={60} />\n            <br />\n            😓 Something went wrong!\n          </h1>\n          <div className={classes.errorsContainer}>\n            <Typography\n              variant=\"body1\"\n              color=\"textPrimary\"\n              className={classes.errorContainer}\n            >\n              Stack Trace: <TextAreaWithCopyButton text={this.state.error} />\n            </Typography>\n            <Typography\n              variant=\"body1\"\n              color=\"textPrimary\"\n              className={classes.errorContainer}\n            >\n              Error Info: <TextAreaWithCopyButton text={this.state.errorInfo} />\n            </Typography>\n          </div>\n          <CreateIssue state={this.state} />\n        </div>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nconst styles = theme => ({\n  errorBoundaryContainer: {\n    background: theme.palette.background.default,\n    overflowY: 'auto',\n    display: 'flex',\n    flexDirection: 'column',\n    justifyContent: 'center',\n    height: '100%',\n    '& h1': {\n      textAlign: 'center',\n    },\n  },\n  title: {\n    color: theme.palette.text.primary,\n  },\n  errorsContainer: {\n    display: 'flex',\n    justifyContent: 'center',\n  },\n  errorContainer: {\n    textAlign: 'center',\n    justifyContent: 'center',\n    margin: '3rem 6rem',\n    width: '30vw',\n  },\n});\n\nexport default withStyles(styles)(ErrorBoundary);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ExtensionsManager/index.js",
    "content": "import React, {useState, useEffect} from 'react';\nimport Button from '@material-ui/core/Button';\nimport TextField from '@material-ui/core/TextField';\nimport HelpOutlineIcon from '@material-ui/icons/HelpOutline';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport FormHelperText from '@material-ui/core/FormHelperText';\nimport IconButton from '@material-ui/core/IconButton';\nimport DeleteIcon from '@material-ui/icons/DeleteOutlined';\nimport FolderOpenIcon from '@material-ui/icons/FolderOpenOutlined';\nimport ExtensionsIcon from '@material-ui/icons/Extension';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport {remote, ipcRenderer} from 'electron';\nimport cx from 'classnames';\nimport {\n  Popper,\n  Fade,\n  Paper,\n  Typography,\n  ClickAwayListener,\n} from '@material-ui/core';\nimport {useTheme, makeStyles} from '@material-ui/core/styles';\nimport styles from './styles.css';\nimport useCommonStyles from '../useCommonStyles';\nimport helpScreenshot from './help-screenshot.png';\n\nconst useStyles = makeStyles(theme => ({\n  adornedEnd: {\n    paddingRight: 0,\n  },\n  extensionsHelp: {\n    display: 'flex',\n    flexDirection: 'column',\n    width: 500,\n    padding: 12,\n    color: 'white',\n    borderRadius: 4,\n    background: theme.palette.mode({\n      light: theme.palette.secondary.main,\n      dark: '#313131',\n    }),\n    boxShadow: '3px 3px 6px 1px black',\n  },\n}));\n\nexport default function ExtensionsManager({triggerNavigationReload}) {\n  const {BrowserWindow} = remote;\n  const getInstalledExtensions = () =>\n    Object.values(BrowserWindow.getDevToolsExtensions()).sort((a, b) =>\n      a.name.localeCompare(b.name)\n    );\n\n  const classes = useStyles();\n  const [anchorEl, setAnchorEl] = useState(null);\n  const [helpOpen, setHelpOpen] = useState(false);\n  const [loading, setLoading] = useState(false);\n  const [errorMessage, setErrorMessage] = useState('');\n  const [extensionId, setExtensionId] = useState('');\n  const [extensions, setExtensions] = useState(getInstalledExtensions());\n  const commonClasses = useCommonStyles();\n  const theme = useTheme();\n\n  useEffect(() => {\n    setErrorMessage('');\n  }, [extensionId]);\n\n  const handleSubmit = async e => {\n    e.preventDefault();\n    if (loading) {\n      return;\n    }\n    // validate the extension id.\n    if (!validateExtensionId(extensionId)) {\n      setErrorMessage('Please enter a valid extension ID');\n      setLoading(false);\n      return;\n    }\n\n    setLoading(true);\n    setErrorMessage('');\n    try {\n      await ipcRenderer.invoke('install-extension', extensionId);\n      setExtensions(getInstalledExtensions());\n      setExtensionId('');\n      triggerNavigationReload();\n    } catch {\n      setErrorMessage('Error while installing the extension.');\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const handleChange = e => {\n    setExtensionId(e.target.value);\n  };\n\n  const handleDelete = name => {\n    ipcRenderer.send('uninstall-extension', name);\n    setExtensions(getInstalledExtensions());\n  };\n\n  const toggleHelp = event => {\n    if (helpOpen) {\n      if (event) {\n        event.stopPropagation();\n      }\n      return setHelpOpen(!helpOpen);\n    }\n    if (!event.currentTarget.type) {\n      return;\n    }\n    setAnchorEl(event.currentTarget);\n    setHelpOpen(!helpOpen);\n  };\n\n  const getLocalExtensionPath = async event => {\n    const localExtensionPath = await ipcRenderer.invoke(\n      'get-local-extension-path'\n    );\n\n    setExtensionId(localExtensionPath);\n  };\n\n  const validateExtensionId = extensionId =>\n    (extensionId.match(/^[a-z]+$/) && extensionId.length === 32) ||\n    /[<>:\"/\\\\|?]/.test(extensionId);\n\n  return (\n    <>\n      <Popper\n        open={helpOpen}\n        anchorEl={anchorEl}\n        placement=\"bottom-start\"\n        transition\n      >\n        {({TransitionProps}) => (\n          <ClickAwayListener onClickAway={toggleHelp}>\n            <Fade {...TransitionProps} timeout={350}>\n              <div className={classes.extensionsHelp}>\n                <p className={cx(styles.extensionsHelpText)}>\n                  Find the extension on Chrome Web Store and copy the extension\n                  ID from the address bar(as shown below).\n                </p>\n                <img\n                  className={styles.extensionsHelpImg}\n                  src={helpScreenshot}\n                />\n              </div>\n            </Fade>\n          </ClickAwayListener>\n        )}\n      </Popper>\n      <div className={commonClasses.sidebarContentSection}>\n        <div className={commonClasses.sidebarContentSectionTitleBar}>\n          <ExtensionsIcon className={styles.extensionManagerIcon} /> Manage\n          Extensions\n        </div>\n        <div className={commonClasses.sidebarContentSectionContainer}>\n          <form className={styles.extensionAdd} onSubmit={handleSubmit}>\n            <TextField\n              type=\"text\"\n              color=\"secondary\"\n              value={extensionId}\n              onChange={handleChange}\n              disabled={loading}\n              placeholder=\"Extension ID\"\n              variant=\"outlined\"\n              error={errorMessage !== ''}\n              size=\"small\"\n              InputProps={{\n                classes: {\n                  adornedEnd: classes.adornedEnd,\n                },\n                endAdornment: (\n                  <InputAdornment position=\"start\">\n                    <IconButton onClick={toggleHelp} size=\"small\" title=\"Help\">\n                      <HelpOutlineIcon\n                        fontSize=\"small\"\n                        htmlColor={theme.palette.lightIcon.main}\n                      />\n                    </IconButton>\n\n                    <IconButton\n                      onClick={getLocalExtensionPath}\n                      size=\"small\"\n                      title=\"Install local devtools extension\"\n                    >\n                      <FolderOpenIcon\n                        fontSize=\"small\"\n                        htmlColor={theme.palette.lightIcon.main}\n                      />\n                    </IconButton>\n                  </InputAdornment>\n                ),\n              }}\n            />\n            <Button onClick={handleSubmit} disabled={extensionId.trim() === ''}>\n              {loading ? <CircularProgress size={22} /> : 'Add'}\n            </Button>\n          </form>\n\n          {errorMessage && (\n            <FormHelperText error>{errorMessage}</FormHelperText>\n          )}\n\n          <FormHelperText className={styles.extensionsNotice}>\n            Note: Only DevTool extensions will work properly, other general\n            browser extensions may not work as intended.\n          </FormHelperText>\n\n          <p className={styles.extensionsLabel}>\n            Installed extensions ({extensions.length})\n          </p>\n\n          {extensions.map(extension => (\n            <ExtensionItem\n              extension={extension}\n              key={extension.name}\n              onDelete={handleDelete}\n            />\n          ))}\n        </div>\n      </div>\n    </>\n  );\n}\n\nfunction ExtensionItem({extension, onDelete}) {\n  const theme = useTheme();\n\n  const handleDelete = () => {\n    onDelete(extension.name);\n  };\n\n  return (\n    <div className={styles.extension}>\n      <div>\n        <span className={styles.extensionName}>{extension.name}</span>\n        <span className={styles.extensionVersion}>{extension.version}</span>\n      </div>\n      <IconButton onClick={handleDelete} size=\"small\" title=\"Delete extension\">\n        <DeleteIcon fontSize=\"small\" htmlColor={theme.palette.lightIcon.main} />\n      </IconButton>\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ExtensionsManager/styles.css",
    "content": ".extensionManagerIcon {\n  margin-right: 8px;\n  margin-left: 10px;\n  width: 30px;\n  height: 30px;\n}\n\n.extensionsLabel {\n  margin: 34px 0 0;\n  font-size: 13px;\n  line-height: 13px;\n  opacity: 0.5;\n}\n\n.extension {\n  width: 100%;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 12px 0;\n  border-bottom: 1px solid #303030;\n}\n\n.extension:last-child {\n  border-bottom-width: 0;\n  padding-bottom: 0;\n}\n\n.extensionName {\n  font-size: 15px;\n}\n\n.extensionVersion {\n  opacity: 0.7;\n  font-size: 12px;\n  margin-left: 8px;\n  display: inline-block;\n}\n\n.extensionAdd {\n  display: flex;\n  justify-content: space-between;\n  align-items: flex-start;\n  margin-bottom: 6px;\n  margin-top: 12px;\n}\n\n.extensionsNotice {\n  margin-top: 12px !important;\n}\n\n.extensionsHelpText {\n  font-size: 12px;\n  margin-top: 0;\n}\n\n.extensionsHelpImg {\n  object-fit: contain;\n  width: 480px;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/Header/index.js",
    "content": "import React from 'react';\nimport Grid from '@material-ui/core/Grid';\nimport {ToastContainer} from 'react-toastify';\nimport {makeStyles} from '@material-ui/core/styles';\nimport AddressBar from '../../containers/AddressBar';\nimport ScrollControlsContainer from '../../containers/ScrollControlsContainer';\nimport HttpAuthDialog from '../HttpAuthDialog';\nimport os from 'os';\nimport PermissionPopup from '../PermissionPopup';\n\nimport NavigationControlsContainer from '../../containers/NavigationControlsContainer';\nimport BookmarksBar from '../../containers/BookmarksBarContainer';\nimport Logo from '../icons/Logo';\nimport ZenButton from '../ZenButton';\nimport cx from 'classnames';\n\nconst Header = props => {\n  const classes = useStyles();\n\n  return (\n    <div className={cx([classes.container, {zenMode: !props.isHeaderVisible}])}>\n      <div className={classes.firstRow}>\n        <Logo className={classes.logo} width={40} height={40} />\n        <Grid\n          container\n          direction=\"row\"\n          justify=\"flex-start\"\n          alignItems=\"center\"\n        >\n          <Grid item>\n            <NavigationControlsContainer />\n          </Grid>\n          <Grid item style={{flex: 1}}>\n            <AddressBar />\n            <PermissionPopup />\n          </Grid>\n          <Grid item>\n            <ScrollControlsContainer />\n          </Grid>\n        </Grid>\n      </div>\n      <BookmarksBar />\n      <HttpAuthDialog />\n      <ToastContainer\n        position=\"top-right\"\n        autoClose={3000}\n        hideProgressBar\n        newestOnTop={false}\n        closeOnClick\n        pauseOnFocusLoss={false}\n        rtl={false}\n        draggable\n        pauseOnHover\n        toastClassName={classes.darkToast}\n      />\n      <ZenButton\n        active={!props.isHeaderVisible}\n        onClick={() => props.setHeaderVisibility(!props.isHeaderVisible)}\n      />\n    </div>\n  );\n};\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    position: 'relative',\n    background: theme.palette.background.l1,\n    display: 'flex',\n    flexDirection: 'column',\n    width: '100%',\n    padding: os.platform() === 'darwin' ? '0 0 5px' : '0 0 0',\n    boxShadow: `0 ${theme.palette.mode({\n      light: '0px',\n      dark: '3px',\n    })} 5px rgba(0, 0, 0, 0.35)`,\n    zIndex: 500,\n    transform: 'translateY(0)',\n    transition: 'transform .1s ease-out',\n    '& .zenButton': {\n      background: theme.palette.background.l1,\n      display: 'none',\n      position: 'absolute',\n      bottom: '0px',\n      left: '50%',\n      transform: 'translate(-50%, 100%)',\n    },\n    '&:hover .zenButton': {\n      display: 'flex',\n    },\n    '&.zenMode': {\n      transform: 'translateY(-100%)',\n    },\n    '&.zenMode .zenButton': {\n      display: 'flex',\n    },\n  },\n  firstRow: {\n    display: 'flex',\n  },\n  logo: {\n    margin: '0 3px',\n  },\n  darkToast: {\n    background: '#696969 !important',\n    borderRadius: '5px !important',\n    fontSize: '15px',\n    fontWeight: 'lighter',\n  },\n}));\n\nexport default Header;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/Header/index.test.js",
    "content": "import React from 'react';\nimport {shallow} from 'enzyme';\nimport {expect} from 'chai';\n\nimport Header from '.';\n\ndescribe('<Header />', () => {\n  it('renders a h1 and BrowserZoom components', () => {\n    const wrapper = shallow(<Header />);\n    expect(wrapper.find('h1')).to.have.lengthOf(1);\n    expect(wrapper.find('ZoomInput')).to.have.lengthOf(1);\n  });\n});\n"
  },
  {
    "path": "desktop-app-legacy/app/components/Headway.js",
    "content": "import React, {useState, useEffect} from 'react';\nimport {makeStyles, useTheme} from '@material-ui/core/styles';\nimport cx from 'classnames';\nimport useCommonStyles from './useCommonStyles';\nimport GiftIcon from './icons/Gift';\n\nfunction Headway() {\n  const [showGadget, setShowGadget] = useState(false);\n  const commonClasses = useCommonStyles();\n  useEffect(() => {\n    window.HW_config = {\n      selector: '#headway', // CSS selector where to inject the badge\n      account: 'xdYpn7',\n      callbacks: {\n        onWidgetReady(widget) {\n          setShowGadget(true);\n        },\n      },\n    };\n    const headwayScript = document.createElement('script');\n    headwayScript.setAttribute('async', '');\n    headwayScript.setAttribute('src', 'https://cdn.headwayapp.co/widget.js');\n    document.head.appendChild(headwayScript);\n    return () => headwayScript.remove();\n  }, []);\n  const theme = useTheme();\n  const classes = useStyles();\n\n  return (\n    <div\n      id=\"headway\"\n      className={cx({\n        [commonClasses.hidden]: !showGadget,\n        [classes.container]: showGadget,\n        [commonClasses.icon]: showGadget,\n      })}\n    >\n      <GiftIcon width=\"20\" height=\"20\" />\n    </div>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    position: 'relative',\n    height: '50px',\n    alignItems: 'center',\n    justifyContent: 'center',\n    display: 'flex',\n    '&:hover': {\n      background: theme.palette.primary.main,\n      opacity: 1,\n    },\n  },\n}));\n\nexport default Headway;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/HorizontalSpacer/index.js",
    "content": "import React from 'react';\nimport {makeStyles} from '@material-ui/core/styles';\n\n/**\n * Application toolbar that appears at the top of a window.\n */\nconst HorizontalSpacer = () => {\n  const classes = useStyles();\n  return <div className={classes.container} />;\n};\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    background: theme.palette.background.l1,\n    padding: '11px 0',\n    zIndex: '10',\n  },\n}));\n\nexport default HorizontalSpacer;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/HttpAuthDialog/index.js",
    "content": "import React, {useRef, useState, useEffect} from 'react';\nimport {ipcRenderer} from 'electron';\nimport Button from '@material-ui/core/Button';\nimport TextField from '@material-ui/core/TextField';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport DialogTitle from '@material-ui/core/DialogTitle';\n\nexport default function HttpAuthDialog() {\n  const [open, setOpen] = useState(false);\n  const [url, setUrl] = useState('');\n  const usernameRef = useRef(null);\n  const passwordRef = useRef(null);\n\n  useEffect(() => {\n    const handler = (event, args) => {\n      setUrl(args.url);\n      setOpen(true);\n    };\n    ipcRenderer.on('http-auth-prompt', handler);\n    return () => {\n      ipcRenderer.removeListener('http-auth-prompt', handler);\n    };\n  }, []);\n\n  function handleClose(status) {\n    if (!status) {\n      ipcRenderer.send('http-auth-promt-response', {url});\n    }\n    ipcRenderer.send('http-auth-promt-response', {\n      url,\n      username: usernameRef.current.querySelector('input').value,\n      password: passwordRef.current.querySelector('input').value,\n    });\n    setOpen(false);\n  }\n\n  return (\n    <div>\n      <Dialog\n        open={open}\n        onClose={handleClose}\n        disableBackdropClick\n        aria-labelledby=\"form-dialog-title\"\n      >\n        <DialogTitle id=\"form-dialog-title\">Sign-in</DialogTitle>\n        <form\n          id=\"my-form-id\"\n          onSubmit={e => {\n            e.preventDefault();\n            handleClose(true);\n          }}\n        >\n          <DialogContent>\n            <DialogContentText>\n              {url ? <strong>{url}</strong> : 'The webpage'} requires HTTP Basic\n              authentication to connect, please enter the details to continue.\n            </DialogContentText>\n\n            <TextField\n              ref={usernameRef}\n              autoFocus\n              margin=\"dense\"\n              id=\"username\"\n              label=\"Username\"\n              type=\"text\"\n              fullWidth\n            />\n            <TextField\n              ref={passwordRef}\n              margin=\"dense\"\n              id=\"password\"\n              label=\"Password\"\n              type=\"password\"\n              fullWidth\n            />\n          </DialogContent>\n          <DialogActions>\n            <Button onClick={() => handleClose(false)} color=\"secondary\">\n              Cancel\n            </Button>\n            <Button\n              variant=\"contained\"\n              onClick={() => handleClose(true)}\n              color=\"primary\"\n              type=\"submit\"\n              primary\n            >\n              Sign In\n            </Button>\n          </DialogActions>\n        </form>\n      </Dialog>\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/KebabMenu.js",
    "content": "import React, {useState, useRef} from 'react';\nimport {useClickAway} from 'react-use';\nimport {motion} from 'framer-motion';\nimport {useTheme, makeStyles} from '@material-ui/core/styles';\nimport Kebab from './icons/Kebab';\n\nexport const KebabMenuItem = ({children, ...otherProps}) => {\n  const classes = useStyles();\n  return (\n    <motion.div className={classes.option} {...otherProps}>\n      {children}\n    </motion.div>\n  );\n};\n\nconst KebabMenu = ({children}) => {\n  const [mouseOn, setMouseOn] = useState(false);\n  const [showMenu, setShowMenu] = useState(false);\n  const theme = useTheme();\n  const classes = useStyles();\n  const menuRef = useRef();\n  useClickAway(menuRef, () => {\n    setShowMenu(false);\n    setMouseOn(false);\n  });\n\n  if (children && !children.length) {\n    children = [children];\n  }\n\n  return (\n    <>\n      <div\n        onMouseEnter={() => setMouseOn(true)}\n        onMouseLeave={() => setMouseOn(false)}\n        ref={menuRef}\n      >\n        <div className={classes.icon} onClick={() => setShowMenu(!showMenu)}>\n          <Kebab\n            height={16}\n            width={12}\n            color={\n              mouseOn || showMenu\n                ? theme.palette.primary.main\n                : theme.palette.text.primary\n            }\n          />\n          {showMenu ? (\n            <motion.div\n              initial={{opacity: 0, height: 0}}\n              animate={{opacity: 1, height: 'auto'}}\n              transition={{\n                type: 'spring',\n                stiffness: 500,\n                damping: 1000,\n              }}\n              className={classes.menu}\n              onClick={() => setMouseOn(false)}\n            >\n              {children.filter(Boolean)}\n            </motion.div>\n          ) : null}\n        </div>\n      </div>\n    </>\n  );\n};\n\nconst useStyles = makeStyles(theme => ({\n  icon: {\n    display: 'flex',\n    alignItems: 'center',\n    justifyContent: 'center',\n    cursor: 'pointer',\n    position: 'relative',\n  },\n  menu: {\n    position: 'absolute',\n    width: 'max-content',\n    top: '16px',\n    right: 0,\n    background: theme.palette.mode({\n      light: theme.palette.grey[300],\n      dark: '#313131',\n    }),\n    fontSize: '12px',\n    zIndex: 1000,\n    boxShadow: '5px 7px 5px 0 rgba(0, 0, 0, 0.75)',\n    borderRadius: '5px',\n  },\n  option: {\n    padding: '8px',\n    '&:hover': {\n      background: theme.palette.mode({\n        light: theme.palette.grey[500],\n        dark: 'black',\n      }),\n    },\n    '&:first-child': {\n      borderTopRightRadius: '5px',\n      borderTopLeftRadius: '5px',\n    },\n    '&:last-child': {\n      borderBottomRightRadius: '5px',\n      borderBottomLeftRadius: '5px',\n    },\n  },\n}));\n\nKebabMenu.Item = KebabMenuItem;\n\nexport default KebabMenu;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/LeftIconsPane/index.js",
    "content": "import React from 'react';\nimport Grid from '@material-ui/core/Grid';\nimport DevicesIcon from '@material-ui/icons/Devices';\nimport SettingsIcon from '@material-ui/icons/Settings';\nimport PhotoLibraryIcon from '@material-ui/icons/PhotoLibraryOutlined';\nimport ExtensionIcon from '@material-ui/icons/Extension';\nimport cx from 'classnames';\nimport NetworkIcon from '../icons/Network';\nimport Logo from '../icons/Logo';\nimport Gift from '../icons/Gift';\nimport Headway from '../Headway';\nimport ZenButton from '../ZenButton';\nimport Tooltip from '@material-ui/core/Tooltip';\n\nimport styles from './styles.css';\nimport useCommonStyles from '../useCommonStyles';\nimport {\n  DEVICE_MANAGER,\n  SCREENSHOT_MANAGER,\n  USER_PREFERENCES,\n  EXTENSIONS_MANAGER,\n  NETWORK_CONFIGURATION,\n} from '../../constants/DrawerContents';\nimport {makeStyles} from '@material-ui/core/styles';\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    position: 'relative',\n    backgroundColor: theme.palette.background.l2,\n    display: 'flex',\n    flexFlow: 'column',\n    marginTop: '-50px',\n    paddingTop: '50px',\n    width: 50,\n    boxShadow: `0 ${theme.palette.mode({\n      light: '3px',\n      dark: '3px',\n    })} 5px rgba(0, 0, 0, 0.35)`,\n    zIndex: 1,\n    transform: 'translateX(0)',\n    transition: 'transform .1s ease-out',\n    '& .zenButton': {\n      position: 'absolute',\n      background: theme.palette.background.l2,\n      top: '50%',\n      right: '0',\n      transformOrigin: 'center',\n      transform: 'translate(100%, -50%) rotate(-90deg) translateY(-30px)',\n      display: 'none',\n    },\n    '&:hover .zenButton': {\n      display: 'flex',\n    },\n    '&.zenMode': {\n      transform: 'translateX(-100%)',\n    },\n    '&.zenMode .zenButton': {\n      display: 'flex',\n    },\n  },\n  leftPaneIcon: {\n    '& svg': {\n      verticalAlign: 'middle',\n    },\n  },\n}));\n\nconst LeftIconsPane = props => {\n  const commonClasses = useCommonStyles();\n  const mStyles = useStyles();\n  const iconProps = {\n    style: {fontSize: 30},\n    height: 30,\n    width: 30,\n  };\n  const toggleState = content => {\n    if (props.drawer.open && props.drawer.content === content) {\n      return props.changeDrawerOpenState(false);\n    }\n\n    props.openDrawerAndSetContent(content);\n  };\n  return (\n    <div\n      className={`${mStyles.container} ${\n        props.isLeftPaneVisible ? '' : 'zenMode'\n      }`}\n    >\n      <Grid\n        container\n        spacing={1}\n        direction=\"column\"\n        alignItems=\"center\"\n        className={cx(styles.utilitySection)}\n      >\n        <Grid\n          item\n          className={cx(commonClasses.icon, styles.icon, {\n            [commonClasses.iconSelected]:\n              props.drawer.open && props.drawer.content === DEVICE_MANAGER,\n          })}\n          onClick={() => toggleState(DEVICE_MANAGER)}\n        >\n          <Tooltip title=\"Device manager\">\n            <div className={cx(mStyles.leftPaneIcon)}>\n              <DevicesIcon {...iconProps} className=\"deviceManagerIcon\" />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid\n          item\n          className={cx(commonClasses.icon, styles.icon, {\n            [commonClasses.iconSelected]:\n              props.drawer.open && props.drawer.content === USER_PREFERENCES,\n          })}\n          onClick={() => toggleState(USER_PREFERENCES)}\n        >\n          <Tooltip title=\"Preferences\">\n            <div className={cx(mStyles.leftPaneIcon)}>\n              <SettingsIcon {...iconProps} className=\"settingsIcon\" />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid\n          item\n          className={cx(commonClasses.icon, styles.icon, {\n            [commonClasses.iconSelected]:\n              props.drawer.open && props.drawer.content === EXTENSIONS_MANAGER,\n          })}\n          onClick={() => toggleState(EXTENSIONS_MANAGER)}\n        >\n          <Tooltip title=\"Manage extensions\">\n            <div className={cx(mStyles.leftPaneIcon)}>\n              <ExtensionIcon {...iconProps} className=\"extensionsIcon\" />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid\n          item\n          className={cx(commonClasses.icon, styles.icon, {\n            [commonClasses.iconSelected]:\n              props.drawer.open &&\n              props.drawer.content === NETWORK_CONFIGURATION,\n          })}\n          onClick={() => toggleState(NETWORK_CONFIGURATION)}\n        >\n          <Tooltip title=\"Manage network\">\n            <div className={cx(mStyles.leftPaneIcon)}>\n              <NetworkIcon\n                {...iconProps}\n                color=\"currentColor\"\n                className=\"networkIcon\"\n              />\n            </div>\n          </Tooltip>\n        </Grid>\n      </Grid>\n      <Headway />\n      {!props.drawer.open && (\n        <ZenButton\n          active={!props.isLeftPaneVisible}\n          onClick={() => props.setLeftPaneVisibility(!props.isLeftPaneVisible)}\n        />\n      )}\n    </div>\n  );\n};\n\nexport default LeftIconsPane;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/LeftIconsPane/styles.css",
    "content": ".icon {\n  margin-bottom: 10px !important;\n}\n\n.utilitySection {\n  flex: 1;\n  margin-top: 50px !important;\n}\n\n.logo {\n  background: white;\n  border-radius: 9%;\n  margin-bottom: 50px;\n  display: flex;\n  justify-content: center;\n  margin: 3px;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/LinkHoverDisplay/index.js",
    "content": "import PropTypes from 'prop-types';\nimport {makeStyles} from '@material-ui/core/styles';\nimport React from 'react';\nimport cx from 'classnames';\n\nconst LinkHoverDisplay = ({visible, url = ''}) => {\n  const componentStyles = useStyles();\n\n  const activeClasses = React.useMemo(() => {\n    const classes = {[componentStyles.container]: true};\n    classes[componentStyles.show] = visible;\n    return cx(classes);\n  }, [visible]);\n\n  return (\n    <div className={activeClasses}>\n      <p className={componentStyles.text}>{url}</p>\n    </div>\n  );\n};\n\nLinkHoverDisplay.propTypes = {\n  url: PropTypes.string,\n  visible: PropTypes.bool,\n};\n\nLinkHoverDisplay.defaultProps = {\n  url: '',\n  visible: false,\n};\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    position: 'absolute',\n    bottom: 0,\n    maxWidth: 0,\n    opacity: 0,\n    zIndex: theme.zIndex.tooltip,\n    borderTopRightRadius: theme.shape.borderRadius,\n    transitionDuration: theme.transitions.duration.enteringScreen,\n    transitionTimingFunction: theme.transitions.easing.easeInOut,\n    backgroundColor: theme.palette.background.l2,\n    border: `1px solid ${theme.palette.background.l0}`,\n    boxSizing: 'border-box',\n    maxHeight: '2rem',\n  },\n  show: {\n    opacity: 1,\n    maxWidth: '95%',\n    padding: '3px 4px',\n  },\n  text: {\n    fontSize: '0.7rem',\n    margin: 0,\n    opacity: 0.7,\n    color: theme.palette.mode({light: '#000', dark: '#fff'}),\n    overflow: 'hidden',\n    whiteSpace: 'nowrap',\n    textOverflow: 'ellipsis',\n  },\n}));\n\nexport default LinkHoverDisplay;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/LiveCssEditor/index.js",
    "content": "import React, { useMemo, useState, useEffect, useRef } from 'react';\nimport { Rnd } from 'react-rnd';\nimport { useSelector, useDispatch } from 'react-redux';\nimport cx from 'classnames';\nimport pubsub from 'pubsub.js';\nimport FormControlLabel from '@material-ui/core/FormControlLabel';\nimport Typography from '@material-ui/core/Typography';\nimport Checkbox from '@material-ui/core/Checkbox';\nimport Input from '@material-ui/core/Input';\nimport SettingsIcon from '@material-ui/icons/Settings';\nimport AceEditor from 'react-ace';\nimport 'ace-builds/src-noconflict/mode-css';\nimport 'ace-builds/src-noconflict/theme-github';\n\nimport useCommonStyles from '../useCommonStyles';\nimport useStyles from './useStyles';\nimport TextAreaWithCopyButton from '../../utils/TextAreaWithCopyButton';\nimport Button from '@material-ui/core/Button';\nimport { APPLY_CSS } from '../../constants/pubsubEvents';\nimport {\n  CSS_EDITOR_MODES,\n  DEVTOOLS_MODES,\n  isHorizontallyStacked,\n  isVeriticallyStacked,\n} from '../../constants/previewerLayouts';\nimport KebabMenu from '../KebabMenu';\nimport { Tooltip } from '@material-ui/core';\nimport DockRight from '../icons/DockRight';\nimport { debounce } from 'lodash';\nimport CSSEditor from '../icons/CSSEditor';\n\nconst getResizingDirections = position => {\n  switch (position) {\n    case CSS_EDITOR_MODES.LEFT:\n      return { right: true };\n    case CSS_EDITOR_MODES.RIGHT:\n      return { left: true };\n    case CSS_EDITOR_MODES.TOP:\n      return { bottom: true };\n    case CSS_EDITOR_MODES.BOTTOM:\n      return { top: true };\n    default:\n      return true;\n  }\n};\n\nconst computeHeight = (position, devToolsConfig) => {\n  if (position === CSS_EDITOR_MODES.UNDOCKED) {\n    return null;\n  }\n  return isVeriticallyStacked(position)\n    ? `calc(100vh - ${10 + headerHeight + statusBarHeight + (devToolsConfig.open && devToolsConfig.mode === DEVTOOLS_MODES.BOTTOM ? devToolsConfig.size.height : 0)}px)`\n    : 300;\n};\n\nconst computeWidth = (position, devToolsConfig) => {\n  if (position === CSS_EDITOR_MODES.UNDOCKED) {\n    return null;\n  }\n  return isHorizontallyStacked(position) ? 'calc(100vw - 50px)' : 400;\n};\n\nconst getDefaultSize = isUndocked => ({\n  width: isUndocked ? 400 : '100%',\n  height: isUndocked ? 300 : '100%',\n});\n\nconst getDefaultPosition = isUndocked => ({\n  x: isUndocked ? 100 : 0,\n  y: isUndocked ? 100 : 0,\n});\n\nconst computeIsUndocked = position => position === CSS_EDITOR_MODES.UNDOCKED;\n\nconst headerHeight = 70;\nconst statusBarHeight = 20;\n\nconst LiveCssEditor = ({\n  browser,\n  isOpen,\n  position,\n  content,\n  boundaryClass,\n  devToolsConfig,\n  changeCSSEditorPosition,\n  onCSSEditorContentChange,\n}) => {\n  const rndRef = useRef();\n  const classes = useStyles();\n  const commonClasses = useCommonStyles();\n  const [autoApply, setAuotApply] = useState(true);\n  const [prevPosition, setPrevPosition] = useState(null);\n  const [height, setHeight] = useState(computeHeight(position, devToolsConfig));\n  const [width, setWidth] = useState(computeWidth(position, devToolsConfig));\n\n  useEffect(() => {\n    setHeight(computeHeight(position, devToolsConfig));\n  }, [devToolsConfig]);\n\n  const onApply = () => {\n    if (!content) {\n      return;\n    }\n    pubsub.publish(APPLY_CSS, [{ css: content }]);\n  };\n\n  useEffect(() => {\n    if (!autoApply) {\n      return;\n    }\n    onApply();\n  }, [content]);\n\n  useEffect(() => {\n    refreshHeight();\n    refreshWidth();\n    if (prevPosition !== position && rndRef) {\n      rndRef.current.updateSize(getDefaultSize(computeIsUndocked(position)));\n      rndRef.current.updatePosition(\n        getDefaultPosition(computeIsUndocked(position))\n      );\n      setPrevPosition(position);\n    }\n  }, [position, devToolsConfig, rndRef]);\n\n  const refreshHeight = () =>\n    setHeight(computeHeight(position, devToolsConfig));\n  const refreshWidth = () => setWidth(computeWidth(position, devToolsConfig));\n\n  const isUndocked = useMemo(() => computeIsUndocked(position), [position]);\n  const enableResizing = useMemo(() => getResizingDirections(position), [\n    position,\n  ]);\n  const disableDragging = useMemo(() => !isUndocked, [isUndocked]);\n\n  return (\n    <div className={classes.wrapper} style={{ height, width }}>\n      <Rnd\n        ref={rndRef}\n        dragHandleClassName={classes.titleBar}\n        disableDragging={disableDragging}\n        enableResizing={enableResizing}\n        style={{ zIndex: 100 }}\n        default={{\n          ...getDefaultPosition(isUndocked),\n          ...getDefaultSize(isUndocked),\n        }}\n        bounds={`.${boundaryClass}`}\n        onResize={(e, dir, ref) => {\n          if (isUndocked) {\n            return;\n          }\n          const { width: _width, height: _height } = ref.getBoundingClientRect();\n          if (width !== _width) {\n            setWidth(_width);\n          }\n          if (height !== _height) {\n            setHeight(_height);\n          }\n        }}\n      >\n        <div className={classes.container}>\n          <div\n            className={cx(\n              classes.titleBar,\n              commonClasses.flexContainerSpaceBetween,\n              {\n                [classes.dragHandle]: !disableDragging,\n              }\n            )}\n          >\n            <span className={commonClasses.flexContainer}>\n              <CSSEditor\n                color=\"currentColor\"\n                height={20}\n                width={25}\n                margin=\"0 5px 0 0\"\n              />\n              Live CSS Editor\n            </span>\n            <KebabMenu>\n              {position !== CSS_EDITOR_MODES.UNDOCKED && (\n                <KebabMenu.Item\n                  onClick={() =>\n                    changeCSSEditorPosition(CSS_EDITOR_MODES.UNDOCKED)\n                  }\n                >\n                  Un-dock Editor\n                </KebabMenu.Item>\n              )}\n              {position !== CSS_EDITOR_MODES.LEFT && (\n                <KebabMenu.Item\n                  onClick={() => changeCSSEditorPosition(CSS_EDITOR_MODES.LEFT)}\n                >\n                  Dock to Left\n                </KebabMenu.Item>\n              )}\n            </KebabMenu>\n          </div>\n          <div className={classes.mainContent}>\n            <AceEditor\n              className={classes.editor}\n              placeholder=\"Enter CSS to apply\"\n              mode=\"css\"\n              theme=\"twilight\"\n              name=\"css\"\n              onChange={debounce(onCSSEditorContentChange, 25, { maxWait: 50 })}\n              fontSize={14}\n              showPrintMargin={true}\n              showGutter={true}\n              highlightActiveLine={true}\n              value={content}\n              width=\"100%\"\n              height=\"100%\"\n              setOptions={{\n                enableBasicAutocompletion: true,\n                enableLiveAutocompletion: true,\n                enableSnippets: false,\n                showLineNumbers: true,\n                tabSize: 2,\n              }}\n            />\n            <FormControlLabel\n              control={\n                <Checkbox\n                  checked={autoApply}\n                  onChange={e => setAuotApply(e.target.checked)}\n                  name=\"Auto apply changes\"\n                  color=\"primary\"\n                  size=\"small\"\n                />\n              }\n              label=\"Auto apply changes\"\n            />\n            {!autoApply && (\n              <Button\n                size=\"small\"\n                color=\"primary\"\n                variant=\"contained\"\n                onClick={onApply}\n              >\n                Apply\n              </Button>\n            )}\n          </div>\n        </div>\n      </Rnd>\n    </div>\n  );\n};\n\nexport default LiveCssEditor;\n\n// HMR integration\nif (module.hot) {\n  module.hot.accept('./LiveCssEditor', () => {\n    const NextLiveCssEditor = require('./LiveCssEditor').default;\n    ReactDOM.render(<NextLiveCssEditor />, document.getElementById('app'));\n  });\n}\n\n"
  },
  {
    "path": "desktop-app-legacy/app/components/LiveCssEditor/useStyles.js",
    "content": "import {makeStyles} from '@material-ui/core/styles';\n\nconst useStyles = makeStyles(theme => ({\n  wrapper: {\n    position: 'relative',\n    display: 'flex',\n    flexShrink: 0,\n    marginBottom: 10,\n  },\n  container: {\n    display: 'flex',\n    flexDirection: 'column',\n    height: '100%',\n    background: theme.palette.background.l2,\n    margin: 5,\n    padding: 10,\n    borderRadius: 2,\n    boxShadow: `0 ${theme.palette.mode({\n      light: '0px',\n      dark: '3px',\n    })} 5px rgba(0, 0, 0, 0.35)`,\n  },\n  titleBar: {\n    margin: '5px 0 10px',\n    alignItems: 'center',\n  },\n  dragHandle: {\n    cursor: 'move',\n  },\n  mainContent: {\n    display: 'flex',\n    flexDirection: 'column',\n    flex: 1,\n  },\n  editor: {\n    height: '100%',\n    marginBottom: 10,\n  },\n}));\n\nexport default useStyles;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NavigationControls/index.js",
    "content": "import React, {Component} from 'react';\nimport cx from 'classnames';\nimport Grid from '@material-ui/core/Grid';\nimport {withStyles, withTheme} from '@material-ui/core/styles';\nimport {ipcRenderer} from 'electron';\nimport {Tooltip} from '@material-ui/core';\nimport HomeIcon from '../icons/Home';\nimport ArrowLeftIcon from '../icons/ArrowLeft';\nimport ArrowRightIcon from '../icons/ArrowRight';\nimport {styles as commonStyles} from '../useCommonStyles';\nimport Cross from '../icons/Cross';\nimport Reload from '../icons/Reload';\nimport {notifyPermissionToHandleReloadOrNewAddress} from '../../utils/permissionUtils.js';\n\nclass NavigationControls extends Component {\n  componentDidMount() {\n    ipcRenderer.on('reload-url', this.props.triggerNavigationReload);\n    ipcRenderer.on('reload-css', this.props.reloadCSS);\n  }\n\n  componentWillUnmount() {\n    ipcRenderer.removeListener(\n      'reload-url',\n      this.props.triggerNavigationReload\n    );\n    ipcRenderer.removeListener('reload-css', this.props.reloadCSS);\n  }\n\n  render() {\n    const {backEnabled, forwardEnabled, classes} = this.props;\n    const deviceLoading = (this.props.devices || []).find(\n      device => device.loading\n    );\n\n    let refreshOrCancel;\n    if (deviceLoading) {\n      refreshOrCancel = (\n        <Grid item className={classes.icon}>\n          <Tooltip title=\"Stop loading this page\">\n            <div\n              className={classes.flexAlignVerticalMiddle}\n              onClick={this.props.triggerStopLoading}\n            >\n              <Cross color=\"currentColor\" padding={6} height={24} width={24} />\n            </div>\n          </Tooltip>\n        </Grid>\n      );\n    } else {\n      refreshOrCancel = (\n        <Grid item className={classes.icon}>\n          <Tooltip title=\"Reload\">\n            <div\n              className={classes.flexAlignVerticalMiddle}\n              onClick={() => {\n                notifyPermissionToHandleReloadOrNewAddress();\n                this.props.triggerNavigationReload();\n              }}\n            >\n              <Reload color=\"currentColor\" padding={4} height={24} width={24} />\n            </div>\n          </Tooltip>\n        </Grid>\n      );\n    }\n\n    return (\n      <div className={classes.navigationControls}>\n        <Grid container spacing={1} alignItems=\"center\">\n          <Grid\n            item\n            className={cx(classes.icon, {\n              [classes.iconDisabled]: !backEnabled,\n              [classes.iconHoverDisabled]: !backEnabled,\n            })}\n          >\n            <div\n              className={cx(classes.iconDisabler, {\n                [classes.iconDisabled]: !backEnabled,\n              })}\n            />\n            <Tooltip title=\"Back\">\n              <div\n                className={classes.flexContainer}\n                onClick={() => {\n                  notifyPermissionToHandleReloadOrNewAddress();\n                  this.props.triggerNavigationBack();\n                }}\n              >\n                <ArrowLeftIcon\n                  width={24}\n                  height={24}\n                  color={\n                    backEnabled\n                      ? 'currentColor'\n                      : this.props.theme.palette.lightIcon.main\n                  }\n                />\n              </div>\n            </Tooltip>\n          </Grid>\n          <Grid\n            item\n            className={cx(classes.icon, {\n              [classes.iconDisabled]: !forwardEnabled,\n              [classes.iconHoverDisabled]: !forwardEnabled,\n            })}\n          >\n            <div\n              className={cx(classes.iconDisabler, {\n                [classes.iconDisabled]: !forwardEnabled,\n              })}\n            />\n            <Tooltip title=\"Forward\">\n              <div\n                className={classes.flexContainer}\n                onClick={() => {\n                  notifyPermissionToHandleReloadOrNewAddress();\n                  this.props.triggerNavigationForward();\n                }}\n              >\n                <ArrowRightIcon\n                  width={24}\n                  height={24}\n                  color={\n                    forwardEnabled\n                      ? 'currentColor'\n                      : this.props.theme.palette.lightIcon.main\n                  }\n                />\n              </div>\n            </Tooltip>\n          </Grid>\n\n          {refreshOrCancel}\n\n          <Grid item className={classes.icon}>\n            <Tooltip title=\"Go to Homepage\">\n              <div\n                className={classes.flexAlignVerticalMiddle}\n                onClick={() => {\n                  if (this.props.address !== this.props.homepage)\n                    notifyPermissionToHandleReloadOrNewAddress();\n                  this.props.goToHomepage();\n                }}\n              >\n                <HomeIcon\n                  color=\"currentColor\"\n                  height={25}\n                  width={25}\n                  padding={5}\n                />\n              </div>\n            </Tooltip>\n          </Grid>\n        </Grid>\n      </div>\n    );\n  }\n}\n\nconst styles = theme => ({\n  ...commonStyles(theme),\n  navigationControls: {\n    padding: '0 10px',\n  },\n});\n\nexport default withStyles(styles)(withTheme(NavigationControls));\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NavigationControls/index.test.js",
    "content": "// @flow\nimport React from 'react';\nimport {shallow, mount} from 'enzyme';\nimport {expect, assert} from 'chai';\nimport sinon from 'sinon';\n\nimport Slider from '@material-ui/core/Slider';\nimport BrowserZoom from '.';\n\ndescribe('<BrowserZoom />', () => {\n  it('Renders label and the slider component ', () => {\n    const wrapper = shallow(<BrowserZoom />);\n    expect(wrapper.find(Slider)).to.have.lengthOf(1);\n  });\n\n  it('Calls the callback on slider change', () => {\n    const onChange = sinon.spy();\n    const wrapper = mount(<BrowserZoom onChange={onChange} />);\n    wrapper.find('.MuiSlider-thumb').simulate('mousedown');\n    wrapper.find('.MuiSlider-thumb').simulate('mouseup');\n    expect(onChange).to.have.property('callCount', 1);\n  });\n\n  /* it('Calls the callback with a number value', () => {\n    const onChange = sinon.spy();\n    const wrapper = mount(<BrowserZoom onChange={onChange} />);\n    wrapper.find('.MuiSlider-thumb').simulate('mousedown');\n    wrapper.find('.MuiSlider-thumb').simulate('mouseup');\n    console.log('spy.args', onChange.args);\n    assert(onChange.calledWith(100));\n  }); */\n});\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NetworkConfiguration/index.js",
    "content": "import React from 'react';\nimport cx from 'classnames';\nimport ClearNetworkCache from '../ClearNetworkCache';\nimport NetworkThrottling from '../NetworkThrottling';\nimport NetworkProxy from '../NetworkProxy';\n\nimport styles from './styles.css';\n\nexport default function NetworkConfiguration({\n  throttling,\n  proxy,\n  onActiveThrottlingProfileChanged,\n  onThrottlingProfilesListChanged,\n  onClearNetworkCache,\n  onToggleUseProxy,\n  onProxyProfileChanged,\n}) {\n  return (\n    <div>\n      <ClearNetworkCache onClearNetworkCache={onClearNetworkCache} />\n      <NetworkThrottling\n        throttling={throttling}\n        onActiveThrottlingProfileChanged={onActiveThrottlingProfileChanged}\n        onThrottlingProfilesListChanged={onThrottlingProfilesListChanged}\n      />\n      <NetworkProxy\n        proxy={proxy}\n        onToggleUseProxy={onToggleUseProxy}\n        onProxyProfileChanged={onProxyProfileChanged}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NetworkConfiguration/styles.css",
    "content": ".label {\n  font-size: 14px;\n  margin-bottom: 5px;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NetworkProxy/ProxyManager/index.js",
    "content": "import React, {useState} from 'react';\nimport cx from 'classnames';\nimport Button from '@material-ui/core/Button';\nimport Grid from '@material-ui/core/Grid';\nimport TextField from '@material-ui/core/TextField';\nimport NumberFormat from 'react-number-format';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport Select from '../../Select';\nimport {getEmptyProxySchemeConfig} from '../../../utils/proxyUtils';\nimport {isNullOrWhiteSpaces} from '../../../utils/stringUtils';\nimport cloneDeep from 'lodash/cloneDeep';\nimport trim from 'lodash/trim';\nimport VisibilityIcon from '@material-ui/icons/Visibility';\nimport VisibilityOffIcon from '@material-ui/icons/VisibilityOff';\nimport IconButton from '@material-ui/core/IconButton';\nimport {shell} from 'electron';\nimport Link from '@material-ui/core/Link';\nimport OpenInNewIcon from '@material-ui/icons/OpenInNew';\nimport {makeStyles} from '@material-ui/core/styles';\n\nconst schemes = ['default', 'http', 'https', 'ftp'];\n\nconst protocolOptions = [\n  {value: 'default', label: '(use default)'},\n  {value: 'direct', label: 'DIRECT'},\n  {value: 'http', label: 'HTTP'},\n  {value: 'https', label: 'HTTPS'},\n  {value: 'socks4', label: 'SOCKS4'},\n  {value: 'socks5', label: 'SOCKS5'},\n];\n\nfunction ProtocolSelector({value, onChange, allowUseDefault = false}) {\n  const opts = allowUseDefault ? protocolOptions : protocolOptions.slice(1);\n  return (\n    <Select\n      options={opts}\n      value={opts.find(x => x.value === value) || opts[0]}\n      onChange={onChange}\n    />\n  );\n}\n\nfunction NumberFormatCustom(props) {\n  const {inputRef, onChange, ...other} = props;\n\n  return (\n    <NumberFormat\n      {...other}\n      getInputRef={inputRef}\n      onValueChange={values => {\n        onChange({\n          target: {\n            name: props.name,\n            value: values.floatValue || '',\n          },\n        });\n      }}\n      allowNegative={false}\n      decimalScale={0}\n    />\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  proxyManagerContainer: {\n    minHeight: '600px',\n    padding: '20px 0',\n  },\n  saveButton: {\n    position: 'absolute',\n    bottom: '25px',\n    right: '25px',\n  },\n  numericField: {\n    '& *': {\n      fontSize: '14px',\n    },\n  },\n  numericFieldDisabled: {\n    '& fieldset': {\n      border: '0',\n    },\n    '& input': {\n      color: 'white',\n    },\n  },\n  titleField: {\n    '& *': {\n      fontSize: '14px',\n    },\n  },\n  removeMarginTop: {\n    marginTop: 0,\n  },\n  removeMarginBottom: {\n    marginBottom: 0,\n  },\n  wilcardsAndMoreLink: {\n    color: theme.palette.text.normal,\n    textDecoration: 'underline',\n  },\n  bypassListField: {\n    marginTop: '18px',\n    width: '50%',\n  },\n  bypassListFieldTextArea: {\n    '& *': {\n      fontFamily: \"'Consolas', 'Courier New', monospace\",\n      whiteSpace: 'pre',\n      fontSize: '14px',\n    },\n  },\n}));\n\nexport default function ProxyManager({proxy, onSave}) {\n  const [profile, setProfile] = useState(cloneDeep(proxy));\n  const [visiblePasswords, setVisiblePasswords] = useState({});\n  const classes = useStyles();\n\n  const changeValue = (scheme, prop, value) => {\n    if (profile[scheme].useDefault && prop !== 'protocol') return;\n    if (prop === 'useDefault' && value === true) {\n      profile[scheme] = getEmptyProxySchemeConfig(true);\n    }\n    if (prop === 'protocol') {\n      if (value === 'direct') profile[scheme] = getEmptyProxySchemeConfig();\n      else profile[scheme].useDefault = false;\n    }\n    profile[scheme][prop] = value;\n    setProfile({...profile});\n  };\n\n  const onSaveClicked = () => {\n    profile.bypassList = (profile.bypassList || [])\n      .map(trim)\n      .filter(x => !isNullOrWhiteSpaces(x));\n    schemes.forEach(s => {\n      if (profile[s].useDefault) {\n        profile[s] = {useDefault: true};\n      } else {\n        trim(profile[s].server);\n      }\n    });\n    onSave(profile);\n  };\n\n  const getDisplayValue = (scheme, prop) => {\n    if (scheme === 'default') return profile.default[prop] || '';\n    if (profile[scheme].useDefault) return profile.default[prop] || '';\n    return profile[scheme][prop] || '';\n  };\n\n  const handleShowPassword = scheme => {\n    visiblePasswords[scheme] = !visiblePasswords[scheme];\n    setVisiblePasswords({...visiblePasswords});\n  };\n\n  const canSeePasswordToggle = scheme => {\n    if (profile[scheme].useDefault)\n      return profile.default.protocol !== 'direct';\n    return profile[scheme].protocol !== 'direct';\n  };\n\n  const onMoreInfo = () => {\n    shell.openExternal(\n      'https://developer.chrome.com/extensions/proxy#bypass_list'\n    );\n  };\n\n  const changeBypassList = e => {\n    profile.bypassList = (e.target.value || '').split('\\n');\n    setProfile({...profile});\n  };\n\n  const isTextFieldDisabled = scheme =>\n    !!profile[scheme].useDefault || profile[scheme].protocol === 'direct';\n\n  return (\n    <div className={cx(classes.proxyManagerContainer)}>\n      <h3 className={classes.removeMarginTop}>Proxy Servers</h3>\n      <Grid container spacing={1}>\n        <Grid container item xs={12} spacing={1}>\n          <Grid item xs={1}>\n            <strong>Scheme</strong>\n          </Grid>\n          <Grid item xs={2}>\n            <strong>Protocol</strong>\n          </Grid>\n          <Grid item xs={3}>\n            <strong>Server</strong>\n          </Grid>\n          <Grid item xs={1}>\n            <strong>Port</strong>\n          </Grid>\n          <Grid item xs={2}>\n            <strong>Username</strong>\n          </Grid>\n          <Grid item xs={3}>\n            <strong>Password</strong>\n          </Grid>\n        </Grid>\n\n        {schemes.map(scheme => (\n          <Grid key={scheme} container item xs={12} spacing={1}>\n            <Grid container item xs={1} alignItems=\"center\">\n              {scheme === 'default' ? `(default)` : `${scheme}://`}\n            </Grid>\n            <Grid item xs={2}>\n              <ProtocolSelector\n                value={\n                  profile[scheme].useDefault\n                    ? 'default'\n                    : profile[scheme].protocol\n                }\n                onChange={val => {\n                  if (val.value === 'default')\n                    changeValue(scheme, 'useDefault', true);\n                  else changeValue(scheme, 'protocol', val.value);\n                }}\n                allowUseDefault={scheme !== 'default'}\n              />\n            </Grid>\n            <Grid item xs={3}>\n              <TextField\n                value={getDisplayValue(scheme, 'server')}\n                fullWidth\n                variant=\"outlined\"\n                className={cx(classes.titleField)}\n                onChange={e => {\n                  changeValue(scheme, 'server', e.target.value);\n                }}\n                disabled={isTextFieldDisabled(scheme)}\n                error={\n                  !profile[scheme].useDefault &&\n                  profile[scheme].protocol !== 'direct' &&\n                  isNullOrWhiteSpaces(profile[scheme].server)\n                }\n                size=\"small\"\n              />\n            </Grid>\n            <Grid item xs={1}>\n              <TextField\n                className={cx(classes.numericField)}\n                value={getDisplayValue(scheme, 'port')}\n                onChange={e => {\n                  changeValue(scheme, 'port', e.target.value);\n                }}\n                fullWidth\n                variant=\"outlined\"\n                InputProps={{\n                  inputComponent: NumberFormatCustom,\n                }}\n                disabled={isTextFieldDisabled(scheme)}\n                size=\"small\"\n              />\n            </Grid>\n            <Grid item xs={2}>\n              <TextField\n                value={getDisplayValue(scheme, 'user')}\n                fullWidth\n                variant=\"outlined\"\n                className={cx(classes.titleField)}\n                onChange={e => {\n                  changeValue(scheme, 'user', e.target.value);\n                }}\n                disabled={isTextFieldDisabled(scheme)}\n                size=\"small\"\n              />\n            </Grid>\n            <Grid item xs={3}>\n              <TextField\n                type={visiblePasswords[scheme] ? 'text' : 'password'}\n                value={getDisplayValue(scheme, 'password')}\n                fullWidth\n                variant=\"outlined\"\n                className={cx(classes.titleField)}\n                onChange={e => {\n                  changeValue(scheme, 'password', e.target.value);\n                }}\n                disabled={isTextFieldDisabled(scheme)}\n                InputProps={{\n                  endAdornment: canSeePasswordToggle(scheme) && (\n                    <InputAdornment position=\"end\">\n                      <IconButton\n                        aria-label=\"toggle password visibility\"\n                        onClick={() => handleShowPassword(scheme)}\n                      >\n                        {visiblePasswords[scheme] ? (\n                          <VisibilityIcon />\n                        ) : (\n                          <VisibilityOffIcon />\n                        )}\n                      </IconButton>\n                    </InputAdornment>\n                  ),\n                }}\n                size=\"small\"\n              />\n            </Grid>\n          </Grid>\n        ))}\n      </Grid>\n      <h3 className={classes.removeMarginBottom}>Bypass List</h3>\n      <small>\n        Servers for which you do not want to use any proxy: (One server on each\n        line.){' '}\n        <Link\n          className={classes.wilcardsAndMoreLink}\n          href=\"#\"\n          onClick={onMoreInfo}\n        >\n          Wilcards and more available <OpenInNewIcon fontSize=\"inherit\" />\n        </Link>\n      </small>\n      <div className={cx(classes.bypassListField)}>\n        <TextField\n          value={(profile.bypassList || []).join('\\n')}\n          rows={9}\n          rowsMax={9}\n          variant=\"outlined\"\n          onChange={changeBypassList}\n          multiline\n          fullWidth\n          className={classes.bypassListFieldTextArea}\n        />\n      </div>\n      <Button\n        variant=\"contained\"\n        color=\"primary\"\n        aria-label=\"clear network cache\"\n        component=\"span\"\n        onClick={onSaveClicked}\n        size=\"large\"\n        className={cx(classes.saveButton)}\n        disabled={schemes.some(\n          scheme =>\n            !profile[scheme].useDefault &&\n            profile[scheme].protocol !== 'direct' &&\n            isNullOrWhiteSpaces(profile[scheme].server)\n        )}\n      >\n        Save\n      </Button>\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NetworkProxy/index.js",
    "content": "import React, {useState} from 'react';\nimport cx from 'classnames';\nimport Button from '@material-ui/core/Button';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport AppBar from '@material-ui/core/AppBar';\nimport Toolbar from '@material-ui/core/Toolbar';\nimport Typography from '@material-ui/core/Typography';\nimport ProxyManager from './ProxyManager';\nimport useCommonStyles from '../useCommonStyles';\nimport FormControlLabel from '@material-ui/core/FormControlLabel';\nimport Checkbox from '@material-ui/core/Checkbox';\nimport {makeStyles} from '@material-ui/core/styles';\nimport ProxyIcon from '../icons/Proxy';\n\nconst useStyles = makeStyles(theme => ({\n  appBar: {\n    position: 'relative',\n  },\n  title: {\n    flex: 1,\n  },\n  preferenceName: {\n    fontSize: '14px',\n  },\n  networkProxyTitle: {\n    marginBottom: 0,\n  },\n  sidebarContentSectionContainer: {\n    marginTop: 0,\n  },\n}));\n\nexport default function NetworkProxy({\n  proxy,\n  onToggleUseProxy,\n  onProxyProfileChanged,\n}) {\n  const [open, setOpen] = useState(false);\n  const closeDialog = () => setOpen(false);\n  const classes = useStyles();\n  const commonClasses = useCommonStyles();\n  const active = !!proxy?.active;\n\n  const onSave = profile => {\n    onProxyProfileChanged(profile);\n    closeDialog();\n  };\n\n  return (\n    <div className={cx(commonClasses.sidebarContentSection)}>\n      <div\n        className={cx(\n          commonClasses.sidebarContentSectionTitleBar,\n          classes.networkProxyTitle\n        )}\n      >\n        <ProxyIcon\n          color=\"white\"\n          height={24}\n          width={24}\n          margin=\"0 5px 0 0\"\n          className={cx(classes.networkProxyIcon)}\n        />{' '}\n        Proxy\n      </div>\n      <div\n        className={cx(\n          commonClasses.sidebarContentSectionContainer,\n          classes.sidebarContentSectionContainer\n        )}\n      >\n        <div>\n          <FormControlLabel\n            control={\n              <Checkbox\n                checked={active}\n                onChange={() => onToggleUseProxy(!active)}\n                name=\"Use Proxy\"\n                color=\"primary\"\n              />\n            }\n            label={\n              <span className={cx(classes.preferenceName)}>Use Proxy</span>\n            }\n          />\n          <Button\n            variant=\"contained\"\n            color=\"primary\"\n            aria-label=\"clear network cache\"\n            component=\"span\"\n            onClick={() => setOpen(true)}\n          >\n            Configure\n          </Button>\n        </div>\n\n        <Dialog\n          className={cx(classes.profileManagerDialog)}\n          maxWidth=\"lg\"\n          fullWidth\n          open={open}\n          scroll=\"paper\"\n          onClose={closeDialog}\n        >\n          <AppBar className={classes.appBar} color=\"secondary\">\n            <Toolbar>\n              <Typography variant=\"h6\" className={classes.title}>\n                Configure Proxy Settings\n              </Typography>\n              <Button color=\"inherit\" onClick={closeDialog}>\n                close\n              </Button>\n            </Toolbar>\n          </AppBar>\n          <DialogContent>\n            <ProxyManager proxy={proxy} onSave={onSave} />\n          </DialogContent>\n        </Dialog>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NetworkThrottling/ProfileManager/index.js",
    "content": "import React, {useState} from 'react';\nimport cx from 'classnames';\nimport Button from '@material-ui/core/Button';\nimport Table from '@material-ui/core/Table';\nimport TableBody from '@material-ui/core/TableBody';\nimport TableCell from '@material-ui/core/TableCell';\nimport TableContainer from '@material-ui/core/TableContainer';\nimport TableHead from '@material-ui/core/TableHead';\nimport TableRow from '@material-ui/core/TableRow';\nimport TableFooter from '@material-ui/core/TableFooter';\nimport CancelOutlinedIcon from '@material-ui/icons/CancelOutlined';\nimport AddCircleOutlineOutlinedIcon from '@material-ui/icons/AddCircleOutlineOutlined';\nimport TextField from '@material-ui/core/TextField';\nimport NumberFormat from 'react-number-format';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport styles from './styles.css';\nimport {makeStyles} from '@material-ui/core/styles';\n\nfunction NumberFormatCustom(props) {\n  const {inputRef, onChange, ...other} = props;\n\n  return (\n    <NumberFormat\n      {...other}\n      getInputRef={inputRef}\n      onValueChange={values => {\n        onChange({\n          target: {\n            name: props.name,\n            value: values.floatValue,\n          },\n        });\n      }}\n      allowNegative={false}\n      decimalScale={0}\n    />\n  );\n}\n\nexport default function ProfileManager({profiles, onSave}) {\n  const [currentProfiles, updateProfiles] = useState(profiles);\n  const [newElement, setNewElement] = useState({type: 'Custom'});\n  const [editModeRows, toggleEditModeRows] = useState({});\n  const themedClasses = useThemedStyles();\n\n  const placeholderIfOnline = (row, component) => {\n    if (row.type !== 'Online') return component;\n    return (\n      <TextField\n        size=\"small\"\n        className={cx(styles.numericField, themedClasses.numericFieldDisabled)}\n        value=\"-\"\n        fullWidth\n        variant=\"outlined\"\n        disabled\n      />\n    );\n  };\n\n  const newElementIsInvalid =\n    newElement.title != null &&\n    (newElement.title.trim() === '' ||\n      newElement.title.length > 20 ||\n      currentProfiles.filter(x => x.title === newElement.title).length !== 0);\n\n  const addNewElement = () => {\n    if (!newElementIsInvalid && newElement.title != null) {\n      setNewElement({type: 'Custom'});\n      updateProfiles([...currentProfiles, newElement]);\n    }\n  };\n\n  const removeProfile = title => {\n    updateProfiles(currentProfiles.filter(p => p.title !== title));\n  };\n\n  const updateProfile = (title, key, value) => {\n    const profile = currentProfiles.find(x => x.title === title);\n    if (profile != null && profile.type === 'Custom') {\n      profile[key] = value;\n      updateProfiles([...currentProfiles]);\n    }\n  };\n\n  const toggleEditMode = row => {\n    if (row.type !== 'Custom') return;\n    editModeRows[row.title] = !editModeRows[row.title];\n    toggleEditModeRows({...editModeRows});\n  };\n\n  return (\n    <div className={cx(styles.profileManagerContainer)}>\n      <TableContainer className={cx(styles.profilesContainer)}>\n        <Table size=\"small\">\n          <TableHead className={cx(styles.profilesHeader)}>\n            <TableRow>\n              <TableCell style={{width: '32%'}}>Name</TableCell>\n              <TableCell style={{width: '21%'}} align=\"right\">\n                Download\n              </TableCell>\n              <TableCell style={{width: '21%'}} align=\"right\">\n                Upload\n              </TableCell>\n              <TableCell style={{width: '21%'}} align=\"right\">\n                Latency\n              </TableCell>\n              <TableCell style={{width: '5%'}} align=\"right\" />\n            </TableRow>\n          </TableHead>\n          <TableBody>\n            {currentProfiles.map(row => (\n              <TableRow key={row.title} className={cx(styles.profilesRow)}>\n                <TableCell\n                  component=\"th\"\n                  scope=\"row\"\n                  className={cx({\n                    [styles.customProfile]: row.type === 'Custom',\n                  })}\n                  onClick={() => toggleEditMode(row)}\n                >\n                  {row.title}\n                </TableCell>\n                <TableCell align=\"right\">\n                  {placeholderIfOnline(\n                    row,\n                    <TextField\n                      size=\"small\"\n                      className={cx(styles.numericField, {\n                        [themedClasses.numericFieldDisabled]:\n                          row.type !== 'Custom' || !editModeRows[row.title],\n                      })}\n                      value={row.downloadKps == null ? '' : row.downloadKps}\n                      onChange={e =>\n                        updateProfile(row.title, 'downloadKps', e.target.value)\n                      }\n                      fullWidth\n                      variant=\"outlined\"\n                      placeholder={\n                        row.type !== 'Custom' || !editModeRows[row.title]\n                          ? ''\n                          : '(optional)'\n                      }\n                      InputProps={{\n                        inputComponent: NumberFormatCustom,\n                        endAdornment: (\n                          <InputAdornment position=\"end\">Kb/s</InputAdornment>\n                        ),\n                      }}\n                      disabled={\n                        row.type !== 'Custom' || !editModeRows[row.title]\n                      }\n                    />\n                  )}\n                </TableCell>\n                <TableCell align=\"right\">\n                  {placeholderIfOnline(\n                    row,\n                    <TextField\n                      size=\"small\"\n                      className={cx(styles.numericField, {\n                        [themedClasses.numericFieldDisabled]:\n                          row.type !== 'Custom' || !editModeRows[row.title],\n                      })}\n                      value={row.uploadKps == null ? '' : row.uploadKps}\n                      onChange={e =>\n                        updateProfile(row.title, 'uploadKps', e.target.value)\n                      }\n                      fullWidth\n                      variant=\"outlined\"\n                      placeholder={\n                        row.type !== 'Custom' || !editModeRows[row.title]\n                          ? ''\n                          : '(optional)'\n                      }\n                      InputProps={{\n                        inputComponent: NumberFormatCustom,\n                        endAdornment: (\n                          <InputAdornment position=\"end\">Kb/s</InputAdornment>\n                        ),\n                      }}\n                      disabled={\n                        row.type !== 'Custom' || !editModeRows[row.title]\n                      }\n                    />\n                  )}\n                </TableCell>\n                <TableCell align=\"right\">\n                  {placeholderIfOnline(\n                    row,\n                    <TextField\n                      size=\"small\"\n                      className={cx(styles.numericField, {\n                        [themedClasses.numericFieldDisabled]:\n                          row.type !== 'Custom' || !editModeRows[row.title],\n                      })}\n                      value={row.latencyMs == null ? '' : row.latencyMs}\n                      onChange={e =>\n                        updateProfile(row.title, 'latencyMs', e.target.value)\n                      }\n                      fullWidth\n                      variant=\"outlined\"\n                      placeholder={\n                        row.type !== 'Custom' || !editModeRows[row.title]\n                          ? ''\n                          : '(optional)'\n                      }\n                      InputProps={{\n                        inputComponent: NumberFormatCustom,\n                        endAdornment: (\n                          <InputAdornment position=\"end\">ms</InputAdornment>\n                        ),\n                      }}\n                      disabled={\n                        row.type !== 'Custom' || !editModeRows[row.title]\n                      }\n                    />\n                  )}\n                </TableCell>\n                <TableCell align=\"right\">\n                  {row.type === 'Custom' && (\n                    <CancelOutlinedIcon\n                      className={cx(themedClasses.actionIcon)}\n                      onClick={() => removeProfile(row.title)}\n                    />\n                  )}\n                </TableCell>\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n      </TableContainer>\n      <TableFooter component={Table}>\n        <TableHead className={cx(styles.profilesHeader)}>\n          <TableRow>\n            <TableCell style={{width: '32%'}}>\n              <TextField\n                size=\"small\"\n                autoFocus\n                value={newElement.title || ''}\n                onChange={e =>\n                  setNewElement({...newElement, title: e.target.value})\n                }\n                error={newElementIsInvalid}\n                fullWidth\n                variant=\"outlined\"\n                placeholder=\"New Profile Name\"\n                className={cx(styles.titleField)}\n              />\n            </TableCell>\n            <TableCell style={{width: '21%'}} align=\"right\">\n              <TextField\n                size=\"small\"\n                className={cx(styles.numericField)}\n                value={\n                  newElement.downloadKps == null ? '' : newElement.downloadKps\n                }\n                onChange={e =>\n                  setNewElement({...newElement, downloadKps: e.target.value})\n                }\n                fullWidth\n                variant=\"outlined\"\n                placeholder=\"(optional)\"\n                InputProps={{\n                  inputComponent: NumberFormatCustom,\n                  endAdornment: (\n                    <InputAdornment position=\"end\">Kb/s</InputAdornment>\n                  ),\n                }}\n              />\n            </TableCell>\n            <TableCell style={{width: '21%'}} align=\"right\">\n              <TextField\n                size=\"small\"\n                className={cx(styles.numericField)}\n                value={newElement.uploadKps == null ? '' : newElement.uploadKps}\n                onChange={e =>\n                  setNewElement({...newElement, uploadKps: e.target.value})\n                }\n                fullWidth\n                variant=\"outlined\"\n                placeholder=\"(optional)\"\n                InputProps={{\n                  inputComponent: NumberFormatCustom,\n                  endAdornment: (\n                    <InputAdornment position=\"end\">Kb/s</InputAdornment>\n                  ),\n                }}\n              />\n            </TableCell>\n            <TableCell style={{width: '21%'}} align=\"right\">\n              <TextField\n                size=\"small\"\n                className={cx(styles.numericField)}\n                value={newElement.latencyMs == null ? '' : newElement.latencyMs}\n                onChange={e =>\n                  setNewElement({...newElement, latencyMs: e.target.value})\n                }\n                fullWidth\n                variant=\"outlined\"\n                placeholder=\"(optional)\"\n                InputProps={{\n                  inputComponent: NumberFormatCustom,\n                  endAdornment: (\n                    <InputAdornment position=\"end\">ms</InputAdornment>\n                  ),\n                }}\n              />\n            </TableCell>\n            <TableCell style={{width: '5%'}} align=\"right\">\n              <AddCircleOutlineOutlinedIcon\n                className={cx(themedClasses.actionIcon)}\n                onClick={addNewElement}\n              />\n            </TableCell>\n          </TableRow>\n        </TableHead>\n      </TableFooter>\n      <Button\n        variant=\"contained\"\n        color=\"primary\"\n        aria-label=\"clear network cache\"\n        component=\"span\"\n        onClick={() => onSave(currentProfiles)}\n        size=\"large\"\n        className={cx(styles.saveButton)}\n      >\n        Save\n      </Button>\n    </div>\n  );\n}\n\nconst useThemedStyles = makeStyles(theme => ({\n  actionIcon: {\n    color: theme.palette.mode({\n      light: theme.palette.grey[500],\n      dark: 'white',\n    }),\n    pointerEvents: 'all',\n    cursor: 'pointer',\n    verticalAlign: 'middle',\n    '&:hover': {\n      color: '#6075ef',\n    },\n  },\n  numericFieldDisabled: {\n    '& input': {\n      color: theme.palette.mode({\n        light: '#000000de',\n        dark: 'white',\n      }),\n    },\n    '& fieldset': {\n      border: 0,\n    },\n  },\n}));\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NetworkThrottling/ProfileManager/styles.css",
    "content": ".profileManagerContainer {\n  height: 600px;\n  padding: 20px;\n}\n\n.profilesContainer {\n  max-height: 430px;\n  overflow-y: auto;\n  flex-grow: 1;\n}\n\n.profilesRow > th,\ntd {\n  padding-right: 16px !important;\n}\n\n.profilesHeader > tr > th {\n  font-size: 20px;\n  font-weight: bold;\n}\n\n.profilesRow > th.customProfile {\n  cursor: pointer;\n}\n\n.saveButton {\n  position: absolute !important;\n  bottom: 25px;\n  right: 25px;\n}\n\n.numericField * {\n  text-align: right;\n  font-size: 14px !important;\n}\n\n.titleField * {\n  font-size: 14px !important;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NetworkThrottling/index.js",
    "content": "import React, {useState} from 'react';\nimport cx from 'classnames';\nimport Button from '@material-ui/core/Button';\nimport NetworkCheckIcon from '@material-ui/icons/NetworkCheck';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport AppBar from '@material-ui/core/AppBar';\nimport Toolbar from '@material-ui/core/Toolbar';\nimport Typography from '@material-ui/core/Typography';\nimport {makeStyles} from '@material-ui/core/styles';\nimport ProfileManager from './ProfileManager';\nimport Select from '../Select';\nimport useCommonStyles from '../useCommonStyles';\nimport styles from './styles.css';\n\nconst useStyles = makeStyles(theme => ({\n  appBar: {\n    position: 'relative',\n  },\n  title: {\n    marginLeft: theme.spacing(2),\n    flex: 1,\n  },\n}));\n\nfunction NetworkThrottling({\n  throttling,\n  onActiveThrottlingProfileChanged,\n  onThrottlingProfilesListChanged,\n}) {\n  const [open, setOpen] = useState(false);\n  const closeDialog = () => setOpen(false);\n  const classes = useStyles();\n  const commonClasses = useCommonStyles();\n\n  const selectedIdx = throttling.findIndex(p => p.active);\n  const options = throttling.map(p => ({\n    value: p.title,\n    label: p.title,\n  }));\n  const selectedOption = options[selectedIdx];\n\n  const onThrottlingProfileChanged = val => {\n    if (val.value !== selectedOption.value)\n      onActiveThrottlingProfileChanged(val.value);\n  };\n\n  const saveThrottlingProfiles = profiles => {\n    onThrottlingProfilesListChanged(profiles);\n    closeDialog();\n  };\n\n  return (\n    <div className={commonClasses.sidebarContentSection}>\n      <div className={commonClasses.sidebarContentSectionTitleBar}>\n        <NetworkCheckIcon className={cx(styles.networkThrottlingIcon)} />{' '}\n        Network Throttling\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div className={cx(styles.throttlingProfileSelectorContainer)}>\n          <Select\n            options={options}\n            value={selectedOption}\n            onChange={onThrottlingProfileChanged}\n          />\n        </div>\n\n        <Button\n          variant=\"contained\"\n          color=\"primary\"\n          aria-label=\"clear network cache\"\n          component=\"span\"\n          onClick={() => setOpen(true)}\n        >\n          Manage Profiles\n        </Button>\n        <Dialog\n          className={cx(styles.profileManagerDialog)}\n          maxWidth=\"md\"\n          fullWidth\n          open={open}\n          scroll=\"paper\"\n          onClose={closeDialog}\n        >\n          <AppBar className={classes.appBar} color=\"secondary\">\n            <Toolbar>\n              <Typography variant=\"h6\" className={classes.title}>\n                Manage Throttling Profiles\n              </Typography>\n              <Button color=\"inherit\" onClick={closeDialog}>\n                close\n              </Button>\n            </Toolbar>\n          </AppBar>\n          <DialogContent>\n            <ProfileManager\n              profiles={[...throttling]}\n              onSave={saveThrottlingProfiles}\n            />\n          </DialogContent>\n        </Dialog>\n      </div>\n    </div>\n  );\n}\n\nexport default NetworkThrottling;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NetworkThrottling/styles.css",
    "content": ".networkThrottlingIcon {\n  margin-right: 5px;\n}\n.throttlingProfileSelectorContainer {\n  margin-bottom: 20px;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/NotificationMessage/index.js",
    "content": "import React from 'react';\nimport Spinner from '../Spinner';\nimport Tick from '../icons/TickAnimation';\n\nexport default function NotificationMessage(props) {\n  return (\n    <div style={{display: 'flex'}}>\n      {props.spinner && (\n        <div style={{marginRight: 5}}>\n          <Spinner />\n        </div>\n      )}\n      {props.tick && (\n        <div style={{marginRight: 5}}>\n          <Tick />\n        </div>\n      )}\n      {props.message}\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/PageNavigator/index.js",
    "content": "import React, {useEffect, useState, useRef} from 'react';\nimport cx from 'classnames';\nimport {motion} from 'framer-motion';\nimport DownIcon from '@material-ui/icons/KeyboardArrowDown';\nimport UpIcon from '@material-ui/icons/KeyboardArrowUp';\nimport CloseIcon from '@material-ui/icons/Close';\nimport ReplayIcon from '@material-ui/icons/Replay';\nimport {Tooltip} from '@material-ui/core';\nimport {ipcRenderer} from 'electron';\nimport {debounce} from 'lodash';\nimport {JSDOM} from 'jsdom';\nimport {makeStyles, useTheme} from '@material-ui/core/styles';\n\nconst {document} = new JSDOM('').window;\nconst queryCheck = s => document.createDocumentFragment().querySelector(s);\nconst INPUT_SOURCES = {TEXT_BOX: 'TEXT_BOX', OPTION_CLICK: 'OPTION_CLICK'};\n\nconst isSelectorValid = selector => {\n  try {\n    queryCheck(selector);\n  } catch {\n    return false;\n  }\n  return true;\n};\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    height: 45,\n    position: 'absolute',\n    top: 50,\n    right: 25,\n    display: 'flex',\n    backgroundColor: theme.palette.background.l2,\n    borderRadius: 3,\n    wordWrap: 'break-word',\n    color: theme.palette.mode({light: '#000', dark: '#fff'}),\n    boxShadow: `0 ${theme.palette.mode({\n      light: '0px',\n      dark: '3px',\n    })} 5px rgba(0, 0, 0, 0.35)`,\n    padding: 5,\n    zIndex: theme.zIndex.tooltip,\n  },\n  textbox: {\n    border: 'none',\n    outline: 'none',\n    borderColor: 'transparent',\n    width: 250,\n    height: '100%',\n    background: 'none',\n    color: theme.palette.mode({light: '#000', dark: '#fff'}),\n    fontFamily: \"'Consolas', 'Courier New', monospace\",\n    whiteSpace: 'pre',\n  },\n  iconsContainer: {\n    display: 'flex',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n  },\n  separator: {\n    borderLeft: '1px solid #636363',\n    margin: 3,\n  },\n  icon: {\n    cursor: 'pointer',\n    width: 30,\n    padding: 5,\n    color: theme.palette.mode({light: '#636363', dark: '#fffc'}),\n    '&:hover': {\n      '& svg': {\n        backgroundColor: theme.palette.primary.light,\n        color: '#fff',\n      },\n    },\n  },\n  btn: {\n    margin: 1,\n    cursor: 'pointer',\n    color: theme.palette.mode({light: '#636363', dark: '#fffc'}),\n    '&:hover': {\n      '& div': {\n        backgroundColor: theme.palette.primary.light,\n        color: '#fff',\n      },\n    },\n\n    '& div': {\n      padding: 5,\n      fontFamily: \"'Consolas', 'Courier New', monospace\",\n      fontSize: 12,\n    },\n  },\n  btnActive: {\n    borderRadius: 2,\n    '& div': {\n      backgroundColor: theme.palette.primary.light,\n      color: '#fff',\n    },\n  },\n}));\n\nconst PageNavigator = props => {\n  const [selector, setSelector] = useState(props.selector || '');\n  const [inputSource, setInputSource] = useState(null);\n  const inputRef = React.createRef();\n  const styles = useStyles();\n\n  useEffect(() => {\n    if (props.active === true) {\n      document.addEventListener('keydown', _handleKeyDown);\n      inputRef.current.focus();\n    } else if (props.active === false) {\n      document.removeEventListener('keydown', _handleKeyDown);\n    }\n    return () => document.removeEventListener('keydown', _handleKeyDown);\n  }, [props.active, inputRef.current]);\n\n  useEffect(() => {\n    if (inputSource === INPUT_SOURCES.OPTION_CLICK) {\n      _navigateNext();\n    }\n  }, [selector, inputSource]);\n\n  const _handleChange = e => {\n    setSelector(e.target.value);\n    if (inputSource !== INPUT_SOURCES.TEXT_BOX) {\n      setInputSource(INPUT_SOURCES.TEXT_BOX);\n    }\n  };\n\n  const handleOptionClick = val => {\n    setSelector(val);\n    setInputSource(INPUT_SOURCES.OPTION_CLICK);\n  };\n\n  const _navigateNext = debounce(() => {\n    if (!isSelectorValid(selector)) props.resetPageNavigator();\n    else props.navigateToNextSelector(selector);\n  }, 25);\n\n  const _navigatePrev = debounce(() => {\n    if (!isSelectorValid(selector)) props.resetPageNavigator();\n    else props.navigateToPrevSelector(selector);\n  }, 25);\n\n  const resetPageNavigator = () => {\n    setSelector('');\n    props.resetPageNavigator();\n  };\n\n  const _closePageNavigator = () => {\n    props.onChangePageNavigatorActive(false);\n  };\n\n  const _handleInputKeyDown = e => {\n    if (e.shiftKey && e.key === 'Enter') {\n      _navigatePrev();\n    } else if (e.key === 'Enter') {\n      _navigateNext();\n    } else if (e.key === 'Escape') {\n      _closePageNavigator();\n    }\n  };\n\n  const _handleKeyDown = e => {\n    if (e.key === 'Escape') {\n      _closePageNavigator();\n    }\n  };\n\n  if (!props.active) {\n    return null;\n  }\n\n  return (\n    <motion.div\n      className={styles.container}\n      initial={{y: props.active ? -70 : 0, scale: 1}}\n      animate={{y: props.active ? 0 : -70, scale: 1}}\n      transition={{\n        type: 'spring',\n        stiffness: 260,\n        damping: 20,\n        delay: 0,\n      }}\n    >\n      <div className={styles.iconsContainer}>\n        <span\n          className={cx(styles.btn, {\n            [styles.btnActive]: selector === 'section',\n          })}\n          onClick={() => handleOptionClick('section')}\n        >\n          <div>section</div>\n        </span>\n        <span\n          className={cx(styles.btn, {[styles.btnActive]: selector === 'h1'})}\n          onClick={() => {\n            handleOptionClick('h1');\n          }}\n        >\n          <div>h1</div>\n        </span>\n        <span\n          className={cx(styles.btn, {[styles.btnActive]: selector === 'h2'})}\n          onClick={() => handleOptionClick('h2')}\n        >\n          <div>h2</div>\n        </span>\n        <span\n          className={cx(styles.btn, {[styles.btnActive]: selector === 'h3'})}\n          onClick={() => handleOptionClick('h3')}\n        >\n          <div>h3</div>\n        </span>\n      </div>\n      <div className={styles.separator} />\n      <div className={styles.textboxContainer}>\n        <input\n          ref={inputRef}\n          className={styles.textbox}\n          type=\"text\"\n          value={selector || ''}\n          onChange={_handleChange}\n          onKeyDown={_handleInputKeyDown}\n          placeholder=\"css selector\"\n        />\n      </div>\n      <div className={styles.separator} />\n      <div className={styles.iconsContainer}>\n        <span className={styles.icon} onClick={_navigatePrev}>\n          <UpIcon />\n        </span>\n        <span className={styles.icon} onClick={_navigateNext}>\n          <DownIcon />\n        </span>\n        <span className={styles.icon} onClick={resetPageNavigator}>\n          <ReplayIcon style={{height: 25, width: 25, padding: 3}} />\n        </span>\n        <span className={styles.icon} onClick={_closePageNavigator}>\n          <CloseIcon style={{height: 25, width: 25, padding: 3}} />\n        </span>\n      </div>\n    </motion.div>\n  );\n};\n\nexport default PageNavigator;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/PermissionPopup/index.js",
    "content": "import React, {useState, useEffect} from 'react';\nimport {ipcRenderer} from 'electron';\nimport Button from '@material-ui/core/Button';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport IconButton from '@material-ui/core/IconButton';\nimport CloseIcon from '@material-ui/icons/Close';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport {makeStyles} from '@material-ui/core/styles';\nimport styles from './styles.module.css';\nimport cx from 'classnames';\nimport pubsub from 'pubsub.js';\nimport {\n  HIDE_PERMISSION_POPUP_DUE_TO_RELOAD,\n  PERMISSION_MANAGEMENT_PREFERENCE_CHANGED,\n} from '../../constants/pubsubEvents';\nimport {\n  getPermissionPageTitle,\n  getPermissionRequestMessage,\n} from '../../utils/permissionUtils.js';\nimport {PERMISSION_MANAGEMENT_OPTIONS} from '../../constants/permissionsManagement';\n\nconst useStyles = makeStyles(theme => ({\n  closeButton: {\n    position: 'absolute',\n    right: theme.spacing(1),\n    top: theme.spacing(1),\n    color: theme.palette.grey[500],\n  },\n  header: {\n    display: 'flex',\n    justifyContent: 'space-between',\n    margin: '10px',\n    fontWeight: '500',\n  },\n  themeBackground: {\n    backgroundColor: theme.palette.mode({\n      light: theme.palette.grey[100],\n      dark: '#252526',\n    }),\n    color: theme.palette.mode({\n      light: 'black',\n      dark: 'white',\n    }),\n  },\n}));\n\nconst tooltipUseStyles = makeStyles(theme => ({\n  tooltip: {\n    backgroundColor: theme.palette.mode({\n      light: `${theme.palette.grey[200]} !important`,\n      dark: `${theme.palette.grey.A700} !important`,\n    }),\n    color: theme.palette.mode({\n      light: 'black !important',\n      dark: 'white !important',\n    }),\n  },\n}));\n\nfunction getMessage(info) {\n  if (info == null) return '';\n  return (\n    <>\n      <strong>{getPermissionPageTitle(info.url)}</strong>&nbsp;\n      {getPermissionRequestMessage(info.permission, info.details)}\n    </>\n  );\n}\n\nexport default function PermissionPopup() {\n  const classes = useStyles();\n  const [permissionInfos, setPermissionInfos] = useState([]);\n\n  useEffect(() => {\n    const promptHandler = (event, args) => {\n      setPermissionInfos(prev => [...prev, args]);\n    };\n\n    const reloadHandler = () => {\n      setPermissionInfos([]);\n    };\n\n    const subscription = pubsub.subscribe(\n      HIDE_PERMISSION_POPUP_DUE_TO_RELOAD,\n      reloadHandler\n    );\n    ipcRenderer.on('permission-prompt', promptHandler);\n    return () => {\n      ipcRenderer.removeListener('permission-prompt', promptHandler);\n      pubsub.unsubscribe(subscription);\n    };\n  }, []);\n\n  useEffect(() => {\n    const preferenceChangedHandler = newPreference => {\n      if (newPreference === PERMISSION_MANAGEMENT_OPTIONS.ASK_ALWAYS) return;\n\n      if (newPreference === PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS) {\n        permissionInfos.forEach(info => {\n          ipcRenderer.send('permission-response', {...info, allowed: true});\n        });\n        setPermissionInfos([]);\n      } else if (newPreference === PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS) {\n        permissionInfos.forEach(info => {\n          ipcRenderer.send('permission-response', {...info, allowed: false});\n        });\n        setPermissionInfos([]);\n      }\n    };\n\n    const subscription = pubsub.subscribe(\n      PERMISSION_MANAGEMENT_PREFERENCE_CHANGED,\n      preferenceChangedHandler\n    );\n    return () => {\n      pubsub.unsubscribe(subscription);\n    };\n  }, [permissionInfos]);\n\n  function handleClose(allowed) {\n    ipcRenderer.send('permission-response', {...permissionInfos[0], allowed});\n    setPermissionInfos(permissionInfos.slice(1));\n  }\n\n  return (\n    <div\n      className={cx(styles.permissionPopup, classes.themeBackground, {\n        [styles.permissionPopupActive]: permissionInfos.length !== 0,\n      })}\n    >\n      <div className={cx(classes.header)}>\n        <div>Permission Request</div>\n        <Tooltip classes={tooltipUseStyles()} title=\"Ignore\" placement=\"left\">\n          <IconButton\n            aria-label=\"close\"\n            onClick={() => handleClose(null)}\n            size=\"small\"\n          >\n            <CloseIcon />\n          </IconButton>\n        </Tooltip>\n      </div>\n      <DialogContent className={styles.permissionPopupMsgContainer}>\n        <DialogContentText className={styles.permissionPopupMsg}>\n          {getMessage(permissionInfos[0])}\n        </DialogContentText>\n      </DialogContent>\n      <DialogActions>\n        <Button\n          variant=\"contained\"\n          type=\"submit\"\n          onClick={() => handleClose(false)}\n          size=\"small\"\n        >\n          Deny\n        </Button>\n        <Button\n          variant=\"contained\"\n          color=\"primary\"\n          type=\"submit\"\n          onClick={() => handleClose(true)}\n          size=\"small\"\n        >\n          Allow\n        </Button>\n      </DialogActions>\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/PermissionPopup/styles.module.css",
    "content": ".permissionPopup {\n  position: absolute;\n  border-radius: 4px;\n  box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.2),\n    0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12);\n  width: 20em;\n  transition: opacity 0.5s ease-out;\n  opacity: 0;\n  height: 0;\n  overflow: hidden;\n}\n\n.permissionPopupActive {\n  opacity: 1;\n  height: auto;\n}\n\n.permissionPopupMsg {\n  margin-bottom: 0 !important;\n  font-size: 0.9rem !important;\n}\n\n.permissionPopupMsgContainer {\n  padding: 0 14px !important;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/PrefersColorSchemeSwitch/index.js",
    "content": "import React, {useState, useEffect} from 'react';\nimport {ipcRenderer} from 'electron';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport LightColorSchemeIcon from '../icons/LightColorScheme';\nimport DarkColorSchemeIcon from '../icons/DarkColorScheme';\n\nexport default function PrefersColorSchemeSwitch({iconProps}) {\n  const [colorScheme, setColorScheme] = useState(\n    window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n  );\n\n  const handleSwitch = () => {\n    setColorScheme(colorScheme === 'dark' ? 'light' : 'dark');\n  };\n\n  useEffect(() => {\n    ipcRenderer.send('prefers-color-scheme-select', colorScheme);\n  }, [colorScheme]);\n\n  return (\n    <Tooltip title=\"Switch color scheme\">\n      <div onClick={handleSwitch}>\n        {colorScheme === 'dark' ? (\n          <DarkColorSchemeIcon {...iconProps} />\n        ) : (\n          <LightColorSchemeIcon {...iconProps} />\n        )}\n      </div>\n    </Tooltip>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/PreviewerLayoutSelector/index.js",
    "content": "import React, {useMemo} from 'react';\nimport {useTheme} from '@material-ui/core/styles';\nimport LayoutIcon from '../icons/Layout';\nimport {\n  HORIZONTAL_LAYOUT,\n  FLEXIGRID_LAYOUT,\n  INDIVIDUAL_LAYOUT,\n} from '../../constants/previewerLayouts';\nimport Select from '../Select';\nimport useCommonStyles from '../useCommonStyles';\n\nfunction PreviewerLayoutSelector(props) {\n  const theme = useTheme();\n  const commonClasses = useCommonStyles();\n  const selectedOption = useMemo(\n    () => options.find(option => option.value === props.value),\n    [props.value]\n  );\n\n  return (\n    <div className={commonClasses.sidebarContentSection}>\n      <div className={commonClasses.sidebarContentSectionTitleBar}>\n        <LayoutIcon height={18} margin={6} color={theme.palette.text.primary} />\n        Layout\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <Select\n          options={options}\n          value={selectedOption}\n          onChange={props.onChange}\n        />\n      </div>\n    </div>\n  );\n}\n\nconst options = [\n  {value: HORIZONTAL_LAYOUT, label: 'Horizontal'},\n  {value: FLEXIGRID_LAYOUT, label: 'FlexiGrid'},\n  {value: INDIVIDUAL_LAYOUT, label: 'Individual'},\n];\n\nexport default PreviewerLayoutSelector;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/QuickFilterDevices/index.js",
    "content": "import React from 'react';\nimport cx from 'classnames';\nimport AndroidIcon from '@material-ui/icons/Android';\nimport DesktopIcon from '@material-ui/icons/DesktopWindows';\nimport MobileIcon from '@material-ui/icons/Smartphone';\nimport TabletIcon from '@material-ui/icons/TabletMac';\nimport {useTheme, makeStyles} from '@material-ui/core/styles';\nimport WindowsIcon from '../icons/Windows';\nimport AppleIcon from '../icons/Apple';\nimport FilterIcon from '../icons/Filter';\nimport useCommonStyles from '../useCommonStyles';\nimport {OS, DEVICE_TYPE} from '../../constants/devices';\nimport {FILTER_FIELDS} from '../../reducers/browser';\n\nfunction QuickFilterDevices(props) {\n  const theme = useTheme();\n  const classes = useStyles();\n  const commonClasses = useCommonStyles();\n\n  return (\n    <div className={commonClasses.sidebarContentSection}>\n      <div className={commonClasses.sidebarContentSectionTitleBar}>\n        <FilterIcon\n          className={classes.filterIcon}\n          width=\"20\"\n          height=\"20\"\n          color={theme.palette.text.primary}\n        />\n        Quick Filters\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div className={classes.filterSection}>\n          <div className={classes.sectionTitle}>Operating System</div>\n          <div className={classes.optionIconsContainer}>\n            <div\n              className={cx(classes.optionIcon, commonClasses.icon, {\n                [commonClasses.iconSelected]:\n                  props.browser.filters[FILTER_FIELDS.OS].indexOf(OS.ios) !==\n                  -1,\n              })}\n              onClick={() => props.toggleFilter(FILTER_FIELDS.OS, OS.ios)}\n            >\n              <AppleIcon color=\"currentColor\" height={40} />\n            </div>\n            <div\n              className={cx(classes.optionIcon, commonClasses.icon, {\n                [commonClasses.iconSelected]:\n                  props.browser.filters[FILTER_FIELDS.OS].indexOf(\n                    OS.android\n                  ) !== -1,\n              })}\n              onClick={() => props.toggleFilter(FILTER_FIELDS.OS, OS.android)}\n            >\n              <AndroidIcon style={{fontSize: 40}} />\n            </div>\n            <div\n              className={cx(classes.optionIcon, commonClasses.icon, {\n                [commonClasses.iconSelected]:\n                  props.browser.filters[FILTER_FIELDS.OS].indexOf(\n                    OS.windowsPhone\n                  ) !== -1,\n              })}\n              onClick={() =>\n                props.toggleFilter(FILTER_FIELDS.OS, OS.windowsPhone)\n              }\n            >\n              <WindowsIcon color=\"currentColor\" height={34} padding={3} />\n            </div>\n            <div\n              className={cx(classes.optionIcon, commonClasses.icon, {\n                [commonClasses.iconSelected]:\n                  props.browser.filters[FILTER_FIELDS.OS].indexOf(OS.pc) !== -1,\n              })}\n              onClick={() => props.toggleFilter(FILTER_FIELDS.OS, OS.pc)}\n            >\n              <DesktopIcon style={{fontSize: 40}} />\n            </div>\n          </div>\n        </div>\n        <div className={classes.filterSection}>\n          <div className={classes.sectionTitle}>Device</div>\n          <div className={classes.optionIconsContainer}>\n            <div\n              className={cx(classes.optionIcon, commonClasses.icon, {\n                [commonClasses.iconSelected]:\n                  props.browser.filters[FILTER_FIELDS.DEVICE_TYPE].indexOf(\n                    DEVICE_TYPE.phone\n                  ) !== -1,\n              })}\n              onClick={() =>\n                props.toggleFilter(FILTER_FIELDS.DEVICE_TYPE, DEVICE_TYPE.phone)\n              }\n            >\n              <MobileIcon style={{fontSize: 35}} />\n            </div>\n            <div\n              className={cx(classes.optionIcon, commonClasses.icon, {\n                [commonClasses.iconSelected]:\n                  props.browser.filters[FILTER_FIELDS.DEVICE_TYPE].indexOf(\n                    DEVICE_TYPE.tablet\n                  ) !== -1,\n              })}\n              onClick={() =>\n                props.toggleFilter(\n                  FILTER_FIELDS.DEVICE_TYPE,\n                  DEVICE_TYPE.tablet\n                )\n              }\n            >\n              <TabletIcon style={{fontSize: 35}} />\n            </div>\n            <div\n              className={cx(classes.optionIcon, commonClasses.icon, {\n                [commonClasses.iconSelected]:\n                  props.browser.filters[FILTER_FIELDS.DEVICE_TYPE].indexOf(\n                    DEVICE_TYPE.desktop\n                  ) !== -1,\n              })}\n              onClick={() =>\n                props.toggleFilter(\n                  FILTER_FIELDS.DEVICE_TYPE,\n                  DEVICE_TYPE.desktop\n                )\n              }\n            >\n              <DesktopIcon style={{fontSize: 40}} />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  filterSection: {\n    marginBottom: '10px',\n  },\n  filterIcon: {\n    marginRight: '5px',\n  },\n  sectionTitle: {\n    margin: '5px',\n  },\n  optionIconsContainer: {\n    display: 'flex',\n    alignItems: 'center',\n  },\n  optionIcon: {\n    display: 'flex',\n    width: '40px',\n    height: '40px',\n    marginRight: '5px',\n    justifyContent: 'center',\n    alignItems: 'center',\n    ...theme.palette.mode({\n      light: {},\n      dark: {},\n    }),\n    '& svg': {\n      padding: '5px',\n    },\n  },\n}));\n\nexport default QuickFilterDevices;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/Renderer/index.js",
    "content": "import React, {useState, useCallback} from 'react';\nimport cx from 'classnames';\nimport {makeStyles} from '@material-ui/core/styles';\nimport WebViewContainer from '../../containers/WebViewContainer';\nimport Spinner from '../Spinner';\nimport {CAPABILITIES} from '../../constants/devices';\nimport useCommonStyles from '../useCommonStyles';\nimport {getDeviceIcon} from '../../utils/iconUtils';\nimport KebabMenu from '../KebabMenu';\nimport {\n  FLIP_ORIENTATION_ALL_DEVICES,\n  SCREENSHOT_ALL_DEVICES,\n} from '../../constants/pubsubEvents';\nimport pubsub from 'pubsub.js';\n\nfunction Renderer(props) {\n  const {device, hidden, transmitNavigatorStatus} = props;\n  const [loading, setLoading] = useState(true);\n  const [isFlip, setFlip] = useState(false);\n  const classes = useStyles();\n  const commonClasses = useCommonStyles();\n  const [finalDimensions, setFinalDimensions] = useState({\n    width: device.width,\n    height: device.height,\n  });\n  const dimension = [finalDimensions.width, 'x', finalDimensions.height];\n\n  const sendFlipStatus = useCallback(\n    status => {\n      setFlip(status);\n    },\n    [isFlip]\n  );\n\n  const _muteDevice = () => {\n    props.onDeviceMutedChange(props.device.id, true);\n  };\n\n  const _unmuteDevice = () => {\n    props.onDeviceMutedChange(device.id, false);\n  };\n\n  return (\n    <div className={cx(classes.container, {[commonClasses.hidden]: hidden})}>\n      <div\n        className={cx(commonClasses.flexAlignVerticalMiddle, classes.header)}\n      >\n        <div\n          className={cx(\n            commonClasses.flexAlignVerticalMiddle,\n            classes.titleContainer\n          )}\n        >\n          {getDeviceIcon(device.type)}\n          <div className={classes.deviceInfo}>\n            <span className={classes.deviceTitle}>{device.name}</span>\n            <div className={classes.deviceSize}>\n              {isFlip ? dimension.reverse().join('') : dimension.join('')}\n            </div>\n            <div className={classes.loaderContainer}>\n              {loading && (\n                <div>\n                  <Spinner size={16} />\n                </div>\n              )}\n            </div>\n          </div>\n        </div>\n        <KebabMenu>\n          <KebabMenu.Item\n            onClick={() =>\n              pubsub.publish(SCREENSHOT_ALL_DEVICES, [{deviceId: device.id}])\n            }\n          >\n            Full Page Screenshot\n          </KebabMenu.Item>\n          <KebabMenu.Item\n            onClick={() =>\n              pubsub.publish(FLIP_ORIENTATION_ALL_DEVICES, [\n                {deviceId: device.id},\n              ])\n            }\n          >\n            Tilt Device\n          </KebabMenu.Item>\n          <KebabMenu.Item\n            onClick={props.device.isMuted ? _unmuteDevice : _muteDevice}\n          >\n            {props.device.isMuted ? 'Unmute Audio' : 'Mute Audio'}\n          </KebabMenu.Item>\n        </KebabMenu>\n      </div>\n      <div>\n        <WebViewContainer\n          device={device}\n          sendFlipStatus={sendFlipStatus}\n          transmitNavigatorStatus={transmitNavigatorStatus}\n          onLoadingStateChange={setLoading}\n          updateResponsiveDimensions={setFinalDimensions}\n          muteDevice={_muteDevice}\n          unmuteDevice={_unmuteDevice}\n        />\n      </div>\n    </div>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    margin: '10px',\n  },\n  header: {\n    justifyContent: 'space-between',\n  },\n  titleContainer: {\n    margin: '3px',\n  },\n  deviceInfo: {\n    display: 'flex',\n    alignItems: 'flex-end',\n  },\n  deviceTitle: {\n    fontSize: '16px',\n    fontWeight: 'normal',\n    margin: '0 6px -2px 4px',\n  },\n  deviceSize: {\n    fontSize: '9px',\n    display: 'flex',\n    height: '15px',\n    alignItems: 'flex-end',\n    color: theme.palette.mode({\n      light: theme.palette.grey[600],\n      dark: 'lightgrey',\n    }),\n    marginRight: '5px',\n  },\n  loaderContainer: {\n    width: '20px',\n  },\n}));\n\nexport default Renderer;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/Renderer/index.test.js",
    "content": "// @flow\nimport React from 'react';\nimport {shallow} from 'enzyme';\nimport {expect} from 'chai';\n\nimport Renderer from '.';\n\nconst testSrc = 'https://testUrl.com';\nconst testDevice1 = {\n  name: 'testDevice1',\n  width: 100,\n  height: 100,\n};\n\ndescribe('<Renderer />', () => {\n  it('Renders the header and the iframe', () => {\n    const wrapper = shallow(<Renderer src={testSrc} device={testDevice1} />);\n    expect(wrapper.find('iframe')).to.have.lengthOf(1);\n    expect(wrapper.find('h2')).to.have.lengthOf(1);\n  });\n\n  it('Renders the header with the device name', () => {\n    const wrapper = shallow(<Renderer src={testSrc} device={testDevice1} />);\n    expect(wrapper.find('h2').text()).to.equal(testDevice1.name);\n  });\n\n  it('Renders the iframe with the given device dimensions', () => {\n    const wrapper = shallow(<Renderer src={testSrc} device={testDevice1} />);\n    expect(wrapper.find('iframe').prop('width')).to.equal(testDevice1.width);\n    expect(wrapper.find('iframe').prop('height')).to.equal(testDevice1.height);\n  });\n\n  it('Renders the iframe with the given url', () => {\n    const wrapper = shallow(<Renderer src={testSrc} device={testDevice1} />);\n    expect(wrapper.find('iframe').prop('src')).to.equal(testSrc);\n  });\n\n  /* it('Calls the callback with a number value', () => {\n    const onChange = sinon.spy();\n    const wrapper = mount(<BrowserZoom onChange={onChange} />);\n    wrapper.find('.MuiSlider-thumb').simulate('mousedown');\n    wrapper.find('.MuiSlider-thumb').simulate('mouseup');\n    console.log('spy.args', onChange.args);\n    assert(onChange.calledWith(100));\n  }); */\n});\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ScreenShotSavePreference/index.js",
    "content": "import React, {useState} from 'react';\nimport TextField from '@material-ui/core/TextField';\nimport IconButton from '@material-ui/core/IconButton';\nimport {useTheme} from '@material-ui/core/styles';\nimport FolderOpenIcon from '@material-ui/icons/FolderOpenOutlined';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport {ipcRenderer} from 'electron';\nimport useStyles from '../UserPreferences/useStyles';\n\nfunction ScreenShotSavePreference({\n  onScreenShotSaveLocationChange,\n  screenShotSavePath,\n}) {\n  const theme = useTheme();\n  const classes = useStyles();\n  const getScreenshotSavePath = async event => {\n    const screenshotSavePathResponseFromIpc = await ipcRenderer.invoke(\n      'get-screen-shot-save-path'\n    );\n    if (screenshotSavePathResponseFromIpc) {\n      onScreenShotSaveLocationChange(\n        'screenShotSavePath',\n        screenshotSavePathResponseFromIpc\n      );\n    }\n  };\n\n  return (\n    <div>\n      <div className={classes.preferenceName}>Save Location:</div>\n      <TextField\n        className={classes.marginTop}\n        type=\"text\"\n        color=\"secondary\"\n        id=\"standard-size-small\"\n        value={screenShotSavePath}\n        placeholder=\"Screenshot save path\"\n        variant=\"outlined\"\n        size=\"small\"\n        InputProps={{\n          endAdornment: (\n            <InputAdornment>\n              <IconButton\n                onClick={getScreenshotSavePath}\n                size=\"small\"\n                title=\"Select Screenshots save location\"\n              >\n                <FolderOpenIcon\n                  fontSize=\"small\"\n                  htmlColor={theme.palette.lightIcon.main}\n                />\n              </IconButton>\n            </InputAdornment>\n          ),\n        }}\n      />\n    </div>\n  );\n}\n\nexport default ScreenShotSavePreference;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ScreenshotManager/index.js",
    "content": "import React from 'react';\n\nexport default function Screenshotmanager(props) {\n  return '';\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ScrollControls/index.js",
    "content": "// @flow\nimport React, {Component, useEffect, useRef, useState} from 'react';\nimport cx from 'classnames';\nimport Grid from '@material-ui/core/Grid';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport {makeStyles, useTheme} from '@material-ui/core/styles';\nimport ScrollDownIcon from '../icons/ScrollDown';\nimport ScrollUpIcon from '../icons/ScrollUp';\nimport Unplug from '../icons/Unplug';\nimport ScreenshotIcon from '../icons/FullScreenshot';\nimport DeviceRotateIcon from '../icons/DeviceRotate';\nimport InspectElementIcon from '../icons/InspectElement';\nimport DesignModeIcon from '../icons/DesignMode';\nimport MutedIcon from '../icons/Muted';\nimport UnmutedIcon from '../icons/Unmuted';\nimport useCommonStyles from '../useCommonStyles';\nimport ZoomContainer from '../../containers/ZoomContainer';\nimport PrefersColorSchemeSwitch from '../PrefersColorSchemeSwitch';\nimport ToggleTouch from '../ToggleTouch';\nimport Muted from '../icons/Muted';\nimport CSSEditor from '../icons/CSSEditor';\nimport styles from '../WebView/style.module.css';\nimport PageNavigatorIcon from '../icons/PageNavigator';\n\nconst useStyles = makeStyles({\n  container: {\n    padding: '0 10px',\n  },\n});\n\nconst useRulerStyles = makeStyles(theme => ({\n  ruler: {borderRight: `1px solid ${theme.palette.text.inactive}`, height: 20},\n}));\n\nconst VerticalRuler = () => {\n  const styles = useRulerStyles();\n\n  return <div className={styles.ruler} />;\n};\n\nconst ScrollControls = ({\n  toggleEventMirroringAllDevices,\n  browser,\n  triggerScrollDown,\n  triggerScrollUp,\n  screenshotAllDevices,\n  flipOrientationAllDevices,\n  toggleInspector,\n  toggleCSSEditor,\n  onAllDevicesMutedChange,\n  onToggleAllDeviceDesignMode,\n  onChangePageNavigatorActive,\n}) => {\n  const [eventMirroring, setEventMirroring] = useState(true);\n  const initialRender = useRef(true);\n  const classes = useStyles();\n  const theme = useTheme();\n  const commonClasses = useCommonStyles();\n  const iconProps = {\n    color: 'currentColor',\n    height: 25,\n    width: 25,\n  };\n\n  useEffect(() => {\n    if (initialRender.current) {\n      initialRender.current = false;\n    } else {\n      toggleEventMirroringAllDevices(eventMirroring);\n    }\n  }, [eventMirroring]);\n\n  const handleEventMirroring = () => {\n    setEventMirroring(!eventMirroring);\n  };\n\n  return (\n    <div className={classes.container}>\n      <Grid container spacing={1} alignItems=\"center\">\n        <Grid\n          item\n          className={cx(commonClasses.icon, {\n            [commonClasses.iconSelected]: browser.CSSEditor.isOpen,\n          })}\n          onClick={toggleCSSEditor}\n        >\n          <Tooltip title=\"Live CSS Editor\">\n            <div>\n              <CSSEditor {...iconProps} />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid\n          item\n          className={cx(commonClasses.icon, {\n            [commonClasses.iconSelected]: browser.isInspecting,\n          })}\n        >\n          <Tooltip title=\"Inspect Element\">\n            <div onClick={toggleInspector}>\n              <InspectElementIcon\n                {...{...iconProps, ...{height: 22, width: 22}}}\n              />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid\n          item\n          className={cx(commonClasses.icon, {\n            [commonClasses.iconSelected]: browser.allDevicesInDesignMode,\n          })}\n        >\n          <Tooltip\n            title={\n              browser.allDevicesInDesignMode\n                ? 'Disable Design Mode on all devices'\n                : 'Enable Design Mode on all devices'\n            }\n          >\n            <div onClick={onToggleAllDeviceDesignMode}>\n              <DesignModeIcon {...{...iconProps, ...{height: 22, width: 22}}} />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid\n          item\n          className={cx(commonClasses.icon, {\n            [commonClasses.iconSelected]: browser.pageNavigator.active,\n          })}\n        >\n          <Tooltip title=\"Page Navigator\">\n            <div\n              onClick={() => {\n                onChangePageNavigatorActive(!browser.pageNavigator.active);\n              }}\n            >\n              <PageNavigatorIcon {...iconProps} height={22} width={22} />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid item>\n          <VerticalRuler />\n        </Grid>\n        <Grid item className={commonClasses.icon}>\n          <PrefersColorSchemeSwitch iconProps={iconProps} />\n        </Grid>\n        <ToggleTouch iconProps={iconProps} />\n        <Grid item className={commonClasses.icon}>\n          <Tooltip\n            title={\n              browser.allDevicesMuted\n                ? 'Unmute all devices'\n                : 'Mute all devices'\n            }\n          >\n            <div onClick={onAllDevicesMutedChange}>\n              {browser.allDevicesMuted ? (\n                <MutedIcon {...iconProps} />\n              ) : (\n                <UnmutedIcon {...iconProps} />\n              )}\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid item className={commonClasses.icon}>\n          <Tooltip title=\"Tilt Devices\">\n            <div onClick={flipOrientationAllDevices}>\n              <DeviceRotateIcon {...iconProps} />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid item>\n          <VerticalRuler />\n        </Grid>\n        <Grid item className={commonClasses.icon}>\n          <Tooltip title=\"Take Screenshot\">\n            <div onClick={screenshotAllDevices}>\n              <ScreenshotIcon {...iconProps} />\n            </div>\n          </Tooltip>\n        </Grid>\n        <Grid\n          item\n          className={cx(commonClasses.icon, {\n            [commonClasses.iconSelected]: !eventMirroring,\n          })}\n        >\n          <Tooltip title=\"Disable event mirroring\">\n            <div onClick={handleEventMirroring}>\n              <Unplug {...iconProps} />\n            </div>\n          </Tooltip>\n        </Grid>\n\n        <ZoomContainer iconProps={iconProps} />\n      </Grid>\n    </div>\n  );\n};\n\nexport default ScrollControls;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ScrollControls/index.test.js",
    "content": "// @flow\nimport React from 'react';\nimport {shallow, mount} from 'enzyme';\nimport {expect, assert} from 'chai';\nimport sinon from 'sinon';\n\nimport Slider from '@material-ui/core/Slider';\nimport BrowserZoom from '.';\n\ndescribe('<BrowserZoom />', () => {\n  it('Renders label and the slider component ', () => {\n    const wrapper = shallow(<BrowserZoom />);\n    expect(wrapper.find(Slider)).to.have.lengthOf(1);\n  });\n\n  it('Calls the callback on slider change', () => {\n    const onChange = sinon.spy();\n    const wrapper = mount(<BrowserZoom onChange={onChange} />);\n    wrapper.find('.MuiSlider-thumb').simulate('mousedown');\n    wrapper.find('.MuiSlider-thumb').simulate('mouseup');\n    expect(onChange).to.have.property('callCount', 1);\n  });\n\n  /* it('Calls the callback with a number value', () => {\n    const onChange = sinon.spy();\n    const wrapper = mount(<BrowserZoom onChange={onChange} />);\n    wrapper.find('.MuiSlider-thumb').simulate('mousedown');\n    wrapper.find('.MuiSlider-thumb').simulate('mouseup');\n    console.log('spy.args', onChange.args);\n    assert(onChange.calledWith(100));\n  }); */\n});\n"
  },
  {
    "path": "desktop-app-legacy/app/components/Select.js",
    "content": "import React, {useMemo} from 'react';\nimport ReactSelect from 'react-select';\nimport {useTheme} from '@material-ui/core/styles';\nimport useIsDarkTheme from './useIsDarkTheme';\n\nfunction Select(props) {\n  const appTheme = useTheme();\n  const isDark = useIsDarkTheme();\n  const styles = useMemo(() => ({\n    control: styles => ({\n      ...styles,\n      backgroundColor: '#ffffff10',\n      borderColor: isDark ? '#cccccc' : '#00000042',\n    }),\n    indicatorSeparator: styles => ({\n      ...styles,\n      backgroundColor: isDark ? '#cccccc' : '#00000042',\n    }),\n    dropdownIndicator: styles => ({\n      ...styles,\n      color: isDark ? '#cccccc' : '#00000042',\n    }),\n    option: (styles, {data, isDisabled, isFocused, isSelected}) => ({\n      ...styles,\n      backgroundColor: isDisabled\n        ? null\n        : isSelected\n        ? appTheme.palette.background.l10\n        : isFocused\n        ? appTheme.palette.background.l5\n        : null,\n      color: appTheme.palette.text.normal,\n\n      ':active': {\n        ...styles[':active'],\n        backgroundColor: !isDisabled && '#ffffff40',\n      },\n    }),\n    input: styles => ({...styles}),\n    placeholder: styles => ({...styles}),\n    singleValue: (styles, {data}) => ({\n      ...styles,\n      color: appTheme.palette.text.primary,\n    }),\n    menu: styles => ({\n      ...styles,\n      background: appTheme.palette.background.l1,\n      zIndex: 100,\n    }),\n  }));\n\n  return (\n    <ReactSelect\n      styles={styles}\n      theme={theme => ({\n        ...theme,\n        colors: {\n          ...theme.colors,\n          primary: appTheme.palette.primary.main,\n        },\n      })}\n      {...props}\n    />\n  );\n}\n\nexport default Select;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/Spinner/index.js",
    "content": "import React from 'react';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport {makeStyles} from '@material-ui/core/styles';\n\nexport default function(props) {\n  const classes = makeStyles({\n    root: {\n      width: '100%',\n      height: '100%',\n      position: 'relative',\n      display: 'flex',\n      justifyContent: 'center',\n      alignItems: 'center',\n    },\n    top: {\n      color: '#ffffff00', // '#eef3fd',\n    },\n    bottom: {\n      color: '#7587ec', // '#6798e5',\n      animationDuration: '550ms',\n      position: 'absolute',\n    },\n  })();\n\n  return (\n    <div className={classes.root}>\n      <CircularProgress\n        variant=\"determinate\"\n        value={100}\n        className={classes.top}\n        size={20}\n        thickness={4}\n        {...props}\n      />\n      <CircularProgress\n        variant=\"indeterminate\"\n        disableShrink\n        className={classes.bottom}\n        size={20}\n        thickness={4}\n        {...props}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/StatusBar/Announcement.js",
    "content": "import React, {useEffect, useState} from 'react';\nimport cx from 'classnames';\nimport {shell} from 'electron';\nimport useStyles from './useStyles';\n\nconst Announcement = () => {\n  const [data, setData] = useState(null);\n  const classes = useStyles();\n  useEffect(() => {\n    (async () => {\n      try {\n        const response = await (\n          await fetch('https://responsively.app/assets/appMessages.json')\n        ).json();\n        setData(response.statusBarMessage);\n      } catch (err) {\n        console.log('Error fetching appMessages.json', err);\n      }\n    })();\n  }, []);\n\n  if (!data) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cx(classes.text, classes.link)}\n      onClick={() => shell.openExternal(data.link)}\n    >\n      <span className={cx('featureSuggestionLink', classes.linkText)}>\n        {data.text}\n      </span>\n    </div>\n  );\n};\n\nexport default Announcement;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/StatusBar/index.js",
    "content": "import React, {useState, useEffect} from 'react';\nimport cx from 'classnames';\nimport {shell, ipcRenderer} from 'electron';\nimport PropTypes from 'prop-types';\nimport Paper from '@material-ui/core/Paper';\nimport useStyles from './useStyles';\nimport Announcement from './Announcement';\nimport Github from '../icons/Github';\nimport Twitter from '../icons/Twitter';\nimport RoadMap from '../icons/RoadMap';\n\nconst Spacer = ({width = 10}) => {\n  const classes = useStyles();\n  return <div className={classes.text} style={{width}} />;\n};\n\nconst AppUpdaterStatusInfoSection = () => {\n  const [status, setAppUpdaterStatus] = useState('idle');\n  const classes = useStyles();\n  useEffect(() => {\n    const handler = (event, args) => {\n      setAppUpdaterStatus(args.nextStatus);\n    };\n    ipcRenderer.on('updater-status-changed', handler);\n    return () => {\n      ipcRenderer.removeListener('updater-status-changed', handler);\n    };\n  }, []);\n\n  let label = '';\n  switch (status) {\n    case 'checking':\n      label = 'Update Info: Checking for Updates...';\n      break;\n    case 'noUpdate':\n      label = 'Update Info: The App is up to date!';\n      break;\n    case 'downloading':\n      label = 'Update Info: Downloading Update...';\n      break;\n    case 'downloaded':\n      label = 'Update Info: Update Downloaded';\n      break;\n    case 'newVersion':\n      label = 'Update Info: New version available!';\n      break;\n    default:\n      label = null;\n      break;\n  }\n  if (label == null) return null;\n  return (\n    <div className={classes.section}>\n      <div>\n        <span className={cx('appUpdaterStatusInfo', classes.linkText)}>\n          {label}\n        </span>\n      </div>\n    </div>\n  );\n};\n\nconst StatusBar = ({visible, zoomLevel}) => {\n  const classes = useStyles();\n  if (!visible) {\n    return null;\n  }\n\n  const zoomPercent = Math.round(zoomLevel * 100);\n\n  return (\n    <Paper elevation={0} className={classes.statusBar}>\n      <div className={classes.section}>\n        <div\n          className={cx(classes.text, classes.link)}\n          onClick={() =>\n            shell.openExternal(\n              'https://github.com/responsively-org/responsively-app'\n            )\n          }\n        >\n          <Github width={14} className={classes.linkIcon} />\n        </div>\n        <div\n          className={cx(classes.text, classes.link)}\n          onClick={() =>\n            shell.openExternal(\n              'https://x.com/intent/follow?original_referer=app&ref_src=twsrc%5Etfw&region=follow_link&screen_name=ResponsivelyApp&tw_p=followbutton'\n            )\n          }\n        >\n          <Twitter width={14} className={classes.linkIcon} />\n        </div>\n        <Spacer />\n        <div\n          className={cx('roadMapLink', classes.text, classes.link)}\n          onClick={() =>\n            shell.openExternal(\n              'https://github.com/responsively-org/responsively-app/projects/12?fullscreen=true'\n            )\n          }\n        >\n          <RoadMap width={14} color=\"grey\" className={classes.linkIcon} />\n          <span className={cx('roadMapLink', classes.linkText)}>RoadMap</span>\n        </div>\n        <Spacer />\n        <div\n          className={cx(classes.text, classes.link)}\n          onClick={() =>\n            shell.openExternal('https://headwayapp.co/responsively-changelog')\n          }\n        >\n          <span className={cx('changeLogLink', classes.linkText)}>\n            Changelog\n          </span>\n        </div>\n        <Spacer />\n        <div className={classes.text}>\n          <span className={cx('zoomText', classes.linkText)}>\n            Zoom: {zoomPercent}%\n          </span>\n        </div>\n      </div>\n      <AppUpdaterStatusInfoSection />\n      <div className={classes.section}>\n        <Announcement />\n      </div>\n    </Paper>\n  );\n};\n\nStatusBar.propTypes = {\n  visible: PropTypes.bool.isRequired,\n};\n\nexport default StatusBar;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/StatusBar/useStyles.js",
    "content": "import {makeStyles} from '@material-ui/core/styles';\n\nconst useStyles = makeStyles({\n  statusBar: {\n    height: '20px',\n    display: 'flex',\n    borderRadius: 0,\n    boxShadow: '0 -3px 5px rgba(0, 0, 0, 0.35)',\n    color: '#fffc',\n    justifyContent: 'space-between',\n    zIndex: 1,\n  },\n  section: {\n    display: 'flex',\n    alignItems: 'center',\n    margin: '0 5px',\n  },\n  text: {\n    display: 'flex',\n    alignItems: 'center',\n    cursor: 'default',\n    color: 'grey',\n    fill: 'grey',\n    borderRadius: 2,\n    height: '100%',\n    padding: '0 5px',\n    filter: 'grayscale(1)',\n  },\n  link: {\n    cursor: 'pointer',\n    '&:hover': {\n      color: 'lightgrey',\n      fill: 'lightgrey',\n      backgroundColor: '#000',\n      filter: 'none',\n    },\n  },\n  linkIcon: {\n    fill: 'inherit',\n    margin: '0 4px',\n  },\n  linkText: {\n    color: 'inherit',\n    fontSize: '12px',\n  },\n});\n\nexport default useStyles;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ToggleTouch/index.js",
    "content": "import React, {useState, useEffect} from 'react';\nimport cx from 'classnames';\nimport Grid from '@material-ui/core/Grid';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport ToggleTouchIcon from '../icons/ToggleTouch';\nimport useCommonStyles from '../useCommonStyles';\n\nexport default function ToggleTouch({iconProps}) {\n  const [isTouchMode, setIsTouchMode] = useState(false);\n  const [isHover, setHover] = useState(false);\n  const [hasPendingState, setPendingState] = useState(false);\n  const [hasTooltip, setTooltip] = useState(false);\n  const commonClasses = useCommonStyles();\n\n  useEffect(() => {\n    const handler = e => {\n      if (isTouchMode && e.key === 'Escape') {\n        handleToggleTouch();\n      }\n    };\n\n    document.addEventListener('keydown', handler);\n\n    return () => document.removeEventListener('keydown', handler);\n  }, [isTouchMode, isHover]);\n\n  const handleToggleTouch = () => {\n    setIsTouchMode(!isTouchMode);\n    setTooltip(!isTouchMode);\n    if (isHover) {\n      return setPendingState(true);\n    }\n    return syncState(!isTouchMode);\n  };\n\n  const syncState = isTouchMode => {\n    const {BrowserWindow} = require('electron').remote;\n    const contents = BrowserWindow.getFocusedWindow().webContents;\n\n    if (!contents.debugger.isAttached()) {\n      contents.debugger.attach('1.3');\n    }\n    contents.debugger.sendCommand('Emulation.setEmitTouchEventsForMouse', {\n      enabled: isTouchMode,\n    });\n  };\n\n  return (\n    <Grid\n      item\n      className={cx(commonClasses.icon, {\n        [commonClasses.iconSelected]: isTouchMode,\n      })}\n      onMouseEnter={() => setHover(true)}\n      onPointerLeave={() => {\n        setHover(false);\n      }}\n      onPointerMove={() => {\n        if (!hasPendingState) {\n          return;\n        }\n        syncState(isTouchMode);\n        setPendingState(false);\n      }}\n    >\n      <Tooltip title={hasTooltip ? '' : 'Toggle Touch Mode'}>\n        <div onClick={handleToggleTouch}>\n          <ToggleTouchIcon {...iconProps} />\n        </div>\n      </Tooltip>\n    </Grid>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/UrlSearchResults/index.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport cx from 'classnames';\nimport DefaultFavIcon from '@material-ui/icons/Public';\nimport useStyles from './useStyles';\n\nconst UrlSearchResults = ({\n  filteredSearchResults,\n  cursorIndex,\n  handleUrlChange,\n}) => {\n  const styles = useStyles();\n  return (\n    <div className={cx(styles.searchBarSuggestionsContainer)}>\n      <ul className={cx(styles.searchBarSuggestionsListUl)}>\n        {filteredSearchResults.map((eachResult, index) => {\n          const favicon = eachResult.pageMeta?.favicons?.[0];\n          const title = eachResult.pageMeta?.title;\n          const url = eachResult.url;\n          return (\n            <li key={url}>\n              <div\n                className={cx(styles.searchBarSuggestionsListItems, {\n                  [styles.searchBarSuggestionsActiveListItems]:\n                    cursorIndex === index,\n                })}\n                onClick={() => handleUrlChange(eachResult.url, index)}\n              >\n                <div className={cx(styles.pageFavIconWrapper)}>\n                  {favicon ? (\n                    <img\n                      className={cx(styles.pageFavIcon)}\n                      src={favicon}\n                      onError={event => {\n                        event.target.style.display = 'none';\n                        event.target.nextSibling.style.display = 'block';\n                      }}\n                    />\n                  ) : (\n                    <div className={cx(styles.pageDefaultFavIconWrapper)}>\n                      <DefaultFavIcon fontSize=\"inherit\" />\n                    </div>\n                  )}\n                  <div\n                    style={{display: 'none'}}\n                    className={cx(styles.pageDefaultFavIconWrapperClassName)}\n                  >\n                    <DefaultFavIcon fontSize=\"inherit\" />\n                  </div>\n                </div>\n                <div className={cx(styles.pageTitleAndUrlContainer)}>\n                  <span className={cx(styles.pageTitle)}>{title}</span>\n                  <span className={cx(styles.pageUrl)}>{url}</span>\n                </div>\n              </div>\n            </li>\n          );\n        })}\n      </ul>\n    </div>\n  );\n};\n\nUrlSearchResults.propTypes = {\n  filteredSearchResults: PropTypes.arrayOf(\n    PropTypes.shape({\n      url: PropTypes.string.isRequired,\n      pageMeta: PropTypes.shape({\n        title: PropTypes.string,\n        favicons: PropTypes.arrayOf(PropTypes.string),\n      }),\n    })\n  ),\n  cursorIndex: PropTypes.number,\n  handleUrlChange: PropTypes.func.isRequired,\n};\n\nUrlSearchResults.defaultProps = {\n  filteredSearchResults: [],\n  cursorIndex: null,\n};\n\nexport default UrlSearchResults;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/UrlSearchResults/useStyles.js",
    "content": "import {makeStyles} from '@material-ui/core/styles';\n\nconst useStyles = makeStyles(theme => ({\n  searchBarSuggestionsContainer: {\n    width: 'calc(100% + 2px)',\n    maxHeight: '20em',\n    position: 'absolute',\n    overflow: 'hidden',\n    left: '-1px',\n    top: '1.8em',\n    background: theme.palette.background.l1,\n    borderRadius: '0 0 14px 14px',\n    borderRight: 'solid 1px #7587ec',\n    borderBottom: 'solid 1px #7587ec',\n    borderLeft: 'solid 1px #7587ec',\n  },\n  searchBarSuggestionsListUl: {\n    padding: '0',\n    margin: '0',\n    listStyle: 'none',\n  },\n  searchBarSuggestionsListItems: {\n    display: 'flex',\n    alignItems: 'center',\n    lineHeight: '22px',\n    color: theme.palette.text.normal,\n    padding: '0.4em 1em',\n    cursor: 'default',\n    '&:hover': {\n      background: 'gray',\n      color: 'white',\n    },\n  },\n  searchBarSuggestionsActiveListItems: {\n    background: theme.palette.primary.main,\n    color: 'white',\n  },\n\n  pageFavIconWrapper: {\n    width: '16px',\n    height: '16px',\n  },\n\n  pageFavIcon: {\n    display: 'block',\n    width: '16px',\n    height: '16px',\n  },\n  pageDefaultFavIconWrapper: {\n    fontSize: '16px',\n    height: '16px',\n  },\n  pageTitleAndUrlContainer: {\n    marginLeft: '16px',\n    whiteSpace: 'nowrap',\n    overflow: 'hidden',\n    textOverflow: 'ellipsis',\n  },\n  pageTitle: {\n    '&::after': {\n      content: '\"-\"',\n      margin: '0 8px',\n    },\n  },\n  pageUrl: {\n    fontWeight: 500,\n  },\n}));\n\nexport default useStyles;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/UserPreferences/index.js",
    "content": "import React, {useMemo} from 'react';\nimport {useSelector, useDispatch} from 'react-redux';\nimport cx from 'classnames';\nimport FormControlLabel from '@material-ui/core/FormControlLabel';\nimport Typography from '@material-ui/core/Typography';\nimport Checkbox from '@material-ui/core/Checkbox';\nimport Button from '@material-ui/core/Button';\nimport TextField from '@material-ui/core/TextField';\nimport Input from '@material-ui/core/Input';\nimport SettingsIcon from '@material-ui/icons/Settings';\nimport FileDownloadIcon from '@material-ui/icons/CloudDownload';\nimport FileUploadIcon from '@material-ui/icons/CloudUpload';\nimport KebabMenu from '../KebabMenu';\n\nimport useCommonStyles from '../useCommonStyles';\nimport useStyles from './useStyles';\nimport Select from '../Select';\nimport {DEVTOOLS_MODES} from '../../constants/previewerLayouts';\nimport {LIGHT_THEME, DARK_THEME} from '../../constants/theme';\nimport ScreenShotSavePreference from '../ScreenShotSavePreference/index';\nimport {userPreferenceSettings} from '../../settings/userPreferenceSettings';\nimport {SCREENSHOT_MECHANISM, STARTUP_PAGE} from '../../constants/values';\nimport {notifyPermissionPreferenceChanged} from '../../utils/permissionUtils.js';\nimport {PERMISSION_MANAGEMENT_OPTIONS} from '../../constants/permissionsManagement';\nimport {\n  setTheme,\n  downloadPreferences,\n  uploadPreferences,\n} from '../../actions/browser';\nimport {deleteSearchResults} from '../../services/searchUrlSuggestions';\n\nfunction UserPreference({\n  devToolsConfig,\n  userPreferences,\n  onUserPreferencesChange,\n  onDevToolsModeChange,\n}) {\n  const classes = useStyles();\n  const commonClasses = useCommonStyles();\n  const dispatch = useDispatch();\n  const state = useSelector(state => state.browser);\n  const themeSource = state.theme;\n  const selectedThemeOption = useMemo(\n    () => themeOptions.find(option => option.value === themeSource),\n    [themeSource]\n  );\n\n  const startupPageOptions = [\n    {\n      label: 'Homepage',\n      value: STARTUP_PAGE.HOME,\n    },\n    {\n      label: 'Blank Page',\n      value: STARTUP_PAGE.BLANK,\n    },\n  ];\n\n  const onChange = (field, value) => {\n    onUserPreferencesChange({...userPreferences, [field]: value});\n  };\n\n  const downloadConfiguration = () => {\n    const configuration = {\n      devices: state.devices,\n      homepage: state.homepage,\n      userPreferences: state.userPreferences,\n      customDevices: state.allDevices.slice(\n        0,\n        state.allDevices.findIndex(item => item.id === '1')\n      ),\n    };\n    const data = JSON.stringify(configuration);\n    const blob = new Blob([data], {type: 'application/json'});\n    const url = window.URL.createObjectURL(blob);\n    dispatch(downloadPreferences(url));\n  };\n\n  const uploadConfiguration = () => {\n    dispatch(uploadPreferences());\n  };\n  return (\n    <div className={commonClasses.sidebarContentSection}>\n      <div\n        className={cx(\n          commonClasses.sidebarContentSectionTitleBar,\n          classes.exportPreferencesButtons\n        )}\n      >\n        <SettingsIcon width={26} margin={2} /> User Preferences\n        <KebabMenu>\n          <KebabMenu.Item onClick={downloadConfiguration}>\n            <div className={classes.containerImportButton}>\n              <span className={classes.importAndExportLabels}>Export</span>\n              <FileDownloadIcon />\n            </div>\n          </KebabMenu.Item>\n          <KebabMenu.Item onClick={uploadConfiguration}>\n            <div className={classes.containerImportButton}>\n              <span className={classes.importAndExportLabels}>Import</span>\n              <FileUploadIcon />\n            </div>\n          </KebabMenu.Item>\n        </KebabMenu>\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div\n          className={cx(\n            commonClasses.flexAlignVerticalMiddle,\n            classes.sectionHeader\n          )}\n        >\n          General\n        </div>\n        <div>\n          <FormControlLabel\n            control={\n              <Checkbox\n                checked={userPreferences.disableSSLValidation || false}\n                onChange={e =>\n                  onChange('disableSSLValidation', e.target.checked)\n                }\n                name=\"Disable SSL Validation\"\n                color=\"primary\"\n              />\n            }\n            label={\n              <span className={classes.preferenceName}>\n                Disable SSL Validation\n              </span>\n            }\n          />\n        </div>\n        <div>\n          <FormControlLabel\n            control={\n              <Checkbox\n                checked={devToolsConfig.mode !== DEVTOOLS_MODES.UNDOCKED}\n                onChange={e => {\n                  if (e.target.checked) {\n                    onDevToolsModeChange(DEVTOOLS_MODES.BOTTOM);\n                  } else {\n                    onDevToolsModeChange(DEVTOOLS_MODES.UNDOCKED);\n                  }\n                }}\n                name=\"Dock dev-tools to Main Window\"\n                color=\"primary\"\n              />\n            }\n            label={\n              <span className={classes.preferenceName}>\n                Dock dev-tools to Main Window\n              </span>\n            }\n          />\n        </div>\n        <div>\n          <FormControlLabel\n            control={\n              <Checkbox\n                checked={userPreferences.reopenLastAddress || false}\n                onChange={e => onChange('reopenLastAddress', e.target.checked)}\n                name=\"Reopen last page during startup\"\n                color=\"primary\"\n              />\n            }\n            label={\n              <span className={classes.preferenceName}>\n                Reopen last page during startup\n              </span>\n            }\n          />\n        </div>\n        <div>\n          <FormControlLabel\n            control={\n              <Checkbox\n                checked={userPreferences.disableSpellCheck || false}\n                onChange={e => {\n                  onChange('disableSpellCheck', e.target.checked);\n                }}\n                name=\"Disable spellcheck\"\n                color=\"primary\"\n              />\n            }\n            label={\n              <div>\n                <span className={classes.preferenceName}>\n                  Disable spellcheck\n                </span>\n                <p className={classes.spellCheckToggleSmallNote}>\n                  Note: Restart required\n                </p>\n              </div>\n            }\n          />\n        </div>\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div\n          className={cx(\n            commonClasses.flexAlignVerticalMiddle,\n            classes.sectionHeader\n          )}\n        >\n          Appearance\n        </div>\n        <div className={classes.marginTop}>\n          <FormControlLabel\n            control={\n              <Input\n                type=\"color\"\n                onChange={e => onChange('deviceOutlineStyle', e.target.value)}\n                name=\"Device Outline Color\"\n                color=\"primary\"\n                value={userPreferences.deviceOutlineStyle}\n                classes={{root: classes.preferenceColor}}\n              />\n            }\n            label={\n              <span className={classes.preferenceName}>\n                Device Outline Color\n              </span>\n            }\n          />\n        </div>\n        <div className={classes.marginTop}>\n          <Typography component=\"span\" className={classes.preferenceName}>\n            Theme:\n          </Typography>\n          <div className={classes.marginTop}>\n            <Select\n              options={themeOptions}\n              value={selectedThemeOption}\n              onChange={option => dispatch(setTheme(option.value))}\n            />\n          </div>\n        </div>\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div\n          className={cx(\n            commonClasses.flexAlignVerticalMiddle,\n            classes.sectionHeader\n          )}\n        >\n          Screenshot\n        </div>\n        <div>\n          <FormControlLabel\n            control={\n              <Checkbox\n                checked={userPreferences.removeFixedPositionedElements || false}\n                onChange={e =>\n                  onChange('removeFixedPositionedElements', e.target.checked)\n                }\n                name=\"Hide fixed positioned elements\"\n                color=\"primary\"\n              />\n            }\n            label={\n              <span className={classes.preferenceName}>\n                Hide fixed positioned elements\n              </span>\n            }\n          />\n        </div>\n        <div>\n          <FormControlLabel\n            control={\n              <Checkbox\n                checked={\n                  userPreferences.screenshotMechanism ===\n                  SCREENSHOT_MECHANISM.V2\n                }\n                onChange={e =>\n                  onChange(\n                    'screenshotMechanism',\n                    e.target.checked\n                      ? SCREENSHOT_MECHANISM.V2\n                      : SCREENSHOT_MECHANISM.V1\n                  )\n                }\n                name=\"Use improved mechanism\"\n                color=\"primary\"\n              />\n            }\n            label={\n              <span className={classes.preferenceName}>\n                Use improved mechanism\n              </span>\n            }\n          />\n        </div>\n        <ScreenShotSavePreference\n          screenShotSavePath={\n            userPreferences.screenShotSavePath ||\n            userPreferenceSettings.getDefaultScreenshotpath()\n          }\n          onScreenShotSaveLocationChange={onChange}\n        />\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div\n          className={cx(\n            commonClasses.flexAlignVerticalMiddle,\n            classes.sectionHeader\n          )}\n        >\n          Permissions\n        </div>\n        <div className={classes.marginTop}>\n          <Select\n            options={permissionsOptions}\n            value={\n              permissionsOptions.find(\n                x => x.value === userPreferences?.permissionManagement\n              ) || permissionsOptions[0]\n            }\n            onChange={val => {\n              notifyPermissionPreferenceChanged(val.value);\n              onChange('permissionManagement', val.value);\n            }}\n          />\n          <p className={classes.permissionsSelectorSmallNote}>\n            <strong>Note:</strong> To ensure this behaviour you should restart\n            Responsively\n          </p>\n        </div>\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div\n          className={cx(\n            commonClasses.flexAlignVerticalMiddle,\n            classes.sectionHeader\n          )}\n        >\n          Startup Page\n        </div>\n        <div className={classes.marginTop}>\n          <Select\n            options={startupPageOptions}\n            value={\n              startupPageOptions.find(\n                x => x.value === userPreferences?.startupPage\n              ) || startupPageOptions[0]\n            }\n            onChange={val => {\n              onChange('startupPage', val.value);\n            }}\n          />\n        </div>\n      </div>\n      <div className={commonClasses.sidebarContentSectionContainer}>\n        <div\n          className={cx(\n            commonClasses.flexAlignVerticalMiddle,\n            classes.sectionHeader\n          )}\n        >\n          Address History\n        </div>\n        <div className={commonClasses.sidebarContentSectionContainer}>\n          <Button\n            variant=\"contained\"\n            color=\"primary\"\n            aria-label=\"clear address history\"\n            component=\"span\"\n            onClick={deleteSearchResults}\n          >\n            Clear Address History\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nconst permissionsOptions = [\n  {\n    value: PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS,\n    label: PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS,\n  },\n  {\n    value: PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS,\n    label: PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS,\n  },\n  {\n    value: PERMISSION_MANAGEMENT_OPTIONS.ASK_ALWAYS,\n    label: PERMISSION_MANAGEMENT_OPTIONS.ASK_ALWAYS,\n  },\n];\n\nconst themeOptions = [\n  {value: LIGHT_THEME, label: 'Light'},\n  {value: DARK_THEME, label: 'Dark'},\n];\n\nexport default UserPreference;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/UserPreferences/useStyles.js",
    "content": "import {makeStyles} from '@material-ui/core/styles';\n\nconst useStyles = makeStyles(theme => ({\n  preferenceName: {\n    fontSize: '14px',\n  },\n  preferenceColor: {\n    width: '24px',\n    height: '20px',\n    margin: '0 10px',\n    border: 'transparent',\n    '& input': {\n      width: '24px',\n      height: '26px',\n      border: 'transparent',\n    },\n  },\n  sectionTitle: {\n    margin: '5px 0',\n  },\n  sectionHeader: {\n    '&:after': {\n      content: '\"\"',\n      flex: 1,\n      marginLeft: 5,\n      height: 1,\n      backgroundColor: theme.palette.text.primary,\n    },\n  },\n  marginTop: {\n    marginTop: '10px',\n  },\n  permissionsSelectorSmallNote: {\n    color: theme.palette.text.primary,\n    margin: 0,\n    fontSize: '0.75rem',\n    marginTop: '10px',\n    textAlign: 'left',\n    fontFamily: \"'Roboto', 'Helvetica', 'Arial', sans-serif\",\n    fontWeight: 400,\n    lineHeight: 1.66,\n    letterSpacing: '0.03333em',\n  },\n  spellCheckToggleSmallNote: {\n    color: theme.palette.text.primary,\n    margin: 0,\n    fontSize: '10px',\n  },\n  exportPreferencesButtons: {\n    display: 'flex',\n    justifyContent: 'space-around',\n  },\n  importAndExportLabels: {\n    fontSize: '12px',\n    paddingRight: '1.5em',\n  },\n  containerImportButton: {\n    display: 'flex',\n    alignItems: 'center',\n  },\n}));\n\nexport default useStyles;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/WebView/index.js",
    "content": "import React, {Component, createRef} from 'react';\nimport {remote, ipcRenderer} from 'electron';\nimport cx from 'classnames';\nimport {Resizable} from 're-resizable';\nimport {Tooltip} from '@material-ui/core';\nimport {withStyles, withTheme} from '@material-ui/core/styles';\nimport debounce from 'lodash/debounce';\nimport pubsub from 'pubsub.js';\nimport BugIcon from '../icons/Bug';\nimport FullScreenshotIcon from '../icons/FullScreenshot';\nimport ScreenshotIcon from '../icons/Screenshot';\nimport DeviceRotateIcon from '../icons/DeviceRotate';\nimport {\n  SCROLL_DOWN,\n  SCROLL_UP,\n  NAVIGATION_BACK,\n  NAVIGATION_FORWARD,\n  NAVIGATION_RELOAD,\n  SCREENSHOT_ALL_DEVICES,\n  FLIP_ORIENTATION_ALL_DEVICES,\n  TOGGLE_DEVICE_MUTED_STATE,\n  RELOAD_CSS,\n  DELETE_STORAGE,\n  ADDRESS_CHANGE,\n  STOP_LOADING,\n  CLEAR_NETWORK_CACHE,\n  SET_NETWORK_TROTTLING_PROFILE,\n  OPEN_CONSOLE_FOR_DEVICE,\n  PROXY_AUTH_ERROR,\n  APPLY_CSS,\n  TOGGLE_DEVICE_DESIGN_MODE_STATE,\n  TOGGLE_EVENT_MIRRORING_ALL_DEVICES,\n  PAGE_NAVIGATOR_CHANGED,\n} from '../../constants/pubsubEvents';\nimport {CAPABILITIES} from '../../constants/devices';\nimport {DESIGN_MODE_JS_VALUES} from '../../constants/values';\n\nimport styles from './style.module.css';\nimport {styles as commonStyles} from '../useCommonStyles';\nimport UnplugIcon from '../icons/Unplug';\nimport {captureScreenshot} from './screenshotUtil';\nimport {\n  DEVTOOLS_MODES,\n  INDIVIDUAL_LAYOUT,\n} from '../../constants/previewerLayouts';\nimport Maximize from '../icons/Maximize';\nimport Minimize from '../icons/Minimize';\nimport Focus from '../icons/Focus';\nimport Unfocus from '../icons/Unfocus';\nimport DesignModeIcon from '../icons/DesignMode';\nimport {captureOnSentry} from '../../utils/logUtils';\nimport {getBrowserSyncEmbedScriptURL} from '../../services/browserSync';\nimport Spinner from '../Spinner';\nimport {isSslValidationFailed} from '../../utils/generalUtils';\n\nconst {BrowserWindow} = remote;\n\nconst MESSAGE_TYPES = {\n  scroll: 'scroll',\n  click: 'click',\n  openDevToolsInspector: 'openDevToolsInspector',\n  openConsole: 'openConsole',\n  tiltDevice: 'tiltDevice',\n  takeScreenshot: 'takeScreenshot',\n  toggleEventMirroring: 'toggleEventMirroring',\n};\n\nclass WebView extends Component {\n  constructor(props) {\n    super(props);\n    this.webviewRef = createRef();\n    this.devToolsWebviewRef = createRef();\n    this.state = {\n      screenshotInProgress: false,\n      isTilted: false,\n      isUnplugged: false,\n      errorCode: null,\n      errorDesc: null,\n      deviceDimensions: {\n        width: this.props.device.width,\n        height: this.props.device.height,\n      },\n      temporaryDims: null,\n      address: this.props.browser.address,\n      proxyAuthError: false,\n      fullDocumentHeight: null,\n      fullDocumentWidth: null,\n      zoomLevel: null,\n    };\n    this.subscriptions = [];\n    this.domLoaded = false;\n    this.liveCssKey = null;\n    this.dbg = null;\n  }\n\n  componentDidMount() {\n    // this.initDeviceEmulationParams();\n    this.webviewRef.current.addEventListener(\n      'ipc-message',\n      this.messageHandler\n    );\n    this.subscriptions.push(\n      pubsub.subscribe('scroll', this.processScrollEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(APPLY_CSS, this.processApplyCssEvent)\n    );\n    this.subscriptions.push(pubsub.subscribe('click', this.processClickEvent));\n    this.subscriptions.push(\n      pubsub.subscribe(SCROLL_DOWN, this.processScrollDownEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(SCROLL_UP, this.processScrollUpEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(NAVIGATION_BACK, this.processNavigationBackEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(NAVIGATION_FORWARD, this.processNavigationForwardEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(NAVIGATION_RELOAD, this.processNavigationReloadEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(RELOAD_CSS, this.processReloadCSSEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(DELETE_STORAGE, this.processDeleteStorageEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(SCREENSHOT_ALL_DEVICES, this.processScreenshotEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(\n        TOGGLE_EVENT_MIRRORING_ALL_DEVICES,\n        this.processToggleEventMirroring\n      )\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(ADDRESS_CHANGE, this.processAddressChangeEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(STOP_LOADING, this.processStopLoadingEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(\n        FLIP_ORIENTATION_ALL_DEVICES,\n        this.processFlipOrientationEvent\n      )\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(TOGGLE_DEVICE_MUTED_STATE, this.processToggleMuteEvent)\n    );\n    this.subscriptions.push(\n      pubsub.subscribe(\n        TOGGLE_DEVICE_DESIGN_MODE_STATE,\n        this.changeDesignModeState\n      )\n    );\n\n    this.subscriptions.push(\n      pubsub.subscribe(\n        SET_NETWORK_TROTTLING_PROFILE,\n        this.setNetworkThrottlingProfile\n      )\n    );\n\n    this.subscriptions.push(\n      pubsub.subscribe(CLEAR_NETWORK_CACHE, this.clearNetworkCache)\n    );\n\n    this.subscriptions.push(\n      pubsub.subscribe(PROXY_AUTH_ERROR, this.onProxyError)\n    );\n\n    this.subscriptions.push(\n      pubsub.subscribe(\n        OPEN_CONSOLE_FOR_DEVICE,\n        this.processOpenConsoleForDeviceEvent\n      )\n    );\n\n    this.subscriptions.push(\n      pubsub.subscribe(PAGE_NAVIGATOR_CHANGED, this.selectorNavigationChanged)\n    );\n\n    this.webviewRef.current.addEventListener('dom-ready', () => {\n      this.initEventTriggers(this.webviewRef.current);\n      this.dbg = this.getWebContents().debugger;\n      if (!this.dbg.isAttached()) {\n        this.dbg.attach();\n        this.dbg.on('message', this._onDebuggerEvent);\n      }\n      if (this.isMobile) this.hideScrollbar();\n    });\n\n    if (this.props.transmitNavigatorStatus) {\n      this.webviewRef.current.addEventListener(\n        'page-favicon-updated',\n        ({favicons}) => this.props.onPageMetaFieldUpdate('favicons', favicons)\n      );\n\n      this.webviewRef.current.addEventListener(\n        'page-title-updated',\n        ({title}) => this.props.onPageMetaFieldUpdate('title', title)\n      );\n\n      this.webviewRef.current.addEventListener('new-window', e => {\n        if (\n          e.url.startsWith('file://') &&\n          !this.webviewRef.current.getURL().startsWith('file://')\n        ) {\n          this.webviewRef.current\n            .executeJavaScript(\n              `{\n                console.error('Not allowed to load local resource');\n              }`\n            )\n            .catch(captureOnSentry);\n          return;\n        }\n        ipcRenderer.send('open-new-window', {url: e.url});\n      });\n    }\n\n    this.webviewRef.current.addEventListener('did-start-loading', () => {\n      this.setState({errorCode: null, errorDesc: null, proxyAuthError: false});\n      this.props.onLoadingStateChange(true);\n      this.props.deviceLoadingChange({id: this.props.device.id, loading: true});\n    });\n    this.webviewRef.current.addEventListener('did-stop-loading', () => {\n      this.props.onLoadingStateChange(false);\n      this.props.deviceLoadingChange({\n        id: this.props.device.id,\n        loading: false,\n      });\n      this.changeDesignModeState({\n        designMode: !!this.props.device.designMode,\n      });\n    });\n    this.webviewRef.current.addEventListener(\n      'did-fail-load',\n      ({errorCode, errorDescription}) => {\n        if (errorCode === -3) {\n          // Aborted error, can be ignored\n          return;\n        }\n        this.setState({\n          errorCode,\n          errorDesc: errorDescription,\n        });\n      }\n    );\n\n    this.webviewRef.current.addEventListener(\n      'login',\n      (event, request, authInfo, callback) => {\n        event.preventDefault();\n        callback('username', 'secret');\n      }\n    );\n\n    const urlChangeHandler = async ({url, isMainFrame = true}) => {\n      if (!isMainFrame || url === this.props.browser.address) {\n        return;\n      }\n      await new Promise(r => setTimeout(r, 200));\n      this.props.onAddressChange(url);\n    };\n\n    const navigationHandler = event => {\n      if (this.props.transmitNavigatorStatus) {\n        this.props.updateNavigatorStatus({\n          backEnabled: this.webviewRef.current.canGoBack(),\n          forwardEnabled: this.webviewRef.current.canGoForward(),\n        });\n      }\n    };\n\n    this.webviewRef.current.addEventListener('will-navigate', urlChangeHandler);\n\n    this.webviewRef.current.addEventListener('did-navigate-in-page', event => {\n      navigationHandler(event);\n      urlChangeHandler(event);\n    });\n\n    this.webviewRef.current.addEventListener('did-navigate', event => {\n      urlChangeHandler(event);\n      navigationHandler(event);\n    });\n\n    this.webviewRef.current.addEventListener('update-target-url', event => {\n      this.props.setHoveredLink(event.url);\n    });\n\n    this.webviewRef.current.addEventListener('devtools-closed', () => {\n      if (\n        this.props.browser.devToolsConfig.mode === DEVTOOLS_MODES.UNDOCKED &&\n        this._isDevToolsOpen()\n      ) {\n        this._toggleDevTools();\n      }\n    });\n  }\n\n  componentDidUpdate(prevProps) {\n    if (prevProps.device.isMuted !== this.props.device.isMuted) {\n      if (this.props.device.isMuted) {\n        this._muteWebView();\n      } else {\n        this._unmuteWebView();\n      }\n    }\n\n    if (prevProps.device.designMode !== this.props.device.designMode) {\n      this.changeDesignModeState({\n        designMode: !!this.props.device.designMode,\n      });\n    }\n  }\n\n  getWebContentsId() {\n    return this.webviewRef.current.getWebContentsId();\n  }\n\n  getWebContents() {\n    return this.getWebContentForId(this.getWebContentsId());\n  }\n\n  getWebContentForId(id) {\n    return remote.webContents.fromId(id);\n  }\n\n  componentWillUnmount() {\n    this.subscriptions.forEach(pubsub.unsubscribe);\n    if (this.dbg && this.dbg.isAttached()) this.dbg.detach();\n  }\n\n  initDeviceEmulationParams = () => {\n    try {\n      return;\n      this.getWebContents().enableDeviceEmulation({\n        screenPosition: this.isMobile ? 'mobile' : 'desktop',\n        screenSize: {\n          width: this.props.device.width,\n          height: this.props.device.height,\n        },\n        deviceScaleFactor: this.props.device.dpr,\n      });\n    } catch (err) {\n      console.log('err', err);\n    }\n  };\n\n  selectorNavigationChanged = ({selector, index}) => {\n    if (selector == null || index == null) return;\n    this.webviewRef.current\n      .executeJavaScript(\n        `{\n          var elements = document.querySelectorAll('${selector}');\n          var len = elements.length;\n          if (len !== 0) {\n            var idx = ((${index} % len) + len) % len;\n            var el = elements[idx];\n            el.scrollIntoView(true);\n          }\n        }`\n      )\n      .catch(captureOnSentry);\n  };\n\n  processNavigationBackEvent = () => {\n    this.webviewRef.current.goBack();\n  };\n\n  processNavigationForwardEvent = () => {\n    this.webviewRef.current.goForward();\n  };\n\n  processNavigationReloadEvent = ({ignoreCache}) => {\n    if (ignoreCache) {\n      return this.webviewRef.current.reloadIgnoringCache();\n    }\n    this.webviewRef.current.reload();\n  };\n\n  processReloadCSSEvent = () => {\n    this.webviewRef.current\n      .executeJavaScript(\n        `{\n        var elements = document.querySelectorAll('link[rel=stylesheet][href]');\n        elements.forEach(element=>{\n          var href = element.href;\n          if(href){\n            var href = href.replace(/[?&]invalidateCacheParam=([^&$]*)/,'');\n            element.href = href + (href.indexOf('?')>=0?'&':'?') + 'invalidateCacheParam=' + (new Date().valueOf());\n          }\n        })\n    }`\n      )\n      .catch(captureOnSentry);\n  };\n\n  processAddressChangeEvent = ({address, force}) => {\n    if (address !== this.webviewRef.current.src) {\n      if (force) {\n        this.webviewRef.current.loadURL(address);\n      }\n      this.setState({\n        address,\n      });\n    }\n  };\n\n  processStopLoadingEvent = () => {\n    this.webviewRef.current.stop();\n  };\n\n  processDeleteStorageEvent = ({storages}) => {\n    this.getWebContents().session.clearStorageData({storages});\n  };\n\n  processScrollEvent = message => {\n    if (\n      this.state.isUnplugged ||\n      message.sourceDeviceId === this.props.device.id\n    ) {\n      return;\n    }\n    this.webviewRef.current.send('scrollMessage', message.position);\n  };\n\n  processClickEvent = message => {\n    if (\n      this.state.isUnplugged ||\n      message.sourceDeviceId === this.props.device.id\n    ) {\n      return;\n    }\n    this.webviewRef.current.send('clickMessage', message);\n  };\n\n  processScrollDownEvent = message => {\n    if (this.state.isUnplugged) {\n      return;\n    }\n    this.webviewRef.current.send('scrollDownMessage');\n  };\n\n  processScrollUpEvent = message => {\n    if (this.state.isUnplugged) {\n      return;\n    }\n    this.webviewRef.current.send('scrollUpMessage');\n  };\n\n  processToggleEventMirroring = async ({status}) => {\n    if (status) {\n      this.setState(\n        () => ({\n          ...this.state,\n          isUnplugged: !status,\n        }),\n        async () => {\n          await this.openBrowserSyncSocket(this.webviewRef.current);\n        }\n      );\n    } else {\n      this.setState(\n        () => ({\n          ...this.state,\n          isUnplugged: !status,\n        }),\n        async () => {\n          await this.closeBrowserSyncSocket(this.webviewRef.current);\n        }\n      );\n    }\n  };\n\n  processScreenshotEvent = async ({\n    now,\n    fullScreen = true,\n    deviceId,\n  }: {\n    now?: Date,\n    fullScreen?: boolean,\n  }) => {\n    if (deviceId && this.props.device.id !== deviceId) {\n      return;\n    }\n    this.setState({screenshotInProgress: true});\n    try {\n      await this.closeBrowserSyncSocket(this.webviewRef.current);\n      await captureScreenshot({\n        address: this.props.browser.address,\n        device: this.props.device,\n        webView: this.webviewRef.current,\n        createSeparateDir: now != null,\n        fullScreen,\n        now,\n        removeFixedPositionedElements: this.props.browser.userPreferences\n          .removeFixedPositionedElements,\n        screenshotMechanism: this.props.browser.userPreferences\n          .screenshotMechanism,\n        setFullDocumentDimensions: this._setFullDocumentDimensions,\n      });\n    } catch (err) {\n      console.log('Error during screen capture', err);\n    }\n    await this.openBrowserSyncSocket(this.webviewRef.current);\n    this.setState({screenshotInProgress: false});\n  };\n\n  processFlipOrientationEvent = (message = {}) => {\n    const {deviceId} = message;\n    if (deviceId && this.props.device.id !== deviceId) {\n      return;\n    }\n    this._flipOrientation();\n  };\n\n  processToggleMuteEvent = ({muted}) => {\n    this.getWebContents().setAudioMuted(muted);\n  };\n\n  changeDesignModeState = ({designMode}) => {\n    this.webviewRef.current\n      .executeJavaScript(\n        `document.designMode = \"${\n          designMode ? DESIGN_MODE_JS_VALUES.ON : DESIGN_MODE_JS_VALUES.OFF\n        }\";`\n      )\n      .catch(captureOnSentry);\n  };\n\n  processOpenDevToolsInspectorEvent = message => {\n    const {\n      x: webViewX,\n      y: webViewY,\n    } = this.webviewRef.current.getBoundingClientRect();\n    const {x: deviceX, y: deviceY} = message;\n    const zoomFactor = this.props.browser.zoomLevel;\n    if (this.props.browser.isInspecting) {\n      this.props.toggleInspector();\n    }\n    if (!this._isDevToolsOpen()) {\n      this._toggleDevTools();\n    }\n    this.getWebContents().inspectElement(\n      Math.round(webViewX + deviceX * zoomFactor),\n      Math.round(webViewY + deviceY * zoomFactor)\n    );\n  };\n\n  processOpenConsoleForDeviceEvent = message => {\n    const {deviceId} = message;\n    if (this.props.device.id !== deviceId) {\n      return;\n    }\n    this._toggleDevTools();\n  };\n\n  setNetworkThrottlingProfile = ({type, downloadKps, uploadKps, latencyMs}) => {\n    // TODO : change this when https://github.com/electron/electron/issues/21250 is solved\n    // if (type === 'Online') {\n    //   this.getWebContents().session.disableNetworkEmulation();\n    // } else if (type === 'Offline') {\n    //   this.getWebContents().session.enableNetworkEmulation({offline: true});\n    // } else if (type === 'Custom') {\n    //   const downloadThroughput = downloadKps != null? downloadKps * 128 : undefined;\n    //   const uploadThroughput = uploadKps != null? uploadKps * 128 : undefined;\n    //   this.getWebContents().session.enableNetworkEmulation({offline: false, latency: latencyMs, downloadThroughput, uploadThroughput });\n    // }\n\n    // WORKAROUND\n    if (type === 'Online') {\n      this.dbg.sendCommand('Network.disable');\n    } else if (type === 'Offline') {\n      this.dbg.sendCommand('Network.enable').then(_ => {\n        this.dbg.sendCommand('Network.emulateNetworkConditions', {\n          offline: true,\n          latency: 0,\n          downloadThroughput: -1,\n          uploadThroughput: -1,\n        });\n      });\n    } else {\n      const downloadThroughput = downloadKps != null ? downloadKps * 128 : -1;\n      const uploadThroughput = uploadKps != null ? uploadKps * 128 : -1;\n      const latency = latencyMs || 0;\n      this.dbg.sendCommand('Network.enable').then(_ => {\n        this.dbg.sendCommand('Network.emulateNetworkConditions', {\n          offline: false,\n          latency,\n          downloadThroughput,\n          uploadThroughput,\n        });\n      });\n    }\n  };\n\n  clearNetworkCache = () => {\n    this.getWebContents().session.clearCache();\n  };\n\n  onProxyError = () => {\n    this.setState({proxyAuthError: true});\n  };\n\n  messageHandler = ({channel: type, args: [message]}) => {\n    if (type !== MESSAGE_TYPES.toggleEventMirroring && this.state.isUnplugged) {\n      return;\n    }\n    switch (type) {\n      case MESSAGE_TYPES.scroll:\n        pubsub.publish('scroll', [message]);\n        return;\n      case MESSAGE_TYPES.click:\n        pubsub.publish('click', [message]);\n        return;\n      case MESSAGE_TYPES.openDevToolsInspector:\n        this.processOpenDevToolsInspectorEvent(message);\n        return;\n      case MESSAGE_TYPES.openConsole:\n        this._toggleDevTools();\n        return;\n      case MESSAGE_TYPES.tiltDevice:\n        this._flipOrientation();\n        return;\n      case MESSAGE_TYPES.takeScreenshot:\n        this.processScreenshotEvent({});\n        return;\n      case MESSAGE_TYPES.toggleEventMirroring:\n        this._unPlug();\n        break;\n      default:\n        break;\n    }\n  };\n\n  initBrowserSync = async webview => {\n    await this.getWebContentForId(webview.getWebContentsId())\n      .executeJavaScript(\n        `\n          var bsScript= document.createElement('script');\n          bsScript.src = '${getBrowserSyncEmbedScriptURL()}';\n          bsScript.async = true;\n          document.body.appendChild(bsScript);\n          true\n        `\n      )\n      .catch(captureOnSentry);\n  };\n\n  closeBrowserSyncSocket = async webview => {\n    await this.getWebContentForId(webview.getWebContentsId())\n      .executeJavaScript(\n        `\n        if(window.___browserSync___){\n          window.___browserSync___.socket.close()\n        }\n        true\n      `\n      )\n      .catch(captureOnSentry);\n  };\n\n  openBrowserSyncSocket = async webview => {\n    await this.getWebContentForId(webview.getWebContentsId())\n      .executeJavaScript(\n        `\n        if(window.___browserSync___){\n          window.___browserSync___.socket.open()\n        }\n        true\n      `\n      )\n      .catch(captureOnSentry);\n  };\n\n  processApplyCssEvent = async message => {\n    if (!message.css || !this.domLoaded) {\n      return;\n    }\n    if (this.liveCssKey) {\n      this.webviewRef.current.removeInsertedCSS(this.liveCssKey);\n      this.liveCssKey = null;\n    }\n    this.liveCssKey = await this.webviewRef.current.insertCSS(message.css);\n  };\n\n  initEventTriggers = async webview => {\n    await this.initBrowserSync(webview);\n    this.getWebContentForId(webview.getWebContentsId())\n      .executeJavaScript(\n        `{\n          responsivelyApp.deviceId = '${this.props.device.id}';\n        }`\n      )\n      .catch(captureOnSentry);\n\n    if (this.state.isUnplugged) {\n      await this.closeBrowserSyncSocket(webview);\n    }\n    this.domLoaded = true;\n  };\n\n  hideScrollbar = () => {\n    this.webviewRef.current.insertCSS(\n      `\n        ::-webkit-scrollbar {\n          display: none;\n        }\n        `\n    );\n  };\n\n  _setFullDocumentDimensions = (\n    fullDocumentHeight,\n    fullDocumentWidth,\n    zoomLevel = null\n  ) => {\n    this.setState({fullDocumentHeight, fullDocumentWidth, zoomLevel});\n  };\n\n  _isDevToolsOpen = () =>\n    !!this.props.browser.devToolsConfig.activeDevTools.find(\n      ({deviceId}) => deviceId === this.props.device.id\n    );\n\n  _toggleDevTools = () => {\n    if (this._isDevToolsOpen()) {\n      return this.props.onDevToolsClose({\n        deviceId: this.props.device.id,\n        webViewId: this.getWebContentsId(),\n      });\n    }\n    this.props.onDevToolsOpen(this.props.device.id, this.getWebContentsId());\n  };\n\n  _flipOrientation = () => {\n    if (!this.isMobile) return;\n\n    if (this.props.sendFlipStatus) {\n      this.props.sendFlipStatus(!this.state.isTilted);\n    }\n    const flippedDeviceDims = {\n      width: this.state.deviceDimensions.height,\n      height: this.state.deviceDimensions.width,\n    };\n    this.setState({\n      isTilted: !this.state.isTilted,\n      deviceDimensions: flippedDeviceDims,\n    });\n  };\n\n  _unPlug = () => {\n    if (this.state.isUnplugged) {\n      this.openBrowserSyncSocket(this.webviewRef.current);\n    } else {\n      this.closeBrowserSyncSocket(this.webviewRef.current);\n    }\n    this.setState({isUnplugged: !this.state.isUnplugged}, () => {\n      this.webviewRef.current.send(\n        'eventsMirroringState',\n        !this.state.isUnplugged\n      );\n    });\n  };\n\n  _toggleDesignMode = () => {\n    const {id: deviceId} = this.props.device;\n    this.props.onToggleDeviceDesignMode(deviceId);\n  };\n\n  _focusDevice = () => {\n    this.props.setPreviewLayout(INDIVIDUAL_LAYOUT);\n    this.props.setFocusedDevice(this.props.device.id);\n  };\n\n  _unfocusDevice = () => {\n    if (this.props.browser.previewer.previousLayout) {\n      this.props.setPreviewLayout(this.props.browser.previewer.previousLayout);\n    }\n  };\n\n  _muteWebView = () => {\n    this.getWebContents().setAudioMuted(true);\n  };\n\n  _unmuteWebView = () => {\n    this.getWebContents().setAudioMuted(false);\n  };\n\n  get isMobile() {\n    return this.props.device.capabilities.indexOf(CAPABILITIES.mobile) > -1;\n  }\n\n  _setResizeDimensions = (event, direction, ref, delta) => {\n    const {temporaryDims} = this.state;\n    const {updateResponsiveDimensions} = this.props;\n    if (!temporaryDims) return;\n    const updatedDeviceDims = {\n      width: temporaryDims.width + delta.width,\n      height: temporaryDims.height + delta.height,\n    };\n    this.setState(\n      {\n        deviceDimensions: updatedDeviceDims,\n      },\n      () => {\n        updateResponsiveDimensions(this.state.deviceDimensions);\n      }\n    );\n  };\n\n  _onDebuggerEvent = async (event, method, params) => {\n    switch (method) {\n      case 'Overlay.inspectNodeRequested':\n        await this._onInspectNodeRequested(params);\n        break;\n      default:\n        break;\n    }\n  };\n\n  _onInspectNodeRequested = async ({backendNodeId}) => {\n    if (!this.props.browser.isInspecting) {\n      return;\n    }\n    const [\n      {\n        model: {\n          content: [x, y],\n        },\n      },\n    ] = await Promise.all([\n      this.dbg.sendCommand('DOM.getBoxModel', {\n        backendNodeId,\n      }),\n      this.dbg.sendCommand('Overlay.setInspectMode', {\n        mode: 'none',\n        highlightConfig: {},\n      }),\n    ]);\n    this.processOpenDevToolsInspectorEvent({x, y});\n  };\n\n  _onMouseEnter = async () => {\n    if (!this.props.browser.isInspecting) {\n      return;\n    }\n    try {\n      await this.dbg.sendCommand('DOM.enable');\n      await this.dbg.sendCommand('Overlay.enable');\n      await this.dbg.sendCommand('Overlay.setInspectMode', {\n        mode: 'searchForNode',\n        highlightConfig: {\n          showInfo: true,\n          showStyles: true,\n          contentColor: {r: 111, g: 168, b: 220, a: 0.66},\n          paddingColor: {r: 147, g: 196, b: 125, a: 0.66},\n          borderColor: {r: 255, g: 229, b: 153, a: 0.66},\n          marginColor: {r: 246, g: 178, b: 107, a: 0.66},\n        },\n      });\n    } catch (err) {\n      console.log('Error enabling overlay', err);\n    }\n  };\n\n  _onMouseLeave = async () => {\n    if (!this.props.browser.isInspecting) {\n      return;\n    }\n    try {\n      await this.dbg.sendCommand('Overlay.disable');\n      await this.dbg.sendCommand('DOM.disable');\n    } catch (err) {\n      console.log('Error disabling overlay', err);\n    }\n  };\n\n  _getWebViewTag = (deviceStyles, containerWidth, containerHeight) => {\n    const {\n      classes,\n      device: {id, useragent, capabilities},\n    } = this.props;\n    const {deviceDimensions, address, isTilted} = this.state;\n    const outlinePx = this.props.browser.userPreferences.deviceOutlineStyle\n      ? 3\n      : 0;\n\n    if (capabilities.includes(CAPABILITIES.responsive)) {\n      return (\n        <Resizable\n          className={styles.resizableView}\n          size={{\n            width: containerWidth + outlinePx,\n            height: containerHeight + outlinePx,\n          }}\n          onResizeStart={() => {\n            const updatedTempDims = {\n              width: deviceDimensions.width,\n              height: deviceDimensions.height,\n            };\n            this.setState({\n              temporaryDims: updatedTempDims,\n            });\n          }}\n          onResize={debounce(this._setResizeDimensions, 25, {maxWait: 50})}\n          onResizeStop={() => {\n            this.setState({\n              temporaryDims: null,\n            });\n          }}\n          handleComponent={{\n            right: (\n              <div\n                className={cx(classes.iconWrapper, styles.iconWrapperE)}\n                {...this.props}\n              >\n                <div className={classes.iconHolder} />\n              </div>\n            ),\n            bottom: (\n              <div\n                className={cx(classes.iconWrapper, styles.iconWrapperS)}\n                {...this.props}\n              >\n                <div className={classes.iconHolder} />\n              </div>\n            ),\n            bottomRight: (\n              <div\n                className={cx(classes.iconWrapper, styles.iconWrapperSE)}\n                {...this.props}\n              >\n                <div className={classes.iconHolder} />\n              </div>\n            ),\n          }}\n        >\n          <webview\n            ref={this.webviewRef}\n            preload=\"./preload.js\"\n            className={cx(styles.device)}\n            src={address}\n            useragent={useragent}\n            style={deviceStyles}\n          />\n        </Resizable>\n      );\n    }\n\n    return (\n      <>\n        <webview\n          ref={this.webviewRef}\n          preload=\"./preload.js\"\n          className={cx(styles.device)}\n          src={address}\n          useragent={useragent}\n          style={deviceStyles}\n          webpreferences={\n            this.props.browser.userPreferences.disableSpellCheck\n              ? 'spellcheck=no'\n              : 'spellcheck=yes'\n          }\n        />\n      </>\n    );\n  };\n\n  render() {\n    const {\n      browser: {zoomLevel, previewer},\n      device: {capabilities},\n      classes,\n      theme,\n    } = this.props;\n    const {\n      deviceDimensions,\n      errorCode,\n      errorDesc,\n      screenshotInProgress,\n      proxyAuthError,\n    } = this.state;\n    const screenshotZoomLevel = screenshotInProgress && this.state.zoomLevel;\n    const outline = `4px solid ${\n      this._isDevToolsOpen()\n        ? `#6075ef`\n        : this.props.browser.userPreferences.deviceOutlineStyle ||\n          'rgba(0, 0, 0, 0)'\n    }`;\n    const deviceStyles = {\n      outline,\n      width:\n        (this.state.screenshotInProgress && this.state.fullDocumentWidth) ||\n        deviceDimensions.width,\n      height:\n        (this.state.screenshotInProgress && this.state.fullDocumentHeight) ||\n        deviceDimensions.height,\n      transform: `scale(${screenshotZoomLevel || zoomLevel})`,\n    };\n    const overlayStyles = {\n      outline,\n      width: deviceDimensions.width,\n      height: deviceDimensions.height,\n      transform: `scale(${zoomLevel})`,\n    };\n\n    const containerWidth = deviceStyles.width * zoomLevel;\n    const containerHeight = deviceStyles.height * zoomLevel;\n\n    const isMuted = this.props.device.isMuted;\n    const isResponsive = capabilities.includes(CAPABILITIES.responsive);\n    const shouldMaximize = previewer.layout !== INDIVIDUAL_LAYOUT;\n    const IconFocus = () => {\n      if (shouldMaximize) return <Focus height={30} padding={6} />;\n      return <Unfocus height={30} padding={6} />;\n    };\n    return (\n      <div\n        className={cx(styles.webViewContainer, {\n          [styles.withMarginRight]: isResponsive,\n        })}\n      >\n        <div className={cx(styles.webViewToolbar)}>\n          <div className={cx(styles.webViewToolbarLeft)}>\n            <Tooltip title=\"Open DevTools\">\n              <div\n                className={cx(styles.webViewToolbarIcons, classes.icon, {\n                  [classes.iconSelected]: this._isDevToolsOpen(),\n                })}\n                onClick={this._toggleDevTools}\n              >\n                <BugIcon width={20} />\n              </div>\n            </Tooltip>\n            <Tooltip title=\"Quick Screenshot\">\n              <div\n                className={cx(styles.webViewToolbarIcons, classes.icon)}\n                onClick={() => this.processScreenshotEvent({fullScreen: false})}\n              >\n                <ScreenshotIcon height={18} />\n              </div>\n            </Tooltip>\n            <Tooltip title=\"Disable event mirroring\">\n              <div\n                className={cx(styles.webViewToolbarIcons, classes.icon, {\n                  [classes.iconSelected]: this.state.isUnplugged,\n                })}\n                onClick={this._unPlug}\n              >\n                <UnplugIcon height={30} />\n              </div>\n            </Tooltip>\n            <Tooltip\n              title={`${\n                this.props.device.designMode ? 'Disable' : 'Enable'\n              } Design Mode`}\n            >\n              <div\n                className={cx(styles.webViewToolbarIcons, classes.icon, {\n                  [classes.iconSelected]: this.props.device.designMode,\n                })}\n                onClick={this._toggleDesignMode}\n              >\n                <DesignModeIcon height={20} />\n              </div>\n            </Tooltip>\n          </div>\n          <div className={cx(styles.webViewToolbarRight)}>\n            <Tooltip\n              title={shouldMaximize ? 'Maximize' : 'Minimize'}\n              disableFocusListener\n            >\n              <div\n                className={cx(styles.webViewToolbarIcons, classes.icon)}\n                onClick={\n                  shouldMaximize ? this._focusDevice : this._unfocusDevice\n                }\n              >\n                <IconFocus />\n              </div>\n            </Tooltip>\n          </div>\n        </div>\n        <div\n          className={classes.deviceContainer}\n          style={{\n            width: containerWidth,\n            height: containerHeight,\n          }}\n          onMouseEnter={this._onMouseEnter}\n          onMouseLeave={this._onMouseLeave}\n        >\n          <div\n            className={cx(styles.deviceOverlay, {\n              [styles.overlayEnabled]: screenshotInProgress,\n            })}\n            style={overlayStyles}\n          >\n            <Spinner size={24} />\n          </div>\n          <div\n            className={cx(styles.deviceOverlay, {\n              [styles.overlayEnabled]: errorCode,\n            })}\n            style={deviceStyles}\n          >\n            <p>ERROR: {errorCode}</p>\n            <p className={cx(styles.errorDesc)}>{errorDesc}</p>\n\n            {proxyAuthError && (\n              <p className={cx(styles.errorDesc)}>Proxy Authentication Error</p>\n            )}\n\n            {isSslValidationFailed(errorCode) && (\n              <p className={cx(classes.errorHelpSuggestion)}>\n                If you wish to proceed, you can disable the SSL validation in\n                the user preferences.\n              </p>\n            )}\n          </div>\n          {this._getWebViewTag(deviceStyles, containerWidth, containerHeight)}\n        </div>\n      </div>\n    );\n  }\n}\n\nconst webViewStyles = theme => ({\n  ...commonStyles(theme),\n  deviceContainer: {\n    position: 'relative',\n    display: 'inline-flex',\n    transformOrigin: 'top left',\n  },\n  iconWrapper: {\n    display: 'flex',\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'center',\n    position: 'absolute',\n    backgroundPosition: 'center',\n    padding: 0,\n    backgroundColor: theme.palette.background.l2,\n    '&:hover': {\n      backgroundColor: theme.palette.background.l5,\n    },\n  },\n  iconHolder: {\n    position: 'relative',\n    display: 'block',\n    height: '7px',\n    cursor: 'pointer',\n    '&::before': {\n      content: '\"\"',\n      position: 'absolute',\n      left: 0,\n      width: '15px',\n      height: '2px',\n      backgroundColor: theme.palette.text.dim,\n    },\n    '&::after': {\n      content: '\"\"',\n      position: 'absolute',\n      width: '15px',\n      height: '2px',\n      backgroundColor: theme.palette.text.dim,\n      bottom: 0,\n    },\n  },\n  errorHelpSuggestion: {\n    position: 'absolute',\n    top: '25%',\n    width: '100%',\n    padding: 35,\n    background: theme.palette.primary.main,\n  },\n});\nexport default withStyles(webViewStyles)(withTheme(WebView));\n"
  },
  {
    "path": "desktop-app-legacy/app/components/WebView/index.test.js",
    "content": "// @flow\nimport React from 'react';\nimport {shallow} from 'enzyme';\nimport {expect} from 'chai';\n\nimport Renderer from '.';\n\nconst testSrc = 'https://testUrl.com';\nconst testDevice1 = {\n  name: 'testDevice1',\n  width: 100,\n  height: 100,\n};\n\ndescribe('<Renderer />', () => {\n  it('Renders the header and the iframe', () => {\n    const wrapper = shallow(<Renderer src={testSrc} device={testDevice1} />);\n    expect(wrapper.find('iframe')).to.have.lengthOf(1);\n    expect(wrapper.find('h2')).to.have.lengthOf(1);\n  });\n\n  it('Renders the header with the device name', () => {\n    const wrapper = shallow(<Renderer src={testSrc} device={testDevice1} />);\n    expect(wrapper.find('h2').text()).to.equal(testDevice1.name);\n  });\n\n  it('Renders the iframe with the given device dimensions', () => {\n    const wrapper = shallow(<Renderer src={testSrc} device={testDevice1} />);\n    expect(wrapper.find('iframe').prop('width')).to.equal(testDevice1.width);\n    expect(wrapper.find('iframe').prop('height')).to.equal(testDevice1.height);\n  });\n\n  it('Renders the iframe with the given url', () => {\n    const wrapper = shallow(<Renderer src={testSrc} device={testDevice1} />);\n    expect(wrapper.find('iframe').prop('src')).to.equal(testSrc);\n  });\n\n  /* it('Calls the callback with a number value', () => {\n    const onChange = sinon.spy();\n    const wrapper = mount(<BrowserZoom onChange={onChange} />);\n    wrapper.find('.MuiSlider-thumb').simulate('mousedown');\n    wrapper.find('.MuiSlider-thumb').simulate('mouseup');\n    console.log('spy.args', onChange.args);\n    assert(onChange.calledWith(100));\n  }); */\n});\n"
  },
  {
    "path": "desktop-app-legacy/app/components/WebView/screenshotUtil.js",
    "content": "// @flow\nimport React from 'react';\nimport {shell, remote} from 'electron';\nimport {toast} from 'react-toastify';\nimport _mergeImg from 'merge-img';\nimport os from 'os';\nimport {promisify} from 'util';\nimport Promise from 'bluebird';\nimport path from 'path';\nimport fs from 'fs-extra';\nimport Jimp from 'jimp';\nimport PromiseWorker from 'promise-worker';\nimport NotificationMessage from '../NotificationMessage';\nimport {userPreferenceSettings} from '../../settings/userPreferenceSettings';\nimport {type Device} from '../../constants/devices';\nimport {captureOnSentry} from '../../utils/logUtils';\nimport {SCREENSHOT_MECHANISM} from '../../constants/values';\nimport mutexify from 'mutexify/promise';\n\nconst mergeImg = Promise.promisifyAll(_mergeImg);\nconst snapshotLock = mutexify();\n\nconst captureScreenshot = async ({\n  address,\n  device,\n  webView,\n  createSeparateDir,\n  now,\n  fullScreen = false,\n  removeFixedPositionedElements,\n  screenshotMechanism,\n  setFullDocumentDimensions,\n}) => {\n  const worker = new Worker('./imageWorker.js');\n  const promiseWorker = new PromiseWorker(worker);\n  const toastId = toast.info(\n    <NotificationMessage\n      spinner\n      message={`Capturing ${device.name} screenshot...`}\n    />,\n    {autoClose: false}\n  );\n  const resultFilename = _getScreenshotFileName(\n    address,\n    device,\n    now,\n    createSeparateDir,\n    fullScreen\n  );\n  const webViewUtils = new WebViewUtils(webView, setFullDocumentDimensions);\n  const insertedCSSKey = await webViewUtils.hideScrollbarAndFixedPositionedElements(\n    removeFixedPositionedElements\n  );\n\n  let images = null;\n\n  if (!fullScreen) {\n    await webViewUtils.getViewportImage(resultFilename);\n  } else if (screenshotMechanism === SCREENSHOT_MECHANISM.V2) {\n    await webViewUtils.captureFullPageV2(resultFilename);\n  } else {\n    images = await webViewUtils.getFullScreenImages(promiseWorker);\n  }\n\n  await webViewUtils.unHideScrollbarAndFixedPositionedElements(\n    insertedCSSKey,\n    removeFixedPositionedElements\n  );\n\n  if (images != null) {\n    toast.update(toastId, {\n      render: (\n        <NotificationMessage\n          spinner\n          message={`Processing ${device.name} screenshot...`}\n        />\n      ),\n      type: toast.TYPE.INFO,\n    });\n\n    const mergedImage = await promiseWorker.postMessage({\n      images,\n      direction: 'vertical',\n      resultFilename,\n    });\n  }\n\n  toast.update(toastId, {\n    render: (\n      <NotificationMessage tick message={`${device.name} screenshot taken!`} />\n    ),\n    type: toast.TYPE.INFO,\n    autoClose: 2000,\n  });\n\n  await _delay(250);\n  shell.showItemInFolder(path.join(resultFilename.dir, resultFilename.file));\n};\n\nclass WebViewUtils {\n  webView: WebviewElement;\n  webContents;\n  setFullDocumentDimensions;\n\n  constructor(webView, setFullDocumentDimensions) {\n    this.webView = webView;\n    this.webContents = remote.webContents.fromId(\n      this.webView.getWebContentsId()\n    );\n    this.setFullDocumentDimensions = setFullDocumentDimensions;\n  }\n\n  getWindowSizeAndScrollDetails(): Promise {\n    return this.webView\n      .executeJavaScript(\n        `\n          responsivelyApp.screenshotVar = {\n            previousScrollPosition : {\n              left: window.scrollX,\n              top: window.scrollY,\n            },\n            scrollHeight: document.body.scrollHeight,\n            scrollWidth: document.body.scrollWidth,\n            viewPortHeight: document.documentElement.clientHeight,\n            viewPortWidth: document.documentElement.clientWidth,\n          };\n          responsivelyApp.screenshotVar;\n        `\n      )\n      .catch(captureOnSentry);\n  }\n\n  async scrollTo(scrollX: number, scrollY: number, doDelay = false): Promise {\n    await this.webView\n      .executeJavaScript(\n        `\n          window.scrollTo(${scrollX}, ${scrollY})\n        `\n      )\n      .catch(captureOnSentry);\n    if (!doDelay) {\n      return;\n    }\n    // wait a little for the scroll to take effect.\n    await _delay(500);\n  }\n\n  async hideScrollbarAndFixedPositionedElements(\n    removeFixedPositionedElements: boolean\n  ): Promise<string> {\n    const key = await this.webView.insertCSS(`\n      body::-webkit-scrollbar {\n        display: none;\n      }\n\n      .responsivelyApp__HiddenForScreenshot {\n        display: none !important;\n      }\n    `);\n\n    if (removeFixedPositionedElements) {\n      await this.webView\n        .executeJavaScript(\n          `\n        responsivelyApp.hideFixedPositionElementsForScreenshot();\n      `\n        )\n        .catch(captureOnSentry);\n    }\n\n    // wait a little for the 'hide' effect to take place.\n    await _delay(200);\n\n    return key;\n  }\n\n  async unHideScrollbarAndFixedPositionedElements(\n    insertedCSSKey,\n    removeFixedPositionedElements: boolean\n  ): Promise {\n    await this.webView.removeInsertedCSS(insertedCSSKey);\n    if (removeFixedPositionedElements) {\n      return this.webView\n        .executeJavaScript(\n          `\n        document.body.classList.remove('responsivelyApp__ScreenshotInProgress');\n        responsivelyApp.unHideElementsHiddenForScreenshot();\n      `\n        )\n        .catch(captureOnSentry);\n    }\n    return Promise.resolve(true);\n  }\n\n  async getScrollPercent(): Promise<Number> {\n    return this.webContents.executeJavaScriptInIsolatedWorld(\n      Math.round(Math.random() * 1000),\n      [\n        {\n          code: `\n          var h = document.documentElement,\n          b = document.body,\n          st = 'scrollTop',\n          sh = 'scrollHeight';\n          ((h[st] || b[st]) / ((h[sh] || b[sh]) - h.clientHeight)) * 100;\n        `,\n        },\n      ]\n    );\n  }\n\n  async scrollViewPort(): Promise {\n    return this.webContents.executeJavaScriptInIsolatedWorld(\n      Math.round(Math.random() * 1000),\n      [\n        {\n          code: `\n            scrollBy(0, window.innerHeight);\n            true\n          `,\n        },\n      ]\n    );\n  }\n\n  async setWhiteBG() {\n    const isTransparentBG = this.webContents.executeJavaScriptInIsolatedWorld(\n      Math.round(Math.random() * 1000),\n      [\n        {\n          code: `\n          const transparentBGStyle = 'rgba(0, 0, 0, 0)';\n          const styles = window.getComputedStyle(document.getElementsByTagName('body')[0])\n          styles.background.indexOf(transparentBGStyle) !== -1 || styles.backgroundColor.indexOf(transparentBGStyle) !== -1\n        `,\n        },\n      ]\n    );\n    if (!isTransparentBG) {\n      return;\n    }\n    this.bgModKey = await this.webView.insertCSS(`\n      body {\n        background-color: white;\n      }\n    `);\n  }\n\n  async setScrollBehaviorToAuto() {\n    this.scrollModKey = await this.webView.insertCSS(`\n      html, body {\n        scroll-behavior: auto !important;\n      }\n    `);\n  }\n\n  async resetScrollBehavior() {\n    if (!this.scrollModKey) {\n      return;\n    }\n    await this.webView.removeInsertedCSS(this.scrollModKey);\n    this.scrollModKey = null;\n  }\n\n  async resetBG() {\n    if (!this.bgModKey) {\n      return;\n    }\n    await this.webView.removeInsertedCSS(this.bgModKey);\n    this.bgModKey = null;\n  }\n\n  async doFullPageScrollToLoadLazyLoadedSections(): Promise {\n    const {scrollHeight: before} = await this.getWindowSizeAndScrollDetails();\n    let scrollPercent = await this.getScrollPercent();\n    while (scrollPercent !== 100 && !Number.isNaN(scrollPercent)) {\n      await this.scrollViewPort();\n      await _delay(100);\n      scrollPercent = await this.getScrollPercent();\n    }\n    const {scrollHeight: after} = await this.getWindowSizeAndScrollDetails();\n  }\n\n  async captureFullPageV2({dir, file}) {\n    this.setWhiteBG();\n    this.setScrollBehaviorToAuto();\n    const {previousScrollPosition} = await this.getWindowSizeAndScrollDetails();\n    await this.doFullPageScrollToLoadLazyLoadedSections();\n    const {\n      scrollHeight,\n      viewPortHeight,\n      scrollWidth,\n      viewPortWidth,\n    } = await this.getWindowSizeAndScrollDetails();\n\n    this.setFullDocumentDimensions(scrollHeight, scrollWidth, 0.01);\n\n    await _delay(500);\n\n    const image = await this.takeSnapshot();\n    this.resetBG();\n    this.resetScrollBehavior();\n    this.setFullDocumentDimensions(null, null, null);\n    await this.writeNativeImageToFile(image, dir, file);\n    await this.scrollTo(\n      previousScrollPosition.left,\n      previousScrollPosition.top\n    );\n  }\n\n  async getViewportImage({dir, file}): Promise {\n    await this.setWhiteBG();\n    const image = await this.takeSnapshot();\n    this.resetBG();\n    await this.writeNativeImageToFile(image, dir, file);\n  }\n\n  async writeNativeImageToFile(image, dir, file) {\n    const ensureDirPromise = fs.ensureDir(dir);\n    const jpg = image.toJPEG(100);\n    await ensureDirPromise;\n    await fs.writeFile(path.join(dir, file), jpg);\n  }\n\n  async getFullScreenImages(promiseWorker: PromiseWorker): Promise {\n    this.setWhiteBG();\n    const {\n      previousScrollPosition,\n      scrollHeight,\n      viewPortHeight,\n      scrollWidth,\n      viewPortWidth,\n    } = await this.getWindowSizeAndScrollDetails();\n\n    const images = [];\n    let scrollX = 0;\n    let scrollY = 0;\n    for (\n      let pageY = 0;\n      scrollY < scrollHeight;\n      pageY++, scrollY = viewPortHeight * pageY\n    ) {\n      scrollX = 0;\n      const columnImages = [];\n      for (\n        let pageX = 0;\n        scrollX < scrollWidth;\n        pageX++, scrollX = viewPortWidth * pageX\n      ) {\n        await this.scrollTo(scrollX, scrollY, true);\n\n        const options = {\n          x: 0,\n          y: 0,\n          width: viewPortWidth,\n          height: viewPortHeight,\n        };\n        if (scrollX + viewPortWidth > scrollWidth) {\n          options.width = scrollWidth - scrollX;\n          options.x = viewPortWidth - options.width;\n        }\n        if (scrollY + viewPortHeight > scrollHeight) {\n          options.height = scrollHeight - scrollY;\n          options.y = viewPortHeight - options.height;\n        }\n        const image = await this.takeSnapshot(options);\n        columnImages.push(image);\n      }\n      const jpgs = columnImages.map(img => img.toJPEG(100));\n      images.push(\n        await promiseWorker.postMessage(\n          {\n            images: jpgs,\n            direction: 'horizontal',\n          },\n          [...jpgs]\n        )\n      );\n    }\n\n    this.scrollTo(previousScrollPosition.left, previousScrollPosition.top);\n    this.resetBG();\n    return images;\n  }\n\n  async takeSnapshot(options): Promise {\n    const release = await snapshotLock();\n    const image = await remote.webContents\n      .fromId(this.webView.getWebContentsId())\n      .capturePage(options);\n    release();\n    return image;\n  }\n}\n\nconst _delay = ms =>\n  new Promise((resolve, reject) => {\n    setTimeout(() => resolve(), ms);\n  });\n\nfunction _getScreenshotFileName(\n  address,\n  device,\n  now = new Date(),\n  createSeparateDir,\n  fullScreen,\n  format = 'jpg'\n) {\n  const dateString = `${now\n    .toLocaleDateString()\n    .split('/')\n    .reverse()\n    .join('-')} at ${now\n    .toLocaleTimeString([], {hour12: true})\n    .replace(/:/g, '.')\n    .toUpperCase()}`;\n  const directoryPath = createSeparateDir ? `${dateString}/` : '';\n  const userSelectedScreenShotSavePath = userPreferenceSettings.getScreenShotSavePath();\n  const specialCharactersRegex = /[\"\\\\/*|:?<>]/g;\n  return {\n    dir: path.join(\n      userSelectedScreenShotSavePath ||\n        path.join(os.homedir(), `Desktop/Responsively-Screenshots`),\n      directoryPath\n    ),\n    file: `${getWebsiteName(address)} ${\n      fullScreen ? '- Full ' : ''\n    }- ${device.name.replace(/\\//g, '-').replace(specialCharactersRegex, '')} - ${dateString}.${format}`,\n  };\n}\n\nconst getWebsiteName = (address: string) => {\n  let domain = '';\n  if (address.startsWith('file://')) {\n    const fileNameStartingIndex = address.lastIndexOf('/') + 1;\n    let htmIndex = address.indexOf('.htm');\n    if (htmIndex === -1) {\n      htmIndex = address.length;\n    }\n    domain = address.substring(fileNameStartingIndex, htmIndex);\n  } else {\n    domain = new URL(address).hostname;\n    domain = domain.replace('www.', '');\n    const dotIndex = domain.indexOf('.');\n    if (dotIndex > -1) {\n      domain = domain.substr(0, domain.indexOf('.'));\n    }\n  }\n  return domain.charAt(0).toUpperCase() + domain.slice(1);\n};\n\nexport {getWebsiteName, captureScreenshot};\n"
  },
  {
    "path": "desktop-app-legacy/app/components/WebView/style.module.css",
    "content": ".webViewContainer {\n  display: flex;\n  flex-direction: column;\n  min-width: 200px;\n}\n\n.webViewToolbar {\n  margin: 8px 3px 3px 3px;\n  display: flex;\n  align-items: center;\n  width: auto;\n  justify-content: space-between;\n}\n\n.webViewToolbarLeft {\n  display: flex;\n}\n\n.webViewToolbarIcons {\n  padding: 0 2px;\n  align-items: center;\n  justify-content: center;\n  display: flex;\n  height: 30px;\n  width: 30px;\n}\n\n.webViewToolbarRight {\n  display: flex;\n}\n\n.webViewToolbarLeft .webViewToolbarIcons {\n  margin-right: 4px;\n}\n\n.webViewToolbarRight .webViewToolbarIcons {\n  margin-left: 4px;\n}\n\n.device {\n  transform-origin: top left;\n  background: white;\n  position: absolute;\n  top: 0;\n  left: 0;\n}\n\n.deviceOverlay {\n  transform-origin: top left;\n  position: absolute;\n  top: 0;\n  left: 0;\n  display: none;\n  z-index: 1;\n  text-align: center;\n  color: #f8f8f8;\n}\n\n.deviceOverlay.overlayEnabled {\n  display: block;\n  background: rgba(0, 0, 0, 0.8);\n}\n\n.errorDesc {\n  font-size: 20px;\n  word-break: break-all;\n}\n\n.iconWrapperS {\n  width: 100%;\n  height: 15px;\n  bottom: -10px;\n  left: 0;\n}\n.iconWrapperE {\n  width: 15px;\n  height: 100%;\n  transform: rotate(180deg);\n  top: 0;\n  right: -10px;\n}\n.iconWrapperSE {\n  right: -5px;\n  bottom: -5px;\n  height: 15px;\n  width: 15px;\n}\n\n.iconWrapperE div {\n  right: 1px;\n  transform: rotate(90deg);\n}\n\n.iconWrapperS div {\n  bottom: 0;\n}\n\n.iconWrapperSE div {\n  right: 3px;\n  top: 2px;\n  height: 6px;\n  transform: rotate(-45deg);\n}\n\n.iconWrapperSE div::before {\n  left: -2px;\n  width: 11px;\n  height: 2px;\n}\n\n.iconWrapperSE div::after {\n  bottom: 0;\n  width: 7px;\n  height: 2px;\n}\n\n.resizableView {\n  margin: 0 1rem 1rem 0;\n}\n\n.withMarginRight {\n  margin-right: 5rem;\n}\n\n/* For tablets (iPad Mini, iPad Pro, general tablet size) */\n@media (max-width: 1024px) {\n  .webViewContainer {\n    min-width: 150px;\n  }\n  .webViewToolbar {\n    margin: 5px 2px;\n    flex-direction: column;\n  }\n  .webViewToolbarIcons {\n    height: 25px;\n    width: 25px;\n  }\n  .resizableView {\n    margin: 0 0.5rem 0.5rem 0;\n  }\n}\n\n/* For mobile devices (iPhone X, iPhone 14 Pro) */\n@media (max-width: 768px) {\n  .webViewContainer {\n    min-width: 120px;\n  }\n  .webViewToolbar {\n    margin: 3px 1px;\n    flex-direction: column;\n    align-items: flex-start;\n  }\n  .webViewToolbarIcons {\n    height: 20px;\n    width: 20px;\n  }\n  .resizableView {\n    margin: 0 0.25rem 0.25rem 0;\n  }\n}\n\n/* For small screens */\n@media (max-width: 480px) {\n  .webViewContainer {\n    min-width: 100px;\n  }\n  .webViewToolbar {\n    margin: 2px 1px;\n    flex-direction: column;\n    align-items: flex-start;\n  }\n  .webViewToolbarIcons {\n    height: 18px;\n    width: 18px;\n  }\n  .resizableView {\n    margin: 0 0.25rem 0.25rem 0;\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ZenButton/index.js",
    "content": "// @flow\nimport React from 'react';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport {useTheme, makeStyles} from '@material-ui/core/styles';\nimport Chevron from '../icons/Chevron';\nimport cx from 'classnames';\n\n/**\n * Button with a Chevron in the middle used for toggling zen mode on/off.\n * @param active Indicates whether zen mode is on or not.\n * @param onClick  Callback function for when the button is clicked.\n */\nconst ZenButton = ({active = false, onClick}) => {\n  const classes = useStyles();\n  const theme = useTheme();\n  return (\n    <div className={cx(['zenButton', classes.container])} onClick={onClick}>\n      <Tooltip title=\"Hide/Show\">\n        <div className={cx([classes.icon, {invert: active}])}>\n          <Chevron width={19} height={8} color={theme.palette.lightIcon.main} />\n        </div>\n      </Tooltip>\n    </div>\n  );\n};\n\nconst useStyles = makeStyles(theme => ({\n  container: {\n    alignItems: 'center',\n    borderRadius: '0 0 8px 8px',\n    display: 'flex',\n    height: '20px',\n    justifyContent: 'center',\n    textAlign: 'center',\n    width: '80px',\n    cursor: 'pointer',\n    boxShadow: '0 5px 5px rgba(0, 0, 0, 0.35)',\n    zIndex: 90000,\n  },\n  icon: {\n    marginTop: '-5px',\n    '&.invert': {\n      transform: 'rotateX(180deg) translateY(-5px)',\n    },\n  },\n}));\n\nexport default ZenButton;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ZoomInput/index.js",
    "content": "import React, {Component, useState, useRef, useEffect} from 'react';\nimport cx from 'classnames';\nimport Slider from '@material-ui/core/Slider';\nimport Typography from '@material-ui/core/Typography';\nimport ZoomInIcon from '@material-ui/icons/ZoomIn';\nimport ZoomOutIcon from '@material-ui/icons/ZoomOut';\nimport PlusIcon from '@material-ui/icons/Add';\nimport MinusIcon from '@material-ui/icons/Minimize';\nimport Grid from '@material-ui/core/Grid';\nimport {makeStyles} from '@material-ui/core/styles';\nimport ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';\nimport ToggleButton from '@material-ui/lab/ToggleButton';\nimport ZoomIcon from '../icons/Zoom';\n\nimport styles from './styles.module.css';\nimport useCommonStyles from '../useCommonStyles';\nimport './otherStyles.css';\nimport {Tooltip} from '@material-ui/core';\nimport {MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL} from '../../constants';\n\nfunction BrowserZoom(props) {\n  const [showExpanded, setShowExpanded] = useState(false);\n  const zoomRef = useRef();\n  const classes = useStyles();\n  const commonClasses = useCommonStyles();\n\n  const handleClickOutside = event => {\n    if (!showExpanded) {\n      return;\n    }\n    if (zoomRef.current && !zoomRef.current.contains(event.target)) {\n      setShowExpanded(false);\n    }\n  };\n  useEffect(() => {\n    // Bind the event listener\n    document.addEventListener('mousedown', handleClickOutside);\n    return () => {\n      // Unbind the event listener on clean up\n      document.removeEventListener('mousedown', handleClickOutside);\n    };\n  });\n\n  const _zoomChange = (_, [action]) => {\n    switch (action) {\n      case 'zoomIn':\n        return props.onZoomChange(props.browser.zoomLevel + 0.1);\n      case 'zoomOut':\n        return props.onZoomChange(props.browser.zoomLevel - 0.1);\n      default:\n        break;\n    }\n  };\n\n  const zoomLevel = props.browser.zoomLevel;\n\n  return (\n    <div\n      ref={zoomRef}\n      className={cx(commonClasses.icon, 'MuiGrid-item', 'MuiGrid-root')}\n    >\n      <Tooltip title=\"Zoom In/Out\">\n        <div onClick={() => setShowExpanded(!showExpanded)}>\n          <ZoomIcon {...props.iconProps} />\n        </div>\n      </Tooltip>\n      <div\n        className={cx(styles.zoomControls, {\n          [commonClasses.hidden]: !showExpanded,\n        })}\n      >\n        <ToggleButtonGroup value={[]} onChange={_zoomChange}>\n          <ToggleButton\n            value=\"zoomOut\"\n            disabled={zoomLevel === MIN_ZOOM_LEVEL}\n            disableRipple\n          >\n            &ndash;\n          </ToggleButton>\n          <Typography\n            className={cx(\n              classes.zoomValue,\n              commonClasses.flexContainer,\n              'MuiToggleButton-root'\n            )}\n          >\n            {Math.round(props.browser.zoomLevel * 100)}%\n          </Typography>\n          <ToggleButton\n            value=\"zoomIn\"\n            disabled={zoomLevel === MAX_ZOOM_LEVEL}\n            disableRipple\n          >\n            +\n          </ToggleButton>\n        </ToggleButtonGroup>\n      </div>\n    </div>\n  );\n}\n\nconst useStyles = makeStyles(theme => ({\n  zoomValue: {\n    width: '75px',\n    color: theme.palette.text.primary,\n  },\n}));\n\nconst marks = [\n  {\n    value: 25,\n    label: '25%',\n  },\n  {\n    value: 50,\n    label: '50%',\n  },\n  {\n    value: 100,\n    label: '100%',\n  },\n  {\n    value: 200,\n    label: '200%',\n  },\n];\n\nexport default BrowserZoom;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ZoomInput/index.test.js",
    "content": "// @flow\nimport React from 'react';\nimport {shallow, mount} from 'enzyme';\nimport {expect, assert} from 'chai';\nimport sinon from 'sinon';\n\nimport Slider from '@material-ui/core/Slider';\nimport BrowserZoom from '.';\n\ndescribe('<BrowserZoom />', () => {\n  it('Renders label and the slider component ', () => {\n    const wrapper = shallow(<BrowserZoom />);\n    expect(wrapper.find(Slider)).to.have.lengthOf(1);\n  });\n\n  it('Calls the callback on slider change', () => {\n    const onChange = sinon.spy();\n    const wrapper = mount(<BrowserZoom onChange={onChange} />);\n    wrapper.find('.MuiSlider-thumb').simulate('mousedown');\n    wrapper.find('.MuiSlider-thumb').simulate('mouseup');\n    expect(onChange).to.have.property('callCount', 1);\n  });\n\n  /* it('Calls the callback with a number value', () => {\n    const onChange = sinon.spy();\n    const wrapper = mount(<BrowserZoom onChange={onChange} />);\n    wrapper.find('.MuiSlider-thumb').simulate('mousedown');\n    wrapper.find('.MuiSlider-thumb').simulate('mouseup');\n    console.log('spy.args', onChange.args);\n    assert(onChange.calledWith(100));\n  }); */\n});\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ZoomInput/otherStyles.css",
    "content": ".MuiSlider-markLabelActive,\n.MuiSlider-markLabel {\n  color: white !important;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/ZoomInput/styles.module.css",
    "content": ".zoomControls {\n  position: absolute;\n  right: 0;\n}\n\n.label {\n  font-size: 1rem;\n  margin-right: 1rem;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Apple.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      data-name=\"Layer 1\"\n      viewBox=\"0 0 24 24\"\n      className=\"appleIcon\"\n    >\n      <path d=\"M14.94,5.19A4.38,4.38,0,0,0,16,2,4.44,4.44,0,0,0,13,3.52,4.17,4.17,0,0,0,12,6.61,3.69,3.69,0,0,0,14.94,5.19Zm2.52,7.44a4.51,4.51,0,0,1,2.16-3.81,4.66,4.66,0,0,0-3.66-2c-1.56-.16-3,.91-3.83.91s-2-.89-3.3-.87A4.92,4.92,0,0,0,4.69,9.39C2.93,12.45,4.24,17,6,19.47,6.8,20.68,7.8,22.05,9.12,22s1.75-.82,3.28-.82,2,.82,3.3.79,2.22-1.24,3.06-2.45a11,11,0,0,0,1.38-2.85A4.41,4.41,0,0,1,17.46,12.63Z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/ArrowLeft.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      data-name=\"Layer 1\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path d=\"M17,11H9.41l3.3-3.29a1,1,0,1,0-1.42-1.42l-5,5a1,1,0,0,0-.21.33,1,1,0,0,0,0,.76,1,1,0,0,0,.21.33l5,5a1,1,0,0,0,1.42,0,1,1,0,0,0,0-1.42L9.41,13H17a1,1,0,0,0,0-2Z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/ArrowRight.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      data-name=\"Layer 1\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path d=\"M17.92,11.62a1,1,0,0,0-.21-.33l-5-5a1,1,0,0,0-1.42,1.42L14.59,11H7a1,1,0,0,0,0,2h7.59l-3.3,3.29a1,1,0,0,0,0,1.42,1,1,0,0,0,1.42,0l5-5a1,1,0,0,0,.21-.33A1,1,0,0,0,17.92,11.62Z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Bug.js",
    "content": "import React, {Fragment} from 'react';\n\nconst Bug = ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      width={width}\n      height={height}\n      fill={color}\n      style={{padding, margin}}\n      viewBox=\"0 0 100 100\"\n      x=\"0px\"\n      y=\"0px\"\n    >\n      <g data-name=\"Group\">\n        <polygon\n          data-name=\"Path\"\n          points=\"39.5 23.6 13.1 50 39.5 76.4 42.4 73.6 18.8 50 42.4 26.4 39.5 23.6\"\n        />\n        <polygon\n          data-name=\"Path\"\n          points=\"60.5 76.4 86.9 50 60.5 23.6 57.6 26.4 81.2 50 57.6 73.6 60.5 76.4\"\n        />\n      </g>\n    </svg>\n  </Fragment>\n);\n\nBug.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default Bug;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/CSSEditor.js",
    "content": "import React from 'react';\n\nconst CSSEditor = ({width, height, color, padding, margin}) => (\n  <svg\n    width={width}\n    height={height}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    style={{margin, padding}}\n    className=\"cssEditor\"\n  >\n    <path\n      d=\"M4.2168 18.1445C3.62695 17.9492 3.18164 17.6309 2.88086 17.1895C2.58398 16.748 2.43359 16.166 2.42969 15.4434V14.2305C2.42969 13.2891 2.09375 12.8184 1.42188 12.8184V11.9688C2.09375 11.9688 2.42969 11.498 2.42969 10.5566V9.34961C2.43359 8.63086 2.58203 8.05078 2.875 7.60938C3.16797 7.16406 3.61523 6.8418 4.2168 6.64258L4.42773 7.31641C3.80273 7.5625 3.48242 8.2168 3.4668 9.2793V10.5449C3.4668 11.4277 3.17969 12.0449 2.60547 12.3965C3.17969 12.748 3.4668 13.3672 3.4668 14.2539V15.5371C3.48633 16.584 3.80469 17.2285 4.42188 17.4707L4.2168 18.1445Z\"\n      fill={color}\n    />\n    <path\n      d=\"M19.2285 17.4707C19.8535 17.2246 20.1758 16.5781 20.1953 15.5312V14.2363C20.1953 13.334 20.5059 12.7207 21.127 12.3965C20.5059 12.0762 20.1953 11.4551 20.1953 10.5332V9.32031C20.1875 8.23438 19.8652 7.56641 19.2285 7.31641L19.4395 6.64258C20.0254 6.83398 20.4648 7.14648 20.7578 7.58008C21.0547 8.00977 21.2109 8.57227 21.2266 9.26758V10.6504C21.2461 11.5293 21.582 11.9688 22.2344 11.9688V12.8184C21.5625 12.8184 21.2266 13.2891 21.2266 14.2305V15.4258C21.2266 16.8477 20.6309 17.7539 19.4395 18.1445L19.2285 17.4707Z\"\n      fill={color}\n    />\n    <path\n      d=\"M7.48047 14.3604C7.76693 14.3604 7.99642 14.2725 8.16895 14.0967C8.34147 13.9176 8.43587 13.6637 8.45215 13.335H9.26758C9.24805 13.8428 9.06738 14.2643 8.72559 14.5996C8.38704 14.9316 7.97201 15.0977 7.48047 15.0977C6.82617 15.0977 6.3265 14.8926 5.98145 14.4824C5.63965 14.069 5.46875 13.4554 5.46875 12.6416V12.0605C5.46875 11.263 5.63965 10.6576 5.98145 10.2441C6.32324 9.82747 6.82129 9.61914 7.47559 9.61914C8.01595 9.61914 8.44401 9.79004 8.75977 10.1318C9.07878 10.4736 9.24805 10.9408 9.26758 11.5332H8.45215C8.43262 11.1426 8.33822 10.8496 8.16895 10.6543C8.00293 10.459 7.77181 10.3613 7.47559 10.3613C7.09147 10.3613 6.80664 10.4883 6.62109 10.7422C6.43555 10.9928 6.33952 11.4062 6.33301 11.9824V12.6562C6.33301 13.278 6.42415 13.7174 6.60645 13.9746C6.79199 14.2318 7.08333 14.3604 7.48047 14.3604ZM12.832 13.6475C12.832 13.4619 12.762 13.3024 12.6221 13.1689C12.4821 13.0355 12.2119 12.8792 11.8115 12.7002C11.346 12.5081 11.0173 12.3438 10.8252 12.207C10.6364 12.0703 10.4948 11.9157 10.4004 11.7432C10.306 11.5674 10.2588 11.359 10.2588 11.1182C10.2588 10.6885 10.415 10.332 10.7275 10.0488C11.0433 9.76237 11.4453 9.61914 11.9336 9.61914C12.4479 9.61914 12.8613 9.76888 13.1738 10.0684C13.4863 10.3678 13.6426 10.752 13.6426 11.2207H12.7832C12.7832 10.9831 12.7035 10.7812 12.5439 10.6152C12.3844 10.446 12.181 10.3613 11.9336 10.3613C11.6797 10.3613 11.4795 10.4281 11.333 10.5615C11.1898 10.6917 11.1182 10.8675 11.1182 11.0889C11.1182 11.2646 11.1702 11.4062 11.2744 11.5137C11.3818 11.6211 11.6374 11.7627 12.041 11.9385C12.6823 12.1891 13.1185 12.4349 13.3496 12.6758C13.5807 12.9134 13.6963 13.2161 13.6963 13.584C13.6963 14.043 13.5384 14.4108 13.2227 14.6875C12.9069 14.9609 12.4837 15.0977 11.9531 15.0977C11.403 15.0977 10.957 14.9398 10.6152 14.624C10.2734 14.3083 10.1025 13.9079 10.1025 13.4229H10.9717C10.9814 13.7158 11.071 13.9453 11.2402 14.1113C11.4095 14.2773 11.6471 14.3604 11.9531 14.3604C12.2396 14.3604 12.4577 14.2969 12.6074 14.1699C12.7572 14.0397 12.832 13.8656 12.832 13.6475ZM17.3926 13.6475C17.3926 13.4619 17.3226 13.3024 17.1826 13.1689C17.0426 13.0355 16.7725 12.8792 16.3721 12.7002C15.9066 12.5081 15.5778 12.3438 15.3857 12.207C15.1969 12.0703 15.0553 11.9157 14.9609 11.7432C14.8665 11.5674 14.8193 11.359 14.8193 11.1182C14.8193 10.6885 14.9756 10.332 15.2881 10.0488C15.6038 9.76237 16.0059 9.61914 16.4941 9.61914C17.0085 9.61914 17.4219 9.76888 17.7344 10.0684C18.0469 10.3678 18.2031 10.752 18.2031 11.2207H17.3438C17.3438 10.9831 17.264 10.7812 17.1045 10.6152C16.945 10.446 16.7415 10.3613 16.4941 10.3613C16.2402 10.3613 16.04 10.4281 15.8936 10.5615C15.7503 10.6917 15.6787 10.8675 15.6787 11.0889C15.6787 11.2646 15.7308 11.4062 15.835 11.5137C15.9424 11.6211 16.1979 11.7627 16.6016 11.9385C17.2428 12.1891 17.679 12.4349 17.9102 12.6758C18.1413 12.9134 18.2568 13.2161 18.2568 13.584C18.2568 14.043 18.099 14.4108 17.7832 14.6875C17.4674 14.9609 17.0443 15.0977 16.5137 15.0977C15.9635 15.0977 15.5176 14.9398 15.1758 14.624C14.834 14.3083 14.6631 13.9079 14.6631 13.4229H15.5322C15.542 13.7158 15.6315 13.9453 15.8008 14.1113C15.9701 14.2773 16.2077 14.3604 16.5137 14.3604C16.8001 14.3604 17.0182 14.2969 17.168 14.1699C17.3177 14.0397 17.3926 13.8656 17.3926 13.6475Z\"\n      fill={color}\n    />\n  </svg>\n);\n\nexport default CSSEditor;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Chevron.js",
    "content": "import React from 'react';\n\n/**\n * Flattened Chevron icon.\n */\nexport default ({width, height, color, padding, margin}) => (\n  <svg\n    height={height}\n    width={width}\n    style={{padding, margin}}\n    viewBox={`0 0 ${width} ${height}`}\n    xmlns=\"http://www.w3.org/2000/svg\"\n    fill=\"none\"\n  >\n    <path\n      d=\"M1 6.5L9.5 2L18 6.5\"\n      stroke={color}\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n    />\n  </svg>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Cross.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 512.001 512.001\"\n      xmlSpace=\"preserve\"\n    >\n      <g>\n        <g>\n          <path\n            d=\"M284.286,256.002L506.143,34.144c7.811-7.811,7.811-20.475,0-28.285c-7.811-7.81-20.475-7.811-28.285,0L256,227.717\n\t\t\tL34.143,5.859c-7.811-7.811-20.475-7.811-28.285,0c-7.81,7.811-7.811,20.475,0,28.285l221.857,221.857L5.858,477.859\n\t\t\tc-7.811,7.811-7.811,20.475,0,28.285c3.905,3.905,9.024,5.857,14.143,5.857c5.119,0,10.237-1.952,14.143-5.857L256,284.287\n\t\t\tl221.857,221.857c3.905,3.905,9.024,5.857,14.143,5.857s10.237-1.952,14.143-5.857c7.811-7.811,7.811-20.475,0-28.285\n\t\t\tL284.286,256.002z\"\n          />\n        </g>\n      </g>\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/CrossChrome.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default function CrossChrome({width, height, color, padding, margin}) {\n  return (\n    <Fragment>\n      <svg\n        height={height}\n        width={width}\n        fill={color}\n        style={{padding, margin}}\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 24 24\"\n      >\n        <path d=\"M13.41,12l4.3-4.29a1,1,0,1,0-1.42-1.42L12,10.59,7.71,6.29A1,1,0,0,0,6.29,7.71L10.59,12l-4.3,4.29a1,1,0,0,0,0,1.42,1,1,0,0,0,1.42,0L12,13.41l4.29,4.3a1,1,0,0,0,1.42,0,1,1,0,0,0,0-1.42Z\" />\n      </svg>\n    </Fragment>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/CrossThin.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      version=\"1.1\"\n      id=\"Capa_1\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"0 0 496.096 496.096\"\n      xmlSpace=\"preserve\"\n      className=\"crossThinIcon\"\n    >\n      <g>\n        <path\n          d=\"M259.41,247.998L493.754,13.654c3.123-3.124,3.123-8.188,0-11.312c-3.124-3.123-8.188-3.123-11.312,0L248.098,236.686\n\t\t\tL13.754,2.342C10.576-0.727,5.512-0.639,2.442,2.539c-2.994,3.1-2.994,8.015,0,11.115l234.344,234.344L2.442,482.342\n\t\t\tc-3.178,3.07-3.266,8.134-0.196,11.312s8.134,3.266,11.312,0.196c0.067-0.064,0.132-0.13,0.196-0.196L248.098,259.31\n\t\t\tl234.344,234.344c3.178,3.07,8.242,2.982,11.312-0.196c2.995-3.1,2.995-8.016,0-11.116L259.41,247.998z\"\n        />\n      </g>\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/DarkColorScheme.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin, className}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      stroke={color}\n      style={{padding, margin}}\n      className={className}\n      fill=\"transparent\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path d=\"M12.7125 4.125C13.3282 5.2901 13.563 6.61893 13.3839 7.92447C13.2049 9.23002 12.621 10.4466 11.7143 11.4029C10.8077 12.3592 9.62401 13.0071 8.32986 13.2555C7.03571 13.5039 5.69626 13.3402 4.5 12.7875C4.7175 14.1791 5.32054 15.4818 6.24075 16.5481C7.16096 17.6144 8.36153 18.4015 9.70631 18.8203C11.0511 19.239 12.4863 19.2725 13.8492 18.9171C15.212 18.5617 16.4481 17.8315 17.4171 16.8094C18.3861 15.7873 19.0494 14.5141 19.3317 13.1342C19.6139 11.7543 19.5039 10.3229 19.0142 9.00239C18.5244 7.68183 17.6744 6.52493 16.5606 5.66285C15.4468 4.80078 14.1137 4.26803 12.7125 4.125Z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/DeleteCookie.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      style=\"isolation:isolate\"\n      viewBox=\"0 0 512 512\"\n      height={height}\n      width={width}\n      fill={color}\n      stroke={color}\n      style={{padding, margin}}\n      className=\"deleteCookieIcon\"\n    >\n      <defs>\n        <clipPath id=\"_clipPath_pH9XtWjIaRon0vADT01aeJAr0MOn4OX2\">\n          <rect width=\"512\" height=\"512\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_pH9XtWjIaRon0vADT01aeJAr0MOn4OX2)\">\n        <path d=\" M 136 216 L 106 216 C 103.348 216 100.804 217.054 98.929 218.929 L 68.929 248.929 C 65.024 252.834 65.024 259.166 68.929 263.072 L 98.929 293.072 C 100.804 294.947 103.348 296.001 106 296.001 L 166 296.001 C 169.466 296.001 172.685 294.206 174.506 291.258 C 176.328 288.311 176.494 284.629 174.944 281.529 L 144.944 221.529 C 143.25 218.14 139.788 216 136 216 Z  M 110.142 276 L 90.142 256 L 110.142 236 L 129.82 236 L 149.82 276 L 110.142 276 Z \" />\n        <path\n          d=\" M 346 386 L 406 386 C 408.652 386 411.196 384.946 413.071 383.071 L 443.071 353.071 C 446.976 349.166 446.976 342.834 443.071 338.928 L 413.071 308.928 C 411.196 307.054 408.652 306 406 306 L 376 306 C 372.212 306 368.75 308.14 367.056 311.528 L 337.056 371.528 C 335.506 374.628 335.672 378.309 337.494 381.257 C 339.315 384.205 342.534 386 346 386 Z  M 382.18 326 L 401.858 326 L 421.858 346 L 401.858 366 L 362.18 366 L 382.18 326 Z \"\n          fill=\"rgb(0,0,0)\"\n        />\n        <path d=\" M 196 146 L 256 146 C 259.466 146 262.685 144.205 264.506 141.257 C 266.328 138.31 266.494 134.628 264.944 131.528 L 234.944 71.528 C 233.25 68.14 229.788 66 226 66 L 196 66 C 193.348 66 190.804 67.054 188.929 68.929 L 158.929 98.929 C 155.024 102.834 155.024 109.166 158.929 113.072 L 188.929 143.072 C 190.804 144.946 193.348 146 196 146 L 196 146 Z  M 200.142 86 L 219.82 86 L 239.82 126 L 200.142 126 L 180.142 106 L 200.142 86 Z \" />\n        <path d=\" M 161.528 384.944 L 221.528 414.944 C 222.949 415.655 224.479 416 225.997 416 C 228.591 416 231.151 414.991 233.071 413.071 L 263.071 383.071 C 264.946 381.195 266 378.652 266 376 L 266 346 C 266 340.478 261.523 336 256 336 L 196 336 C 193.348 336 190.804 337.054 188.929 338.929 L 158.929 368.929 C 156.656 371.202 155.615 374.429 156.129 377.602 C 156.644 380.774 158.653 383.507 161.528 384.944 L 161.528 384.944 Z  M 200.142 356 L 246 356 L 246 371.857 L 224.025 393.832 L 182.881 373.261 L 200.142 356 Z \" />\n        <path d=\" M 323.071 263.071 L 353.071 233.071 C 355.344 230.798 356.385 227.571 355.871 224.398 C 355.356 221.225 353.347 218.493 350.472 217.055 L 290.472 187.055 C 286.621 185.13 281.973 185.885 278.929 188.928 L 248.929 218.928 C 247.054 220.805 246 223.348 246 226 L 246 256 C 246 261.522 250.477 266 256 266 L 316 266 C 318.652 266 321.196 264.946 323.071 263.071 Z  M 266 246 L 266 230.143 L 287.975 208.168 L 329.119 228.739 L 311.858 246 L 266 246 Z \" />\n        <circle\n          vectorEffect=\"non-scaling-stroke\"\n          cx=\"468\"\n          cy=\"378\"\n          r=\"10\"\n          fill=\"rgb(0,0,0)\"\n        />\n        <path d=\" M 503.483 210.456 C 500.633 208.991 497.254 208.98 494.394 210.428 C 487.912 213.709 477.47 216 469 216 C 440.767 216 416 192.636 416 166 C 416 160.478 411.523 156 406 156 C 378.43 156 356 133.57 356 106 C 356 100.478 351.523 96 346 96 C 319.364 96 296 71.233 296 43 C 296 34.538 298.292 24.094 301.574 17.602 C 303.019 14.743 303.008 11.364 301.542 8.515 C 300.076 5.666 297.334 3.693 294.168 3.206 C 279.927 1.019 267.799 0 256 0 C 117.659 0 0 118.201 0 256 C 0 394.121 117.976 512 256 512 C 330.553 512 400.878 477.456 449.394 421.103 C 452.998 416.917 452.526 410.604 448.34 407 C 444.155 403.396 437.84 403.869 434.237 408.054 C 418.318 426.543 399.939 442.435 379.536 455.394 L 353.071 428.929 C 351.196 427.054 348.652 426 346 426 L 316 426 C 312.212 426 308.749 428.141 307.056 431.528 L 277.346 490.957 C 270.238 491.639 263.082 492 256 492 C 194.14 492 133.684 465.826 88.672 419.9 L 113.071 395.501 C 114.946 393.625 116 391.082 116 388.43 L 116 346 C 116 342.785 114.455 339.767 111.847 337.888 C 109.239 336.008 105.885 335.495 102.838 336.514 L 43.563 356.273 C 28.135 324.739 20 290.252 20 256 C 20 226.825 25.643 198.269 36.766 170.908 L 38.929 173.071 C 40.804 174.946 43.348 176 46 176 L 106 176 C 109.466 176 112.685 174.205 114.506 171.257 C 116.328 168.31 116.494 164.628 114.944 161.528 L 84.944 101.528 C 84.361 100.361 83.556 99.355 82.612 98.521 C 127.814 49.076 191.401 20 256 20 C 263.302 20 270.791 20.436 278.98 21.35 C 277.092 28.364 276 36.021 276 43 C 276 79.262 302.916 110.366 336.617 115.314 C 340.778 146.478 365.521 171.221 396.685 175.382 C 401.634 209.084 432.739 236 469 236 C 475.982 236 483.639 234.908 490.65 233.021 C 491.565 241.21 492 248.698 492 256 C 492 282.473 487.292 308.697 478.008 333.945 C 476.102 339.129 478.759 344.876 483.943 346.782 C 489.125 348.689 494.874 346.031 496.78 340.847 C 506.879 313.383 512 284.836 512 256 C 512 244.201 510.981 232.073 508.794 217.832 C 508.308 214.664 506.333 211.922 503.483 210.456 Z  M 322.181 446 L 341.858 446 L 361.611 465.754 C 342.412 475.785 322.301 482.992 301.531 487.298 L 322.181 446 Z  M 96 384.287 L 75.244 405.043 C 67.102 395.279 59.753 384.934 53.271 374.117 L 96 359.874 L 96 384.287 Z  M 89.82 156 L 50.142 156 L 45.661 151.519 C 52.037 139.118 59.55 127.217 68.051 116 L 69.82 116 L 89.82 156 Z \" />\n        <g>\n          <path\n            d=\" M 389.675 263.675 C 322.222 263.675 267.35 318.547 267.35 386 C 267.35 453.453 322.222 508.325 389.675 508.325 C 457.128 508.325 512 453.453 512 386 C 512 318.547 457.128 263.675 389.675 263.675 Z \"\n            fill=\"rgb(244,67,54)\"\n          />\n          <path d=\" M 434.722 416.632 C 438.707 420.619 438.707 427.06 434.722 431.047 C 432.734 433.035 430.125 434.033 427.513 434.033 C 424.904 434.033 422.295 433.035 420.307 431.047 L 389.675 400.413 L 359.043 431.047 C 357.055 433.035 354.446 434.033 351.837 434.033 C 349.225 434.033 346.616 433.035 344.628 431.047 C 340.643 427.06 340.643 420.619 344.628 416.632 L 375.262 386 L 344.628 355.368 C 340.643 351.381 340.643 344.94 344.628 340.953 C 348.615 336.968 355.056 336.968 359.043 340.953 L 389.675 371.587 L 420.307 340.953 C 424.294 336.968 430.735 336.968 434.722 340.953 C 438.707 344.94 438.707 351.381 434.722 355.368 L 404.088 386 L 434.722 416.632 Z \" />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/DeleteStorage.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      style=\"isolation:isolate\"\n      viewBox=\"0 0 512 512\"\n      height={height}\n      width={width}\n      fill={color}\n      stroke={color}\n      style={{padding, margin}}\n      className=\"deleteStorageIcon\"\n    >\n      <defs>\n        <clipPath id=\"_clipPath_qbuwJBHdB5UgzBtLzd9gB6R7rRnewpsN\">\n          <rect width=\"512\" height=\"512\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_qbuwJBHdB5UgzBtLzd9gB6R7rRnewpsN)\">\n        <g>\n          <g>\n            <path d=\" M 256 0 C 136.384 0 42.667 42.176 42.667 96 C 42.667 149.824 136.384 192 256 192 C 375.616 192 469.333 149.824 469.333 96 C 469.333 42.176 375.616 0 256 0 Z  M 256 170.667 C 142.848 170.667 64 131.307 64 96 C 64 60.693 142.848 21.333 256 21.333 C 369.152 21.333 448 60.693 448 96 C 448 131.307 369.152 170.667 256 170.667 Z \" />\n          </g>\n        </g>\n        <g>\n          <g>\n            <path d=\" M 458.667 192 C 452.779 192 448 196.779 448 202.667 C 448 237.974 369.152 277.334 256 277.334 C 142.848 277.334 64 237.974 64 202.667 C 64 196.779 59.221 192 53.333 192 C 47.445 192 42.666 196.779 42.666 202.667 C 42.666 256.491 136.383 298.667 255.999 298.667 C 375.615 298.667 469.332 256.491 469.332 202.667 C 469.333 196.779 464.555 192 458.667 192 Z \" />\n          </g>\n        </g>\n        <g>\n          <g>\n            <path d=\" M 458.667 298.667 C 452.779 298.667 448 303.446 448 309.334 C 448 344.64 369.152 384 256 384 C 142.848 384 64 344.64 64 309.333 C 64 303.445 59.221 298.666 53.333 298.666 C 47.445 298.666 42.666 303.445 42.666 309.333 C 42.666 363.157 136.383 405.333 255.999 405.333 C 375.615 405.333 469.332 363.157 469.332 309.333 C 469.333 303.445 464.555 298.667 458.667 298.667 Z \" />\n          </g>\n        </g>\n        <g>\n          <g>\n            <path d=\" M 458.667 85.333 C 452.779 85.333 448 90.112 448 96 L 448 416 C 448 451.307 369.152 490.667 256 490.667 C 142.848 490.667 64 451.307 64 416 L 64 96 C 64 90.112 59.221 85.333 53.333 85.333 C 47.445 85.333 42.667 90.112 42.667 96 L 42.667 416 C 42.667 469.824 136.384 512 256 512 C 375.616 512 469.333 469.824 469.333 416 L 469.333 96 C 469.333 90.112 464.555 85.333 458.667 85.333 Z \" />\n          </g>\n        </g>\n        <g>\n          <path\n            d=\" M 388 264 C 319.624 264 264 319.624 264 388 C 264 456.376 319.624 512 388 512 C 456.376 512 512 456.376 512 388 C 512 319.624 456.376 264 388 264 Z \"\n            fill=\"rgb(244,67,54)\"\n          />\n          <path d=\" M 433.664 419.051 C 437.703 423.093 437.703 429.622 433.664 433.664 C 431.649 435.679 429.003 436.691 426.356 436.691 C 423.711 436.691 421.066 435.679 419.051 433.664 L 388 402.611 L 356.949 433.664 C 354.934 435.679 352.289 436.691 349.644 436.691 C 346.997 436.691 344.351 435.679 342.336 433.664 C 338.297 429.622 338.297 423.093 342.336 419.051 L 373.389 388 L 342.336 356.949 C 338.297 352.907 338.297 346.378 342.336 342.336 C 346.378 338.297 352.907 338.297 356.949 342.336 L 388 373.389 L 419.051 342.336 C 423.093 338.297 429.622 338.297 433.664 342.336 C 437.703 346.378 437.703 352.907 433.664 356.949 L 402.611 388 L 433.664 419.051 Z \" />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/DesignMode.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      className=\"designModeIcon\"\n      viewBox=\"0 0 36 36\"\n    >\n      <path\n        d=\"M28 30H6V8h13.22l2-2H6a2 2 0 0 0-2 2v22a2 2 0 0 0 2 2h22a2 2 0 0 0 2-2V15l-2 2z\"\n        className=\"clr-i-outline clr-i-outline-path-1\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M33.53 5.84l-3.37-3.37a1.61 1.61 0 0 0-2.28 0L14.17 16.26l-1.11 4.81A1.61 1.61 0 0 0 14.63 23a1.69 1.69 0 0 0 .37 0l4.85-1.07L33.53 8.12a1.61 1.61 0 0 0 0-2.28zM18.81 20.08l-3.66.81l.85-3.63L26.32 6.87l2.82 2.82zM30.27 8.56l-2.82-2.82L29 4.16L31.84 7z\"\n        className=\"clr-i-outline clr-i-outline-path-2\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/DeviceRotate.js",
    "content": "import React, {Fragment} from 'react';\n\nconst DeviceRotate = ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      data-name=\"Layer 1\"\n      viewBox=\"0 0 100 100\"\n      x=\"0px\"\n      y=\"0px\"\n      className=\"deviceRotateIcon\"\n    >\n      <g data-name=\"Group\">\n        <path\n          data-name=\"Compound Path\"\n          d=\"M8.8,62.2a7.2,7.2,0,0,0,2.1,5.2L32.7,89.1a7.3,7.3,0,0,0,10.3,0L89.1,43a7.3,7.3,0,0,0,0-10.3L67.3,10.9a7.3,7.3,0,0,0-10.3,0L10.9,57A7.2,7.2,0,0,0,8.8,62.2ZM48,78.4,21.6,52,54.9,18.7,81.3,45.1ZM64.5,13.7,86.3,35.5a3.3,3.3,0,0,1,0,4.7l-2.2,2.2L57.7,15.9l2.2-2.2A3.4,3.4,0,0,1,64.5,13.7ZM13.7,59.9l5.1-5.1L45.2,81.2l-5.1,5.1a3.4,3.4,0,0,1-4.7,0L13.7,64.5a3.3,3.3,0,0,1,0-4.7Z\"\n        />\n        <rect\n          data-name=\"Path\"\n          x=\"26.4\"\n          y=\"69.2\"\n          width=\"4\"\n          height=\"4.88\"\n          transform=\"translate(-42.4 41.1) rotate(-45)\"\n        />\n        <path\n          data-name=\"Path\"\n          d=\"M92.8,68.1A14.2,14.2,0,0,1,78.6,82.3H65.8l7.4-8.7-3-2.6L58.9,84.3,70.2,97.5l3-2.6-7.4-8.6H78.6A18.2,18.2,0,0,0,96.8,68.1Z\"\n        />\n        <path\n          data-name=\"Path\"\n          d=\"M21.4,17.7H34.2l-7.4,8.7,3,2.6L41.1,15.7,29.8,2.5l-3,2.6,7.4,8.6H21.4A18.2,18.2,0,0,0,3.2,31.9h4A14.2,14.2,0,0,1,21.4,17.7Z\"\n        />\n      </g>\n    </svg>\n  </Fragment>\n);\n\nDeviceRotate.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default DeviceRotate;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Devices.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 100 100\"\n      enableBackground=\"new 0 0 100 100\"\n    >\n      <path d=\"M17.914,60.871v9.576h3.573v-9.576H17.914z M66.886,25.279v9.703h3.573V17.425c0-2.659-2.181-4.82-4.842-4.82h-3.075v7.853  C64.975,20.729,66.886,22.765,66.886,25.279z\" />\n      <path d=\"M65.617,12.605H22.775c-2.68,0-4.861,2.161-4.861,4.82v63.183c0,2.68,2.181,4.842,4.841,4.842h32.91v-8.436H26.348  c-1.495,0-2.844-0.664-3.739-1.723v-0.023c-0.706-0.85-1.122-1.91-1.122-3.074V25.279c0-2.68,2.182-4.841,4.861-4.841h35.674  c0.166,0,0.355,0,0.52,0.02c2.433,0.271,4.344,2.307,4.344,4.821v9.703h3.573V17.425C70.459,14.766,68.278,12.605,65.617,12.605z   M44.425,78.676c1.495,0,2.722,1.207,2.722,2.701c0,1.516-1.227,2.721-2.722,2.721c-1.516,0-2.721-1.205-2.721-2.721  C41.704,79.883,42.909,78.676,44.425,78.676z\" />\n      <path d=\"M82.239,37.995h-20.88c-1.769,0-3.181,1.413-3.181,3.18v41.513c0,1.203,0.646,2.223,1.622,2.762  c0.456,0.27,0.996,0.416,1.559,0.416h20.88c1.765,0,3.2-1.414,3.2-3.178V41.174C85.439,39.407,84.004,37.995,82.239,37.995z   M60.527,46.326c0-1.351,0.832-2.493,2.015-2.95c0.354-0.146,0.749-0.229,1.165-0.229h16.185c1.766,0,3.2,1.415,3.2,3.179v30.813  c0,1.766-1.435,3.18-3.2,3.18H63.707c-1.746,0-3.18-1.414-3.18-3.18V46.326z M71.664,84.971c-0.976,0-1.767-0.789-1.767-1.787  c0-0.166,0.021-0.311,0.083-0.455c0.022-0.189,0.105-0.355,0.208-0.5c0,0,0-0.02,0.021-0.041c0.312-0.457,0.852-0.77,1.454-0.77  c0.997,0,1.786,0.791,1.786,1.766C73.45,84.182,72.661,84.971,71.664,84.971z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/DockBottom.js",
    "content": "import React from 'react';\n\nimport styles from './styles.module.css';\n\nexport default function DockBottom({width, height, color, padding, margin}) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"0 0 14.14 12\"\n      width={width}\n      height={height}\n      fill={color}\n      style={{padding, margin}}\n      className={styles.chromeIcon}\n    >\n      <defs>\n        <clipPath id=\"_clipPath_oN17WILa5750lHQzygNFwNgKorN1U9tL\">\n          <rect width=\"14.14\" height=\"12\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_oN17WILa5750lHQzygNFwNgKorN1U9tL)\">\n        <path\n          d=\" M 0 0 L 14.14 0 L 14.14 12 L 0 12 L 0 0 Z  M 2.07 2.052 L 12.07 2.052 L 12.07 7.052 L 2.07 7.052 L 2.07 2.052 Z \"\n          fillRule=\"evenodd\"\n          fill={color}\n        />\n      </g>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/DockRight.js",
    "content": "import React from 'react';\n\nimport styles from './styles.module.css';\n\nexport default function DockRight({width, height, color, padding, margin}) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"971 549.189 26.921 23.075\"\n      width={width}\n      height={height}\n      fill={color}\n      style={{padding, margin}}\n      className={styles.chromeIcon}\n    >\n      <defs>\n        <clipPath id=\"_clipPath_2OGwdnVsHCmr21dCKCMnSfgqfcYXnpYG\">\n          <rect x=\"971\" y=\"549.189\" width=\"26.921\" height=\"23.075\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_2OGwdnVsHCmr21dCKCMnSfgqfcYXnpYG)\">\n        <path\n          d=\" M 971 549.189 L 997.921 549.189 L 997.921 572.264 L 971 572.264 L 971 549.189 Z  M 974.395 552.727 L 988.395 552.727 L 988.395 568.727 L 974.395 568.727 L 974.395 552.727 Z \"\n          fillRule=\"evenodd\"\n          fill={color}\n        />\n      </g>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/DoubleLeftArrow.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      style={{padding, margin}}\n      viewBox=\"0 0 1240 1240\"\n      version=\"1.1\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      className=\"drawerCloseIcon\"\n    >\n      <g\n        id=\"chevronsLeft\"\n        stroke=\"none\"\n        strokeWidth=\"1\"\n        fill=\"none\"\n        fillRule=\"evenodd\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <g\n          id=\"Group\"\n          transform=\"translate(619.500000, 620.000000) rotate(90.000000) translate(-619.500000, -620.000000) translate(409.000000, 406.000000)\"\n          stroke={color}\n          strokeWidth=\"50\"\n        >\n          <polyline\n            id=\"Path\"\n            transform=\"translate(210.500000, 321.000000) scale(1, -1) translate(-210.500000, -321.000000) \"\n            points=\"0 428 210.5 214 421 428\"\n          />\n          <polyline\n            id=\"Path\"\n            transform=\"translate(210.500000, 107.000000) scale(1, -1) translate(-210.500000, -107.000000) \"\n            points=\"0 214 210.5 0 421 214\"\n          />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Filter.js",
    "content": "import React from 'react';\n\nfunction Filter({width, height, color, className}) {\n  return (\n    <svg\n      width={width}\n      height={height}\n      stroke={color}\n      className={className}\n      fill=\"none\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.5\"\n        d=\"M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z\"\n      />\n    </svg>\n  );\n}\n\nFilter.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default Filter;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Focus.js",
    "content": "import React, {Fragment} from 'react';\n\nconst Focus = ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"731.153 408.441 103.001 103.001\"\n    >\n      <defs>\n        <clipPath id=\"_clipPath_0UnFyX2qgqi5GGDxd6p0UnBBSVGla8iP\">\n          <rect x=\"731.153\" y=\"408.441\" width=\"103.001\" height=\"103.001\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_0UnFyX2qgqi5GGDxd6p0UnBBSVGla8iP)\">\n        <g>\n          <path\n            d=\" M 757.654 503.243 C 733.755 489.445 725.555 458.84 739.353 434.942 C 753.151 411.043 783.755 402.843 807.654 416.641 C 831.553 430.438 839.753 461.043 825.955 484.942 C 812.157 508.84 781.553 517.041 757.654 503.243 Z \"\n            fill=\"none\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            stroke={color}\n            strokeLinejoin=\"miter\"\n            strokeLinecap=\"square\"\n            strokeMiterlimit=\"3\"\n          />\n          <path\n            d=\" M 757.905 459.942 L 759.673 482.923 L 782.654 484.691\"\n            fill=\"none\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            stroke={color}\n            strokeLinejoin=\"round\"\n            strokeLinecap=\"round\"\n            strokeMiterlimit=\"3\"\n          />\n          <path\n            d=\" M 782.654 435.193 L 805.635 436.961 L 807.403 459.942\"\n            fill=\"none\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            stroke={color}\n            strokeLinejoin=\"round\"\n            strokeLinecap=\"round\"\n            strokeMiterlimit=\"3\"\n          />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n\nFocus.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default Focus;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/FullScreenshot.js",
    "content": "import React, {Fragment} from 'react';\n\nconst FullScreenshot = ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      width={width}\n      height={height}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 100 100\"\n      className=\"screenshotIcon\"\n    >\n      <g transform=\"translate(0,-952.36218)\">\n        <path\n          d=\"m 83,958.36218 c -1.1046,0 -2,0.89543 -2,2 l 0,7 -7,0 c -1.10457,0 -2,0.8954 -2,2 0,1.1046 0.89543,2 2,2 l 7,0 0,7 c 0,1.10457 0.8954,2 2,2 1.1046,0 2,-0.89543 2,-2 l 0,-7 7,0 c 1.10457,0 2,-0.8954 2,-2 0,-1.1046 -0.89543,-2 -2,-2 l -7,0 0,-7 c 0,-1.10457 -0.8954,-2 -2,-2 z m -48,22 c -0.78068,0.007 -1.3909,0.40265 -1.71875,0.96875 l -6.4375,11.03125 -12.84375,0 c -4.3973999,0 -8,3.6026 -8,8.00002 l 0,38 c 0,4.3974 3.6026001,8 8,8 l 62,0 c 4.3974,0 8,-3.6026 8,-8 l 0,-38 c 0,-4.39742 -3.6026,-8.00002 -8,-8.00002 l -12.84375,0 -6.4375,-11.03125 C 56.3641,980.74168 55.68774,980.36046 55,980.36218 l -20,0 z m 1.125,4 17.75,0 6.40625,11 c 0.34687,0.60075 1.02507,0.99534 1.71875,1 l 14,0 c 2.25056,0 4,1.74944 4,4.00002 l 0,38 c 0,2.2505 -1.74944,4 -4,4 l -62,0 c -2.25056,0 -4,-1.7495 -4,-4 l 0,-38 c 0,-2.25058 1.74944,-4.00002 4,-4.00002 l 14,0 c 0.69368,-0.005 1.37188,-0.39925 1.71875,-1 l 6.40625,-11 z M 45,1002.3622 c -8.81287,0 -16,7.1871 -16,16 0,8.8128 7.18713,16 16,16 8.81286,0 16,-7.1872 16,-16 0,-8.8129 -7.18714,-16 -16,-16 z m 0,4 c 6.6511,0 12,5.3489 12,12 0,6.6511 -5.3489,12 -12,12 -6.65111,0 -12,-5.3489 -12,-12 0,-6.6511 5.34889,-12 12,-12 z\"\n          fill={color}\n          fillOpacity=\"1\"\n          stroke=\"none\"\n          marker=\"none\"\n          visibility=\"visible\"\n          display=\"inline\"\n          overflow=\"visible\"\n        />\n      </g>\n    </svg>\n  </Fragment>\n);\n\nFullScreenshot.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default FullScreenshot;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Gift.js",
    "content": "import React, {Fragment} from 'react';\n\nfunction Gift({width, height, color, className}) {\n  return (\n    <Fragment>\n      <svg\n        height={height}\n        width={width}\n        fill={color}\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 20 20\"\n        className={className}\n      >\n        <path d=\"M14.83 4H20v6h-1v10H1V10H0V4h5.17A3 3 0 0 1 10 .76 3 3 0 0 1 14.83 4zM8 10H3v8h5v-8zm4 0v8h5v-8h-5zM8 6H2v2h6V6zm4 0v2h6V6h-6zM8 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm4 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z\" />\n      </svg>\n    </Fragment>\n  );\n}\n\nGift.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default Gift;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Github.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default function Github({\n  width,\n  height,\n  color,\n  padding,\n  margin,\n  className,\n}) {\n  return (\n    <Fragment>\n      <svg\n        height={height}\n        width={width}\n        fill={color}\n        style={{padding, margin}}\n        xmlns=\"http://www.w3.org/2000/svg\"\n        data-name=\"Layer 1\"\n        viewBox=\"0 0 24 24\"\n        className={`githubIcon ${className}`}\n      >\n        <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n      </svg>\n    </Fragment>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Globe.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default function Globe({\n  width,\n  height,\n  color,\n  padding,\n  margin,\n  className,\n}) {\n  return (\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{enableBackground: 'new 0 0 356.926 356.926', padding, margin}}\n      version=\"1.1\"\n      id=\"Capa_1\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 356.926 356.926\"\n      xmlSpace=\"preserve\"\n      className={`globeIcon ${className}`}\n    >\n      <g>\n        <g>\n          <path\n            d=\"M211.89,213.669c0-10.475-8.521-18.997-18.996-18.997c-0.401,0-0.799,0.017-1.193,0.041v2.406\n       c0.396-0.028,0.79-0.061,1.193-0.061c9.158,0,16.608,7.452,16.608,16.611s-7.45,16.61-16.608,16.61\n       c-0.269,0-0.53-0.027-0.795-0.041v0.897v1.509v4.723H186.2v3.182h13.388v-3.182h-5.104v-4.774\n       C204.218,231.781,211.89,223.607,211.89,213.669z\"\n          />\n          <g>\n            <polygon\n              points=\"260.072,79.408 260.398,82.045 256.447,82.882 255.913,88.517 260.677,88.517 266.998,87.913 270.251,84.021 \n         266.788,82.678 264.883,80.488 262.024,75.858 260.677,69.329 255.286,70.409 253.799,72.721 253.799,75.312 256.378,77.084 \t\t\t\n         \"\n            />\n            <polygon\n              points=\"255.495,81.569 255.773,78.037 252.637,76.683 248.233,77.706 244.945,82.94 244.945,86.344 248.768,86.344 \t\t\t\n         \"\n            />\n            <path\n              d=\"M164.852,96.598l-0.976,2.498h-4.7v2.428h1.121c0,0,0.07,0.511,0.168,1.191l2.876-0.238l1.783-1.121l0.465-2.248\n         l2.335-0.204l0.912-1.888l-2.138-0.442L164.852,96.598z\"\n            />\n            <polygon points=\"152.739,101.001 152.565,103.366 155.976,103.081 156.324,100.705 154.279,99.096 \t\t\t\" />\n            <path\n              d=\"M356.868,176.633c-0.047-5.223-0.313-10.398-0.802-15.505c-1.662-17.01-5.717-33.311-11.828-48.589\n         c-0.441-1.127-0.859-2.283-1.336-3.41c-8.121-19.183-19.531-36.646-33.474-51.721c-0.906-0.987-1.835-1.952-2.765-2.916\n         c-2.649-2.736-5.333-5.415-8.156-7.971C266.788,17.631,224.642,0,178.463,0C131.896,0,89.447,17.957,57.635,47.271\n         c-7.413,6.832-14.221,14.303-20.408,22.285C13.919,99.717,0,137.49,0,178.463c0,98.398,80.059,178.463,178.463,178.463\n         c69.225,0,129.316-39.643,158.897-97.399c6.32-12.327,11.247-25.491,14.569-39.294c0.837-3.486,1.58-7.018,2.208-10.585\n         c1.801-10.137,2.788-20.56,2.788-31.196C356.902,177.859,356.868,177.249,356.868,176.633z M323.278,105.306l1.022-1.162\n         c1.359,2.637,2.649,5.304,3.846,8.028l-1.708-0.07l-3.172,0.436v-7.233H323.278z M297.484,74.156l0.023-7.971\n         c2.812,2.975,5.508,6.036,8.087,9.214l-3.207,4.781l-11.247-0.111l-0.696-2.341L297.484,74.156z M82.214,54.364v-0.302h3.567\n         l0.325-1.226h5.838v2.55l-1.691,2.236h-8.052v-3.259H82.214z M87.925,62.323c0,0,3.578-0.61,3.892-0.61c0.296,0,0,3.573,0,3.573\n         l-8.081,0.511l-1.534-1.847L87.925,62.323z M334.642,133.156h-13.06l-7.971-5.92l-8.365,0.808v5.112h-2.648l-2.848-2.033\n         l-14.512-3.671v-9.4l-18.38,1.423l-5.705,3.062h-7.285l-3.59-0.36l-8.854,4.926v9.254l-18.097,13.065l1.5,5.583h3.677\n         l-0.964,5.315l-2.58,0.953l-0.133,13.884l15.633,17.823h6.819l0.407-1.081h12.246l3.531-3.265h6.948l3.812,3.811l10.328,1.069\n         l-1.359,13.757l11.503,20.28l-6.064,11.572l0.406,5.438l4.775,4.752v13.095l6.251,8.412v10.897h5.391\n         c-30.046,36.913-75.823,60.534-127.026,60.534c-90.312,0-163.783-73.454-163.783-163.777c0-22.732,4.665-44.401,13.077-64.089\n         v-5.106l5.855-7.11c2.033-3.846,4.212-7.582,6.542-11.235l0.25,2.974l-6.791,8.261c-2.108,3.985-4.084,8.052-5.855,12.217v9.312\n         l6.791,3.276v12.955l6.535,11.136l5.316,0.808l0.68-3.817l-6.245-9.661l-1.237-9.388h3.677l1.557,9.673l9.051,13.193l-2.33,4.27\n         l5.734,8.795l14.291,3.532v-2.306l5.711,0.808l-0.534,4.078l4.484,0.825l6.948,1.888l9.8,11.171l12.507,0.941l1.237,10.207\n         l-8.58,5.984l-0.39,9.115l-1.237,5.588l12.386,15.5l0.947,5.32c0,0,4.49,1.209,5.048,1.209c0.535,0,10.062,7.227,10.062,7.227\n         v28.024l3.393,0.964l-2.294,12.92l5.71,7.634l-1.068,12.827l7.563,13.269l9.696,8.47l9.731,0.197l0.952-3.148l-7.163-6.029\n         l0.418-2.986l1.272-3.684l0.273-3.741l-4.839-0.14l-2.44-3.066l4.021-3.881l0.546-2.916l-4.496-1.29l0.261-2.719l6.402-0.976\n         l9.73-4.672l3.265-6.006l10.196-13.06l-2.312-10.213l3.131-5.438l9.399,0.278l6.327-5.02l2.051-19.693l7.04-8.877l1.237-5.704\n         l-6.39-2.045l-4.224-6.942l-14.419-0.145l-11.444-4.351l-0.534-8.162l-3.811-6.675l-10.335-0.145l-5.995-9.382l-5.298-2.585\n         l-0.273,2.858l-9.672,0.569l-3.532-4.926l-10.079-2.045l-8.302,9.603l-13.065-2.23l-0.953-14.727l-9.527-1.632l3.805-7.221\n         l-1.092-4.148l-12.531,8.371l-7.877-0.964l-2.817-6.158l1.737-6.355l4.339-8.005l9.998-5.072h19.322l-0.064,5.891l6.948,3.235\n         l-0.558-10.062l5.007-5.037l10.103-6.64l0.703-4.659l10.068-10.486l10.707-5.937l-0.941-0.773l7.256-6.826l2.655,0.703\n         l1.214,1.522l2.76-3.062l0.68-0.296l-3.021-0.43l-3.084-0.987v-2.963l1.632-1.33h3.579l1.655,0.726l1.418,2.858l1.737-0.267\n         v-0.244l0.5,0.163l5.02-0.772l0.714-2.463l2.852,0.726v2.667l-2.666,1.818h0.018l0.377,2.928l9.115,2.794c0,0,0,0.035,0.023,0.11\n         l2.079-0.18l0.146-3.939l-7.209-3.282l-0.396-1.894l5.972-2.033l0.273-5.722l-6.245-3.805l-0.412-9.667l-8.581,4.218h-3.143\n         l0.837-7.355l-11.688-2.748l-4.816,3.654v11.119l-8.673,2.754l-3.486,7.244l-3.758,0.604v-9.277l-8.162-1.133l-4.096-2.667\n         l-1.639-6.007l14.611-8.54l7.14-2.179l0.72,4.804l3.991-0.215l0.308-2.411l4.166-0.599l0.07-0.842l-1.784-0.738l-0.407-2.544\n         l5.118-0.43l3.091-3.213l0.18-0.238l0.035,0.012l0.941-0.976l10.753-1.354l4.746,4.032l-12.467,6.64l15.871,3.747l2.045-5.31\n         h6.948l2.44-4.625l-4.903-1.226v-5.856l-15.359-6.803l-10.62,1.226l-6.001,3.125l0.407,7.628l-6.257-0.953l-0.964-4.212\n         l6.007-5.449l-10.898-0.535l-3.125,0.953l-1.359,3.677l4.084,0.686l-0.813,4.084l-6.936,0.406l-1.092,2.725L118.987,52.4\n         c0,0-0.273-5.711-0.703-5.711c-0.389,0,7.901-0.145,7.901-0.145l5.995-5.85l-3.271-1.632l-4.339,4.223l-7.222-0.406l-4.403-6.019\n         h-9.254L94.03,44.07h8.848l0.796,2.597l-2.307,2.172l9.807,0.279l1.487,3.532l-11.032-0.407l-0.546-2.725l-6.925-1.499\n         l-3.689-2.033l-8.255,0.069c27.043-19.699,60.284-31.358,96.226-31.358c41.403,0,79.263,15.476,108.124,40.915l-1.929,3.474\n         l-7.564,2.962l-3.194,3.462l0.743,4.02l3.893,0.546l2.358,5.867l6.704-2.713l1.127,7.86h-2.045l-5.519-0.819l-6.111,1.022\n         l-5.926,8.377l-8.458,1.319l-1.221,7.25l3.579,0.842l-1.046,4.665l-8.412-1.69l-7.703,1.69l-1.639,4.293l1.325,9.01l4.531,2.115\n         l7.61-0.046l5.123-0.465l1.58-4.078l8.018-10.422l5.264,1.081l5.193-4.7l0.976,3.678l12.78,8.621l-1.557,2.108l-5.763-0.308\n         l2.23,3.137l3.556,0.79l4.159-1.737l-0.093-5.002l1.859-0.923l-1.487-1.575l-8.528-4.758l-2.254-6.314h7.099l2.243,2.248\n         l6.134,5.257l0.244,6.367l6.332,6.733l2.348-9.231l4.392-2.394l0.802,7.552l4.287,4.7l8.54-0.139\n         c1.661,4.247,3.148,8.563,4.427,12.978L334.642,133.156z M97.324,81.092l4.27-2.044l3.881,0.929l-1.324,5.211l-4.183,1.319\n         L97.324,81.092z M120.073,93.35v3.37h-9.783l-3.689-1.028l0.918-2.341l4.7-1.94h6.437v1.94H120.073z M124.582,98.05v3.259\n         l-2.463,1.58l-3.044,0.575c0,0,0-4.903,0-5.415H124.582z M121.822,96.72v-3.893l3.363,3.067L121.822,96.72z M123.355,104.568\n         v3.178l-2.347,2.347h-5.211l0.813-3.573l2.463-0.215l0.5-1.226L123.355,104.568z M110.39,98.05h5.408l-6.948,9.696l-2.852-1.534\n         l0.616-4.084L110.39,98.05z M132.529,103.464v3.166h-5.211l-1.417-2.062v-2.951h0.406L132.529,103.464z M127.748,99.096\n         l1.475-1.557l2.498,1.557l-1.999,1.656L127.748,99.096z M337.291,141.428l0.511-0.61c0.232,0.93,0.441,1.859,0.662,2.789\n         L337.291,141.428z\"\n            />\n            <path d=\"M27.734,109.268v5.106c1.771-4.177,3.747-8.231,5.855-12.223L27.734,109.268z\" />\n          </g>\n        </g>\n      </g>\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/GoArrow.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      width={width}\n      height={height}\n      fill={color}\n      version=\"1.1\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 129 129\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      enableBackground=\"new 0 0 129 129\"\n    >\n      <g>\n        <path d=\"m40.4,121.3c-0.8,0.8-1.8,1.2-2.9,1.2s-2.1-0.4-2.9-1.2c-1.6-1.6-1.6-4.2 0-5.8l51-51-51-51c-1.6-1.6-1.6-4.2 0-5.8 1.6-1.6 4.2-1.6 5.8,0l53.9,53.9c1.6,1.6 1.6,4.2 0,5.8l-53.9,53.9z\" />\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Home.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      version=\"1.1\"\n      id=\"Capa_1\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"0 0 63.699 63.699\"\n      xmlSpace=\"preserve\"\n      className=\"gotoHomeIcon\"\n    >\n      <g>\n        <path\n          d=\"M63.663,29.424c-0.143-1.093-0.701-2.065-1.575-2.737l-11.715-9.021V8.608c0-2.275-1.851-4.126-4.125-4.126\n\t\tc-2.273,0-4.125,1.851-4.125,4.126v2.705l-7.758-5.975c-0.718-0.551-1.612-0.856-2.517-0.856c-0.906,0-1.801,0.304-2.519,0.857\n\t\tL1.606,26.687c-1.802,1.389-2.139,3.983-0.751,5.785c0.788,1.022,1.979,1.608,3.271,1.608c0.664,0,1.302-0.153,1.88-0.451V55.09\n\t\tc0,2.275,1.851,4.127,4.126,4.127h18.534V39.732h6.351v19.482h18.271c2.274,0,4.125-1.85,4.125-4.127V33.472\n\t\tc0.649,0.399,1.387,0.608,2.157,0.608c1.289,0,2.482-0.586,3.27-1.606C63.514,31.601,63.807,30.518,63.663,29.424z M59.819,30.144\n\t\tc-0.08,0.105-0.189,0.122-0.247,0.122c-0.069,0-0.132-0.021-0.188-0.065L53.6,25.748V55.09c0,0.173-0.14,0.312-0.311,0.312H38.832\n\t\tl0.001-19.484H24.852v19.484H10.132c-0.171,0-0.31-0.141-0.31-0.312V25.96L4.315,30.2c-0.056,0.043-0.119,0.065-0.188,0.065\n\t\tc-0.059,0-0.167-0.017-0.248-0.121c-0.065-0.084-0.07-0.171-0.062-0.229c0.007-0.058,0.034-0.141,0.118-0.205L31.661,8.363\n\t\tc0.138-0.105,0.239-0.106,0.379,0l13.899,10.703V8.608c0-0.172,0.14-0.311,0.311-0.311s0.312,0.139,0.312,0.311v10.935\n\t\tl13.205,10.166c0.084,0.064,0.108,0.147,0.116,0.205C59.891,29.975,59.885,30.062,59.819,30.144z\"\n        />\n      </g>\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/HomePlus.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      stroke={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"0 0 66 66\"\n      className=\"setHomeIcon\"\n    >\n      <defs>\n        <clipPath id=\"_clipPath_1jbp17xur21ul8z5IuRkxmo4gJ5cDGqz\">\n          <rect width=\"66\" height=\"66\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_1jbp17xur21ul8z5IuRkxmo4gJ5cDGqz)\">\n        <g>\n          <path d=\" M 47.973 42.767 C 47.865 41.943 47.445 41.211 46.786 40.704 L 37.958 33.907 L 37.958 27.081 C 37.958 25.367 36.564 23.972 34.85 23.972 C 33.137 23.972 31.742 25.367 31.742 27.081 L 31.742 29.119 L 25.896 24.617 C 25.355 24.202 24.681 23.972 23.999 23.972 C 23.316 23.972 22.642 24.201 22.101 24.618 L 1.21 40.704 C -0.147 41.751 -0.401 43.706 0.645 45.064 C 1.238 45.834 2.136 46.275 3.109 46.275 C 3.61 46.275 4.091 46.16 4.526 45.935 L 4.526 62.107 C 4.526 63.821 5.921 65.217 7.635 65.217 L 21.601 65.217 L 21.601 50.534 L 26.387 50.534 L 26.387 65.215 L 40.155 65.215 C 41.869 65.215 43.263 63.821 43.263 62.105 L 43.263 45.817 C 43.752 46.118 44.309 46.275 44.889 46.275 C 45.86 46.275 46.759 45.834 47.353 45.065 C 47.861 44.407 48.082 43.591 47.973 42.767 Z  M 45.076 43.309 C 45.016 43.388 44.934 43.401 44.89 43.401 C 44.838 43.401 44.791 43.385 44.749 43.352 L 40.39 39.997 L 40.39 62.107 C 40.39 62.237 40.285 62.342 40.156 62.342 L 29.262 62.342 L 29.263 47.66 L 18.727 47.66 L 18.727 62.342 L 7.635 62.342 C 7.506 62.342 7.402 62.236 7.402 62.107 L 7.402 40.156 L 3.252 43.351 C 3.21 43.384 3.162 43.4 3.11 43.4 C 3.066 43.4 2.984 43.388 2.923 43.309 C 2.874 43.246 2.871 43.18 2.877 43.137 C 2.882 43.093 2.902 43.03 2.965 42.982 L 23.858 26.896 C 23.962 26.817 24.038 26.817 24.144 26.896 L 34.617 34.962 L 34.617 27.081 C 34.617 26.951 34.723 26.847 34.852 26.847 C 34.98 26.847 35.087 26.951 35.087 27.081 L 35.087 35.321 L 45.037 42.982 C 45.1 43.03 45.119 43.092 45.125 43.136 C 45.131 43.182 45.126 43.248 45.076 43.309 Z \" />\n        </g>\n        <g>\n          <line\n            x1=\"51\"\n            y1=\"2\"\n            x2=\"51\"\n            y2=\"28\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            strokeLinejoin=\"miter\"\n            strokeLinecap=\"round\"\n            strokeMiterlimit=\"3\"\n          />\n          <line\n            x1=\"64\"\n            y1=\"15\"\n            x2=\"38\"\n            y2=\"15\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            strokeLinejoin=\"miter\"\n            strokeLinecap=\"round\"\n            strokeMiterlimit=\"3\"\n          />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/InspectElement.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin, style}) => (\n  <Fragment>\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"1037.909 556.137 16 16\"\n      width={width}\n      height={height}\n      fill={color}\n      style={{isolation: 'isolate', padding, margin, ...style}}\n      className=\"inspectElementIcon\"\n    >\n      <defs>\n        <clipPath id=\"_clipPath_NlBZVvucrUfAyfaqZqVDKpIaZEkvYgwZ\">\n          <rect x=\"1037.909\" y=\"556.137\" width=\"16\" height=\"16\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_NlBZVvucrUfAyfaqZqVDKpIaZEkvYgwZ)\">\n        <g>\n          <g opacity=\"0.5\">\n            <path\n              d=\" M 1037.909 556.137 L 1053.909 556.137 L 1053.909 572.137 L 1037.909 572.137 L 1037.909 556.137 Z \"\n              fill=\"none\"\n            />\n          </g>\n          <path\n            d=\" M 1043.909 570.137 L 1041.409 570.137 C 1040.409 570.137 1039.909 569.637 1039.909 568.637 L 1039.909 559.637 C 1039.909 558.637 1040.409 558.137 1041.409 558.137 L 1050.409 558.137 C 1051.909 558.137 1051.909 559.605 1051.909 559.637 L 1051.909 562.137 L 1050.909 562.137 L 1050.909 559.137 L 1040.909 559.137 L 1040.909 569.137 L 1043.909 569.137 L 1043.909 570.137 Z  M 1052.909 565.137 L 1049.909 567.137 L 1052.909 570.137 L 1051.909 571.137 L 1048.909 568.137 L 1046.909 571.137 L 1044.909 563.137 L 1052.909 565.137 Z \"\n            fill={color}\n          />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/InspectElementChrome.js",
    "content": "import React from 'react';\nimport cx from 'classnames';\n\nimport styles from './styles.module.css';\n\nexport default function InspectElementChrome({\n  width,\n  height,\n  color,\n  padding,\n  margin,\n  style,\n  selected,\n}) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"1037.909 556.137 16 16\"\n      width={width}\n      height={height}\n      fill={color}\n      style={{isolation: 'isolate', padding, margin, ...style}}\n      className={cx(styles.chromeIcon, {[styles.selected]: selected})}\n    >\n      <defs>\n        <clipPath id=\"_clipPath_NlBZVvucrUfAyfaqZqVDKpIaZEkvYgwZ\">\n          <rect x=\"1037.909\" y=\"556.137\" width=\"16\" height=\"16\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_NlBZVvucrUfAyfaqZqVDKpIaZEkvYgwZ)\">\n        <g>\n          <g opacity=\"0.5\">\n            <path\n              d=\" M 1037.909 556.137 L 1053.909 556.137 L 1053.909 572.137 L 1037.909 572.137 L 1037.909 556.137 Z \"\n              fill=\"none\"\n            />\n          </g>\n          <path\n            d=\" M 1043.909 570.137 L 1041.409 570.137 C 1040.409 570.137 1039.909 569.637 1039.909 568.637 L 1039.909 559.637 C 1039.909 558.637 1040.409 558.137 1041.409 558.137 L 1050.409 558.137 C 1051.909 558.137 1051.909 559.605 1051.909 559.637 L 1051.909 562.137 L 1050.909 562.137 L 1050.909 559.137 L 1040.909 559.137 L 1040.909 569.137 L 1043.909 569.137 L 1043.909 570.137 Z  M 1052.909 565.137 L 1049.909 567.137 L 1052.909 570.137 L 1051.909 571.137 L 1048.909 568.137 L 1046.909 571.137 L 1044.909 563.137 L 1052.909 565.137 Z \"\n            fill={color}\n          />\n        </g>\n      </g>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Kebab.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default function Kebab({width, height, color, padding, margin}) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 100 125\"\n      enableBackground=\"new 0 0 100 100\"\n      xmlSpace=\"preserve\"\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      className=\"kebab\"\n    >\n      <circle cx=\"50\" cy=\"17.5\" r=\"12.5\" />\n      <circle cx=\"50\" cy=\"50\" r=\"12.5\" />\n      <circle cx=\"50\" cy=\"82.5\" r=\"12.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Layout.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 32 32\"\n      enableBackground=\"new 0 0 32 32\"\n    >\n      <path d=\"M32 14c0 1.104-0.896 2-2 2H20c-1.104 0-2-0.896-2-2V2c0-1.104 0.896-2 2-2h10c1.104 0 2 0.896 2 2v3c0 0.552-0.447 1-1 1s-1-0.448-1-1V2H20v12h10V9c0-0.552 0.447-1 1-1s1 0.448 1 1V14z\" />\n      <path d=\"M27 32c-0.553 0-1-0.448-1-1s0.447-1 1-1h3v-8H20v8h3c0.553 0 1 0.448 1 1s-0.447 1-1 1h-3c-1.104 0-2-0.896-2-2v-8c0-1.104 0.896-2 2-2h10c1.104 0 2 0.896 2 2v8c0 1.104-0.896 2-2 2H27z\" />\n      <path d=\"M0 2c0-1.104 0.896-2 2-2h10c1.104 0 2 0.896 2 2v28c0 1.104-0.896 2-2 2H2c-1.104 0-2-0.896-2-2v-3c0-0.552 0.447-1 1-1s1 0.448 1 1v3h10V2H2v21c0 0.552-0.447 1-1 1s-1-0.448-1-1V2z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/LightBulb.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{enableBackground: 'new 0 0 480.8 480.8', padding}}\n      version=\"1.1\"\n      id=\"Capa_1\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 480.8 480.8\"\n      xmlSpace=\"preserve\"\n    >\n      <path\n        style={{fill: color}}\n        d=\"M317.112,314.4c-22.4,22.4-19.6,67.6-19.6,67.6h-113.6c0,0,2.4-45.2-19.6-67.6\n\tc-24.4-21.6-40-52.8-40-87.6c0-64,52-116,116-116s116,52,116,116C356.312,261.6,341.112,292.8,317.112,314.4L317.112,314.4z\"\n      />\n      <g>\n        <path\n          style={{fill: '#E5E5E5'}}\n          d=\"M300.712,417.6c0,6-4.8,10.8-10.8,10.8h-98.8c-6,0-10.8-4.8-10.8-10.8l0,0c0-6,4.8-10.8,10.8-10.8\n\t\th98.4C295.512,406.8,300.712,411.6,300.712,417.6L300.712,417.6z\"\n        />\n        <path\n          style={{fill: '#E5E5E5'}}\n          d=\"M285.912,462.4c0,6-4.8,10.8-10.8,10.8h-69.2c-6,0-10.8-4.8-10.8-10.8l0,0c0-6,4.8-10.8,10.8-10.8\n\t\th69.2C281.112,451.6,285.912,456.4,285.912,462.4L285.912,462.4z\"\n        />\n      </g>\n      <g>\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M323.112,318.4c26-23.6,40.8-56.8,40.8-91.6c0-68-55.6-123.6-123.6-123.6s-123.6,55.6-123.6,123.6\n\t\tc0,35.6,15.6,69.6,42,92.8c19.6,19.6,17.6,61.2,17.6,61.6c0,2,0.8,4,2,5.6c1.6,1.6,3.6,2.4,5.6,2.4h113.2c2,0,4-0.8,5.6-2.4\n\t\ts2-3.6,2-5.6c0-0.4-2-42,17.6-61.6C322.712,319.2,323.112,318.8,323.112,318.4z M311.912,308.4c-0.8,0.4-1.2,1.2-1.6,2\n\t\tc-17.6,18.8-20.4,49.6-20.8,64h-98c-0.4-14.8-3.6-46.8-22.4-65.6c-23.6-20.8-37.2-50.4-37.2-81.6c0-60,48.8-108.4,108.4-108.4\n\t\tc60,0,108.4,48.8,108.4,108.4C348.712,258,335.512,288,311.912,308.4z\"\n        />\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M240.312,135.2c-4,0-7.6,3.2-7.6,7.6c0,4,3.2,7.6,7.6,7.6c44.8,0,81.2,36.4,81.2,81.2\n\t\tc0,4,3.2,7.6,7.6,7.6c4,0,7.6-3.2,7.6-7.6C336.712,178.4,293.512,135.2,240.312,135.2z\"\n        />\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M308.312,417.6c0-10.4-8.4-18.4-18.4-18.4h-98.8c-10.4,0-18.4,8.4-18.4,18.4\n\t\tc0,10.4,8.4,18.4,18.4,18.4h98.4C299.912,436,308.312,428,308.312,417.6z M289.512,420.8h-98.4c-2,0-3.2-1.6-3.2-3.2\n\t\tc0-2,1.6-3.2,3.2-3.2h98.4c2,0,3.2,1.6,3.2,3.2C293.112,419.6,291.512,420.8,289.512,420.8z\"\n        />\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M275.112,444h-69.2c-10.4,0-18.4,8.4-18.4,18.4c0,10.4,8.4,18.4,18.4,18.4h69.2\n\t\tc10.4,0,18.4-8.4,18.4-18.4C293.512,452.4,285.112,444,275.112,444z M275.112,465.6h-69.2c-2,0-3.2-1.6-3.2-3.2\n\t\tc0-2,1.6-3.2,3.2-3.2h69.2c2,0,3.2,1.6,3.2,3.2C278.312,464.4,277.112,465.6,275.112,465.6z\"\n        />\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M247.912,58.8V7.6c0-4-3.2-7.6-7.6-7.6c-4,0-7.6,3.2-7.6,7.6v51.6c0,4,3.2,7.6,7.6,7.6\n\t\tC244.712,66.4,247.912,63.2,247.912,58.8z\"\n        />\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M366.312,38c-3.6-2.4-8-1.2-10.4,2l-28.4,42.8c-2.4,3.6-1.2,8,2,10.4c1.2,0.8,2.8,1.2,4,1.2\n\t\tc2.4,0,4.8-1.2,6.4-3.2l28.4-42.8C370.712,45.2,369.512,40.4,366.312,38z\"\n        />\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M149.912,92.8c1.2,0,2.8-0.4,4-1.2c3.6-2.4,4.4-6.8,2.4-10.4l-27.6-43.2c-2.4-3.6-6.8-4.4-10.4-2.4\n\t\tc-3.6,2.4-4.4,6.8-2.4,10.4l27.6,43.2C145.112,91.6,147.512,92.8,149.912,92.8z\"\n        />\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M43.912,128.8l45.2,24.4c1.2,0.8,2.4,0.8,3.6,0.8c2.8,0,5.2-1.6,6.8-4c2-3.6,0.8-8.4-3.2-10.4\n\t\tl-45.2-24.4c-3.6-2-8.4-0.8-10.4,3.2C39.112,122.4,40.312,126.8,43.912,128.8z\"\n        />\n        <path\n          style={{fill: '#210B20'}}\n          d=\"M387.912,154.4c1.2,0,2.4-0.4,3.6-0.8l45.2-24.4c3.6-2,5.2-6.4,3.2-10.4c-2-3.6-6.4-5.2-10.4-3.2\n\t\tl-45.2,24.4c-3.6,2-5.2,6.4-3.2,10.4C382.312,152.8,385.112,154.4,387.912,154.4z\"\n        />\n      </g>\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/LightColorScheme.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin, className}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      stroke={color}\n      style={{padding, margin}}\n      className={className}\n      fill=\"transparent\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path d=\"M17.5 12C17.5 15.0376 15.0376 17.5 12 17.5C8.96243 17.5 6.5 15.0376 6.5 12C6.5 8.96243 8.96243 6.5 12 6.5C15.0376 6.5 17.5 8.96243 17.5 12Z\" />\n      <path d=\"M4 4L6 6M20 12H23H20ZM1 12H4H1ZM12 20V23V20ZM12 1V4V1ZM18 6L20 4L18 6ZM4 20L6 18L4 20ZM18 18L20 20L18 18Z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Logo.js",
    "content": "// @flow\nimport React from 'react';\nimport logoImage from '../../../resources/logo.svg';\n\nexport default function Logo({\n  width,\n  height,\n  color,\n  padding,\n  margin,\n  className,\n}) {\n  return (\n    <img\n      src={logoImage}\n      height={height}\n      width={width}\n      alt=\"\"\n      className={`logoIcon ${className ?? ''}`}\n    />\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Maximize.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 512.00533 512\"\n    >\n      <path d=\"m10.671875 170.667969c-5.886719 0-10.66796875-4.777344-10.66796875-10.664063v-149.335937c0-5.886719 4.78124975-10.66406275 10.66796875-10.66406275h149.332031c5.890625 0 10.667969 4.77734375 10.667969 10.66406275 0 5.890625-4.777344 10.667969-10.667969 10.667969h-138.664062v138.667968c0 5.886719-4.78125 10.664063-10.667969 10.664063zm0 0\" />\n      <path d=\"m501.339844 170.667969c-5.890625 0-10.667969-4.777344-10.667969-10.664063v-138.667968h-138.667969c-5.886718 0-10.664062-4.777344-10.664062-10.667969 0-5.886719 4.777344-10.66406275 10.664062-10.66406275h149.335938c5.886718 0 10.664062 4.77734375 10.664062 10.66406275v149.335937c0 5.886719-4.777344 10.664063-10.664062 10.664063zm0 0\" />\n      <path d=\"m160.003906 512.003906h-149.332031c-5.886719 0-10.66796875-4.78125-10.66796875-10.667968v-149.332032c0-5.890625 4.78124975-10.667968 10.66796875-10.667968s10.667969 4.777343 10.667969 10.667968v138.664063h138.664062c5.890625 0 10.667969 4.78125 10.667969 10.667969 0 5.886718-4.777344 10.667968-10.667969 10.667968zm0 0\" />\n      <path d=\"m501.339844 512.003906h-149.335938c-5.886718 0-10.664062-4.78125-10.664062-10.667968 0-5.886719 4.777344-10.667969 10.664062-10.667969h138.667969v-138.664063c0-5.890625 4.777344-10.667968 10.667969-10.667968 5.886718 0 10.664062 4.777343 10.664062 10.667968v149.332032c0 5.886718-4.777344 10.667968-10.664062 10.667968zm0 0\" />\n      <path d=\"m181.339844 192.003906c-2.730469 0-5.460938-1.046875-7.554688-3.117187l-170.664062-170.664063c-4.160156-4.160156-4.160156-10.925781 0-15.085937 4.160156-4.15625 10.921875-4.15625 15.082031 0l170.667969 170.667969c4.160156 4.160156 4.160156 10.921874 0 15.082031-2.070313 2.070312-4.800782 3.117187-7.53125 3.117187zm0 0\" />\n      <path d=\"m330.671875 192.003906c-2.730469 0-5.460937-1.046875-7.550781-3.117187-4.160156-4.160157-4.160156-10.921875 0-15.082031l170.664062-170.667969c4.160156-4.15625 10.925782-4.15625 15.085938 0 4.160156 4.160156 4.160156 10.925781 0 15.085937l-170.667969 170.664063c-2.070313 2.070312-4.800781 3.117187-7.53125 3.117187zm0 0\" />\n      <path d=\"m501.339844 512.003906c-2.730469 0-5.460938-1.046875-7.554688-3.117187l-170.664062-170.664063c-4.160156-4.160156-4.160156-10.925781 0-15.085937 4.160156-4.15625 10.921875-4.15625 15.082031 0l170.667969 170.667969c4.160156 4.160156 4.160156 10.921874 0 15.082031-2.070313 2.070312-4.800782 3.117187-7.53125 3.117187zm0 0\" />\n      <path d=\"m10.671875 512.003906c-2.730469 0-5.460937-1.046875-7.550781-3.117187-4.160156-4.160157-4.160156-10.921875 0-15.082031l170.664062-170.667969c4.160156-4.15625 10.925782-4.15625 15.085938 0 4.160156 4.160156 4.160156 10.925781 0 15.085937l-170.667969 170.664063c-2.070313 2.070312-4.800781 3.117187-7.53125 3.117187zm0 0\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Minimize.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      version=\"1.1\"\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 612 612\"\n    >\n      <g>\n        <g id=\"_x36_\">\n          <g>\n            <path\n              d=\"M248.542,343.929H78.879c-10.452,0-18.917,8.428-18.917,18.842c0,10.395,8.465,18.84,18.917,18.84h125.351L0,584.979\n\t\t\t\tl26.751,26.639l204.019-203.18l-0.592,123.822c0,10.395,8.465,18.84,18.917,18.84c10.452,0,18.917-8.426,18.917-18.84v-169.51\n\t\t\t\tc0-5.58-2.312-10.09-5.981-13.186C258.573,346.126,253.815,343.929,248.542,343.929z M533.141,230.388H407.79L612,27.019\n\t\t\t\tL585.248,0.382l-204,203.178l0.593-123.822c0-10.395-8.465-18.841-18.917-18.841s-18.917,8.427-18.917,18.841v169.51\n\t\t\t\tc0,5.58,2.312,10.089,5.961,13.166c3.439,3.478,8.179,5.675,13.472,5.675h169.662c10.452,0,18.918-8.427,18.918-18.841\n\t\t\t\tC552.038,238.834,543.573,230.388,533.141,230.388z\"\n            />\n          </g>\n        </g>\n      </g>\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n      <g />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Muted.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default function Muted({width, height, color, padding, margin}) {\n  return (\n    <Fragment>\n      <svg\n        viewBox=\"0 0 22 27.5\"\n        version=\"1.1\"\n        x=\"0px\"\n        y=\"0px\"\n        height={height}\n        width={width}\n        fill={color}\n        style={{padding, margin}}\n        xmlns=\"http://www.w3.org/2000/svg\"\n        xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n        className=\"muteIcon\"\n        xmlSpace=\"preserve\"\n      >\n        <g stroke=\"none\" strokeWidth=\"1\" fill=\"none\" fillRule=\"evenodd\">\n          <path\n            d=\"M0,10.0029293 L0,15.9970707 C0,16.5509732 0.45097518,17 0.990777969,17 L5,17 L10.4704221,20.7253518 C11.3151847,21.4293205 12,21.1099416 12,19.9998938 L12,6.00010618 C12,4.89547804 11.3117237,4.57356358 10.4704221,5.27464823 L5,9 L0.990777969,9 C0.443586406,9 0,9.43788135 0,10.0029293 Z M5,10 L5.36204994,10 L5.6401844,9.76822128 L11,6.15551758 L11.0151624,19.8775937 L5.6401844,16.2317787 L5.36204994,16 L5,16 L0.990777969,16 C1.00229279,16 1,10.0029293 1,10.0029293 C1,9.9930141 5,10 5,10 Z\"\n            fill={color}\n          />\n        </g>\n      </svg>\n    </Fragment>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Network.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      viewBox=\"0 0 269.393 269.393\"\n      version=\"1.1\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      className=\"muteIcon\"\n      xmlSpace=\"preserve\"\n    >\n      <path\n        d=\"M134.696,0C60.424,0,0,60.425,0,134.696s60.424,134.696,134.696,134.696s134.696-60.425,134.696-134.696\n        S208.968,0,134.696,0z M136.869,252.518c-12.91,0-25.84-8.779-36.409-24.721c-4.421-6.669-8.316-14.428-11.614-22.979h38.351v20.879\n        c0,4.143,3.358,7.5,7.5,7.5s7.5-3.357,7.5-7.5v-20.879h42.616C173.729,233.559,156.188,252.518,136.869,252.518z M15.242,142.196\n        H39.23c2.921,11.683,12.5,20.75,24.457,22.928c1.12,8.63,2.689,16.884,4.66,24.693H28.471\n        C20.961,175.403,16.303,159.28,15.242,142.196z M142.196,17.372c13.801,2.521,26.969,14.745,37.083,34.544\n        c-3.816,3.294-6.688,7.649-8.15,12.607h-28.934V17.372z M196.607,83.585c-6.375,0-11.563-5.187-11.563-11.563\n        s5.187-11.563,11.563-11.563s11.563,5.187,11.563,11.563S202.983,83.585,196.607,83.585z M171.13,79.522\n        c3.01,10.206,11.992,17.875,22.887,18.934c1.574,9.312,2.518,18.938,2.841,28.74h-54.661V79.522H171.13z M127.196,127.196H99.257\n        c-2.597-10.386-10.455-18.707-20.568-21.958c1.209-8.956,2.981-17.578,5.238-25.716h43.269V127.196z M69.244,150.634\n        c-8.788,0-15.938-7.149-15.938-15.938s7.149-15.938,15.938-15.938s15.938,7.149,15.938,15.938S78.032,150.634,69.244,150.634z\n        M63.697,104.267c-11.961,2.175-21.545,11.243-24.467,22.93H15.242c1.063-17.104,5.731-33.247,13.257-47.674H68.42\n        C66.401,87.448,64.81,95.734,63.697,104.267z M78.7,164.15c10.108-3.253,17.961-11.572,20.557-21.954h27.939v47.621h-43.29\n        C81.657,181.696,79.906,173.082,78.7,164.15z M142.196,189.817v-47.621h54.667c-0.576,17.011-3.092,33.174-7.111,47.621H142.196z\n        M211.869,142.196h18.328c4.142,0,7.5-3.357,7.5-7.5s-3.358-7.5-7.5-7.5h-18.331c-0.332-10.757-1.371-21.329-3.116-31.561\n        c6.386-3.297,11.269-9.105,13.336-16.113h18.809c8.618,16.521,13.499,35.287,13.499,55.174c0,19.866-4.871,38.614-13.471,55.121\n        h-35.547C209.143,174.965,211.356,158.863,211.869,142.196z M231.602,64.522h-9.517c-3.245-11.005-13.435-19.063-25.477-19.063\n        c-1.242,0-2.463,0.092-3.66,0.258c-4.421-8.751-9.442-16.33-14.927-22.601C199.615,31.531,218.182,46.041,231.602,64.522z\n        M127.196,15.242v49.281H88.854c3.211-8.316,6.994-15.876,11.277-22.426c2.267-3.467,1.294-8.115-2.173-10.382\n        c-3.467-2.268-8.115-1.293-10.382,2.173c-5.761,8.811-10.693,19.19-14.666,30.635H37.791\n        C58.186,36.436,90.473,17.524,127.196,15.242z M37.752,204.817H72.81c6.236,18.028,14.801,32.962,24.934,43.73\n        C73.41,240.63,52.477,225.118,37.752,204.817z M178.108,246.242c4.755-5.473,9.176-11.948,13.18-19.369\n        c3.662-6.787,6.855-14.192,9.587-22.056h30.765C218.235,223.299,199.686,237.815,178.108,246.242z\"\n      />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/PageNavigator.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin, style}) => (\n  <Fragment>\n    <svg\n      viewBox=\"0 0 24 24\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      width={width}\n      height={height}\n      fill={color}\n      style={{padding, margin, ...style}}\n      className=\"navigatorIcon\"\n    >\n      <path\n        d=\"M19.31 18.9c.44-.69.69-1.52.69-2.4c0-2.5-2-4.5-4.5-4.5S11 14 11 16.5s2 4.5 4.5 4.5c.87 0 1.69-.25 2.38-.68L21 23.39L22.39 22l-3.08-3.1m-3.81.1a2.5 2.5 0 0 1 0-5a2.5 2.5 0 0 1 0 5M21 9h-2V7h2v2m0-4h-2V3h1c.55 0 1 .45 1 1v1m-2 6.03V11h2v2h-.03A6.608 6.608 0 0 0 19 11.03M17 5h-2V3h2v2m-4 0h-2V3h2v2M3 7h2v2H3V7m4 12h2v2H7v-2m-4-8h2v2H3v-2m1-8h1v2H3V4c0-.55.45-1 1-1m5 2H7V3h2v2M3 19h2v2H4c-.55 0-1-.45-1-1v-1m0-4h2v2H3v-2z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Pictures.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path d=\"M19,2H5A3,3,0,0,0,2,5V19a3,3,0,0,0,3,3H19a2.81,2.81,0,0,0,.49-.05l.3-.07.07,0h0l.05,0,.37-.14.13-.07c.1-.06.21-.11.31-.18a3.79,3.79,0,0,0,.38-.32l.07-.09a2.69,2.69,0,0,0,.27-.32l.09-.13a2.31,2.31,0,0,0,.18-.35,1,1,0,0,0,.07-.15c.05-.12.08-.25.12-.38l0-.15A2.6,2.6,0,0,0,22,19V5A3,3,0,0,0,19,2ZM5,20a1,1,0,0,1-1-1V14.69l3.29-3.3h0a1,1,0,0,1,1.42,0L17.31,20Zm15-1a1,1,0,0,1-.07.36,1,1,0,0,1-.08.14.94.94,0,0,1-.09.12l-5.35-5.35.88-.88a1,1,0,0,1,1.42,0h0L20,16.69Zm0-5.14L18.12,12a3.08,3.08,0,0,0-4.24,0l-.88.88L10.12,10a3.08,3.08,0,0,0-4.24,0L4,11.86V5A1,1,0,0,1,5,4H19a1,1,0,0,1,1,1ZM13.5,6A1.5,1.5,0,1,0,15,7.5,1.5,1.5,0,0,0,13.5,6Z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Proxy.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n\t<svg\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\txmlnsXlink=\"http://www.w3.org/1999/xlink\" \n\t\tversion=\"1.1\"\n        x=\"0px\"\n        y=\"0px\"\n        height={height}\n        width={width}\n        fill={color}\n        style={{padding, margin}}\n\t\tclassName=\"proxyIcon\"\n\t\tviewBox=\"0 0 34.5 34.5\"\n\t>\n\t\t<g className=\"currentLayer\">\n\t\t\t<title>Layer 1</title>\n\t\t\t<path d=\"M30 30h-8v-8h8zm-6-2h4v-4h-4z\" fill=\"currentColor\" id=\"svg_1\"/>\n\t\t\t<path d=\"M20 27H8a6 6 0 0 1 0-12h2v2H8a4 4 0 0 0 0 8h12z\" fill=\"currentColor\" id=\"svg_2\"/>\n\t\t\t<path d=\"M20 20h-8v-8h8zm-6-2h4v-4h-4z\" fill=\"currentColor\" id=\"svg_3\"/>\n\t\t\t<path d=\"M24 17h-2v-2h2a4 4 0 0 0 0-8H12V5h12a6 6 0 0 1 0 12z\" fill=\"currentColor\" id=\"svg_4\"/>\n\t\t\t<path d=\"M10 10H2V2h8zM4 8h4V4H4z\" fill=\"currentColor\" id=\"svg_5\"/>\n\t\t\t<rect fill=\"currentColor\" strokeDashoffset=\"\" fillRule=\"nonzero\" id=\"svg_6\" x=\"13\" y=\"13\" width=\"7\" height=\"7\"/>\n\t\t</g>\n\t</svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Reload.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n    >\n      <path d=\"M21,11a1,1,0,0,0-1,1,8.05,8.05,0,1,1-2.22-5.5h-2.4a1,1,0,0,0,0,2h4.53a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4.77A10,10,0,1,0,22,12,1,1,0,0,0,21,11Z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/RoadMap.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default function RoadMap({\n  width,\n  height,\n  color,\n  padding,\n  margin,\n  className,\n}) {\n  return (\n    <Fragment>\n      <svg\n        height={height}\n        width={width}\n        fill={color}\n        style={{padding, margin}}\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 24 24\"\n        className={`roadMapIcon ${className}`}\n      >\n        <g stroke=\"none\" strokeWidth=\"1\" fill=\"none\" fillRule=\"evenodd\">\n          <rect opacity=\"0\" x=\"0\" y=\"0\" width=\"24\" height=\"24\" />\n          <path\n            d=\"M16.5428932,17.4571068 L11,11.9142136 L11,4 C11,3.44771525 11.4477153,3 12,3 C12.5522847,3 13,3.44771525 13,4 L13,11.0857864 L17.9571068,16.0428932 L20.1464466,13.8535534 C20.3417088,13.6582912 20.6582912,13.6582912 20.8535534,13.8535534 C20.9473216,13.9473216 21,14.0744985 21,14.2071068 L21,19.5 C21,19.7761424 20.7761424,20 20.5,20 L15.2071068,20 C14.9309644,20 14.7071068,19.7761424 14.7071068,19.5 C14.7071068,19.3673918 14.7597852,19.2402148 14.8535534,19.1464466 L16.5428932,17.4571068 Z\"\n            fill={color}\n            fillRule=\"nonzero\"\n          />\n          <path\n            d=\"M7.24478854,17.1447885 L9.2464466,19.1464466 C9.34021479,19.2402148 9.39289321,19.3673918 9.39289321,19.5 C9.39289321,19.7761424 9.16903559,20 8.89289321,20 L3.52893218,20 C3.25278981,20 3.02893218,19.7761424 3.02893218,19.5 L3.02893218,14.136039 C3.02893218,14.0034307 3.0816106,13.8762538 3.17537879,13.7824856 C3.37064094,13.5872234 3.68722343,13.5872234 3.88248557,13.7824856 L5.82567301,15.725673 L8.85405776,13.1631936 L10.1459422,14.6899662 L7.24478854,17.1447885 Z\"\n            fill={color}\n            fillRule=\"nonzero\"\n            opacity=\"0.8\"\n          />\n        </g>\n      </svg>\n    </Fragment>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Screenshot.js",
    "content": "import React, {Fragment} from 'react';\n\nconst Screenshot = ({width, height, color, padding, margin}) => (\n  <svg\n    width={width}\n    height={height}\n    fill={color}\n    viewBox=\"0 0 100 100\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className=\"quickScreenshot\"\n  >\n    <path\n      d=\"M35 28C34.2193 28.007 33.6091 28.4026 33.2812 28.9687L26.8438 40H14C9.6026 40 6 43.6026 6 48V86C6 90.3974 9.6026 94 14 94H76C80.3974 94 84 90.3974 84 86V48C84 43.6026 80.3974 40 76 40H63.1562L56.7188 28.9687C56.3641 28.3795 55.6877 27.9983 55 28H35ZM36.125 32H53.875L60.2812 43C60.6281 43.6007 61.3063 43.9953 62 44H76C78.2506 44 80 45.7494 80 48V86C80 88.2505 78.2506 90 76 90H14C11.7494 90 10 88.2505 10 86V48C10 45.7494 11.7494 44 14 44H28C28.6937 43.995 29.3719 43.6007 29.7188 43L36.125 32ZM45 50C36.1871 50 29 57.1871 29 66C29 74.8128 36.1871 82 45 82C53.8129 82 61 74.8128 61 66C61 57.1871 53.8129 50 45 50ZM45 54C51.6511 54 57 59.3489 57 66C57 72.6511 51.6511 78 45 78C38.3489 78 33 72.6511 33 66C33 59.3489 38.3489 54 45 54Z\"\n      fill={color}\n    />\n    <path\n      d=\"M59 27.6102L66.5758 5H84L73.3939 23.7119H80.9697L59 51L66.5758 27.6102H59Z\"\n      fill={color}\n      stroke={color}\n      strokeWidth=\"2\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\nScreenshot.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default Screenshot;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/ScrollDown.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"0 0 100 100\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      className=\"scrollDownIcon\"\n    >\n      <g stroke=\"none\" strokeWidth=\"1\" fillRule=\"evenodd\">\n        <g>\n          <path d=\"M32,23.004355 L32,23.004355 L32,48.995645 C32,58.9442689 40.056121,67 50,67 C59.942622,67 68,58.9403509 68,48.995645 L68,23.004355 C68,13.0557311 59.943879,5 50,5 C40.057378,5 32,13.0596491 32,23.004355 L32,23.004355 Z M28,23.004355 C28,10.8516853 37.847064,1 50,1 C62.1502645,1 72,10.8438387 72,23.004355 L72,48.995645 C72,61.1483147 62.152936,71 50,71 C37.8497355,71 28,61.1561613 28,48.995645 L28,23.004355 L28,23.004355 Z\" />\n          <path d=\"M48,17.0085302 C48,15.8992496 48.8877296,15 50,15 C51.1045695,15 52,15.9019504 52,17.0085302 L52,24.9914698 C52,26.1007504 51.1122704,27 50,27 C48.8954305,27 48,26.0980496 48,24.9914698 L48,17.0085302 Z\" />\n          <path d=\"M50,95.1715729 L58.5857864,86.5857864 C59.366835,85.8047379 60.633165,85.8047379 61.4142136,86.5857864 C62.1952621,87.366835 62.1952621,88.633165 61.4142136,89.4142136 L51.4142136,99.4142136 C51.0236893,99.8047379 50.5118446,100 50,100 C49.4881554,100 48.9763107,99.8047379 48.5857864,99.4142136 L38.5857864,89.4142136 C37.8047379,88.633165 37.8047379,87.366835 38.5857864,86.5857864 C39.366835,85.8047379 40.633165,85.8047379 41.4142136,86.5857864 L50,95.1715729 Z\" />\n          <path d=\"M50,82.1715729 L58.5857864,73.5857864 C59.366835,72.8047379 60.633165,72.8047379 61.4142136,73.5857864 C62.1952621,74.366835 62.1952621,75.633165 61.4142136,76.4142136 L51.4142136,86.4142136 C51.0236893,86.8047379 50.5118446,87 50,87 C49.4881554,87 48.9763107,86.8047379 48.5857864,86.4142136 L38.5857864,76.4142136 C37.8047379,75.633165 37.8047379,74.366835 38.5857864,73.5857864 C39.366835,72.8047379 40.633165,72.8047379 41.4142136,73.5857864 L50,82.1715729 Z\" />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/ScrollUp.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"0 0 100 100\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      className=\"scrollUpIcon\"\n    >\n      <g stroke=\"none\" strokeWidth=\"1\" fillRule=\"evenodd\">\n        <g>\n          <path d=\"M42,21c0.5,0,1-0.2,1.4-0.6l6.6-6.6l6.6,6.6C57,20.8,57.5,21,58,21s1-0.2,1.4-0.6c0.8-0.8,0.8-2,0-2.8l-8-8  c-0.8-0.8-2-0.8-2.8,0l-8,8c-0.8,0.8-0.8,2,0,2.8C41,20.8,41.5,21,42,21z\" />\n          <path d=\"M40.6,31.4C41,31.8,41.5,32,42,32s1-0.2,1.4-0.6l6.6-6.6l6.6,6.6C57,31.8,57.5,32,58,32s1-0.2,1.4-0.6c0.8-0.8,0.8-2,0-2.8  l-8-8c-0.8-0.8-2-0.8-2.8,0l-8,8C39.8,29.4,39.8,30.6,40.6,31.4z\" />\n          <path d=\"M50,37c-9.9,0-18,8.1-18,18v18c0,9.9,8.1,18,18,18s18-8.1,18-18V55C68,45.1,59.9,37,50,37z M64,73c0,7.7-6.3,14-14,14  s-14-6.3-14-14V55c0-7.7,6.3-14,14-14s14,6.3,14,14V73z\" />\n          <path d=\"M50,48c-1.1,0-2,0.9-2,2v6c0,1.1,0.9,2,2,2s2-0.9,2-2v-6C52,48.9,51.1,48,50,48z\" />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Start.js",
    "content": "import React from 'react';\n\nfunction Start({width, height, strokeColor, fillColor, padding, margin}) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={width}\n      height={height}\n      viewBox=\"0 0 24 24\"\n      strokeWidth=\"2\"\n      stroke={strokeColor}\n      style={{padding, margin}}\n      fill=\"none\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path stroke=\"none\" d=\"M0 0h24v24H0z\" />\n      <path\n        fill={fillColor}\n        d=\"M12 17.75l-6.172 3.245 1.179-6.873-4.993-4.867 6.9-1.002L12 2l3.086 6.253 6.9 1.002-4.993 4.867 1.179 6.873z\"\n      />\n    </svg>\n  );\n}\n\nexport default Start;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/TickAnimation/index.js",
    "content": "import React, {Fragment} from 'react';\nimport styles from './styles.css';\n\nexport default ({width, height, color = '#ffffff80', padding}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      className={styles.iconCheck}\n      viewBox=\"0 0 225 225\"\n      version=\"1.1\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      xmlSpace=\"preserve\"\n    >\n      <g transform=\"matrix(1,0,0,1,-178.667,-170.667)\">\n        <g className={styles.check} transform=\"matrix(1,0,0,1,176,113)\">\n          <path\n            d=\"M65,166L101,202L165,138\"\n            style={{\n              fill: 'none',\n              stroke: color,\n              strokeWidth: 16.67,\n            }}\n          />\n        </g>\n        <circle\n          className={styles.circle}\n          cx=\"291\"\n          cy=\"283\"\n          r=\"104\"\n          style={{stroke: color}}\n        />\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/TickAnimation/styles.css",
    "content": ".iconCheck {\n  text-align: center;\n  margin: auto;\n  width: 20px;\n  height: 20px;\n  fill-rule: evenodd;\n  clip-rule: evenodd;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n  stroke-miterlimit: 1.5;\n}\n\n.iconCheck .circle {\n  animation-name: circle;\n  animation-duration: 2s;\n  stroke-dashoffset: 0;\n  stroke-dasharray: 1000;\n  animation-timing-function: ease-in-out;\n  fill: none;\n  stroke-width: 16.67px;\n}\n\n.iconCheck .check {\n  animation-name: check;\n  animation-duration: 2s;\n  stroke-dashoffset: 500;\n  stroke-dasharray: 1000;\n  animation-delay: 1s;\n  animation-timing-function: ease-in-out;\n}\n\n@keyframes circle {\n  from {\n    stroke-dashoffset: 1000;\n  }\n  to {\n    stroke-dashoffset: 0;\n  }\n}\n\n@keyframes check {\n  from {\n    stroke-dashoffset: 0;\n  }\n  to {\n    stroke-dashoffset: 500;\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/ToggleTouch.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"818.476 592.083 34.524 57.917\"\n      className=\"toggleTouchIcon\"\n    >\n      <defs>\n        <clipPath id=\"_clipPath_inAKUGi0XXoKqouCz03bzTlih31devFu\">\n          <rect x=\"818.476\" y=\"592.083\" width=\"34.524\" height=\"57.917\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_inAKUGi0XXoKqouCz03bzTlih31devFu)\">\n        <path d=\" M 831 603 C 830.062 603 829.117 603.313 828.344 603.969 C 827.488 604.704 826.997 605.778 827 606.906 L 827 628.438 C 826.559 628.598 826.156 628.645 825.344 629.344 C 824.168 630.359 823 632.184 823 634.906 C 823 636.879 823.785 638.684 824.094 639.406 L 824.094 639.438 L 826.906 645.031 C 826.91 645.039 826.902 645.055 826.906 645.063 C 828.383 648.105 831.539 650 834.906 650 L 844 650 C 848.945 650 853 645.945 853 641 L 853 625 C 853 624.18 852.871 623.05 852.312 622 C 851.754 620.95 850.598 620 849 620 C 847.906 620 847.094 620.375 846.437 620.875 C 846.328 620.575 846.273 620.238 846.125 619.969 C 845.504 618.848 844.367 618 843 618 C 842.026 617.99 841.082 618.334 840.344 618.969 C 839.68 617.87 838.559 617 837 617 C 836.219 617 835.574 617.246 835 617.594 L 835 607 C 835 605.734 834.46 604.676 833.687 604 C 832.914 603.324 831.937 603 831 603 Z  M 831 605 C 831.46 605 832 605.176 832.375 605.5 C 832.75 605.824 833 606.266 833 607 L 833 623.97 L 835 624.011 L 835 621 C 835 620.125 835.66 619 837 619 C 838.34 619 839 620.125 839 621 L 839 624 C 839.008 624.546 839.454 624.985 840 624.984 C 840.546 624.985 840.992 624.546 841 624 C 841 623.465 841.133 620.934 841.406 620.594 C 841.68 620.254 842.082 620 843 620 C 843.633 620 843.996 620.258 844.375 620.938 C 844.754 621.618 845 623.718 845 625 L 847 625 C 847 624.418 847.105 622.91 847.344 622.594 C 847.582 622.277 847.964 622 849 622 C 849.902 622 850.246 622.34 850.563 622.938 C 850.879 623.535 851 624.418 851 625 L 851 641 C 851 644.855 847.855 648 844 648 L 834.906 648 C 832.286 648 829.816 646.5 828.688 644.156 L 825.906 638.594 C 825.902 638.586 825.91 638.57 825.906 638.562 C 825.613 637.875 825 636.32 825 634.906 C 825 632.726 825.832 631.586 826.656 630.875 C 827.48 630.165 828.25 629.969 828.25 629.969 C 828.692 629.855 829 629.456 829 629 L 829 606.906 C 829 606.238 829.25 605.82 829.625 605.5 C 830 605.18 830.539 605 831 605 Z \" />\n        <g>\n          <path\n            d=\" M 827.085 614.357 C 822.602 612.776 819.584 608.311 820.017 603.358 C 820.532 597.471 825.73 593.11 831.617 593.625 C 837.504 594.14 841.866 599.337 841.351 605.225 C 840.969 609.586 838.018 613.109 834.108 614.403\"\n            fill=\"none\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            stroke={color}\n            strokeLinejoin=\"miter\"\n            strokeLinecap=\"square\"\n            strokeMiterlimit=\"3\"\n          />\n          <path\n            d=\" M 827.56 608.924 C 825.937 607.822 824.939 605.897 825.122 603.805 C 825.391 600.735 828.101 598.461 831.17 598.73 C 834.24 598.998 836.514 601.708 836.245 604.778 C 836.127 606.132 835.534 607.331 834.648 608.229\"\n            fill=\"none\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            stroke={color}\n            strokeLinejoin=\"miter\"\n            strokeLinecap=\"square\"\n            strokeMiterlimit=\"3\"\n          />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Twitter.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default function Twitter({\n  width,\n  height,\n  color,\n  padding,\n  margin,\n  className,\n}) {\n  return (\n    <Fragment>\n      <svg\n        height={height}\n        width={width}\n        fill={color}\n        style={{padding, margin}}\n        xmlns=\"http://www.w3.org/2000/svg\"\n        data-name=\"Layer 1\"\n        viewBox=\"0 0 24 24\"\n        className={`twitterIcon ${className}`}\n      >\n        <path d=\"M23.954 4.569c-.885.389-1.83.654-2.825.775 1.014-.611 1.794-1.574 2.163-2.723-.951.555-2.005.959-3.127 1.184-.896-.959-2.173-1.559-3.591-1.559-2.717 0-4.92 2.203-4.92 4.917 0 .39.045.765.127 1.124C7.691 8.094 4.066 6.13 1.64 3.161c-.427.722-.666 1.561-.666 2.475 0 1.71.87 3.213 2.188 4.096-.807-.026-1.566-.248-2.228-.616v.061c0 2.385 1.693 4.374 3.946 4.827-.413.111-.849.171-1.296.171-.314 0-.615-.03-.916-.086.631 1.953 2.445 3.377 4.604 3.417-1.68 1.319-3.809 2.105-6.102 2.105-.39 0-.779-.023-1.17-.067 2.189 1.394 4.768 2.209 7.557 2.209 9.054 0 13.999-7.496 13.999-13.986 0-.209 0-.42-.015-.63.961-.689 1.8-1.56 2.46-2.548l-.047-.02z\" />\n      </svg>\n    </Fragment>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/UnDock.js",
    "content": "import React from 'react';\nimport cx from 'classnames';\n\nimport styles from './styles.module.css';\n\nexport default function UnDock({\n  width,\n  height,\n  color,\n  padding,\n  margin,\n  style,\n  selected,\n}) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      viewBox=\"928 570.5 14 12\"\n      width={width}\n      height={height}\n      fill={color}\n      style={{isolation: 'isolate', padding, margin, ...style}}\n      className={cx(styles.chromeIcon, {[styles.selected]: selected})}\n    >\n      <defs>\n        <clipPath id=\"_clipPath_r74TLLWuZiUzm0aGFcLYZf703TqDT1Pi\">\n          <rect x=\"928\" y=\"570.5\" width=\"14\" height=\"12\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_r74TLLWuZiUzm0aGFcLYZf703TqDT1Pi)\">\n        <g>\n          <path\n            d=\" M 930 573.5 L 928 573.5 L 928 582.5 L 939 582.5 L 939 580.5 L 930 580.5 L 930 573.5 Z \"\n            fill={color}\n          />\n          <path\n            d=\" M 933 572.5 L 940 572.5 L 940 577.5 L 933 577.5 L 933 572.5 Z  M 931 570.5 L 931 579.5 L 942 579.5 L 942 570.5 L 931 570.5 Z \"\n            fill={color}\n          />\n        </g>\n      </g>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Unfocus.js",
    "content": "import React, {Fragment} from 'react';\n\nconst Unfocus = ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"725.999 355.999 103.001 103.001\"\n    >\n      <defs>\n        <clipPath id=\"_clipPath_h8sxV9rYEahs9RnRvaBS9986KyxpspD8\">\n          <rect x=\"725.999\" y=\"355.999\" width=\"103.001\" height=\"103.001\" />\n        </clipPath>\n      </defs>\n      <g clipPath=\"url(#_clipPath_h8sxV9rYEahs9RnRvaBS9986KyxpspD8)\">\n        <g>\n          <path\n            d=\" M 752.499 450.801 C 728.601 437.003 720.4 406.398 734.198 382.499 C 747.996 358.601 778.601 350.4 802.499 364.198 C 826.398 377.996 834.598 408.601 820.801 432.499 C 807.003 456.398 776.398 464.598 752.499 450.801 Z \"\n            fill=\"none\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            stroke={color}\n            strokeLinejoin=\"miter\"\n            strokeLinecap=\"square\"\n            strokeMiterlimit=\"3\"\n          />\n          <path\n            d=\" M 745.681 414.57 L 768.661 416.338 L 770.429 439.319\"\n            fill=\"none\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            stroke={color}\n            strokeLinejoin=\"round\"\n            strokeLinecap=\"round\"\n            strokeMiterlimit=\"3\"\n          />\n          <path\n            d=\" M 784.571 375.679 L 786.339 398.66 L 809.32 400.428\"\n            fill=\"none\"\n            vectorEffect=\"non-scaling-stroke\"\n            strokeWidth=\"1\"\n            stroke={color}\n            strokeLinejoin=\"round\"\n            strokeLinecap=\"round\"\n            strokeMiterlimit=\"3\"\n          />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n\nUnfocus.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default Unfocus;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Unmuted.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      viewBox=\"0 0 22 27.5\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      className=\"unMuteIcon\"\n      xmlSpace=\"preserve\"\n    >\n      <g stroke=\"none\" strokeWidth=\"1\" fill=\"none\" fillRule=\"evenodd\">\n        <path\n          d=\"M0,10.0029293 L0,15.9970707 C0,16.5509732 0.45097518,17 0.990777969,17 L5,17 L10.4704221,20.7253518 C11.3151847,21.4293205 12,21.1099416 12,19.9998938 L12,6.00010618 C12,4.89547804 11.3117237,4.57356358 10.4704221,5.27464823 L5,9 L0.990777969,9 C0.443586406,9 0,9.43788135 0,10.0029293 Z M5,10 L5.36204994,10 L5.6401844,9.76822128 L11,6.15551758 L11.0151624,19.8775937 L5.6401844,16.2317787 L5.36204994,16 L5,16 L0.990777969,16 C1.00229279,16 1,10.0029293 1,10.0029293 C1,9.9930141 5,10 5,10 Z M18.5205674,5.81619094 C18.8213714,6.22407458 19.1236046,6.71123345 19.4064951,7.27579615 C20.2188597,8.89702697 20.7070313,10.8081405 20.7070313,13 C20.7070313,15.1918595 20.2188597,17.102973 19.4064951,18.7242039 C19.1236046,19.2887665 18.8213714,19.7759254 18.5205674,20.1838091 C18.3437681,20.423545 18.2099826,20.5805024 18.1402313,20.6527732 C17.9484632,20.8514679 17.9540784,21.1680006 18.1527732,21.3597687 C18.3514679,21.5515368 18.6680006,21.5459216 18.8597687,21.3472268 C18.9592069,21.244197 19.1215029,21.053791 19.3253799,20.7773382 C19.6590363,20.3249075 19.9912635,19.7894023 20.3005361,19.172189 C21.180066,17.4169168 21.7070313,15.3539321 21.7070313,13 C21.7070313,10.6460679 21.180066,8.5830832 20.3005361,6.82781103 C19.9912635,6.21059768 19.6590363,5.67509248 19.3253799,5.22266176 C19.1215029,4.94620903 18.9592069,4.75580304 18.8597687,4.65277317 C18.6680006,4.45407839 18.3514679,4.4484632 18.1527732,4.64023129 C17.9540784,4.83199937 17.9484632,5.14853206 18.1402313,5.34722683 C18.2099826,5.41949756 18.3437681,5.57645504 18.5205674,5.81619094 Z M16.3251635,7.70181933 C16.5167178,8.06240028 16.708866,8.47428786 16.8883872,8.92944368 C17.4003501,10.227468 17.7070312,11.6058036 17.7070312,13 C17.7070312,14.3941964 17.4003501,15.772532 16.8883872,17.0705563 C16.708866,17.5257121 16.5167178,17.9375997 16.3251635,18.2981807 C16.211874,18.5114364 16.1255066,18.6579007 16.0793835,18.7296636 C15.9300808,18.9619637 15.9973636,19.2713139 16.2296636,19.4206165 C16.4619637,19.5699192 16.7713139,19.5026364 16.9206165,19.2703364 C16.9811829,19.1761012 17.0815219,19.0059434 17.2082837,18.7673275 C17.4168149,18.3747891 17.6247521,17.9290564 17.818644,17.4374654 C18.3735757,16.0304988 18.7070312,14.531829 18.7070312,13 C18.7070312,11.468171 18.3735757,9.96950121 17.818644,8.56253455 C17.6247521,8.07094355 17.4168149,7.62521089 17.2082837,7.23267251 C17.0815219,6.99405662 16.9811829,6.82389884 16.9206165,6.72966364 C16.7713139,6.49736355 16.4619637,6.43008084 16.2296636,6.57938349 C15.9973636,6.72868614 15.9300808,7.03803628 16.0793835,7.27033636 C16.1255066,7.34209934 16.211874,7.48856362 16.3251635,7.70181933 Z M14.535138,10.7026223 C14.8259933,11.4369323 15,12.215672 15,13 C15,13.784328 14.8259933,14.5630677 14.535138,15.2973777 C14.3607219,15.7377188 14.1882874,16.0609353 14.0799062,16.2288521 C13.9301553,16.4608636 13.9968407,16.770343 14.2288521,16.9200938 C14.4608636,17.0698447 14.770343,17.0031593 14.9200938,16.7711479 C15.0617126,16.5517356 15.2642781,16.1720407 15.464862,15.665635 C15.7990067,14.8220336 16,13.9225201 16,13 C16,12.0774799 15.7990067,11.1779664 15.464862,10.334365 C15.2642781,9.82795932 15.0617126,9.44826441 14.9200938,9.22885213 C14.770343,8.99684071 14.4608636,8.93015533 14.2288521,9.07990616 C13.9968407,9.22965699 13.9301553,9.53913645 14.0799062,9.77114787 C14.1882874,9.9390647 14.3607219,10.2622812 14.535138,10.7026223 Z\"\n          fill={color}\n        />\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Unplug.js",
    "content": "import React, {Fragment} from 'react';\n\nconst Unplug = ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 100 100\"\n      enableBackground=\"new 0 0 100 100\"\n      xmlSpace=\"preserve\"\n      className=\"unPlugIcon\"\n    >\n      <g>\n        <g>\n          <path d=\"M79.859,20.143c-0.778-0.777-2.037-0.777-2.815,0l-9.222,9.223c-1.901-1.334-4.156-2.061-6.527-2.061    c-3.046,0-5.909,1.186-8.061,3.34l-5.621,5.619c-0.374,0.373-0.583,0.879-0.583,1.406s0.209,1.035,0.583,1.408l13.308,13.309    c0.39,0.389,0.898,0.582,1.408,0.582c0.509,0,1.019-0.193,1.407-0.582l5.621-5.619c3.962-3.965,4.38-10.141,1.275-14.584    l9.227-9.227C80.637,22.18,80.637,20.92,79.859,20.143z M66.543,43.953l-4.214,4.211L51.835,37.67l4.215-4.211    c1.4-1.402,3.264-2.174,5.245-2.174c1.982,0,3.846,0.773,5.248,2.174C69.434,36.354,69.434,41.059,66.543,43.953z\" />\n          <path d=\"M51.213,52.229l-4.352,4.352l-3.441-3.44l4.351-4.351c0.778-0.777,0.778-2.037,0-2.814c-0.777-0.777-2.036-0.777-2.814,0    l-4.351,4.351l-2.12-2.12c-0.777-0.777-2.037-0.777-2.814,0l-5.619,5.619c-3.964,3.965-4.383,10.141-1.276,14.585l-8.635,8.634    c-0.777,0.777-0.777,2.037,0,2.814c0.389,0.389,0.898,0.584,1.407,0.584c0.51,0,1.019-0.195,1.408-0.584l8.632-8.631    c1.9,1.334,4.154,2.061,6.525,2.061c3.045,0,5.907-1.186,8.062-3.34l5.619-5.619c0.777-0.777,0.777-2.037,0-2.814l-2.118-2.119    l4.352-4.352c0.777-0.777,0.777-2.037,0-2.814S51.99,51.451,51.213,52.229z M43.36,67.133c-1.402,1.4-3.266,2.174-5.247,2.174    s-3.845-0.773-5.247-2.174c-2.893-2.893-2.893-7.6,0-10.494l4.211-4.211l10.494,10.494L43.36,67.133z\" />\n        </g>\n      </g>\n    </svg>\n  </Fragment>\n);\n\nUnplug.defaultProps = {\n  color: 'currentColor',\n};\n\nexport default Unplug;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Windows.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 88 88\"\n      className=\"windowsIcon\"\n    >\n      <path d=\"m0 12.402 35.687-4.8602.0156 34.423-35.67.20313zm35.67 33.529.0277 34.453-35.67-4.9041-.002-29.78zm4.3261-39.025 47.318-6.906v41.527l-47.318.37565zm47.329 39.349-.0111 41.34-47.318-6.6784-.0663-34.739z\" />\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/Zoom.js",
    "content": "import React, {Fragment} from 'react';\n\nexport default ({width, height, color, padding, margin}) => (\n  <Fragment>\n    <svg\n      height={height}\n      width={width}\n      fill={color}\n      style={{padding, margin}}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      version=\"1.1\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 100 100\"\n      enableBackground=\"new 0 0 100 100\"\n      className=\"zoomIcon\"\n    >\n      <g>\n        <path d=\"M91.414,88.586L70.815,67.987C76.522,61.614,80,53.207,80,44C80,24.149,63.851,8,44,8S8,24.149,8,44   s16.149,36,36,36c9.207,0,17.614-3.478,23.987-9.185l20.599,20.599C88.977,91.805,89.488,92,90,92s1.023-0.195,1.414-0.586   C92.195,90.633,92.195,89.367,91.414,88.586z M44,76c-17.645,0-32-14.355-32-32s14.355-32,32-32s32,14.355,32,32S61.645,76,44,76z\" />\n        <path d=\"M62,42H46V26c0-1.104-0.896-2-2-2s-2,0.896-2,2v16H26c-1.104,0-2,0.896-2,2s0.896,2,2,2h16v16   c0,1.104,0.896,2,2,2s2-0.896,2-2V46h16c1.104,0,2-0.896,2-2S63.104,42,62,42z\" />\n      </g>\n    </svg>\n  </Fragment>\n);\n"
  },
  {
    "path": "desktop-app-legacy/app/components/icons/styles.module.css",
    "content": ".chromeIcon {\n  fill: rgb(165, 165, 165);\n  margin-right: 5px;\n  height: 12px;\n  cursor: pointer;\n}\n\n.chromeIcon.selected {\n  fill: #7587ec;\n}\n\n@media (prefers-color-scheme: light) {\n  .chromeIcon {\n    fill: #555;\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/components/useCommonStyles.js",
    "content": "import {makeStyles} from '@material-ui/core/styles';\n\nconst styles = theme => ({\n  sidebarContentSection: {\n    background: theme.palette.mode({\n      light: theme.palette.grey[300],\n      dark: '#00000040',\n    }),\n    borderRadius: '5px',\n    margin: '8px',\n    padding: '8px',\n  },\n  sidebarContentSectionTitleBar: {\n    display: 'flex',\n    alignItems: 'center',\n    padding: '5px',\n    marginBottom: '5px',\n    textTransform: 'uppercase',\n    fontSize: '14px',\n  },\n  sidebarContentSectionContainer: {\n    margin: '8px 8px 25px 8px',\n  },\n  icon: {\n    cursor: 'pointer',\n    position: 'relative',\n    opacity: 0.8,\n    borderRadius: '5px',\n    '&:hover': {\n      opacity: 1,\n      backgroundColor: theme.palette.primary.main,\n      color: 'white',\n    },\n  },\n  iconSelected: {\n    backgroundColor: theme.palette.primary.light,\n    color: 'white',\n  },\n  iconRound: {\n    borderRadius: '50%',\n  },\n  iconDisabled: {\n    opacity: 0.3,\n    cursor: 'auto',\n  },\n  iconHoverDisabled: {\n    '&:hover': {\n      opacity: 0.3,\n      backgroundColor: 'unset',\n    },\n  },\n  iconDisabler: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    display: 'none',\n    zIndex: 2,\n    height: '100%',\n    width: '100%',\n  },\n  flexContainer: {\n    display: 'flex',\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  flexAlignVerticalMiddle: {\n    display: 'flex',\n    alignItems: 'center',\n  },\n  flexContainerSpaceBetween: {\n    display: 'flex',\n    justifyContent: 'space-between',\n  },\n  fullWidth: {\n    width: '100%',\n  },\n  hidden: {\n    display: 'none',\n  },\n});\n\nexport default makeStyles(styles);\nexport {styles};\n"
  },
  {
    "path": "desktop-app-legacy/app/components/useCreateTheme.js",
    "content": "import {useMemo} from 'react';\nimport {createMuiTheme} from '@material-ui/core/styles';\nimport {grey} from '@material-ui/core/colors';\nimport useIsDarkTheme from './useIsDarkTheme';\n\nfunction useCreateTheme() {\n  const isDark = useIsDarkTheme();\n  return useMemo(() => createMuiTheme(isDark ? darkTheme : lightTheme), [\n    isDark,\n  ]);\n}\n\nconst themeProps = {MuiButtonBase: {disableRipple: true}};\n\nconst lightTheme = {\n  props: themeProps,\n  palette: {\n    type: 'light',\n    primary: {\n      light: '#3450db',\n      main: '#2e47d0',\n    },\n    secondary: {\n      main: '#424242',\n    },\n    background: {\n      l0: '#f8f8f8',\n      l1: '#ffffff',\n      l2: '#e7e7e7',\n      l5: '#efefef',\n      l10: '#d2d2d2',\n      l20: '#8a8a8a',\n    },\n    border: {\n      color: '#000000',\n    },\n    header: {\n      main: '#F5F5F5',\n    },\n    scrollbar: {\n      main: '#7c7c7c',\n    },\n    text: {\n      active: '#1e1e1e',\n      dim: '#606060',\n      inactive: '#b7b7b7',\n      normal: '#363636',\n    },\n    lightIcon: {\n      main: 'black',\n    },\n    mode({light, dark}) {\n      return light;\n    },\n  },\n};\n\nconst darkTheme = {\n  props: themeProps,\n  palette: {\n    type: 'dark',\n    primary: {\n      light: '#7587ec',\n      main: '#536be7',\n    },\n    secondary: {\n      main: '#424242',\n    },\n    divider: grey[500],\n    background: {\n      l0: '#1e1e1e',\n      l1: '#2f2f33',\n      l2: '#383838',\n      l5: '#8a8a8a',\n      l10: '#9e9e9e',\n      l20: '#aeaeae',\n    },\n    border: {\n      color: '#ffffff',\n    },\n    header: {\n      main: '#252526',\n    },\n    scrollbar: {\n      main: '#9c9c9c',\n    },\n    text: {\n      active: '#ffffff',\n      dim: '#868686',\n      inactive: '#838383',\n      normal: '#f8f8f8',\n    },\n    lightIcon: {\n      main: '#ffffff90',\n    },\n    mode({light, dark}) {\n      return dark;\n    },\n  },\n};\n\nexport default useCreateTheme;\n"
  },
  {
    "path": "desktop-app-legacy/app/components/useIsDarkTheme.js",
    "content": "import useMediaQuery from '@material-ui/core/useMediaQuery';\nimport {useSelector} from 'react-redux';\nimport {LIGHT_THEME, DARK_THEME} from '../constants/theme';\nimport {setTheme} from '../actions/browser';\n\nfunction useIsDarkTheme() {\n  const themeSource = useSelector(state => state.browser.theme);\n  return themeSource === DARK_THEME;\n}\n\nexport default useIsDarkTheme;\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/DrawerContents.js",
    "content": "export const DEVICE_MANAGER = 'DEVICE_MANAGER';\nexport const SCREENSHOT_MANAGER = 'SCREENSHOT_MANAGER';\nexport const USER_PREFERENCES = 'USER_PREFERENCES';\nexport const EXTENSIONS_MANAGER = 'EXTENSIONS_MANAGER';\nexport const NETWORK_CONFIGURATION = 'NETWORK_CONFIGURATION';\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/browserSync.js",
    "content": "export const BROWSER_SYNC_VERSION = '2.26.7';\nexport const BROWSER_SYNC_PORT = '12719';\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/chromeEmulatedDevices.js",
    "content": "// https://cs.chromium.org/codesearch/f/chromium/src/third_party/blink/renderer/devtools/front_end/emulated_devices/module.json\n// https://source.chromium.org/chromium/chromium/src/+/master:third_party/devtools-frontend/src/front_end/emulated_devices/module.json?q=emulated_devices%2Fmodule.json&ss=chromium\n\nexport default {\n  extensions: [\n    {\n      id: '1',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'iPhone 4',\n        screen: {\n          horizontal: {\n            width: 480,\n            height: 320,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 320,\n            height: 480,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '2',\n      type: 'emulated-device',\n      order: 30,\n      device: {\n        'show-by-default': false,\n        title: 'iPhone 5/SE',\n        screen: {\n          horizontal: {\n            outline: {\n              image: '@url(iPhone5-landscape.svg)',\n              insets: {\n                left: 115,\n                top: 25,\n                right: 115,\n                bottom: 28,\n              },\n            },\n            width: 568,\n            height: 320,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            outline: {\n              image: '@url(iPhone5-portrait.svg)',\n              insets: {\n                left: 29,\n                top: 105,\n                right: 25,\n                bottom: 111,\n              },\n            },\n            width: 320,\n            height: 568,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '3',\n      type: 'emulated-device',\n      order: 31,\n      device: {\n        'show-by-default': false,\n        title: 'iPhone 6/7/8',\n        screen: {\n          horizontal: {\n            outline: {\n              image: '@url(iPhone6-landscape.svg)',\n              insets: {\n                left: 106,\n                top: 28,\n                right: 106,\n                bottom: 28,\n              },\n            },\n            width: 667,\n            height: 375,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            outline: {\n              image: '@url(iPhone6-portrait.svg)',\n              insets: {\n                left: 28,\n                top: 105,\n                right: 28,\n                bottom: 105,\n              },\n            },\n            width: 375,\n            height: 667,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '4',\n      type: 'emulated-device',\n      order: 32,\n      device: {\n        'show-by-default': false,\n        title: 'iPhone 6/7/8 Plus',\n        screen: {\n          horizontal: {\n            outline: {\n              image: '@url(iPhone6Plus-landscape.svg)',\n              insets: {\n                left: 109,\n                top: 29,\n                right: 109,\n                bottom: 27,\n              },\n            },\n            width: 736,\n            height: 414,\n          },\n          'device-pixel-ratio': 3,\n          vertical: {\n            outline: {\n              image: '@url(iPhone6Plus-portrait.svg)',\n              insets: {\n                left: 26,\n                top: 107,\n                right: 30,\n                bottom: 111,\n              },\n            },\n            width: 414,\n            height: 736,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '5',\n      type: 'emulated-device',\n      order: 33,\n      device: {\n        'show-by-default': true,\n        title: 'iPhone X',\n        screen: {\n          horizontal: {\n            width: 812,\n            height: 375,\n          },\n          'device-pixel-ratio': 3,\n          vertical: {\n            width: 375,\n            height: 812,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '6',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'BlackBerry Z30',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 360,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 360,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '7',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Nexus 4',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 384,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 384,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '8',\n      type: 'emulated-device',\n      device: {\n        title: 'Nexus 5',\n        type: 'phone',\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        capabilities: ['touch', 'mobile'],\n        'show-by-default': false,\n        screen: {\n          'device-pixel-ratio': 3,\n          vertical: {\n            width: 360,\n            height: 640,\n          },\n          horizontal: {\n            width: 640,\n            height: 360,\n          },\n        },\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 25, right: 0, bottom: 48},\n            image:\n              '@url(google-nexus-5-vertical-default-1x.png) 1x, @url(google-nexus-5-vertical-default-2x.png) 2x',\n          },\n          {\n            title: 'navigation bar',\n            orientation: 'vertical',\n            insets: {left: 0, top: 80, right: 0, bottom: 48},\n            image:\n              '@url(google-nexus-5-vertical-navigation-1x.png) 1x, @url(google-nexus-5-vertical-navigation-2x.png) 2x',\n          },\n          {\n            title: 'keyboard',\n            orientation: 'vertical',\n            insets: {left: 0, top: 80, right: 0, bottom: 312},\n            image:\n              '@url(google-nexus-5-vertical-keyboard-1x.png) 1x, @url(google-nexus-5-vertical-keyboard-2x.png) 2x',\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 25, right: 42, bottom: 0},\n            image:\n              '@url(google-nexus-5-horizontal-default-1x.png) 1x, @url(google-nexus-5-horizontal-default-2x.png) 2x',\n          },\n          {\n            title: 'navigation bar',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 80, right: 42, bottom: 0},\n            image:\n              '@url(google-nexus-5-horizontal-navigation-1x.png) 1x, @url(google-nexus-5-horizontal-navigation-2x.png) 2x',\n          },\n          {\n            title: 'keyboard',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 80, right: 42, bottom: 202},\n            image:\n              '@url(google-nexus-5-horizontal-keyboard-1x.png) 1x, @url(google-nexus-5-horizontal-keyboard-2x.png) 2x',\n          },\n        ],\n      },\n    },\n    {\n      id: '9',\n      type: 'emulated-device',\n      device: {\n        title: 'Nexus 5X',\n        type: 'phone',\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        capabilities: ['touch', 'mobile'],\n        'show-by-default': false,\n        screen: {\n          'device-pixel-ratio': 2.625,\n          vertical: {\n            outline: {\n              image: '@url(Nexus5X-portrait.svg)',\n              insets: {left: 18, top: 88, right: 22, bottom: 98},\n            },\n            width: 412,\n            height: 732,\n          },\n          horizontal: {\n            outline: {\n              image: '@url(Nexus5X-landscape.svg)',\n              insets: {left: 88, top: 21, right: 98, bottom: 19},\n            },\n            width: 732,\n            height: 412,\n          },\n        },\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 24, right: 0, bottom: 48},\n            image:\n              '@url(google-nexus-5x-vertical-default-1x.png) 1x, @url(google-nexus-5x-vertical-default-2x.png) 2x',\n          },\n          {\n            title: 'navigation bar',\n            orientation: 'vertical',\n            insets: {left: 0, top: 80, right: 0, bottom: 48},\n            image:\n              '@url(google-nexus-5x-vertical-navigation-1x.png) 1x, @url(google-nexus-5x-vertical-navigation-2x.png) 2x',\n          },\n          {\n            title: 'keyboard',\n            orientation: 'vertical',\n            insets: {left: 0, top: 80, right: 0, bottom: 342},\n            image:\n              '@url(google-nexus-5x-vertical-keyboard-1x.png) 1x, @url(google-nexus-5x-vertical-keyboard-2x.png) 2x',\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 24, right: 48, bottom: 0},\n            image:\n              '@url(google-nexus-5x-horizontal-default-1x.png) 1x, @url(google-nexus-5x-horizontal-default-2x.png) 2x',\n          },\n          {\n            title: 'navigation bar',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 80, right: 48, bottom: 0},\n            image:\n              '@url(google-nexus-5x-horizontal-navigation-1x.png) 1x, @url(google-nexus-5x-horizontal-navigation-2x.png) 2x',\n          },\n          {\n            title: 'keyboard',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 80, right: 48, bottom: 222},\n            image:\n              '@url(google-nexus-5x-horizontal-keyboard-1x.png) 1x, @url(google-nexus-5x-horizontal-keyboard-2x.png) 2x',\n          },\n        ],\n      },\n    },\n    {\n      id: '10',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Nexus 6',\n        screen: {\n          horizontal: {\n            width: 732,\n            height: 412,\n          },\n          'device-pixel-ratio': 3.5,\n          vertical: {\n            width: 412,\n            height: 732,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '11',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Nexus 6P',\n        screen: {\n          horizontal: {\n            outline: {\n              image: '@url(Nexus6P-landscape.svg)',\n              insets: {left: 94, top: 17, right: 88, bottom: 17},\n            },\n            width: 732,\n            height: 412,\n          },\n          'device-pixel-ratio': 3.5,\n          vertical: {\n            outline: {\n              image: '@url(Nexus6P-portrait.svg)',\n              insets: {left: 16, top: 94, right: 16, bottom: 88},\n            },\n            width: 412,\n            height: 732,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '12',\n      type: 'emulated-device',\n      order: 20,\n      device: {\n        'show-by-default': false,\n        title: 'Pixel 2',\n        screen: {\n          horizontal: {\n            width: 731,\n            height: 411,\n          },\n          'device-pixel-ratio': 2.625,\n          vertical: {\n            width: 411,\n            height: 731,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '13',\n      type: 'emulated-device',\n      order: 21,\n      device: {\n        'show-by-default': false,\n        title: 'Pixel 2 XL',\n        screen: {\n          horizontal: {\n            width: 823,\n            height: 411,\n          },\n          'device-pixel-ratio': 3.5,\n          vertical: {\n            width: 411,\n            height: 823,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '14',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'LG Optimus L70',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 384,\n          },\n          'device-pixel-ratio': 1.25,\n          vertical: {\n            width: 384,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '15',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Nokia N9',\n        screen: {\n          horizontal: {\n            width: 854,\n            height: 480,\n          },\n          'device-pixel-ratio': 1,\n          vertical: {\n            width: 480,\n            height: 854,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '16',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Nokia Lumia 520',\n        screen: {\n          horizontal: {\n            width: 533,\n            height: 320,\n          },\n          'device-pixel-ratio': 1.5,\n          vertical: {\n            width: 320,\n            height: 533,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '17',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Microsoft Lumia 550',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 360,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 640,\n            height: 360,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '18',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Microsoft Lumia 950',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 360,\n          },\n          'device-pixel-ratio': 4,\n          vertical: {\n            width: 360,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '19',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Galaxy S III',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 360,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 360,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '20',\n      type: 'emulated-device',\n      order: 10,\n      device: {\n        'show-by-default': false,\n        title: 'Galaxy S5',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 360,\n          },\n          'device-pixel-ratio': 3,\n          vertical: {\n            width: 360,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '21',\n      type: 'emulated-device',\n      order: 1,\n      device: {\n        'show-by-default': false,\n        title: 'JioPhone 2',\n        screen: {\n          horizontal: {\n            width: 320,\n            height: 240,\n          },\n          'device-pixel-ratio': 1,\n          vertical: {\n            width: 240,\n            height: 320,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '22',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Kindle Fire HDX',\n        screen: {\n          horizontal: {\n            width: 1280,\n            height: 800,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 800,\n            height: 1280,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true',\n        type: 'tablet',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '23',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'iPad Mini',\n        screen: {\n          horizontal: {\n            width: 1024,\n            height: 768,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 768,\n            height: 1024,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',\n        type: 'tablet',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '24',\n      type: 'emulated-device',\n      order: 40,\n      device: {\n        'show-by-default': true,\n        title: 'iPad',\n        screen: {\n          horizontal: {\n            outline: {\n              image: '@url(iPad-landscape.svg)',\n              insets: {\n                left: 112,\n                top: 56,\n                right: 116,\n                bottom: 52,\n              },\n            },\n            width: 1024,\n            height: 768,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            outline: {\n              image: '@url(iPad-portrait.svg)',\n              insets: {\n                left: 52,\n                top: 114,\n                right: 55,\n                bottom: 114,\n              },\n            },\n            width: 768,\n            height: 1024,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',\n        type: 'tablet',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '25',\n      type: 'emulated-device',\n      order: 41,\n      device: {\n        'show-by-default': false,\n        title: 'iPad Pro',\n        screen: {\n          horizontal: {\n            width: 1366,\n            height: 1024,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 1024,\n            height: 1366,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1',\n        type: 'tablet',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '26',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Blackberry PlayBook',\n        screen: {\n          horizontal: {\n            width: 1024,\n            height: 600,\n          },\n          'device-pixel-ratio': 1,\n          vertical: {\n            width: 600,\n            height: 1024,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+',\n        type: 'tablet',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '27',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Nexus 10',\n        screen: {\n          horizontal: {\n            width: 1280,\n            height: 800,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 800,\n            height: 1280,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36',\n        type: 'tablet',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '28',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Nexus 7',\n        screen: {\n          horizontal: {\n            width: 960,\n            height: 600,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 600,\n            height: 960,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36',\n        type: 'tablet',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '29',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Galaxy Note 3',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 360,\n          },\n          'device-pixel-ratio': 3,\n          vertical: {\n            width: 360,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '30',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Galaxy Note II',\n        screen: {\n          horizontal: {\n            width: 640,\n            height: 360,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 360,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '31',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': true,\n        title: 'Generic Laptop',\n        screen: {\n          horizontal: {\n            width: 1280,\n            height: 950,\n          },\n          'device-pixel-ratio': 1,\n          vertical: {\n            width: 950,\n            height: 1280,\n          },\n        },\n        capabilities: ['touch'],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '32',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Laptop with HiDPI screen',\n        screen: {\n          horizontal: {\n            width: 1440,\n            height: 900,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 900,\n            height: 1440,\n          },\n        },\n        capabilities: [],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '33',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Laptop with MDPI screen',\n        screen: {\n          horizontal: {\n            width: 1280,\n            height: 800,\n          },\n          'device-pixel-ratio': 1,\n          vertical: {\n            width: 800,\n            height: 1280,\n          },\n        },\n        capabilities: [],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '36',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'MacBook Air',\n        screen: {\n          horizontal: {\n            width: 1440,\n            height: 900,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 900,\n            height: 1440,\n          },\n        },\n        capabilities: [],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '37',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'MacBook Pro 13\"',\n        screen: {\n          horizontal: {\n            width: 2560,\n            height: 1600,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 1600,\n            height: 2580,\n          },\n        },\n        capabilities: [],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '38',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'MacBook Pro 15\"',\n        screen: {\n          horizontal: {\n            width: 2880,\n            height: 1800,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 1800,\n            height: 2880,\n          },\n        },\n        capabilities: [],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '39',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'MacBook Pro 16\"',\n        screen: {\n          horizontal: {\n            width: 3072,\n            height: 1920,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 1920,\n            height: 3072,\n          },\n        },\n        capabilities: [],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '40',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: '4K Display',\n        screen: {\n          horizontal: {\n            width: 3840,\n            height: 2160,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 2160,\n            height: 3840,\n          },\n        },\n        capabilities: [],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '41',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: '5K Display',\n        screen: {\n          horizontal: {\n            width: 5120,\n            height: 2880,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 2880,\n            height: 5120,\n          },\n        },\n        capabilities: [],\n        'user-agent': '',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '34',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Responsive Mode',\n        screen: {\n          horizontal: {\n            width: 500,\n            height: 790,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 500,\n            height: 790,\n          },\n        },\n        capabilities: ['responsive', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '42',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'Moto G4',\n        screen: {\n          horizontal: {\n            outline: {\n              image: '@url(optimized/MotoG4-landscape.avif)',\n              insets: {\n                left: 91,\n                top: 30,\n                right: 74,\n                bottom: 30,\n              },\n            },\n            width: 640,\n            height: 360,\n          },\n          'device-pixel-ratio': 3,\n          vertical: {\n            outline: {\n              image: '@url(optimized/MotoG4-portrait.avif)',\n              insets: {\n                left: 30,\n                top: 91,\n                right: 30,\n                bottom: 74,\n              },\n            },\n            width: 360,\n            height: 640,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 6.0.1; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        'user-agent-metadata': {\n          platform: 'Android',\n          platformVersion: '6.0.1',\n          architecture: '',\n          model: 'Moto G (4)',\n          mobile: true,\n        },\n        type: 'phone',\n      },\n    },\n    {\n      id: '43',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        'dual-screen': true,\n        title: 'Surface Duo',\n        screen: {\n          horizontal: {\n            width: 720,\n            height: 540,\n          },\n          'device-pixel-ratio': 2.5,\n          vertical: {\n            width: 540,\n            height: 720,\n          },\n          'vertical-spanned': {\n            width: 1114,\n            height: 720,\n            hinge: {\n              width: 34,\n              height: 720,\n              x: 540,\n              y: 0,\n              contentColor: {\n                r: 38,\n                g: 38,\n                b: 38,\n                a: 1,\n              },\n            },\n          },\n          'horizontal-spanned': {\n            width: 720,\n            height: 1114,\n            hinge: {\n              width: 720,\n              height: 34,\n              x: 0,\n              y: 540,\n              contentColor: {\n                r: 38,\n                g: 38,\n                b: 38,\n                a: 1,\n              },\n            },\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'spanned',\n            orientation: 'vertical-spanned',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'spanned',\n            orientation: 'horizontal-spanned',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '44',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        'dual-screen': true,\n        title: 'Galaxy Fold',\n        screen: {\n          horizontal: {\n            width: 653,\n            height: 280,\n          },\n          'device-pixel-ratio': 3,\n          vertical: {\n            width: 280,\n            height: 653,\n          },\n          'vertical-spanned': {\n            width: 717,\n            height: 512,\n          },\n          'horizontal-spanned': {\n            width: 512,\n            height: 717,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'spanned',\n            orientation: 'vertical-spanned',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'spanned',\n            orientation: 'horizontal-spanned',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '45',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'FUJITSU Display E24-9 TOUCH',\n        screen: {\n          horizontal: {\n            width: 1920,\n            height: 1080,\n          },\n          'device-pixel-ratio': 1,\n          vertical: {\n            width: 1080,\n            height: 1920,\n          },\n        },\n        capabilities: ['touch', 'responsive'],\n        'user-agent':\n          'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36',\n        type: 'notebook',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '46',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'iPhone 12/13 Pro Max',\n        screen: {\n          horizontal: {\n            width: 926,\n            height: 428,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 428,\n            height: 926,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n    {\n      id: '47',\n      type: 'emulated-device',\n      device: {\n        'show-by-default': false,\n        title: 'iPhone 12/13 Pro',\n        screen: {\n          horizontal: {\n            width: 844,\n            height: 390,\n          },\n          'device-pixel-ratio': 2,\n          vertical: {\n            width: 390,\n            height: 844,\n          },\n        },\n        capabilities: ['touch', 'mobile'],\n        'user-agent':\n          'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53',\n        type: 'phone',\n        modes: [\n          {\n            title: 'default',\n            orientation: 'vertical',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n          {\n            title: 'default',\n            orientation: 'horizontal',\n            insets: {left: 0, top: 0, right: 0, bottom: 0},\n          },\n        ],\n      },\n    },\n  ],\n  dependencies: ['emulation'],\n  scripts: [],\n  resources: [],\n};\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/devices.js",
    "content": "// @flow\nimport settings from 'electron-settings';\nimport chromeEmulatedDevices from './chromeEmulatedDevices';\nimport {ACTIVE_DEVICES, CUSTOM_DEVICES} from './settingKeys';\n\nexport const OS: {[key: string]: OS} = {\n  ios: 'iOS',\n  android: 'Android',\n  windowsPhone: 'Windows Phone',\n  pc: 'PC',\n};\n\nexport const DEVICE_TYPE: {[key: string]: DeviceType} = {\n  phone: 'phone',\n  tablet: 'tablet',\n  desktop: 'notebook',\n};\n\nexport const CAPABILITIES: {[key: string]: Capability} = {\n  mobile: 'mobile',\n  touch: 'touch',\n  responsive: 'responsive',\n};\n\nexport const SOURCE: {[key: string]: Source} = {\n  chrome: 'chrome',\n  custom: 'custom',\n};\n\ntype OSType = OS.ios | OS.android | OS.windowsPhone | OS.pc;\n\ntype DeviceType = DEVICE_TYPE.phone | DEVICE_TYPE.tablet | DEVICE_TYPE.desktop;\n\ntype Capability = CAPABILITIES.mobile | CAPABILITIES.touch;\n\ntype Source = SOURCE.chrome | SOURCE.custom;\n\nconst chromeVersion = process.versions.chrome || '83.0.4103.106';\n\nexport type Device = {\n  id: number,\n  added: boolean,\n  width: number,\n  height: number,\n  name: string,\n  useragent: string,\n  capabilities: Array<Capability>,\n  os: OSType,\n  type: DeviceType,\n  source: Source,\n  isMuted: boolean,\n  designMode: boolean,\n};\n\nfunction getOS(device) {\n  if (device.capabilities.indexOf('mobile') > -1) {\n    const useragent = device['user-agent'];\n    if (useragent.indexOf('like Mac OS X') > -1) {\n      return OS.ios;\n    }\n    if (useragent.indexOf('Lumia') > -1) {\n      return OS.windowsPhone;\n    }\n    return OS.android;\n  }\n  return OS.pc;\n}\n\nfunction getUserAgent(device) {\n  let deviceUserAgent = device['user-agent'];\n  if (deviceUserAgent && deviceUserAgent.includes('Chrome/%s')) {\n    deviceUserAgent = deviceUserAgent.replace('%s', chromeVersion);\n  }\n  return deviceUserAgent;\n}\n\nexport default function getAllDevices() {\n  const chromeDefaultDevices = chromeEmulatedDevices.extensions\n    .sort((a, b) => a.order - b.order)\n    .map(({id, order, device}) => {\n      const dimension =\n        device.type === DEVICE_TYPE.desktop\n          ? device.screen.horizontal\n          : device.screen.vertical;\n\n      return {\n        id,\n        name: device.title,\n        width: dimension.width,\n        height: dimension.height,\n        useragent: getUserAgent(device),\n        capabilities: device.capabilities,\n        added: device['show-by-default'],\n        os: getOS(device),\n        type: device.type,\n        source: SOURCE.chrome,\n      };\n    });\n\n  const userCustomDevices = settings.get(CUSTOM_DEVICES) || [];\n\n  return [...userCustomDevices, ...chromeDefaultDevices];\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/index.js",
    "content": "// @flow\n\n/**\n * Default zoom level for the application.\n */\nexport const DEFAULT_ZOOM_LEVEL = 0.6;\n\n/**\n * Minimum zoom threshold for the application.\n */\nexport const MIN_ZOOM_LEVEL = 0.2;\n\n/**\n * Maximum zoom threshold for the application.\n */\nexport const MAX_ZOOM_LEVEL = 2.0;\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/license.js",
    "content": "export const DEACTIVATION_REASON = {\n  REVALIDATION: 'REVALIDATION',\n};\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/permissionsManagement.js",
    "content": "const PERMISSION_MANAGEMENT_OPTIONS = {\n  ALLOW_ALWAYS: 'Allow always',\n  DENY_ALWAYS: 'Deny always',\n  ASK_ALWAYS: 'Ask always',\n};\n\nObject.freeze(PERMISSION_MANAGEMENT_OPTIONS);\n\nexport {PERMISSION_MANAGEMENT_OPTIONS};\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/previewerLayouts.js",
    "content": "export const HORIZONTAL_LAYOUT = 'HORIZONTAL';\nexport const FLEXIGRID_LAYOUT = 'FLEXIGRID';\nexport const INDIVIDUAL_LAYOUT = 'INDIVIDUAL';\n\nexport const DEVTOOLS_MODES = {\n  BOTTOM: 'BOTTOM',\n  RIGHT: 'RIGHT',\n  UNDOCKED: 'UNDOCKED',\n};\n\nexport const CSS_EDITOR_MODES = {\n  BOTTOM: 'BOTTOM',\n  LEFT: 'LEFT',\n  RIGHT: 'RIGHT',\n  TOP: 'TOP',\n  UNDOCKED: 'UNDOCKED',\n};\n\nexport const isVeriticallyStacked = mode =>\n  mode === CSS_EDITOR_MODES.LEFT || mode === CSS_EDITOR_MODES.RIGHT;\n\nexport const isHorizontallyStacked = mode =>\n  mode === CSS_EDITOR_MODES.TOP || mode === CSS_EDITOR_MODES.BOTTOM;\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/pubsubEvents.js",
    "content": "export const SCROLL_DOWN = 'SCROLL_DOWN';\nexport const SCROLL_UP = 'SCROLL_UP';\nexport const NAVIGATION_BACK = 'NAVIGATION_BACK';\nexport const NAVIGATION_FORWARD = 'NAVIGATION_FORWARD';\nexport const NAVIGATION_RELOAD = 'NAVIGATION_RELOAD';\nexport const RELOAD_CSS = 'RELOAD_CSS';\nexport const ADDRESS_CHANGE = 'ADDRESS_CHANGE';\nexport const DELETE_STORAGE = 'DELETE_STORAGE';\nexport const SCREENSHOT_ALL_DEVICES = 'SCREENSHOT_ALL_DEVICES';\nexport const FLIP_ORIENTATION_ALL_DEVICES = 'FLIP_ORIENTATION_ALL_DEVICES';\nexport const TOGGLE_DEVICE_MUTED_STATE = 'TOGGLE_DEVICE_MUTED_STATE';\nexport const TOGGLE_EVENT_MIRRORING_ALL_DEVICES =\n  'TOGGLE_EVENT_MIRRORING_ALL_DEVICES';\nexport const STOP_LOADING = 'STOP_LOADING';\nexport const APPLY_CSS = 'APPLY_CSS';\n\nexport const SET_NETWORK_TROTTLING_PROFILE = 'SET_NETWORK_TROTTLING_PROFILE';\nexport const CLEAR_NETWORK_CACHE = 'CLEAR_NETWORK_CACHE';\nexport const OPEN_CONSOLE_FOR_DEVICE = 'OPEN_CONSOLE_FOR_DEVICE';\nexport const PROXY_AUTH_ERROR = 'PROXY_AUTH_ERROR';\n\n// status bar events\nexport const STATUS_BAR_VISIBILITY_CHANGE = 'status-bar-visibility-change';\n\nexport const HIDE_PERMISSION_POPUP_DUE_TO_RELOAD =\n  'HIDE_PERMISSION_POPUP_DUE_TO_RELOAD';\nexport const PERMISSION_MANAGEMENT_PREFERENCE_CHANGED =\n  'PERMISSION_MANAGEMENT_PREFERENCE_CHANGED';\n\nexport const TOGGLE_DEVICE_DESIGN_MODE_STATE =\n  'TOGGLE_DEVICE_DESIGN_MODE_STATE';\n\nexport const PAGE_NAVIGATOR_CHANGED = 'PAGE_NAVIGATOR_CHANGED';\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/routes.js",
    "content": "export default {\n  HOME: '/',\n  COUNTER: '/counter',\n};\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/searchResultSettings.js",
    "content": "export const ADD_SEARCH_RESULTS = 'ADD_SEARCH_RESULTS';\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/settingKeys.js",
    "content": "export const ACTIVE_DEVICES = 'activeDevices';\nexport const CUSTOM_DEVICES = 'customDevices';\nexport const USER_PREFERENCES = 'userPreferences';\nexport const CAN_PROMPT_MOVE_TO_APPLICATIONS = 'canPromptMoveToApplications';\nexport const NETWORK_CONFIGURATION = 'networkConfiguration';\nexport const BOOKMARKS = 'bookmarks';\nexport const STATUS_BAR_VISIBILITY = 'statusBarVisibility';\nexport const APP_NOTIFICATION = 'appNotification';\nexport const LAYOUT = 'layout';\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/theme.js",
    "content": "export const LIGHT_THEME = 'light';\nexport const DARK_THEME = 'dark';\n"
  },
  {
    "path": "desktop-app-legacy/app/constants/values.js",
    "content": "export const SCREENSHOT_MECHANISM = {\n  V1: 'V1',\n  V2: 'V2',\n};\n\nexport const SSL_ERROR_CODES = {\n  FIRST: -200,\n  LAST: -299,\n};\n\nexport const DESIGN_MODE_JS_VALUES = {\n  ON: 'on',\n  OFF: 'off',\n};\n\nexport const STARTUP_PAGE = {\n  BLANK: 'BLANK',\n  HOME: 'HOME',\n};\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/AddDeviceContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport AddDevice from '../../components/DeviceManager/AddDevice';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(AddDevice);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/AddressBar/index.js",
    "content": "import React, {useEffect} from 'react';\nimport {ipcRenderer} from 'electron';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport AddressInput from '../../components/AddressInput';\nimport * as BrowserActions from '../../actions/browser';\nimport {toggleBookmarkUrl} from '../../actions/bookmarks';\n\nconst AddressBar = props => {\n  const handler = (_, url) => {\n    props.onAddressChange(url);\n  };\n  useEffect(() => {\n    ipcRenderer.on('address-change', handler);\n    return () => ipcRenderer.removeListener('address-change', handler);\n  }, []);\n  return (\n    <AddressInput\n      address={props.browser.address}\n      onChange={props.onAddressChange}\n      homepage={props.browser.homepage}\n      setHomepage={props.setCurrentAddressAsHomepage}\n      isBookmarked={props.isBookmarked}\n      toggleBookmark={url =>\n        props.toggleBookmarkUrl(url, props.browser.currentPageMeta)\n      }\n      deleteCookies={props.deleteCookies}\n      deleteStorage={props.deleteStorage}\n    />\n  );\n};\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n    isBookmarked: state.bookmarks.bookmarks.some(\n      b => b.url === state.browser.address\n    ),\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators({...BrowserActions, toggleBookmarkUrl}, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(AddressBar);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/BookmarksBarContainer/index.js",
    "content": "import React, {useCallback} from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport * as BrowserActions from '../../actions/browser';\nimport * as BookmarksActions from '../../actions/bookmarks';\nimport BookmarksBar from '../../components/BookmarksBar';\n\nconst BookmarksBarContainer = props => {\n  const handleBookmarkClick = useCallback(bookmark => {\n    props.onAddressChange(bookmark.url);\n  }, []);\n\n  const handleBookmarkDelete = useCallback(bookmark => {\n    props.toggleBookmarkUrl(bookmark.url);\n  }, []);\n\n  return (\n    <BookmarksBar\n      bookmarks={props.bookmarks}\n      onBookmarkClick={handleBookmarkClick}\n      onBookmarkDelete={handleBookmarkDelete}\n      onBookmarkEdit={props.editBookmark}\n    />\n  );\n};\n\nfunction mapStateToProps(state) {\n  return {\n    bookmarks: state.bookmarks.bookmarks,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(\n    {\n      onAddressChange: BrowserActions.onAddressChange,\n      ...BookmarksActions,\n    },\n    dispatch\n  );\n}\n\nexport default connect(\n  mapStateToProps,\n  mapDispatchToProps\n)(BookmarksBarContainer);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/Browser/index.js",
    "content": "// @flow\nimport React, {Fragment} from 'react';\nimport {bindActionCreators} from 'redux';\nimport {connect} from 'react-redux';\nimport Grid from '@material-ui/core/Grid';\nimport DevicePreviewerContainer from '../DevicePreviewerContainer';\nimport DrawerContainer from '../DrawerContainer';\nimport * as BrowserActions from '../../actions/browser';\nimport {DEVTOOLS_MODES} from '../../constants/previewerLayouts';\nimport LeftIconsPaneContainer from '../LeftIconsPaneContainer';\nimport HeaderContainer from '../HeaderContainer';\nimport os from 'os';\nimport HorizontalSpacer from '../../components/HorizontalSpacer';\nimport AppNotification from '../../components/AppNotification/AppNotification';\n\nconst Browser = ({browser}) => (\n  <Fragment>\n    {os.platform() === 'darwin' && <HorizontalSpacer />}\n    <HeaderContainer />\n    <div style={{display: 'flex', height: '100%', overflow: 'auto'}}>\n      <LeftIconsPaneContainer />\n      <div\n        style={{\n          display: 'flex',\n          flexDirection: 'row',\n          overflowX: 'hidden',\n          flexGrow: 1,\n          flexBasis: 0,\n        }}\n      >\n        <DrawerContainer />\n        <div\n          style={{\n            display: 'flex',\n            flex: 1,\n            height: '100%',\n            flexDirection: 'column',\n            overflow: 'hidden',\n          }}\n        >\n          <DevicePreviewerContainer />\n          {browser.devToolsConfig.open &&\n          browser.devToolsConfig.mode === DEVTOOLS_MODES.BOTTOM ? (\n            <div\n              style={{\n                display: 'flex',\n                width: '100%',\n                height: browser.devToolsConfig.size.height,\n              }}\n            />\n          ) : null}\n        </div>\n        {browser.devToolsConfig.open &&\n        browser.devToolsConfig.mode === DEVTOOLS_MODES.RIGHT ? (\n          <div\n            style={{\n              height: '100%',\n              width: browser.devToolsConfig.size.width,\n              display: 'flex',\n            }}\n          />\n        ) : null}\n      </div>\n    </div>\n    <AppNotification />\n  </Fragment>\n);\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Browser);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/DevToolResizerContainer/index.js",
    "content": "// @flow\nimport React, {useEffect} from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport DevToolsResizer from '../../components/DevToolsResizer';\nimport * as BrowserActions from '../../actions/browser';\nimport {useMediaQuery} from '@material-ui/core';\nimport {DARK_THEME} from '../../constants/theme';\n\nconst Component = props => {\n  const isDarkTheme = useMediaQuery(`(prefers-color-scheme: ${DARK_THEME})`);\n  return <DevToolsResizer {...props} isDarkTheme={isDarkTheme} />;\n};\n\nfunction mapStateToProps(state) {\n  return {\n    ...state.browser.devToolsConfig,\n    isInspecting: state.browser.isInspecting,\n    devices: state.browser.devices,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Component);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/DeviceDrawerContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport DeviceDrawer from '../../components/DeviceDrawer';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(DeviceDrawer);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/DeviceManagerContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport DeviceManager from '../../components/DeviceManager';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(DeviceManager);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/DevicePreviewerContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {bindActionCreators} from 'redux';\nimport {connect} from 'react-redux';\nimport DevicesPreviewer from '../../components/DevicesPreviewer';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(DevicesPreviewer);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/DevicesOverviewContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport DevicesOverview from '../../components/DevicesOverview';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(DevicesOverview);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/DrawerContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport Drawer from '../../components/Drawer';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    drawer: state.browser.drawer,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Drawer);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/ExtensionsManagerContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport ExtensionsManager from '../../components/ExtensionsManager';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(ExtensionsManager);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/HeaderContainer/index.js",
    "content": "// @flow\n\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport Header from '../../components/Header';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    isHeaderVisible: state.browser.isHeaderVisible,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Header);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/HomePage.js",
    "content": "// @flow\nimport React, {Component} from 'react';\nimport Home from '../components/Home';\n\ntype Props = {};\n\nexport default class HomePage extends Component<Props> {\n  props: Props;\n\n  render() {\n    return <Home />;\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/LeftIconsPaneContainer/index.js",
    "content": "// @flow\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport LeftIconsPane from '../../components/LeftIconsPane';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    drawer: state.browser.drawer,\n    isLeftPaneVisible: state.browser.isLeftPaneVisible,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(LeftIconsPane);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/LinkHoverDisplayContainer/index.js",
    "content": "import React from 'react';\nimport {connect} from 'react-redux';\nimport LinkHoverDisplay from '../../components/LinkHoverDisplay';\n\nconst LinkHoverDisplayContainer = ({url}) => (\n  <LinkHoverDisplay visible={!!url.length} url={url} />\n);\n\nconst mapState = state => ({\n  url: state.browser.hoveredLink,\n});\n\nexport default connect(mapState)(LinkHoverDisplayContainer);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/NavigationControlsContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport * as BrowserActions from '../../actions/browser';\nimport NavigationControls from '../../components/NavigationControls';\n\nfunction mapStateToProps(state) {\n  const {\n    navigatorStatus: {backEnabled, forwardEnabled},\n    devices,\n    address,\n    homepage,\n  } = state.browser;\n  return {backEnabled, forwardEnabled, devices, address, homepage};\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(NavigationControls);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/NetworkConfigurationContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport NetworkConfiguration from '../../components/NetworkConfiguration';\nimport * as NetworkConfigActions from '../../actions/networkConfig';\n\nfunction mapStateToProps(state) {\n  return {\n    throttling: state.browser.networkConfiguration.throttling,\n    proxy: state.browser.networkConfiguration.proxy,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(NetworkConfigActions, dispatch);\n}\n\nexport default connect(\n  mapStateToProps,\n  mapDispatchToProps\n)(NetworkConfiguration);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/PageNavigatorContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport * as BrowserActions from '../../actions/browser';\nimport PageNavigator from '../../components/PageNavigator';\n\nfunction mapStateToProps(state) {\n  return {\n    active: !!state.browser.pageNavigator.active,\n    selector: state.browser.pageNavigator.selector,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(PageNavigator);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/QuickFilterDevicesContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport QuickFilterDevices from '../../components/QuickFilterDevices';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(QuickFilterDevices);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/Root.js",
    "content": "import React, {Component} from 'react';\nimport {Provider} from 'react-redux';\nimport log from 'electron-log';\nimport {makeStyles} from '@material-ui/core/styles';\nimport {ThemeProvider} from '@material-ui/styles';\nimport {remote} from 'electron';\nimport AppContent from '../AppContent';\nimport ErrorBoundary from '../components/ErrorBoundary';\nimport {\n  registerShortcut,\n  clearAllShortcuts,\n  unregisterShortcut,\n} from '../shortcut-manager/renderer-shortcut-manager';\nimport {\n  onZoomChange,\n  triggerScrollUp,\n  triggerScrollDown,\n  screenshotAllDevices,\n  flipOrientationAllDevices,\n  toggleInspector,\n  goToHomepage,\n  triggerNavigationBack,\n  triggerNavigationForward,\n  deleteCookies,\n  deleteStorage,\n  triggerNavigationReload,\n} from '../actions/browser';\nimport {toggleBookmarkUrl} from '../actions/bookmarks';\nimport pubsub from 'pubsub.js';\nimport {PROXY_AUTH_ERROR} from '../constants/pubsubEvents';\nimport useCreateTheme from '../components/useCreateTheme';\nimport {DEFAULT_ZOOM_LEVEL} from '../constants';\n\nfunction App() {\n  const theme = useCreateTheme();\n\n  return (\n    <ThemeProvider theme={theme}>\n      {process.env.NODE_ENV !== 'development' ? (\n        <ErrorBoundary>\n          <AppContent />\n        </ErrorBoundary>\n      ) : (\n        <AppContent />\n      )}\n    </ThemeProvider>\n  );\n}\n\nexport default class Root extends Component {\n  componentDidMount() {\n    this.registerAllShortcuts();\n    remote.session.defaultSession.webRequest.onErrorOccurred(details => {\n      if (\n        this.props.store.getState().browser.address === details.url &&\n        details.statusCode === 407\n      )\n        pubsub.publish(PROXY_AUTH_ERROR);\n    });\n  }\n\n  componentWillUnmount() {\n    clearAllShortcuts();\n    document.removeEventListener('wheel', this.onWheel);\n    remote.session.defaultSession.webRequest.onErrorOccurred(null);\n  }\n\n  registerAllShortcuts = () => {\n    const {store} = this.props;\n    document.addEventListener('wheel', this.onWheel);\n\n    registerShortcut(\n      {\n        id: 'Reload',\n        title: 'Reload',\n        accelerators: ['mod+r', 'f5'],\n        index: 1,\n      },\n      () => {\n        store.dispatch(triggerNavigationReload());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'ZoomIn',\n        title: 'Zoom In',\n        accelerators: ['mod+=', 'mod++', 'mod+shift+='],\n        index: 6,\n      },\n      () => {\n        store.dispatch(onZoomChange(store.getState().browser.zoomLevel + 0.1));\n      },\n      true\n    );\n\n    registerShortcut(\n      {id: 'ZoomOut', title: 'Zoom Out', accelerators: ['mod+-'], index: 7},\n      () => {\n        store.dispatch(onZoomChange(store.getState().browser.zoomLevel - 0.1));\n      },\n      true\n    );\n\n    registerShortcut(\n      {id: 'ZoomReset', title: 'Zoom Reset', accelerators: ['mod+0'], index: 8},\n      () => {\n        store.dispatch(onZoomChange(DEFAULT_ZOOM_LEVEL));\n      },\n      true\n    );\n\n    registerShortcut(\n      {id: 'EditUrl', title: 'Edit URL', accelerators: ['mod+l'], index: 9},\n      () => {\n        document.getElementById('adress').select();\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'ScroolUp',\n        title: 'Scroll Up',\n        accelerators: ['mod+pageup'],\n        index: 10,\n      },\n      () => {\n        store.dispatch(triggerScrollUp());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'ScroolDown',\n        title: 'Scroll Down',\n        accelerators: ['mod+pagedown'],\n        index: 11,\n      },\n      () => {\n        store.dispatch(triggerScrollDown());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'Screenshot',\n        title: 'Take Screenshot',\n        accelerators: ['mod+prtsc', 'mod+shift+s'],\n        index: 12,\n      },\n      () => {\n        store.dispatch(screenshotAllDevices());\n      },\n      true,\n      'keyup'\n    );\n\n    registerShortcut(\n      {\n        id: 'TiltDevices',\n        title: 'Tilt Devices',\n        accelerators: ['mod+tab'],\n        index: 13,\n      },\n      () => {\n        store.dispatch(flipOrientationAllDevices());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'ToggleInspector',\n        title: 'Toggle Inspector',\n        accelerators: ['mod+i'],\n        index: 14,\n      },\n      () => {\n        store.dispatch(toggleInspector());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'OpenHome',\n        title: 'Go to Homepage',\n        accelerators: ['alt+home'],\n        index: 15,\n      },\n      () => {\n        store.dispatch(goToHomepage());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'BackAPage',\n        title: 'Back a Page',\n        accelerators: ['alt+left'],\n        index: 16,\n      },\n      () => {\n        store.dispatch(triggerNavigationBack());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'ForwardAPage',\n        title: 'Forward a Page',\n        accelerators: ['alt+right'],\n        index: 17,\n      },\n      () => {\n        store.dispatch(triggerNavigationForward());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'DeleteStorage',\n        title: 'Delete Storage',\n        accelerators: ['mod+del'],\n        index: 18,\n      },\n      () => {\n        store.dispatch(deleteStorage());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'DeleteCookies',\n        title: 'Delete Cookies',\n        accelerators: ['mod+shift+del'],\n        index: 19,\n      },\n      () => {\n        store.dispatch(deleteCookies());\n      },\n      true\n    );\n\n    registerShortcut(\n      {\n        id: 'AddBookmark',\n        title: 'Add Bookmark',\n        accelerators: ['mod+d'],\n        index: 20,\n      },\n      () => {\n        store.dispatch(toggleBookmarkUrl(store.getState().browser.address));\n      },\n      true\n    );\n  };\n\n  onWheel = e => {\n    if (e.ctrlKey) {\n      const {store} = this.props;\n      store.dispatch(\n        onZoomChange(\n          store.getState().browser.zoomLevel + (e.deltaY < 0 ? 0.1 : -0.1)\n        )\n      );\n    }\n  };\n\n  render() {\n    const {store} = this.props;\n    return (\n      <Provider store={store}>\n        <App />\n      </Provider>\n    );\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/ScrollControlsContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport ScrollControls from '../../components/ScrollControls';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(ScrollControls);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/StatusBarContainer/index.js",
    "content": "// @flow\nimport React, {useEffect} from 'react';\nimport {ipcRenderer} from 'electron';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport StatusBar from '../../components/StatusBar';\nimport {toggleStatusBarVisibility as _toggleStatusBarVisibility} from '../../actions/statusBar';\nimport {STATUS_BAR_VISIBILITY_CHANGE} from '../../constants/pubsubEvents';\n\nconst StatusBarContainer = ({visible, zoomLevel, toggleStatusBarVisibility}) => {\n  useEffect(() => {\n    const handler = () => {\n      toggleStatusBarVisibility();\n    };\n\n    ipcRenderer.on(STATUS_BAR_VISIBILITY_CHANGE, handler);\n\n    return () =>\n      ipcRenderer.removeListener(STATUS_BAR_VISIBILITY_CHANGE, handler);\n  }, []);\n\n  return <StatusBar visible={visible} zoomLevel={zoomLevel}/>;\n};\n\nfunction mapStateToProps(state) {\n  return {\n    visible: state.statusBar.visible,\n    zoomLevel: state.browser.zoomLevel\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(\n    {toggleStatusBarVisibility: _toggleStatusBarVisibility},\n    dispatch\n  );\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(StatusBarContainer);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/UserPreferencesContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport UserPreferences from '../../components/UserPreferences';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    userPreferences: state.browser.userPreferences,\n    devToolsConfig: state.browser.devToolsConfig,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(UserPreferences);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/WebViewContainer/index.js",
    "content": "// @flow\nimport React from 'react';\nimport {bindActionCreators} from 'redux';\nimport {connect} from 'react-redux';\nimport WebView from '../../components/WebView';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(WebView);\n"
  },
  {
    "path": "desktop-app-legacy/app/containers/ZoomContainer/index.js",
    "content": "import React from 'react';\nimport {connect} from 'react-redux';\nimport {bindActionCreators} from 'redux';\n\nimport ZoomInput from '../../components/ZoomInput';\nimport * as BrowserActions from '../../actions/browser';\n\nfunction mapStateToProps(state) {\n  return {\n    browser: state.browser,\n  };\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return bindActionCreators(BrowserActions, dispatch);\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(ZoomInput);\n"
  },
  {
    "path": "desktop-app-legacy/app/imageWorker.js",
    "content": "const registerPromiseWorker = require('promise-worker/register');\nconst mergeImg = require('merge-img');\nconst {promisify} = require('util');\nconst Jimp = require('jimp');\nconst os = require('os');\nconst path = require('path');\nconst uuid = require('uuid');\nconst fs = require('fs-extra');\n\nconst UUID = uuid.v4;\nconst tempDir = path.join(os.tmpdir(), UUID());\n\nregisterPromiseWorker(({images, direction, resultFilename}) => {\n  if (direction === 'horizontal') {\n    return stitchHorizontally(images);\n  }\n  return stitchVertically(images, resultFilename);\n});\n\nasync function stitchHorizontally(images) {\n  const result = await mergeImg(\n    images.map(img => ({src: Buffer.from(img)})),\n    {\n      direction: false,\n    }\n  );\n  const tempPath = await writeToTempFile(result);\n  return tempPath;\n}\n\nasync function writeToTempFile(image) {\n  return new Promise(async (resolve, reject) => {\n    await fs.ensureDir(tempDir);\n    const tempPath = path.join(tempDir, `${UUID()}.jpg`);\n    await image.write(tempPath, err => {\n      if (err) {\n        return reject(err);\n      }\n      return resolve(tempPath);\n    });\n  });\n}\n\nasync function stitchVertically(images, {dir, file}) {\n  const result = await mergeImg(\n    await Promise.all(\n      images.map(async img => {\n        const JimpImg = await Jimp.read(img);\n        return {\n          src: await JimpImg.getBufferAsync('image/jpeg'),\n        };\n      })\n    ),\n    {\n      direction: true,\n    }\n  );\n  await fs.ensureDir(dir);\n  await Promise.all([\n    result.write(path.join(dir, file)),\n    ...images.map(image => fs.remove(image)),\n  ]);\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/index.js",
    "content": "/* eslint-disable import/first */\nrequire('dotenv').config();\n\nimport React from 'react';\nimport {remote} from 'electron';\nimport {render} from 'react-dom';\nimport {AppContainer} from 'react-hot-loader';\nimport Root from './containers/Root';\nimport {configureStore} from './store/configureStore';\nimport './app.global.css';\nimport * as Sentry from '@sentry/electron';\nimport appMetadata from './services/db/appMetadata';\n\nrequire('dotenv').config();\n\nif (remote.getGlobal('process').env.NODE_ENV !== 'development') {\n  Sentry.init({\n    dsn: 'https://f2cdbc6a88aa4a068a738d4e4cfd3e12@sentry.io/1553155',\n    environment: remote.getGlobal('process').env.NODE_ENV,\n    beforeSend: (event, hint) => {\n      if (\n        hint &&\n        hint.originalException &&\n        (hint.originalException.message || '').indexOf(') loading ') > -1\n      ) {\n        return null;\n      }\n      event.tags = {appVersion: remote.app.getVersion()};\n      return event;\n    },\n  });\n}\n\nif (window.heap) {\n  window.heap.addUserProperties({appVersion: remote.app.getVersion()});\n  window.heap.addUserProperties({openCount: appMetadata.getOpenCount()});\n}\n\nconst store = configureStore();\n\nrender(\n  <AppContainer>\n    <Root store={store} />\n  </AppContainer>,\n  document.getElementById('root')\n);\n\nif (module.hot) {\n  module.hot.accept('./containers/Root', () => {\n    // eslint-disable-next-line global-require\n    const NextRoot = require('./containers/Root').default;\n    render(\n      <AppContainer>\n        <NextRoot store={store} />\n      </AppContainer>,\n      document.getElementById('root')\n    );\n  });\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/main.dev.js",
    "content": "/* eslint global-require: off */\nrequire('dotenv').config();\n/**\n * This module executes inside of electron's main process. You can start\n * electron renderer process from here and communicate with the other processes\n * through IPC.\n *\n * When running `yarn build` or `yarn build-main`, this file is compiled to\n * `./app/main.prod.js` using webpack. This gives us some performance wins.\n *\n * @flow\n */\nimport electron, {\n  app,\n  BrowserWindow,\n  BrowserView,\n  globalShortcut,\n  ipcMain,\n  nativeTheme,\n  webContents,\n  shell,\n  dialog,\n  session,\n} from 'electron';\nimport settings from 'electron-settings';\nimport log from 'electron-log';\nimport * as Sentry from '@sentry/electron';\nimport installExtension, {\n  REACT_DEVELOPER_TOOLS,\n  REDUX_DEVTOOLS,\n} from 'electron-devtools-installer';\nimport fs from 'fs';\nimport MenuBuilder from './menu';\nimport {USER_PREFERENCES, NETWORK_CONFIGURATION} from './constants/settingKeys';\nimport {STARTUP_PAGE} from './constants/values';\nimport {migrateDeviceSchema} from './settings/migration';\nimport {DEVTOOLS_MODES} from './constants/previewerLayouts';\nimport {initMainShortcutManager} from './shortcut-manager/main-shortcut-manager';\nimport {appUpdater} from './app-updater';\nimport trimStart from 'lodash/trimStart';\nimport isURL from 'validator/lib/isURL';\nimport {\n  confirmMove,\n  conflictHandler,\n  movingFailed,\n} from './move-to-applications';\nimport {\n  initBrowserSync,\n  getBrowserSyncHost,\n  getBrowserSyncEmbedScriptURL,\n  closeBrowserSync,\n  stopWatchFiles,\n  watchFiles,\n} from './utils/browserSync';\nimport {getHostFromURL, normalize} from './utils/urlUtils';\nimport {getPermissionSettingPreference} from './utils/permissionUtils';\nimport browserSync from 'browser-sync';\nimport {captureOnSentry} from './utils/logUtils';\nimport appMetadata from './services/db/appMetadata';\nimport {convertToProxyConfig} from './utils/proxyUtils';\nimport {PERMISSION_MANAGEMENT_OPTIONS} from './constants/permissionsManagement';\nimport {endSession, startSession} from './utils/analytics';\nimport {getStartupPage, getLastOpenedAddress} from './utils/navigatorUtils';\n\nconst path = require('path');\nconst URL = require('url').URL;\n\nconst HOME_PAGE = 'HOME_PAGE';\nconst LAST_OPENED_ADDRESS = 'LAST_OPENED_ADDRESS';\n\nmigrateDeviceSchema();\n\nif (process.env.NODE_ENV !== 'development') {\n  Sentry.init({\n    dsn: 'https://f2cdbc6a88aa4a068a738d4e4cfd3e12@sentry.io/1553155',\n    environment: process.env.NODE_ENV,\n    beforeSend: (event, hint) => {\n      // Suppress address already in use error\n      if (\n        (event?.exception?.values?.[0]?.value || '').indexOf(\n          'listen EADDRINUSE: address already in use'\n        ) > -1\n      ) {\n        return null;\n      }\n      event.tags = {appVersion: app.getVersion()};\n      return event;\n    },\n  });\n}\n\nconst protocol = 'responsively';\n\nlet hasActiveWindow = false;\n\nlet mainWindow = null;\nlet urlToOpen = null;\nlet devToolsView = null;\nlet fileToOpen = null;\n\nconst httpAuthCallbacks = {};\nconst permissionCallbacks = {};\n\nif (process.env.NODE_ENV === 'production') {\n  const sourceMapSupport = require('source-map-support');\n  sourceMapSupport.install();\n}\n\nif (\n  process.env.NODE_ENV === 'development' ||\n  process.env.DEBUG_PROD === 'true'\n) {\n  require('electron-debug')({isEnabled: true});\n}\n\nconst openWithHandler = filePath => {\n  fileToOpen = null;\n  if (\n    filePath != null &&\n    !filePath.startsWith('http://') &&\n    !filePath.startsWith('https://') &&\n    (filePath.endsWith('.html') || filePath.endsWith('.htm'))\n  ) {\n    if (filePath.startsWith('file://')) fileToOpen = filePath;\n    else fileToOpen = `file://${filePath}`;\n    return true;\n  }\n  return false;\n};\n\nconst setProxyOnStart = () => {\n  const proxyConfig = (settings.get(NETWORK_CONFIGURATION) || {}).proxy;\n  if (proxyConfig != null && proxyConfig.active) {\n    session.defaultSession.setProxy(convertToProxyConfig(proxyConfig));\n  }\n};\n\n/**\n * Add event listeners...\n */\napp.on('will-finish-launching', () => {\n  if (process.platform === 'win32') {\n    urlToOpen = process.argv.filter(i => /^responsively/.test(i))[0];\n  }\n  if (['win32', 'darwin'].includes(process.platform)) {\n    if (process.argv.length >= 2) {\n      if (!openWithHandler(process.argv[1])) {\n        app.setAsDefaultProtocolClient(protocol, process.execPath, [\n          path.resolve(process.argv[1]),\n        ]);\n      }\n    } else {\n      app.setAsDefaultProtocolClient(protocol);\n    }\n  }\n  if (\n    !fileToOpen &&\n    !urlToOpen &&\n    process.argv.length >= 2 &&\n    !openWithHandler(process.argv[1]) &&\n    isURL(process.argv[1], {\n      protocols: ['http', 'https', 'file'],\n      require_tld: false,\n    })\n  ) {\n    urlToOpen = process.argv[1];\n  }\n});\n\napp.on('open-file', async (event, filePath) => {\n  event.preventDefault();\n  let htmlFile = filePath;\n  if (process.platform === 'win32' && process.argv.length >= 2) {\n    htmlFile = process.argv[1];\n  }\n  if (openWithHandler(htmlFile)) {\n    if (mainWindow) {\n      openFile(fileToOpen);\n    } else if (!hasActiveWindow) {\n      await createWindow();\n    }\n  }\n});\n\napp.on('open-url', async (event, url) => {\n  if (mainWindow) {\n    openUrl(url);\n  } else {\n    urlToOpen = url;\n    if (!hasActiveWindow) {\n      await createWindow();\n    }\n  }\n});\n\napp.on('window-all-closed', () => {\n  endSession();\n  hasActiveWindow = false;\n  ipcMain.removeAllListeners();\n  ipcMain.removeHandler('install-extension');\n  ipcMain.removeHandler('get-local-extension-path');\n  ipcMain.removeHandler('get-screen-shot-save-path');\n  ipcMain.removeHandler('request-browser-sync');\n  closeBrowserSync();\n  // Respect the OSX convention of having the application in memory even\n  // after all windows have been closed\n  if (process.platform !== 'darwin') {\n    app.quit();\n  }\n});\n\napp.on(\n  'certificate-error',\n  (event, webContents, url, error, certificate, callback) => {\n    if (\n      getHostFromURL(url) === getBrowserSyncHost() ||\n      (settings.get(USER_PREFERENCES) || {}).disableSSLValidation === true\n    ) {\n      event.preventDefault();\n      callback(true);\n    }\n  }\n);\n\napp.on('login', (event, webContents, request, authInfo, callback) => {\n  event.preventDefault();\n  const {url} = request;\n  if (authInfo.isProxy) {\n    const proxyConfig = (settings.get(NETWORK_CONFIGURATION) || {}).proxy;\n    if (proxyConfig != null && proxyConfig.active) {\n      const schConfig =\n        proxyConfig[url.substr(0, url.indexOf(':')).toLowerCase()];\n      if (schConfig != null && !schConfig.useDefault) {\n        callback(schConfig.user, schConfig.password);\n      } else {\n        callback(proxyConfig.default.user, proxyConfig.default.password);\n      }\n    }\n  } else {\n    if (httpAuthCallbacks[url]) {\n      return httpAuthCallbacks[url].push(callback);\n    }\n    httpAuthCallbacks[url] = [callback];\n    mainWindow.webContents.send('http-auth-prompt', {url});\n  }\n});\n\nipcMain.on('set-proxy-profile', async (_, proxyProfile) => {\n  if (proxyProfile == null || proxyProfile.length === 0) return;\n  await session.defaultSession.clearAuthCache();\n  await session.defaultSession.setProxy(proxyProfile);\n});\n\napp.on('activate', async (event, hasVisibleWindows) => {\n  if (hasVisibleWindows || hasActiveWindow) {\n    return;\n  }\n  await createWindow();\n});\n\napp.on('ready', async () => {\n  if (hasActiveWindow) {\n    return;\n  }\n  if (\n    process.platform === 'darwin' &&\n    !app.isInApplicationsFolder() &&\n    (await confirmMove(dialog))\n  ) {\n    try {\n      app.moveToApplicationsFolder({\n        conflictHandler: conflictHandler.bind(this, dialog),\n      });\n    } catch (e) {\n      movingFailed(dialog);\n    }\n  }\n  // Set theme based on user preference\n  const themeSource = (settings.get(USER_PREFERENCES) || {}).theme;\n  if (themeSource) {\n    nativeTheme.themeSource = themeSource;\n  }\n  await createWindow();\n});\n\nconst chooseOpenWindowHandler = url => {\n  if (url == null || url.trim() === '' || url === 'about:blank#blocked')\n    return 'none';\n\n  if (url === 'about:blank') return 'useWindow';\n\n  if (isURL(url, {protocols: ['http', 'https'], require_tld: false}))\n    return 'useWindow';\n\n  let urlObj = null;\n  try {\n    urlObj = new URL(url);\n  } catch {}\n\n  if (\n    urlObj != null &&\n    urlObj.protocol === 'file:' &&\n    (urlObj.pathname.endsWith('.html') || urlObj.pathname.endsWith('.htm'))\n  )\n    return 'useWindow';\n\n  return 'useShell';\n};\n\nconst installExtensions = async () => {\n  const extensions = [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS];\n  try {\n    await installExtension(extensions);\n  } catch (err) {\n    console.log('Error installing extensions', err);\n  }\n};\n\nconst openUrl = url => {\n  mainWindow.webContents.send(\n    'address-change',\n    normalize(url.replace(`${protocol}://`, ''))\n  );\n  mainWindow.show();\n};\n\nconst openFile = filePath => {\n  mainWindow.webContents.send('address-change', normalize(filePath));\n  mainWindow.show();\n};\n\nfunction getUserPreferences(): UserPreferenceType {\n  return settings.get(USER_PREFERENCES) || {};\n}\n\nconst createWindow = async () => {\n  appMetadata.incrementOpenCount();\n  hasActiveWindow = true;\n  setProxyOnStart();\n\n  if (process.env.NODE_ENV === 'development') {\n    await installExtensions();\n  }\n\n  const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize;\n\n  const iconPath = path.resolve(__dirname, '../resources/icons/64x64.png');\n  mainWindow = new BrowserWindow({\n    show: false,\n    width,\n    height,\n    webPreferences: {\n      nodeIntegration: true,\n      nodeIntegrationInWorker: true,\n      webviewTag: true,\n      enableRemoteModule: true,\n      contextIsolation: false,\n    },\n    titleBarStyle: 'hidden',\n    icon: iconPath,\n  });\n\n  await initBrowserSync();\n  ipcMain.handle('request-browser-sync', (event, data) => {\n    const browserSyncOptions = {\n      url: getBrowserSyncEmbedScriptURL(),\n    };\n    return browserSyncOptions;\n  });\n\n  mainWindow.loadURL(`file://${__dirname}/app.html`);\n\n  mainWindow.webContents.on('did-finish-load', () => {\n    startSession();\n    if (process.platform === 'darwin') {\n      // Trick to make the transparent title bar draggable\n      mainWindow.webContents\n        .executeJavaScript(\n          `\n            var div = document.createElement(\"div\");\n            div.style.position = \"absolute\";\n            div.style.top = 0;\n            div.style.height = \"23px\";\n            div.style.width = \"100%\";\n            div.style[\"-webkit-app-region\"] = \"drag\";\n            div.style['-webkit-user-select'] = 'none';\n            document.body.appendChild(div);\n            true;\n          `\n        )\n        .catch(captureOnSentry);\n    }\n  });\n\n  initMainShortcutManager();\n\n  const onResize = () => {\n    const [width, height] = mainWindow.getContentSize();\n    mainWindow.webContents.send('window-resize', {height, width});\n  };\n\n  mainWindow.on('resize', onResize);\n\n  mainWindow.once('ready-to-show', () => {\n    if (urlToOpen) {\n      openUrl(urlToOpen);\n      urlToOpen = null;\n    } else if (fileToOpen) {\n      openFile(fileToOpen);\n      fileToOpen = null;\n    } else {\n      openUrl(\n        getUserPreferences().reopenLastAddress\n          ? getLastOpenedAddress()\n          : getStartupPage()\n      );\n      mainWindow.show();\n    }\n    mainWindow.maximize();\n    onResize();\n  });\n\n  session.defaultSession.setPermissionRequestHandler(\n    (webContents, permission, callback, details) => {\n      const preferences = getPermissionSettingPreference();\n\n      const reqUrl = webContents.getURL();\n\n      if (permissionCallbacks[reqUrl] == null) permissionCallbacks[reqUrl] = {};\n\n      if (permissionCallbacks[reqUrl][permission] == null) {\n        permissionCallbacks[reqUrl][permission] = {\n          called: false,\n          allowed: null,\n          callbacks: [],\n        };\n      }\n\n      const entry = permissionCallbacks[reqUrl][permission];\n\n      if (preferences === PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS) {\n        entry.callbacks.forEach(callback => callback(true));\n        entry.callbacks = [];\n        entry.allowed = true;\n        entry.called = true;\n        return callback(true);\n      }\n      if (preferences === PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS) {\n        entry.callbacks.forEach(callback => callback(false));\n        entry.callbacks = [];\n        entry.allowed = false;\n        entry.called = true;\n        return callback(false);\n      }\n\n      if (entry.called) {\n        if (entry.allowed == null) return;\n        return callback(entry.allowed);\n      }\n\n      if (entry.callbacks.length === 0) {\n        entry.callbacks.push(callback);\n\n        mainWindow.webContents.send('permission-prompt', {\n          url: reqUrl,\n          permission,\n          details,\n        });\n      } else {\n        entry.callbacks.push(callback);\n      }\n    }\n  );\n\n  session.defaultSession.setPermissionCheckHandler(\n    (webContents, permission) => {\n      const reqUrl = webContents.getURL();\n\n      let entry = permissionCallbacks[reqUrl];\n      if (entry != null) entry = entry[permission];\n\n      if (entry == null || !entry.called) {\n        return null;\n      }\n\n      return entry.allowed;\n    }\n  );\n\n  ipcMain.on('permission-response', (evnt, ...args) => {\n    if (args[0] == null) return;\n    const {url, permission, allowed} = args[0];\n\n    let entry = permissionCallbacks[url];\n    if (entry != null) entry = entry[permission];\n\n    if (entry != null && !entry.called) {\n      entry.called = true;\n      entry.allowed = allowed;\n      if (allowed != null)\n        entry.callbacks.forEach(callback => callback(allowed));\n      entry.callbacks = [];\n    }\n  });\n\n  ipcMain.on('reset-ignored-permissions', evnt => {\n    Object.entries(permissionCallbacks).forEach(([_, permissions]) => {\n      Object.entries(permissions).forEach(([_, entry]) => {\n        if (entry.called && entry.allowed == null) entry.called = false;\n        entry.callbacks = [];\n      });\n    });\n  });\n\n  ipcMain.on('start-watching-file', async (event, fileInfo) => {\n    let path = fileInfo.path.replace('file://', '');\n    if (process.platform === 'win32') {\n      path = trimStart(path, '/');\n    }\n    app.addRecentDocument(path);\n    await stopWatchFiles();\n    watchFiles(path);\n  });\n\n  ipcMain.on('stop-watcher', async () => {\n    await stopWatchFiles();\n  });\n\n  ipcMain.on('open-new-window', (event, data) => {\n    const handler = chooseOpenWindowHandler(data.url);\n\n    if (handler === 'useWindow') {\n      let win = new BrowserWindow({\n        width: 800,\n        height: 600,\n        webPreferences: {\n          devTools: false,\n        },\n      });\n      win.setMenu(null);\n      win.loadURL(data.url);\n      win.once('ready-to-show', () => {\n        win.show();\n      });\n      win.on('closed', () => {\n        win = null;\n      });\n    } else if (handler === 'useShell') {\n      shell.openExternal(data.url);\n    }\n  });\n\n  ipcMain.on('http-auth-promt-response', (event, ...args) => {\n    if (!args[0].url) {\n      return;\n    }\n    const {url, username, password} = args[0];\n    if (!httpAuthCallbacks[url]) {\n      return;\n    }\n    httpAuthCallbacks[url].forEach(cb => cb(username, password));\n    httpAuthCallbacks[url] = null;\n  });\n\n  ipcMain.on('prefers-color-scheme-select', (event, scheme) => {\n    if (!scheme) {\n      return;\n    }\n    nativeTheme.themeSource = scheme;\n  });\n\n  ipcMain.handle('install-extension', (event, extensionId) => {\n    let isLocalExtension;\n\n    try {\n      isLocalExtension = fs.statSync(extensionId).isDirectory();\n    } catch {\n      isLocalExtension = false;\n    }\n\n    if (isLocalExtension) {\n      return electron.BrowserWindow.addDevToolsExtension(extensionId);\n    }\n\n    const id = extensionId\n      .replace(/\\/$/, '')\n      .split('/')\n      .pop();\n\n    return installExtension(id, true);\n  });\n\n  ipcMain.on('uninstall-extension', (event, name) =>\n    BrowserWindow.removeDevToolsExtension(name)\n  );\n\n  ipcMain.handle('get-local-extension-path', async event => {\n    try {\n      const {filePaths = []} = await dialog.showOpenDialog({\n        properties: ['openDirectory'],\n      });\n\n      const [localExtensionPath = ''] = filePaths;\n      return localExtensionPath;\n    } catch {\n      return '';\n    }\n  });\n\n  ipcMain.handle('get-screen-shot-save-path', async event => {\n    try {\n      const {filePaths = []} = await dialog.showOpenDialog({\n        properties: ['openDirectory'],\n      });\n      return filePaths[0];\n    } catch {\n      return '';\n    }\n  });\n\n  ipcMain.on('open-devtools', (event, ...args) => {\n    const {webViewId, bounds, mode} = args[0];\n    if (!webViewId) {\n      return;\n    }\n    const webView = webContents.fromId(webViewId);\n\n    if (mode === DEVTOOLS_MODES.UNDOCKED) {\n      return webView.openDevTools();\n    }\n\n    devToolsView = new BrowserView();\n    mainWindow.setBrowserView(devToolsView);\n    devToolsView.setBounds(bounds);\n    webView.setDevToolsWebContents(devToolsView.webContents);\n    webView.openDevTools();\n    devToolsView.webContents\n      .executeJavaScript(\n        `\n          (async function () {\n            const sleep = ms => (new Promise(resolve => setTimeout(resolve, ms)));\n            var retryCount = 0;\n            var done = false;\n            while(retryCount < 10 && !done) {\n              try {\n                retryCount++;\n                document.querySelectorAll('div[slot=\"insertion-point-main\"]')[0].shadowRoot.querySelectorAll('.tabbed-pane-left-toolbar.toolbar')[0].style.display = 'none'\n                done = true\n              } catch(err){\n                await sleep(100);\n              }\n            }\n          })()\n        `\n      )\n      .catch(captureOnSentry);\n  });\n\n  ipcMain.on('close-devtools', (event, ...args) => {\n    const {webViewId} = args[0];\n    if (!webViewId) {\n      return;\n    }\n    const _webContents = webContents.fromId(webViewId);\n    if (_webContents) {\n      _webContents.closeDevTools();\n    }\n    if (!devToolsView) {\n      return;\n    }\n    mainWindow.removeBrowserView(devToolsView);\n    devToolsView.destroy();\n    devToolsView = null;\n  });\n\n  ipcMain.on('resize-devtools', (event, ...args) => {\n    const {bounds} = args[0];\n    if (!bounds || !devToolsView) {\n      return;\n    }\n    devToolsView.setBounds(bounds);\n  });\n\n  ipcMain.on('download-preferences', (event, ...args) => {\n    session.defaultSession.downloadURL(args[0].url);\n  });\n\n  mainWindow.on('closed', () => {\n    mainWindow = null;\n  });\n\n  mainWindow.webContents.on(\n    'new-window',\n    (event, url, frameName, disposition, options) => {\n      if (url?.indexOf('headwayapp.co') !== -1) {\n        event.preventDefault();\n        shell.openExternal(url);\n      }\n    }\n  );\n\n  const menuBuilder = new MenuBuilder(mainWindow);\n  menuBuilder.buildMenu();\n\n  appUpdater.on('status-changed', nextStatus => {\n    menuBuilder.buildMenu(true);\n    mainWindow.webContents.send('updater-status-changed', {nextStatus});\n  });\n  // Remove this if your app does not use auto updates\n  appUpdater\n    .checkForUpdatesAndNotify()\n    .catch(err => console.log('Error while updating app', err));\n};\n"
  },
  {
    "path": "desktop-app-legacy/app/menu.js",
    "content": "// @flow\nimport {\n  app,\n  dialog,\n  Menu,\n  shell,\n  BrowserWindow,\n  clipboard,\n  screen,\n} from 'electron';\nimport fs from 'fs';\nimport url from 'url';\nimport {getEnvironmentInfo, getPackageJson} from './utils/generalUtils';\nimport {\n  getAllShortcuts,\n  registerShortcut,\n} from './shortcut-manager/main-shortcut-manager';\nimport {appUpdater, AppUpdaterStatus} from './app-updater';\nimport {statusBarSettings} from './settings/statusBarSettings';\nimport {STATUS_BAR_VISIBILITY_CHANGE} from './constants/pubsubEvents';\nimport {userPreferenceSettings} from './settings/userPreferenceSettings';\n\nconst path = require('path');\n\nexport default class MenuBuilder {\n  mainWindow: BrowserWindow;\n\n  constructor(mainWindow: BrowserWindow) {\n    this.mainWindow = mainWindow;\n  }\n\n  aboutClick() {\n    const iconPath = path.join(__dirname, '../resources/icons/64x64.png');\n    const title = 'Responsively';\n    const {description} = getPackageJson();\n    const {\n      appVersion,\n      electronVersion,\n      chromeVersion,\n      nodeVersion,\n      v8Version,\n      osInfo,\n    } = getEnvironmentInfo();\n\n    const usefulInfo = `Version: ${appVersion}\\nElectron: ${electronVersion}\\nChrome: ${chromeVersion}\\nNode.js: ${nodeVersion}\\nV8: ${v8Version}\\nOS: ${osInfo}`;\n    const detail = description ? `${description}\\n\\n${usefulInfo}` : usefulInfo;\n    let buttons = ['OK', 'Copy'];\n    let cancelId = 0;\n    let defaultId = 1;\n    if (process.platform === 'linux') {\n      buttons = ['Copy', 'OK'];\n      cancelId = 1;\n      defaultId = 0;\n    }\n    dialog\n      .showMessageBox(BrowserWindow.getAllWindows()[0], {\n        type: 'none',\n        buttons,\n        title,\n        message: title,\n        detail,\n        noLink: true,\n        icon: iconPath,\n        cancelId,\n        defaultId,\n      })\n      .then(({response}) => {\n        if (response === defaultId) {\n          clipboard.writeText(usefulInfo, 'clipboard');\n        }\n      });\n  }\n\n  subMenuHelp = {\n    label: 'Help',\n    submenu: [\n      {\n        label: 'Website',\n        click() {\n          shell.openExternal('https://responsively.app/');\n        },\n      },\n      {\n        label: 'Open Source',\n        click() {\n          shell.openExternal(\n            'https://github.com/responsively-org/responsively-app'\n          );\n        },\n      },\n      {\n        label: 'Report Issues',\n        click() {\n          shell.openExternal(\n            'https://github.com/responsively-org/responsively-app/issues'\n          );\n        },\n      },\n      {\n        label: 'Find on ProductHunt',\n        click() {\n          shell.openExternal('https://www.producthunt.com/posts/responsively');\n        },\n      },\n      {\n        label: 'Follow on Twitter',\n        click() {\n          shell.openExternal(\n            'https://x.com/intent/follow?original_referer=app&ref_src=twsrc%5Etfw&region=follow_link&screen_name=ResponsivelyApp&tw_p=followbutton'\n          );\n        },\n      },\n      {\n        type: 'separator',\n      },\n      {\n        label: 'Keyboard Shortcuts',\n        click: () => {\n          const {getCursorScreenPoint, getDisplayNearestPoint} = screen;\n\n          let win = new BrowserWindow({\n            parent: BrowserWindow.getFocusedWindow(),\n            frame: false,\n            webPreferences: {\n              devTools: false,\n              nodeIntegration: true,\n              additionalArguments: [JSON.stringify(getAllShortcuts())],\n            },\n          });\n\n          const currentScreen = getDisplayNearestPoint(getCursorScreenPoint());\n          win.setPosition(currentScreen.workArea.x, currentScreen.workArea.y);\n\n          win.center();\n\n          win.loadURL(\n            url.format({\n              protocol: 'file',\n              pathname: path.join(__dirname, 'shortcuts.html'),\n            })\n          );\n\n          win.once('ready-to-show', () => {\n            win.show();\n          });\n\n          win.on('blur', () => {\n            win.close();\n          });\n\n          win.on('closed', () => {\n            win = null;\n          });\n        },\n      },\n      {\n        type: 'separator',\n      },\n      {\n        label: 'Check for Updates...',\n        id: 'CHECK_FOR_UPDATES',\n        click() {\n          appUpdater.checkForUpdatesAndNotify().then(r => {\n            if (\n              r == null ||\n              r.updateInfo == null ||\n              r.updateInfo.version === getPackageJson().version\n            ) {\n              dialog.showMessageBox(BrowserWindow.getAllWindows()[0], {\n                type: 'info',\n                title: 'Responsively',\n                message: 'The app is up to date! 🎉',\n              });\n            }\n          });\n        },\n      },\n      {\n        type: 'separator',\n      },\n      {\n        label: 'About',\n        accelerator: 'F1',\n        click: this.aboutClick,\n        shortcutIndex: 5,\n      },\n    ],\n  };\n\n  subMenuFile = {\n    label: 'File',\n    submenu: [\n      {\n        label: 'Open HTML file',\n        accelerator: 'CommandOrControl+O',\n        shortcutIndex: 0,\n        click: () => {\n          const selected = dialog.showOpenDialogSync({\n            filters: [{name: 'HTML', extensions: ['htm', 'html']}],\n          });\n\n          if (!selected || !selected.length || !selected[0]) {\n            return;\n          }\n          let filePath = selected[0];\n\n          filePath = url.format({\n            protocol: 'file',\n            pathname: filePath,\n          });\n          this.mainWindow.webContents.send('address-change', filePath);\n        },\n      },\n      {\n        label: 'Open Screenshots folder',\n        click: () => {\n          try {\n            const userSelectedScreenShotSavePath = userPreferenceSettings.getScreenShotSavePath();\n            const dir =\n              userSelectedScreenShotSavePath ||\n              userPreferenceSettings.getDefaultScreenshotpath();\n            if (!fs.existsSync(dir)) {\n              fs.mkdirSync(dir);\n            }\n            shell.openPath(dir);\n          } catch (err) {\n            console.log('Error opening screenshots folder', err);\n          }\n        },\n      },\n      {\n        type: 'separator',\n      },\n      {\n        role: process.platform === 'darwin' ? 'close' : 'quit',\n      },\n    ],\n  };\n\n  getCheckForUpdatesMenuState() {\n    const updaterStatus = appUpdater.getCurrentStatus();\n    let label = 'Check for Updates...';\n    let enabled = true;\n\n    switch (updaterStatus) {\n      case AppUpdaterStatus.Idle:\n        label = 'Check for Updates...';\n        enabled = true;\n        break;\n      case AppUpdaterStatus.Checking:\n        label = 'Checking for Updates...';\n        enabled = false;\n        break;\n      case AppUpdaterStatus.NoUpdate:\n        label = 'No Updates';\n        enabled = false;\n        break;\n      case AppUpdaterStatus.Downloading:\n        label = 'Downloading Update...';\n        enabled = false;\n        break;\n      case AppUpdaterStatus.NewVersion:\n        label = 'New version available';\n        enabled = false;\n        break;\n      case AppUpdaterStatus.Downloaded:\n        label = 'Update Downloaded';\n        enabled = false;\n        break;\n      default:\n        break;\n    }\n\n    return {label, enabled};\n  }\n\n  buildMenu(isUpdate: boolean = false) {\n    if (isUpdate) {\n      const chkUpdtMenu = this.subMenuHelp.submenu.find(\n        x => x.id === 'CHECK_FOR_UPDATES'\n      );\n      const {label, enabled} = this.getCheckForUpdatesMenuState();\n      chkUpdtMenu.label = label;\n      chkUpdtMenu.enabled = enabled;\n    }\n\n    if (\n      process.env.NODE_ENV === 'development' ||\n      process.env.DEBUG_PROD === 'true'\n    ) {\n      this.setupDevelopmentEnvironment();\n    }\n\n    const template =\n      process.platform === 'darwin'\n        ? this.buildDarwinTemplate()\n        : this.buildDefaultTemplate();\n\n    if (!isUpdate) {\n      this.registerMenuShortcuts(template);\n    }\n    const menu = Menu.buildFromTemplate(template);\n    Menu.setApplicationMenu(menu);\n\n    return menu;\n  }\n\n  setupDevelopmentEnvironment() {\n    this.mainWindow.openDevTools();\n    this.mainWindow.webContents.on('context-menu', (e, props) => {\n      const {x, y} = props;\n\n      Menu.buildFromTemplate([\n        {\n          label: 'Inspect element',\n          click: () => {\n            this.mainWindow.inspectElement(x, y);\n          },\n        },\n      ]).popup(this.mainWindow);\n    });\n  }\n\n  buildDarwinTemplate() {\n    const subMenuAbout = {\n      label: 'Responsively',\n      submenu: [\n        {\n          label: 'About ResponsivelyApp',\n          selector: 'orderFrontStandardAboutPanel:',\n          click: this.aboutClick,\n        },\n        {type: 'separator'},\n        // {label: 'Services', submenu: []},\n        {type: 'separator'},\n        {\n          label: 'Hide ResponsivelyApp',\n          accelerator: 'Command+H',\n          selector: 'hide:',\n        },\n        {\n          label: 'Hide Others',\n          accelerator: 'Command+Shift+H',\n          selector: 'hideOtherApplications:',\n        },\n        {label: 'Show All', selector: 'unhideAllApplications:'},\n        {type: 'separator'},\n        {\n          label: 'Quit',\n          accelerator: 'Command+Q',\n          click: () => {\n            app.quit();\n          },\n        },\n      ],\n    };\n    const subMenuEdit = {\n      label: 'Edit',\n      submenu: [\n        {label: 'Undo', accelerator: 'Command+Z', selector: 'undo:'},\n        {\n          label: 'Redo',\n          accelerator: 'Shift+Command+Z',\n          selector: 'redo:',\n        },\n        {type: 'separator'},\n        {label: 'Cut', accelerator: 'Command+X', selector: 'cut:'},\n        {label: 'Copy', accelerator: 'Command+C', selector: 'copy:'},\n        {label: 'Paste', accelerator: 'Command+V', selector: 'paste:'},\n        {\n          label: 'Select All',\n          accelerator: 'Command+A',\n          selector: 'selectAll:',\n        },\n      ],\n    };\n    const subMenuViewDev = {\n      label: 'View',\n      submenu: [\n        {\n          label: 'Reload',\n          accelerator: 'CommandOrControl+R',\n          skipRegistration: true,\n          click: () => {\n            this.mainWindow.webContents.reload();\n          },\n        },\n        {\n          label: 'Reload Ignoring Cache',\n          accelerator: 'CommandOrControl+Shift+R',\n          shortcutIndex: 3,\n          click: () => {\n            this.mainWindow.webContents.send('reload-url', {ignoreCache: true});\n          },\n        },\n        {\n          label: '&ReloadCSS',\n          accelerator: 'Alt+R',\n          shortcutIndex: 2,\n          click: () => {\n            this.mainWindow.webContents.send('reload-css');\n          },\n        },\n        {\n          label: 'Toggle Full Screen',\n          accelerator: 'Ctrl+Command+F',\n          shortcutIndex: 4,\n          click: () => {\n            this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());\n          },\n        },\n        {\n          label: 'Toggle Developer Tools',\n          accelerator: 'Command+Shift+I',\n          click: () => {\n            this.mainWindow.toggleDevTools();\n          },\n        },\n        {\n          label: 'Show Status Bar',\n          type: 'checkbox',\n          checked: statusBarSettings.getVisibility(),\n          click: () => {\n            this.mainWindow.webContents.send(STATUS_BAR_VISIBILITY_CHANGE);\n          },\n        },\n      ],\n    };\n    const subMenuViewProd = {\n      label: 'View',\n      submenu: [\n        {\n          label: 'Reload',\n          accelerator: 'CommandOrControl+R',\n          skipRegistration: true,\n          click: () => {\n            this.mainWindow.webContents.send('reload-url');\n          },\n        },\n        {\n          label: 'Reload Ignoring Cache',\n          accelerator: 'CommandOrControl+Shift+R',\n          shortcutIndex: 3,\n          click: () => {\n            this.mainWindow.webContents.send('reload-url', {ignoreCache: true});\n          },\n        },\n        {\n          label: '&ReloadCSS',\n          accelerator: 'Alt+R',\n          shortcutIndex: 2,\n          click: () => {\n            this.mainWindow.webContents.send('reload-css');\n          },\n        },\n        {\n          label: 'Toggle Full Screen',\n          accelerator: 'Ctrl+Command+F',\n          shortcutIndex: 4,\n          click: () => {\n            this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());\n          },\n        },\n        {\n          label: 'Show Status Bar',\n          type: 'checkbox',\n          checked: statusBarSettings.getVisibility(),\n          click: () => {\n            this.mainWindow.webContents.send(STATUS_BAR_VISIBILITY_CHANGE);\n          },\n        },\n      ],\n    };\n    const subMenuWindow = {\n      label: 'Window',\n      submenu: [\n        {\n          label: 'Minimize',\n          accelerator: 'Command+M',\n          selector: 'performMiniaturize:',\n        },\n        {\n          label: 'Close',\n          accelerator: 'Command+W',\n          selector: 'performClose:',\n        },\n        {type: 'separator'},\n        {label: 'Bring All to Front', selector: 'arrangeInFront:'},\n      ],\n    };\n\n    const subMenuView =\n      process.env.NODE_ENV === 'development' ? subMenuViewDev : subMenuViewProd;\n\n    return [\n      subMenuAbout,\n      this.subMenuFile,\n      subMenuEdit,\n      subMenuView,\n      subMenuWindow,\n      this.subMenuHelp,\n    ];\n  }\n\n  buildDefaultTemplate() {\n    const templateDefault = [\n      this.subMenuFile,\n      {\n        label: '&View',\n        submenu:\n          process.env.NODE_ENV === 'development'\n            ? [\n                {\n                  label: '&Reload',\n                  accelerator: 'CommandOrControl+R',\n                  skipRegistration: true,\n                  click: () => {\n                    this.mainWindow.webContents.reload();\n                  },\n                },\n                {\n                  label: 'Reload Ignoring Cache',\n                  accelerator: 'CommandOrControl+Shift+R',\n                  shortcutIndex: 3,\n                  click: () => {\n                    this.mainWindow.webContents.send('reload-url', {\n                      ignoreCache: true,\n                    });\n                  },\n                },\n                {\n                  label: 'Toggle &Full Screen',\n                  accelerator: 'F11',\n                  shortcutIndex: 4,\n                  click: () => {\n                    this.mainWindow.setFullScreen(\n                      !this.mainWindow.isFullScreen()\n                    );\n                  },\n                },\n                {\n                  label: 'Toggle &Developer Tools',\n                  accelerator: 'Alt+Ctrl+I',\n                  click: () => {\n                    this.mainWindow.toggleDevTools();\n                  },\n                },\n                {\n                  label: 'Show Status Bar',\n                  type: 'checkbox',\n                  checked: statusBarSettings.getVisibility(),\n                  click: () => {\n                    this.mainWindow.webContents.send(\n                      STATUS_BAR_VISIBILITY_CHANGE\n                    );\n                  },\n                },\n              ]\n            : [\n                {\n                  label: '&Reload',\n                  accelerator: 'CommandOrControl+R',\n                  skipRegistration: true,\n                  click: () => {\n                    this.mainWindow.webContents.send('reload-url');\n                  },\n                },\n                {\n                  label: '&ReloadCSS',\n                  accelerator: 'Alt+R',\n                  shortcutIndex: 2,\n                  click: () => {\n                    this.mainWindow.webContents.send('reload-css');\n                  },\n                },\n                {\n                  label: 'Reload Ignoring Cache',\n                  accelerator: 'CommandOrControl+Shift+R',\n                  shortcutIndex: 3,\n                  click: () => {\n                    this.mainWindow.webContents.send('reload-url', {\n                      ignoreCache: true,\n                    });\n                  },\n                },\n                {\n                  label: 'Toggle &Full Screen',\n                  accelerator: 'F11',\n                  shortcutIndex: 4,\n                  click: () => {\n                    this.mainWindow.setFullScreen(\n                      !this.mainWindow.isFullScreen()\n                    );\n                  },\n                },\n                {\n                  label: 'Show Status Bar',\n                  type: 'checkbox',\n                  checked: statusBarSettings.getVisibility(),\n                  click: () => {\n                    this.mainWindow.webContents.send(\n                      STATUS_BAR_VISIBILITY_CHANGE\n                    );\n                  },\n                },\n              ],\n      },\n      this.subMenuHelp,\n    ];\n\n    return templateDefault;\n  }\n\n  registerMenuShortcuts(\n    template: Array<MenuItemConstructorOptions | MenuItem>,\n    id: string = 'Menu'\n  ) {\n    if ((template || []).length === 0) return;\n\n    for (let i = 0; i < template.length; i++) {\n      const item = template[i];\n      if (item == null || item.skipRegistration) continue;\n\n      const label = (item.label || `submenu${i}`).split('&').join('');\n      const levelId = `${id}_${label}`;\n\n      if (item.accelerator != null)\n        registerShortcut({\n          id: levelId,\n          title: label,\n          accelerators: [item.accelerator],\n          index: item.shortcutIndex,\n        });\n\n      if (item.submenu == null) continue;\n\n      if (Array.isArray(item.submenu))\n        this.registerMenuShortcuts(item.submenu, levelId);\n      else this.registerMenuShortcuts([item.submenu], levelId);\n    }\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/move-to-applications.js",
    "content": "/*\n  conflict handler methods for moveToApplication event on mac\n  https://www.electronjs.org/docs/api/app#appmovetoapplicationsfolderoptions-macos\n*/\n\n// @flow\n\nimport settings from 'electron-settings';\nimport {CAN_PROMPT_MOVE_TO_APPLICATIONS} from './constants/settingKeys';\n\nlet conflictMessage = 'An app of this name already exists';\nconst moveFailedMessage = 'Something went wrong while moving application';\n\nconst confirmMove: boolean = async dialog => {\n  if (process.env.NODE_ENV !== 'production') {\n    return false;\n  }\n  const canPrompt = settings.get(CAN_PROMPT_MOVE_TO_APPLICATIONS) ?? true;\n  if (!canPrompt) {\n    return false;\n  }\n  const {response, checkboxChecked} = await dialog.showMessageBox({\n    type: 'error',\n    message: 'Move to Applications folder?',\n    detail:\n      'Responsively App should be in the Applications folder to be able to work properly.',\n    checkboxLabel: `Don't ask me again`,\n    buttons: ['Move', 'Cancel'],\n  });\n  if (checkboxChecked) {\n    settings.set(CAN_PROMPT_MOVE_TO_APPLICATIONS, false);\n  }\n  return response === 0;\n};\n\nconst conflictHandler = (conflictType, dialog) => {\n  if (conflictType === 'exists' || conflictType === 'existsAndRunning') {\n    conflictMessage =\n      conflictType === 'existsAndRunning'\n        ? `${conflictMessage} and running`\n        : conflictMessage;\n\n    return (\n      dialog.showMessageBoxSync({\n        type: 'question',\n        buttons: ['Halt Move', 'Continue Move'],\n        defaultId: 0,\n        message: conflictMessage,\n      }) === 1\n    );\n  }\n};\n\nconst movingFailed = dialog => {\n  dialog.showMessageBoxSync({\n    type: 'error',\n    message: moveFailedMessage,\n  });\n};\n\nexport {conflictHandler, movingFailed, confirmMove};\n"
  },
  {
    "path": "desktop-app-legacy/app/preload.js",
    "content": "const {ipcRenderer, remote} = require('electron');\n\nconst {Menu} = remote;\nconst {MenuItem} = remote;\n\nwindow._bot = true;\n\nconst menu = new Menu();\nlet rightClickPosition = null;\n\nmenu.append(\n  new MenuItem({\n    label: 'Take Screenshot',\n    click(menuItem, browserWindow, event) {\n      window.responsivelyApp.sendMessageToHost('takeScreenshot');\n    },\n  })\n);\nmenu.append(\n  new MenuItem({\n    label: 'Tilt Device',\n    click(menuItem, browserWindow, event) {\n      window.responsivelyApp.sendMessageToHost('tiltDevice');\n    },\n  })\n);\nmenu.append(\n  new MenuItem({\n    id: 'mirror-events',\n    label: 'Mirror Events',\n    type: 'checkbox',\n    checked: true,\n    click(menuItem, browserWindow, event) {\n      window.responsivelyApp.sendMessageToHost('toggleEventMirroring');\n    },\n  })\n);\n\nmenu.append(new MenuItem({type: 'separator'}));\n\nmenu.append(\n  new MenuItem({\n    label: 'Inspect',\n    click(menuItem, browserWindow, event) {\n      window.responsivelyApp.sendMessageToHost(\n        'openDevToolsInspector',\n        rightClickPosition\n      );\n    },\n  })\n);\nmenu.append(\n  new MenuItem({\n    label: 'Open Console',\n    click(menuItem, browserWindow, event) {\n      window.responsivelyApp.sendMessageToHost('openConsole');\n    },\n  })\n);\n\nwindow.addEventListener(\n  'contextmenu',\n  e => {\n    e.preventDefault();\n    rightClickPosition = {x: e.x, y: e.y};\n    menu.popup(remote.getCurrentWindow());\n  },\n  false\n);\n\nglobal.responsivelyApp = {\n  sendMessageToHost: (type, message) => {\n    if (!message) {\n      message = {};\n    }\n    message.sourceDeviceId = window.responsivelyApp.deviceId;\n    ipcRenderer.sendToHost(type, message);\n  },\n  cssPath: el => {\n    if (!(el instanceof Element)) return;\n    const path = [];\n    while (el.nodeType === Node.ELEMENT_NODE) {\n      let selector = el.nodeName.toLowerCase();\n      let sib = el;\n      let nth = 1;\n      while (\n        sib.nodeType === Node.ELEMENT_NODE &&\n        (sib = sib.previousElementSibling) &&\n        nth++\n      );\n      selector += `:nth-child(${nth})`;\n      path.unshift(selector);\n      el = el.parentNode;\n    }\n    return path.join(' > ');\n  },\n  eventFire: (el, etype) => {\n    if (el.fireEvent) {\n      el.fireEvent(`on${etype}`);\n    } else {\n      const evObj = document.createEvent('Events');\n      evObj.initEvent(etype, true, false);\n      evObj.responsivelyAppProcessed = true;\n      el.dispatchEvent(evObj);\n    }\n  },\n  hideFixedPositionElementsForScreenshot: () => {\n    const elems = document.body.getElementsByTagName('*');\n    for (const elem of elems) {\n      const computedStyle = window.getComputedStyle(elem, null);\n      if (computedStyle.getPropertyValue('position') === 'fixed') {\n        elem.classList.add('responsivelyApp__HiddenForScreenshot');\n      }\n    }\n  },\n  unHideElementsHiddenForScreenshot: () => {\n    const elems = document.body.querySelectorAll(\n      '.responsivelyApp__HiddenForScreenshot'\n    );\n    for (const elem of elems) {\n      elem.classList.remove('responsivelyApp__HiddenForScreenshot');\n    }\n  },\n};\n\nipcRenderer.on('scrollMessage', (event, args) => {\n  window.scrollTo({\n    top: args.y,\n    left: args.x,\n  });\n});\n\nipcRenderer.on('clickMessage', (event, args) => {\n  const elem = document.querySelector(args.cssPath);\n  if (!elem) {\n    return;\n  }\n  window.responsivelyApp.lastClickElement = elem;\n  if (elem.click) {\n    elem.click();\n    return;\n  }\n  window.responsivelyApp.eventFire(elem, 'click');\n});\n\nipcRenderer.on('scrollDownMessage', (event, args) => {\n  window.scrollTo({\n    top: window.scrollY + 250,\n    left: window.scrollX + 250,\n    behavior: 'smooth',\n  });\n});\n\nipcRenderer.on('scrollUpMessage', (event, args) => {\n  window.scrollTo({\n    top: window.scrollY - 250,\n    left: window.scrollX - 250,\n    behavior: 'smooth',\n  });\n});\n\nipcRenderer.on('eventsMirroringState', (event, args) => {\n  menu.getMenuItemById('mirror-events').checked = args;\n});\n"
  },
  {
    "path": "desktop-app-legacy/app/reducers/bookmarks.js",
    "content": "import settings from 'electron-settings';\nimport {TOGGLE_BOOKMARK, EDIT_BOOKMARK} from '../actions/bookmarks';\nimport {BOOKMARKS} from '../constants/settingKeys';\nimport {getWebsiteName} from '../components/WebView/screenshotUtil';\n\ntype BookmarksType = {\n  title: string,\n  url: string,\n};\n\nfunction fetchBookmarks(): BookmarksType {\n  return settings.get(BOOKMARKS) || [];\n}\n\nfunction persistBookmarks(bookmarks) {\n  settings.set(BOOKMARKS, bookmarks);\n}\n\nexport default function browser(\n  state: BrowserStateType = {\n    bookmarks: fetchBookmarks(),\n  },\n  action: Action\n) {\n  switch (action.type) {\n    case TOGGLE_BOOKMARK:\n      let {bookmarks} = state;\n      const bookmark = {\n        title: action.title || getWebsiteName(action.url),\n        url: action.url,\n      };\n      if (bookmarks.find(b => b.url === action.url)) {\n        bookmarks = bookmarks.filter(b => b.url !== action.url);\n      } else {\n        bookmarks = [...bookmarks, bookmark];\n      }\n      persistBookmarks(bookmarks);\n      return {...state, bookmarks};\n    case EDIT_BOOKMARK:\n      const updatedBookmarks = state.bookmarks.map(b =>\n        b === action.bookmark ? {...b, title: action.title, url: action.url} : b\n      );\n      persistBookmarks(updatedBookmarks);\n      return {...state, bookmarks: updatedBookmarks};\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/reducers/browser.js",
    "content": "// @flow\nimport {ipcRenderer, remote} from 'electron';\nimport settings from 'electron-settings';\nimport {\n  NEW_ADDRESS,\n  NEW_ZOOM_LEVEL,\n  NEW_SCROLL_POSITION,\n  NEW_NAVIGATOR_STATUS,\n  NEW_DRAWER_CONTENT,\n  NEW_PREVIEWER_CONFIG,\n  NEW_ACTIVE_DEVICES,\n  NEW_CUSTOM_DEVICE,\n  NEW_FILTERS,\n  NEW_HOMEPAGE,\n  NEW_USER_PREFERENCES,\n  DELETE_CUSTOM_DEVICE,\n  NEW_DEV_TOOLS_CONFIG,\n  NEW_INSPECTOR_STATUS,\n  NEW_WINDOW_SIZE,\n  DEVICE_LOADING,\n  NEW_FOCUSED_DEVICE,\n  NEW_PAGE_META_FIELD,\n  TOGGLE_ALL_DEVICES_MUTED,\n  TOGGLE_DEVICE_MUTED,\n  NEW_THEME,\n  NEW_CSS_EDITOR_STATUS,\n  NEW_CSS_EDITOR_POSITION,\n  NEW_CSS_EDITOR_CONTENT,\n  TOGGLE_ALL_DEVICES_DESIGN_MODE,\n  TOGGLE_DEVICE_DESIGN_MODE,\n  SET_HEADER_VISIBILITY,\n  SET_LEFT_PANE_VISIBILITY,\n  SET_HOVERED_LINK,\n  SET_STARTUP_PAGE,\n  UPDATE_PAGE_NAVIGATOR,\n  TOGGLE_PAGE_NAVIGATOR,\n  LOAD_CUSTOM_DEVICES,\n} from '../actions/browser';\nimport {\n  CHANGE_ACTIVE_THROTTLING_PROFILE,\n  SAVE_THROTTLING_PROFILES,\n  CHANGE_PROXY_PROFILE,\n  TOGGLE_USE_PROXY,\n} from '../actions/networkConfig';\nimport type {Action} from './types';\nimport getAllDevices from '../constants/devices';\nimport type {Device} from '../constants/devices';\nimport {\n  FLEXIGRID_LAYOUT,\n  INDIVIDUAL_LAYOUT,\n  DEVTOOLS_MODES,\n  CSS_EDITOR_MODES,\n} from '../constants/previewerLayouts';\nimport {DEVICE_MANAGER} from '../constants/DrawerContents';\nimport {\n  ACTIVE_DEVICES,\n  USER_PREFERENCES,\n  CUSTOM_DEVICES,\n  NETWORK_CONFIGURATION,\n  LAYOUT,\n} from '../constants/settingKeys';\nimport {\n  getHomepage,\n  saveHomepage,\n  saveLastOpenedAddress,\n  saveStartupPage,\n} from '../utils/navigatorUtils';\nimport {updateExistingUrl} from '../services/searchUrlSuggestions';\nimport {normalizeZoomLevel} from '../utils/browserUtils';\nimport {DEFAULT_ZOOM_LEVEL} from '../constants';\n\nexport const FILTER_FIELDS = {\n  OS: 'OS',\n  DEVICE_TYPE: 'DEVICE_TYPE',\n};\n\ntype ScrollPositionType = {\n  x: number,\n  y: number,\n};\n\ntype NavigatorStatusType = {\n  backEnabled: boolean,\n  forwardEnabled: boolean,\n};\n\ntype WindowSizeType = {\n  width: number,\n  height: number,\n};\n\ntype DevToolsOpenModeType = DEVTOOLS_MODES.BOTTOM | DEVTOOLS_MODES.RIGHT;\n\ntype WindowBoundsType = {\n  x: number,\n  y: number,\n  width: number,\n  height: number,\n};\n\ntype DevToolInfo = {\n  deviceId: string,\n  webViewId: number,\n};\n\ntype DevToolsConfigType = {\n  size: WindowSizeType,\n  open: Boolean,\n  activeDevTools: Array<DevToolInfo>,\n  mode: DevToolsOpenModeType,\n  bounds: WindowBoundsType,\n};\n\ntype DrawerType = {\n  open: boolean,\n  content: string,\n};\n\ntype PreviewerType = {\n  layout: string,\n  previousLayout: string,\n  focusedDeviceId: string,\n};\n\ntype PageMetaType = {\n  title: String,\n  favicons: Array<string>,\n};\n\ntype UserPreferenceType = {\n  disableSSLValidation: boolean,\n  reopenLastAddress: boolean,\n  disableSpellCheck: boolean,\n  drawerState: boolean,\n  devToolsOpenMode: DevToolsOpenModeType,\n  deviceOutlineStyle: string,\n  zoomLevel: number,\n  removeFixedPositionedElements: boolean,\n  screenshotMechanism: string,\n  permissionManagement: 'Ask always' | 'Allow always' | 'Deny always',\n};\n\ntype FilterFieldType = FILTER_FIELDS.OS | FILTER_FIELDS.DEVICE_TYPE;\n\ntype FilterType = {[key: FilterFieldType]: Array<string>};\n\ntype NetworkThrottlingProfileType = {\n  type: 'Online' | 'Offline' | 'Preset' | 'Custom',\n  title: string,\n  downloadKps: number,\n  uploadKps: number,\n  latencyMs: number,\n  active: boolean,\n};\n\ntype ProxyRuleType = {\n  protocol: 'direct' | 'http' | 'https' | 'socks4' | 'socks5',\n  server: string,\n  port: number,\n  user: string,\n  password: string,\n  useDefault: boolean,\n};\n\ntype NetworkProxyProfileType = {\n  active: boolean,\n  default: ProxyRuleType,\n  http: ProxyRuleType,\n  https: ProxyRuleType,\n  ftp: ProxyRuleType,\n  bypassList: string[],\n};\n\ntype NetworkConfigurationType = {\n  throttling: NetworkThrottlingProfileType[],\n  proxy: NetworkProxyProfileType,\n};\n\ntype CSSEditorStateType = {\n  isOpen: boolean,\n  position: String,\n  content: String,\n};\n\ntype PageNavigator = {\n  active: boolean,\n  selector: string,\n  index: number | null,\n};\n\nexport type BrowserStateType = {\n  devices: Array<Device>,\n  homepage: string,\n  address: string,\n  currentPageMeta: PageMetaType,\n  zoomLevel: number,\n  scrollPosition: ScrollPositionType,\n  navigatorStatus: NavigatorStatusType,\n  drawer: DrawerType,\n  previewer: PreviewerType,\n  filters: FilterType,\n  userPreferences: UserPreferenceType,\n  bookmarks: BookmarksType,\n  devToolsConfig: DevToolsConfigType,\n  cssEditor: CSSEditorStateType,\n  isInspecting: boolean,\n  windowSize: WindowSizeType,\n  allDevicesMuted: boolean,\n  networkConfiguration: NetworkConfigurationType,\n  allDevicesInDesignMode: boolean,\n  isHeaderVisible: boolean,\n  isLeftPaneVisible: boolean,\n  pageNavigator: PageNavigator,\n};\n\nlet _activeDevices = null;\n\nfunction _saveActiveDevices(devices) {\n  settings.set(\n    ACTIVE_DEVICES,\n    devices.map(device => device.name)\n  );\n  _activeDevices = devices;\n}\n\nfunction _getActiveDevices() {\n  if (_activeDevices) {\n    return _activeDevices;\n  }\n  const activeDeviceNames = settings.get(ACTIVE_DEVICES);\n  let activeDevices = null;\n  if (activeDeviceNames && activeDeviceNames.length) {\n    activeDevices = activeDeviceNames\n      .map(name => getAllDevices().find(device => device.name === name))\n      .filter(Boolean);\n  }\n  if (!activeDevices || !activeDevices.length) {\n    activeDevices = getAllDevices().filter(device => device.added);\n    _saveActiveDevices(activeDevices);\n  }\n\n  if (activeDevices) {\n    activeDevices.forEach(device => {\n      device.loading = false;\n      device.isMuted = false;\n      device.designMode = false;\n    });\n  }\n  return activeDevices;\n}\n\nfunction _getUserPreferences(): UserPreferenceType {\n  return settings.get(USER_PREFERENCES) || {};\n}\n\nfunction _setUserPreferences(userPreferences) {\n  settings.set(USER_PREFERENCES, userPreferences);\n}\n\nexport function getBounds(mode, _size, windowSize) {\n  const size = _size || getDefaultDevToolsWindowSize(mode, windowSize);\n  const {width, height} = windowSize;\n  if (mode === DEVTOOLS_MODES.RIGHT) {\n    const viewWidth = size.width;\n    const viewHeight = size.height - 64 - 10;\n    return {\n      x: width - viewWidth,\n      y: height - viewHeight,\n      width: viewWidth,\n      height: viewHeight,\n    };\n  }\n  const viewHeight = size.height - 20;\n  return {\n    x: 0,\n    y: height - viewHeight,\n    width,\n    height: viewHeight,\n  };\n}\n\nexport function getDefaultDevToolsWindowSize(mode, windowSize) {\n  const {width, height} = windowSize;\n  if (mode === DEVTOOLS_MODES.RIGHT) {\n    return {width: Math.round(width * 0.25), height};\n  }\n  return {width, height: Math.round(height * 0.33)};\n}\n\nfunction getWindowSize() {\n  return remote.screen.getPrimaryDisplay().workAreaSize;\n}\n\nfunction _getUserPreferencesDevToolsMode() {\n  return _getUserPreferences().devToolsOpenMode || DEVTOOLS_MODES.BOTTOM;\n}\n\nfunction _updateFileWatcher(newURL) {\n  if (\n    newURL.startsWith('file://') &&\n    (newURL.endsWith('.html') || newURL.endsWith('.htm'))\n  )\n    ipcRenderer.send('start-watching-file', {\n      path: newURL,\n    });\n  else ipcRenderer.send('stop-watcher');\n}\n\nfunction _getHomepage() {\n  const homepage = getHomepage();\n  _updateFileWatcher(homepage);\n  return homepage;\n}\n\nfunction _getNetworkConfiguration(): NetworkConfigurationType {\n  return settings.get(NETWORK_CONFIGURATION) || {};\n}\n\nfunction _setNetworkConfiguration(\n  networkConfiguration: NetworkConfigurationType\n) {\n  settings.set(NETWORK_CONFIGURATION, networkConfiguration);\n}\n\nfunction getLayout() {\n  return settings.get(LAYOUT) || FLEXIGRID_LAYOUT;\n}\n\nfunction setLayout(layout) {\n  settings.set(LAYOUT, layout);\n}\n\nexport default function browser(\n  state: BrowserStateType = {\n    devices: _getActiveDevices(),\n    homepage: _getHomepage(),\n    currentPageMeta: {},\n    zoomLevel:\n      normalizeZoomLevel(_getUserPreferences().zoomLevel) || DEFAULT_ZOOM_LEVEL,\n    theme: _getUserPreferences().theme,\n    previousZoomLevel: null,\n    scrollPosition: {x: 0, y: 0},\n    navigatorStatus: {backEnabled: false, forwardEnabled: false},\n    drawer: {\n      open:\n        _getUserPreferences().drawerState === null\n          ? true\n          : _getUserPreferences().drawerState,\n      content: DEVICE_MANAGER,\n    },\n    previewer: {layout: getLayout()},\n    filters: {[FILTER_FIELDS.OS]: [], [FILTER_FIELDS.DEVICE_TYPE]: []},\n    userPreferences: _getUserPreferences(),\n    allDevices: getAllDevices(),\n    devToolsConfig: {\n      size: getDefaultDevToolsWindowSize(\n        _getUserPreferencesDevToolsMode(),\n        getWindowSize()\n      ),\n      open: false,\n      mode: _getUserPreferencesDevToolsMode(),\n      activeDevTools: [],\n      bounds: getBounds(\n        _getUserPreferencesDevToolsMode(),\n        null,\n        getWindowSize()\n      ),\n    },\n    isInspecting: false,\n    CSSEditor: {isOpen: false, position: CSS_EDITOR_MODES.LEFT, content: ''},\n    windowSize: getWindowSize(),\n    allDevicesMuted: false,\n    networkConfiguration: _getNetworkConfiguration(),\n    allDevicesInDesignMode: false,\n    isHeaderVisible: true,\n    isLeftPaneVisible: true,\n    hoveredLink: '',\n    pageNavigator: {selector: null, index: null, active: false},\n  },\n  action: Action\n) {\n  switch (action.type) {\n    case NEW_ADDRESS:\n      saveLastOpenedAddress(action.address);\n      _updateFileWatcher(action.address);\n      updateExistingUrl(action.address);\n      return {...state, address: action.address, currentPageMeta: {}};\n    case NEW_PAGE_META_FIELD:\n      updateExistingUrl(state.address, {\n        name: action.name,\n        value: action.value,\n      });\n      return {\n        ...state,\n        currentPageMeta: {\n          ...state.currentPageMeta,\n          [action.name]: action.value,\n        },\n      };\n    case NEW_HOMEPAGE:\n      const {homepage} = action;\n      saveHomepage(homepage);\n      return {...state, homepage};\n    case NEW_ZOOM_LEVEL:\n      _setUserPreferences({\n        ...state.userPreferences,\n        zoomLevel: action.zoomLevel,\n      });\n      return {...state, zoomLevel: action.zoomLevel};\n    case NEW_THEME:\n      _setUserPreferences({\n        ...state.userPreferences,\n        theme: action.theme,\n      });\n      return {...state, theme: action.theme};\n    case NEW_SCROLL_POSITION:\n      return {...state, scrollPosition: action.scrollPosition};\n    case NEW_NAVIGATOR_STATUS:\n      return {...state, navigatorStatus: action.navigatorStatus};\n    case NEW_DRAWER_CONTENT:\n      _setUserPreferences({\n        ...state.userPreferences,\n        drawerState: action.drawer.open,\n      });\n      return {...state, drawer: action.drawer};\n    case NEW_PREVIEWER_CONFIG:\n      const updateObject = {previewer: action.previewer};\n      updateObject.previewer.previousLayout = state.previewer.layout;\n      setLayout(action.previewer.layout);\n\n      if (\n        state.previewer.layout !== INDIVIDUAL_LAYOUT &&\n        action.previewer.layout === INDIVIDUAL_LAYOUT\n      ) {\n        updateObject.zoomLevel = 1;\n        updateObject.previousZoomLevel = state.zoomLevel;\n      }\n      if (\n        state.previewer.layout === INDIVIDUAL_LAYOUT &&\n        action.previewer.layout !== INDIVIDUAL_LAYOUT\n      ) {\n        updateObject.zoomLevel = state.previousZoomLevel;\n        updateObject.previousZoomLevel = null;\n      }\n      return {...state, ...updateObject};\n    case NEW_FOCUSED_DEVICE:\n      return {...state, previewer: action.previewer};\n    case NEW_ACTIVE_DEVICES:\n      _saveActiveDevices(action.devices);\n      return {...state, devices: action.devices};\n    case NEW_CUSTOM_DEVICE:\n      const existingDevices = settings.get(CUSTOM_DEVICES) || [];\n      settings.set(CUSTOM_DEVICES, [action.device, ...existingDevices]);\n      return {...state, allDevices: getAllDevices()};\n    case LOAD_CUSTOM_DEVICES:\n      const actualDevices = settings.get(CUSTOM_DEVICES);\n      settings.set(CUSTOM_DEVICES, [...action.devices, ...actualDevices]);\n      return {...state, allDevices: getAllDevices()};\n    case DELETE_CUSTOM_DEVICE:\n      const existingCustomDevices = settings.get(CUSTOM_DEVICES) || [];\n      settings.set(\n        CUSTOM_DEVICES,\n        existingCustomDevices.filter(device => device.id !== action.device.id)\n      );\n      return {...state, allDevices: getAllDevices()};\n    case NEW_FILTERS:\n      return {...state, filters: action.filters};\n    case NEW_USER_PREFERENCES:\n      _setUserPreferences(action.userPreferences);\n      return {...state, userPreferences: action.userPreferences};\n    case NEW_DEV_TOOLS_CONFIG:\n      const newState = {...state, devToolsConfig: action.config};\n      if (state.devToolsConfig.mode !== action.config.mode) {\n        const newUserPreferences = {\n          ...state.userPreferences,\n          devToolsOpenMode: action.config.mode,\n        };\n        _setUserPreferences(newUserPreferences);\n        newState.userPreferences = newUserPreferences;\n      }\n      return newState;\n    case NEW_CSS_EDITOR_STATUS:\n      return {...state, CSSEditor: {...state.CSSEditor, isOpen: action.status}};\n    case NEW_CSS_EDITOR_POSITION:\n      return {\n        ...state,\n        CSSEditor: {...state.CSSEditor, position: action.position},\n      };\n    case NEW_CSS_EDITOR_CONTENT:\n      return {\n        ...state,\n        CSSEditor: {...state.CSSEditor, content: action.content},\n      };\n    case NEW_INSPECTOR_STATUS:\n      return {...state, isInspecting: action.status};\n    case NEW_WINDOW_SIZE:\n      return {...state, windowSize: action.size};\n    case DEVICE_LOADING:\n      const newDevicesList = state.devices.map(device =>\n        device.id === action.device.id\n          ? {...device, loading: action.device.loading}\n          : device\n      );\n      return {...state, devices: newDevicesList};\n    case TOGGLE_ALL_DEVICES_MUTED:\n      const updatedDevices = state.devices;\n      updatedDevices.forEach(d => (d.isMuted = action.allDevicesMuted));\n      return {\n        ...state,\n        allDevicesMuted: action.allDevicesMuted,\n        devices: updatedDevices,\n      };\n    case TOGGLE_DEVICE_MUTED:\n      const updatedDeviceIndex = state.devices.findIndex(\n        x => x.id === action.deviceId\n      );\n      if (updatedDeviceIndex === -1) return {...state};\n      state.devices[updatedDeviceIndex] = {\n        ...state.devices[updatedDeviceIndex],\n        isMuted: action.isMuted,\n      };\n      return {\n        ...state,\n        allDevicesMuted: state.devices.every(x => x.isMuted),\n        devices: [...state.devices],\n      };\n    case CHANGE_ACTIVE_THROTTLING_PROFILE:\n      const throttling = state.networkConfiguration.throttling;\n      const activeProfile = throttling.find(x => x.title === action.title);\n      if (activeProfile != null) {\n        throttling.forEach(x => (x.active = false));\n        activeProfile.active = true;\n      }\n      return {\n        ...state,\n        networkConfiguration: {\n          ...state.networkConfiguration,\n          throttling: [...throttling],\n        },\n      };\n    case SAVE_THROTTLING_PROFILES:\n      action.profiles.forEach(x => (x.active = false));\n      action.profiles[0].active = true;\n      _setNetworkConfiguration({\n        ...state.networkConfiguration,\n        throttling: action.profiles,\n      });\n      return {\n        ...state,\n        networkConfiguration: {\n          ...state.networkConfiguration,\n          throttling: action.profiles,\n        },\n      };\n    case TOGGLE_USE_PROXY:\n      const proxy = state.networkConfiguration.proxy;\n      proxy.active = !!action.useProxy;\n      _setNetworkConfiguration({\n        ...state.networkConfiguration,\n        proxy,\n      });\n      return {\n        ...state,\n        networkConfiguration: {\n          ...state.networkConfiguration,\n          proxy: {...proxy},\n        },\n      };\n    case CHANGE_PROXY_PROFILE:\n      _setNetworkConfiguration({\n        ...state.networkConfiguration,\n        proxy: action.profile,\n      });\n      return {\n        ...state,\n        networkConfiguration: {\n          ...state.networkConfiguration,\n          proxy: action.profile,\n        },\n      };\n    case TOGGLE_ALL_DEVICES_DESIGN_MODE:\n      const nextDevices = state.devices;\n      const nextDesginModeForAll = !state.allDevicesInDesignMode;\n      nextDevices.forEach(d => (d.designMode = nextDesginModeForAll));\n      return {\n        ...state,\n        allDevicesInDesignMode: nextDesginModeForAll,\n        devices: nextDevices,\n      };\n    case TOGGLE_DEVICE_DESIGN_MODE:\n      const deviceIndex = state.devices.findIndex(\n        x => x.id === action.deviceId\n      );\n      if (deviceIndex === -1) return {...state};\n      const nextDesignModeForDevice = !state.devices[deviceIndex].designMode;\n      state.devices[deviceIndex] = {\n        ...state.devices[deviceIndex],\n        designMode: nextDesignModeForDevice,\n      };\n      return {\n        ...state,\n        allDevicesInDesignMode: state.devices.every(x => x.designMode),\n        devices: [...state.devices],\n      };\n    case SET_HEADER_VISIBILITY:\n      return {\n        ...state,\n        isHeaderVisible: action.isVisible,\n      };\n\n    case SET_LEFT_PANE_VISIBILITY:\n      return {\n        ...state,\n        isLeftPaneVisible: action.isVisible,\n      };\n    case SET_HOVERED_LINK:\n      return {\n        ...state,\n        hoveredLink: action.url,\n      };\n    case SET_STARTUP_PAGE:\n      saveStartupPage(action.value);\n      return {...state};\n    case UPDATE_PAGE_NAVIGATOR:\n      state.pageNavigator.selector = action.selector;\n      state.pageNavigator.index = action.index;\n      return {\n        ...state,\n      };\n    case TOGGLE_PAGE_NAVIGATOR:\n      state.pageNavigator.active = action.active;\n      return {\n        ...state,\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/reducers/index.js",
    "content": "// @flow\nimport {combineReducers} from 'redux';\nimport browser from './browser';\nimport bookmarks from './bookmarks';\nimport statusBar from './statusBar';\n\nexport default function createRootReducer() {\n  return combineReducers({\n    browser,\n    bookmarks,\n    statusBar,\n  });\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/reducers/statusBar.js",
    "content": "// @flow\nimport {TOGGLE_STATUS_BAR_VISIBILITY} from '../actions/statusBar';\nimport {statusBarSettings} from '../settings/statusBarSettings';\n\nexport type StatusBarStateType = {visible: boolean};\n\nexport default function app(\n  state: StatusBarStateType = {visible: statusBarSettings.getVisibility()},\n  action: Action\n) {\n  switch (action.type) {\n    case TOGGLE_STATUS_BAR_VISIBILITY:\n      return {\n        ...state,\n        visible: action.visible,\n      };\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/reducers/types.js",
    "content": "import type {Dispatch as ReduxDispatch, Store as ReduxStore} from 'redux';\nimport type {BrowserStateType as _BrowserStateType} from './browser';\n\nexport type BrowserStateType = _BrowserStateType;\n\nexport type counterStateType = {\n  +counter: number,\n};\n\nexport type RootStateType = {\n  browser: BrowserStateType,\n};\n\nexport type Action = {\n  +type: string,\n};\n\nexport type GetState = () => RootStateType;\n\nexport type Dispatch = ReduxDispatch<Action>;\n\nexport type Store = ReduxStore<GetState, Action>;\n"
  },
  {
    "path": "desktop-app-legacy/app/services/browserSync/index.js",
    "content": "import {ipcRenderer} from 'electron';\nimport browserSync from 'browser-sync';\n\nlet browserSyncOptions;\n\ninitializeBrowserSyncOptions();\n\nasync function initializeBrowserSyncOptions() {\n  if (!browserSyncOptions) {\n    browserSyncOptions = await ipcRenderer.invoke('request-browser-sync');\n  }\n}\n\nexport function getBrowserSyncEmbedScriptURL() {\n  if (browserSyncOptions) {\n    return browserSyncOptions.url;\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/services/db/appMetadata.js",
    "content": "const db = require('electron-settings');\n\nconst OPEN_COUNT = 'openCount';\n\nclass AppMetadata {\n  incrementOpenCount() {\n    const count = db.get(OPEN_COUNT) || 0;\n    db.set(OPEN_COUNT, count + 1);\n  }\n\n  getOpenCount() {\n    return db.get(OPEN_COUNT) || 0;\n  }\n}\n\nexport default new AppMetadata();\n"
  },
  {
    "path": "desktop-app-legacy/app/services/db/index.js",
    "content": "import appMetadataDB from './appMetadata';\n\nexport default {appMetadataDB};\n"
  },
  {
    "path": "desktop-app-legacy/app/services/searchUrlSuggestions/index.js",
    "content": "import React from 'react';\nimport filter from 'lodash/filter';\nimport settings from 'electron-settings';\nimport {ADD_SEARCH_RESULTS} from '../../constants/searchResultSettings';\n\nlet previousSearchResults = settings.get(ADD_SEARCH_RESULTS);\n\nexport const getExistingSearchResults = () => settings.get(ADD_SEARCH_RESULTS);\n\nconst addUrlToSearchResults = url => settings.set(ADD_SEARCH_RESULTS, url);\n\nexport const deleteSearchResults = () => {\n  settings.delete(ADD_SEARCH_RESULTS);\n  previousSearchResults = [];\n};\n\nconst _sortedExistingUrlSearchResult = filteredData => {\n  // Most visited site should appear first in the list\n  filteredData.sort((a, b) => {\n    if (a.visitedCount > b.visitedCount) {\n      return -1;\n    }\n\n    if (a.visitedCount < b.visitedCount) {\n      return 1;\n    }\n    return 0;\n  });\n  return filteredData;\n};\n\nexport const searchUrlUtils = url => {\n  if (url) {\n    const filteredData = filter(previousSearchResults, eachResult => {\n      if (eachResult.pageMeta?.title) {\n        return (\n          eachResult.pageMeta.title.toLowerCase().includes(url) ||\n          eachResult.url.toLowerCase().includes(url)\n        );\n      }\n      return eachResult.url.toLowerCase().includes(url);\n    });\n    const finalResult = _sortedExistingUrlSearchResult(filteredData);\n    return finalResult;\n  }\n  return [];\n};\n\nconst normalizeURL = url => {\n  if (\n    url.indexOf('?') === -1 &&\n    !url.endsWith('/') &&\n    url.indexOf('file://') === -1\n  ) {\n    url = `${url}/`;\n  }\n  return url;\n};\n\nexport const updateExistingUrl = (url, pageMeta = null) => {\n  url = normalizeURL(url);\n  if (previousSearchResults?.length) {\n    let updatedSearchResults = [...previousSearchResults];\n\n    const index = updatedSearchResults.findIndex(\n      eachSearchResult => eachSearchResult.url === url\n    );\n\n    if (index !== (undefined || -1 || null)) {\n      updatedSearchResults[index].visitedCount =\n        1 + updatedSearchResults[index].visitedCount;\n      if (pageMeta) {\n        updatedSearchResults[index].pageMeta = {\n          ...updatedSearchResults[index].pageMeta,\n          [pageMeta.name]: pageMeta.value,\n        };\n      }\n    } else {\n      updatedSearchResults = [\n        {url, visitedCount: 1},\n        ...updatedSearchResults,\n      ].slice(0, 300);\n    }\n\n    addUrlToSearchResults(updatedSearchResults);\n    previousSearchResults = updatedSearchResults;\n  } else {\n    const addNewUrl = [];\n    addNewUrl.push({\n      url,\n      visitedCount: 1,\n    });\n\n    addUrlToSearchResults(addNewUrl);\n    previousSearchResults = addNewUrl;\n  }\n  return previousSearchResults;\n};\n"
  },
  {
    "path": "desktop-app-legacy/app/settings/migration.js",
    "content": "import settings from 'electron-settings';\nimport {\n  ACTIVE_DEVICES,\n  USER_PREFERENCES,\n  NETWORK_CONFIGURATION,\n  LAYOUT,\n} from '../constants/settingKeys';\nimport {SCREENSHOT_MECHANISM, STARTUP_PAGE} from '../constants/values';\nimport {PERMISSION_MANAGEMENT_OPTIONS} from '../constants/permissionsManagement';\nimport {DARK_THEME} from '../constants/theme';\nimport {FLEXIGRID_LAYOUT} from '../constants/previewerLayouts';\n\nexport function migrateDeviceSchema() {\n  if (settings.get('USER_PREFERENCES')) {\n    settings.set(USER_PREFERENCES, settings.get('USER_PREFERENCES'));\n    settings.delete('USER_PREFERENCES');\n  }\n\n  _ensureDefaultNetworkConfig();\n  _handleScreenshotMechanismPreferences();\n  _handleScreenshotFixedElementsPreferences();\n  _handleDeviceSchema();\n  _handlePermissionsDefaultPreferences();\n  _handleColorThemePreferences();\n  _handleLayout();\n  _ensureDefaultStartupPage();\n}\n\nfunction _ensureDefaultStartupPage() {\n  const userPreferences = settings.get(USER_PREFERENCES) || {};\n  const defaultStartupPage = userPreferences.startupPage;\n  if (defaultStartupPage != null) return;\n\n  userPreferences.startupPage = STARTUP_PAGE.HOME;\n  settings.set(USER_PREFERENCES, userPreferences);\n}\n\nfunction _ensureDefaultNetworkConfig() {\n  const ntwrk = settings.get(NETWORK_CONFIGURATION) || {};\n\n  if (ntwrk.throttling == null || ntwrk.proxy == null) {\n    ntwrk.throttling =\n      ntwrk.throttling || _getDefaultNetworkThrottlingProfiles();\n    ntwrk.proxy = ntwrk.proxy || _getDefaultNetworkProxyProfile();\n    settings.set(NETWORK_CONFIGURATION, ntwrk);\n  }\n}\n\nfunction _getDefaultNetworkThrottlingProfiles() {\n  return [\n    {\n      type: 'Online',\n      title: 'Online',\n      active: true,\n    },\n    {\n      type: 'Offline',\n      title: 'Offline',\n      downloadKps: 0,\n      uploadKps: 0,\n      latencyMs: 0,\n    },\n    // https://github.com/ChromeDevTools/devtools-frontend/blob/4f404fa8beab837367e49f68e29da427361b1f81/front_end/sdk/NetworkManager.js#L251-L265\n    {\n      type: 'Preset',\n      title: 'Slow 3G',\n      downloadKps: 400,\n      uploadKps: 400,\n      latencyMs: 2000,\n    },\n    {\n      type: 'Preset',\n      title: 'Fast 3G',\n      downloadKps: 1475,\n      uploadKps: 675,\n      latencyMs: 563,\n    },\n  ];\n}\n\nfunction _getDefaultNetworkProxyProfile() {\n  return {\n    active: false,\n    default: {protocol: 'direct'},\n    http: {useDefault: true},\n    https: {useDefault: true},\n    ftp: {useDefault: true},\n    bypassList: ['127.0.0.1', '::1', 'localhost'],\n  };\n}\n\nconst _handleDeviceSchema = () => {\n  const activeDevices = settings.get(ACTIVE_DEVICES);\n  if (!activeDevices || !activeDevices.length || !activeDevices[0].width) {\n    return;\n  }\n  settings.set(\n    ACTIVE_DEVICES,\n    activeDevices.map(device => device.name)\n  );\n};\n\nconst _handleScreenshotFixedElementsPreferences = () => {\n  const userPreferences = settings.get(USER_PREFERENCES) || {};\n\n  if (userPreferences.removeFixedPositionedElements != null) {\n    return;\n  }\n\n  userPreferences.removeFixedPositionedElements = true;\n  settings.set(USER_PREFERENCES, userPreferences);\n};\n\nconst _handleScreenshotMechanismPreferences = () => {\n  const userPreferences = settings.get(USER_PREFERENCES) || {};\n\n  if (userPreferences.screenshotMechanism != null) {\n    return;\n  }\n\n  userPreferences.screenshotMechanism = SCREENSHOT_MECHANISM.V2;\n  // If mechanism is not set previously and initialized to v2, then set removeFixedPositionedElements to false\n  // as that was mainly required for the v1 mechanism.\n  userPreferences.removeFixedPositionedElements = false;\n  settings.set(USER_PREFERENCES, userPreferences);\n};\n\nconst _handlePermissionsDefaultPreferences = () => {\n  const userPreferences = settings.get(USER_PREFERENCES) || {};\n\n  if (userPreferences.permissionManagement != null) {\n    return;\n  }\n\n  userPreferences.permissionManagement =\n    PERMISSION_MANAGEMENT_OPTIONS.ASK_ALWAYS;\n  settings.set(USER_PREFERENCES, userPreferences);\n};\n\nconst _handleColorThemePreferences = () => {\n  const userPreferences = settings.get(USER_PREFERENCES) || {};\n\n  if (userPreferences.theme != null) {\n    return;\n  }\n\n  userPreferences.theme = DARK_THEME;\n  settings.set(USER_PREFERENCES, userPreferences);\n};\n\nconst _handleLayout = () => {\n  const layout = settings.get(LAYOUT);\n  if (layout) {\n    return;\n  }\n\n  settings.set(LAYOUT, FLEXIGRID_LAYOUT);\n};\n\nexport default {migrateDeviceSchema};\n"
  },
  {
    "path": "desktop-app-legacy/app/settings/statusBarSettings.js",
    "content": "import settings from 'electron-settings';\nimport {STATUS_BAR_VISIBILITY} from '../constants/settingKeys';\n\nclass StatusBarSettings {\n  constructor() {\n    const visibility = settings.get(STATUS_BAR_VISIBILITY);\n\n    if (visibility === undefined) {\n      settings.set(STATUS_BAR_VISIBILITY, true);\n    }\n  }\n\n  getVisibility = () => settings.get(STATUS_BAR_VISIBILITY);\n\n  setVisibility = visible => {\n    settings.set(STATUS_BAR_VISIBILITY, visible);\n  };\n}\n\nconst statusBarSettingsInstance = new StatusBarSettings();\n\nexport {statusBarSettingsInstance as statusBarSettings};\n"
  },
  {
    "path": "desktop-app-legacy/app/settings/userPreferenceSettings.js",
    "content": "import settings from 'electron-settings';\nimport {USER_PREFERENCES} from '../constants/settingKeys';\nimport * as os from 'os';\n\nconst path = require('path');\n\nclass UserPreferenceSettings {\n  getScreenShotSavePath = () =>\n    (settings.get(USER_PREFERENCES) || {}).screenShotSavePath;\n  getDefaultScreenshotpath = () =>\n    path.join(os.homedir(), `Desktop/Responsively-Screenshots`);\n}\n\nconst userPreferenceSettingsInstance = new UserPreferenceSettings();\n\nexport {userPreferenceSettingsInstance as userPreferenceSettings};\n"
  },
  {
    "path": "desktop-app-legacy/app/shortcut-manager/main-shortcut-manager.js",
    "content": "import {ipcMain} from 'electron';\nimport {\n  REGISTER_CHANNEL,\n  UNREGISTER_CHANNEL,\n  REGISTER_REPLY_CHANNEL,\n  UNREGISTER_REPLY_CHANNEL,\n  GET_ALL_CHANNEL,\n  CLEAR_CHANNEL,\n  ShortcutDefinition,\n  validateDefinition,\n} from './shared';\n\nlet shortcuts: Map<string, ShortcutDefinition>;\n\nfunction validate(shortcut: ShortcutDefinition, checkUnique = true) {\n  return (\n    validateDefinition(shortcut) && !(checkUnique && shortcuts.has(shortcut.id))\n  );\n}\n\nexport function registerShortcut(shortcut: ShortcutDefinition): boolean {\n  if (!validate(shortcut)) return false;\n  shortcuts.set(shortcut.id, shortcut);\n  return true;\n}\n\nexport function unregisterShortcut(id: string): boolean {\n  if (id == null || !shortcuts.has(id)) return false;\n  return shortcuts.delete(id);\n}\n\nexport function clearAllShortcuts() {\n  shortcuts.clear();\n}\n\nexport function getAllShortcuts(): ShortcutDefinition[] {\n  return [...shortcuts.values()].sort(\n    (a, b) =>\n      (a.index ?? Number.MAX_SAFE_INTEGER) -\n      (b.index ?? Number.MAX_SAFE_INTEGER)\n  );\n}\n\nexport function initMainShortcutManager() {\n  shortcuts = new Map();\n\n  ipcMain.on(REGISTER_CHANNEL, (event, definition: ShortcutDefinition) => {\n    const ok = registerShortcut(definition);\n    const id = definition == null ? null : definition.id;\n    event.reply(REGISTER_REPLY_CHANNEL, {ok, id});\n  });\n\n  ipcMain.on(UNREGISTER_CHANNEL, (event, id: string) => {\n    const ok = unregisterShortcut(id);\n    event.reply(UNREGISTER_REPLY_CHANNEL, {ok, id});\n  });\n\n  ipcMain.removeHandler(GET_ALL_CHANNEL);\n\n  ipcMain.handle(GET_ALL_CHANNEL, () => getAllShortcuts());\n\n  ipcMain.on(CLEAR_CHANNEL, clearAllShortcuts);\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/shortcut-manager/renderer-shortcut-manager.js",
    "content": "import {ipcRenderer} from 'electron';\nimport Mousetrap from 'mousetrap';\nimport {\n  REGISTER_CHANNEL,\n  UNREGISTER_CHANNEL,\n  REGISTER_REPLY_CHANNEL,\n  UNREGISTER_REPLY_CHANNEL,\n  GET_ALL_CHANNEL,\n  CLEAR_CHANNEL,\n  ShortcutDefinition,\n  validateDefinition,\n  CommunicationResponse,\n} from './shared';\n\nimport 'mousetrap/plugins/global-bind/mousetrap-global-bind';\n\nMousetrap.addKeycodes({\n  44: 'prtsc',\n});\n\nconst reg: Map<string, string[]> = new Map();\n\nfunction validate(shortcut: ShortcutDefinition, checkUnique = true) {\n  return validateDefinition(shortcut) && !(checkUnique && reg.has(shortcut.id));\n}\n\nexport function registerShortcut(\n  definition: ShortcutDefinition,\n  callback: e => any,\n  global: boolean = false,\n  action?: 'keypres' | 'keydown' | 'keyup'\n): boolean {\n  if (!validate(definition) || callback == null) return false;\n  reg.set(definition.id, definition.accelerators);\n  ipcRenderer.send(REGISTER_CHANNEL, definition);\n  if (global) Mousetrap.bindGlobal(definition.accelerators, callback, action);\n  else Mousetrap.bind(definition.accelerators, callback, action);\n  return true;\n}\n\nexport function unregisterShortcut(id: string): boolean {\n  if (id == null || !reg.has(id)) return false;\n  const accelerators = reg.get(id);\n  reg.delete(id);\n  ipcRenderer.send(UNREGISTER_CHANNEL, id);\n  Mousetrap.unbind(accelerators);\n}\n\nexport function getAllShortcuts() {\n  return ipcRenderer.invoke(GET_ALL_CHANNEL);\n}\n\nexport function clearAllShortcuts() {\n  ipcRenderer.send(CLEAR_CHANNEL);\n  reg.clear();\n  Mousetrap.reset();\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/shortcut-manager/shared.js",
    "content": "export const REGISTER_CHANNEL = 'register-shortcut';\nexport const UNREGISTER_CHANNEL = 'unregister-shortcut';\nexport const REGISTER_REPLY_CHANNEL = 'register-shortcut-reply';\nexport const UNREGISTER_REPLY_CHANNEL = 'unregister-shortcut-reply';\nexport const GET_ALL_CHANNEL = 'get-all-shortcuts';\nexport const CLEAR_CHANNEL = 'clear-shortcuts';\nexport type ShortcutDefinition = {\n  index: Number,\n  id: string,\n  title: string,\n  accelerators: string[],\n};\nexport type CommunicationResponse = {\n  ok: boolean,\n  id: string,\n};\n\nexport function validateDefinition(shortcut: ShortcutDefinition): boolean {\n  return (\n    shortcut != null &&\n    shortcut.id != null &&\n    shortcut.title != null &&\n    (shortcut.accelerators || []).length !== 0 &&\n    shortcut.accelerators.every(x => x != null)\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/shortcuts.html",
    "content": "<html>\n  <head>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"app.global.css\" />\n    <style>\n      body {\n        background-color: #1e1e1e;\n        color: white;\n        display: flex;\n        justify-content: space-between;\n        flex-direction: column;\n        font-family: 'Roboto', sans-serif;\n      }\n      ul {\n        margin: 20px 50px;\n        overflow-y: auto;\n        max-height: 38vh;\n      }\n      li {\n        display: flex;\n        flex-direction: row;\n        flex-wrap: wrap;\n        align-items: center;\n        padding: 5px;\n      }\n      .header {\n        font-size: 2em;\n        display: flex;\n        justify-content: center;\n        margin: 100px 0 50px;\n      }\n      .label {\n        width: 250px;\n        font-size: large;\n      }\n      .key {\n        display: flex;\n        text-align: right;\n        padding: 7px;\n        margin: 0 10px;\n        justify-content: center;\n        align-items: center;\n        font-size: large;\n        background-color: black;\n        border-radius: 5px;\n        font-family: 'Consolas', 'Courier New', monospace;\n        white-space: pre;\n      }\n      .btn-wrapper {\n        display: flex;\n        justify-content: center;\n        background-color: transparent;\n        padding-bottom: 50px;\n      }\n      #close-btn {\n        font-family: 'Roboto', sans-serif;\n        font-size: x-large;\n        padding: 10 20 10 20px;\n        border: 0;\n        border-radius: 5px;\n        background-color: black;\n        color: white;\n        cursor: pointer;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"header\">Keyboard Shortcuts</div>\n    <ul id=\"list\"></ul>\n    <div class=\"btn-wrapper\">\n      <button id=\"close-btn\">Close</button>\n    </div>\n    <script>\n      function firstToUpperCase(s) {\n        if (!s) {\n          return '';\n        }\n        return s[0].toUpperCase() + s.slice(1);\n      }\n      function getNumPadName(n) {\n        const lo = n.toLowerCase();\n        if (lo === 'dec') return 'Numpad .';\n        if (lo === 'add') return 'Numpad +';\n        if (lo === 'sub') return 'Numpad -';\n        if (lo === 'mult') return 'Numpad *';\n        if (lo === 'div') return 'Numpad /';\n        return 'Numpad ' + firstToUpperCase(n);\n      }\n      function getKeyName(k) {\n        const lo = k.toLowerCase();\n        if (lo === 'mod') return 'Cmd/Ctrl';\n        if (lo === 'commandorcontrol' || lo === 'cmdorctrl') return 'Cmd/Ctrl';\n        if (lo === 'command') return 'Cmd';\n        if (lo === 'control') return 'Ctrl';\n        if (lo === 'plus') return '+';\n        if (lo === 'return') return 'Enter';\n        if (lo === 'escape') return 'Esc';\n        if (lo === 'prtsc') return 'PrtSc';\n        if (lo.startsWith('num')) return getNumPadName(k.slice(3));\n        return firstToUpperCase(k);\n      }\n      function mapAccelerator(acc) {\n        if (acc === 'mod++') {\n          return ['Cmd/Ctrl', '+'];\n        }\n        return acc.split('+').map(getKeyName);\n      }\n\n      let registeredShortcuts = [];\n      try {\n        registeredShortcuts = JSON.parse(window.process.argv.slice(-1)).reduce(\n          (arr, def) => {\n            arr.push([def.title, mapAccelerator(def.accelerators[0])]);\n            def.accelerators\n              .slice(1)\n              .forEach(acc => arr.push(['', mapAccelerator(acc)]));\n            return arr;\n          },\n          []\n        );\n      } catch (err) {\n        console.log('Error while processing shortcuts', err);\n      }\n\n      const shortcutNodes = registeredShortcuts.map(([label, keys]) => {\n        const liElem = document.createElement('li');\n\n        const labelElem = document.createElement('div');\n        const labelTextNode = document.createTextNode(label);\n        labelElem.classList.add('label');\n        labelElem.appendChild(labelTextNode);\n\n        liElem.appendChild(labelElem);\n\n        keys.forEach((k, idx) => {\n          const keyElem = document.createElement('div');\n          const keyTextNode = document.createTextNode(k);\n          keyElem.classList.add('key');\n          keyElem.appendChild(keyTextNode);\n          if (idx != 0) {\n            liElem.append('+');\n          }\n          liElem.appendChild(keyElem);\n        });\n\n        return liElem;\n      });\n\n      shortcutNodes.forEach(node => {\n        document.getElementById('list').append(node);\n      });\n\n      const remote = require('electron').remote;\n      const BrowserWindow = remote.BrowserWindow;\n\n      document.onreadystatechange = () => {\n        if (document.readyState === 'complete') {\n          document.getElementById('close-btn').addEventListener('click', () => {\n            const window = BrowserWindow.getFocusedWindow();\n            window.close();\n          });\n        }\n      };\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "desktop-app-legacy/app/store/configureStore.dev.js",
    "content": "import {createStore, applyMiddleware, compose} from 'redux';\nimport thunk from 'redux-thunk';\nimport {createLogger} from 'redux-logger';\nimport createRootReducer from '../reducers';\n\nconst rootReducer = createRootReducer();\n\nconst configureStore = initialState => {\n  // Redux Configuration\n  const middleware = [];\n  const enhancers = [];\n\n  // Thunk Middleware\n  middleware.push(thunk);\n\n  // Logging Middleware\n  const logger = createLogger({\n    level: 'info',\n    collapsed: true,\n  });\n\n  // Skip redux logs in console during the tests\n  if (process.env.NODE_ENV !== 'test') {\n    middleware.push(logger);\n  }\n\n  // Redux DevTools Configuration\n  const actionCreators = {};\n\n  // If Redux DevTools Extension is installed use it, otherwise use Redux compose\n  /* eslint-disable no-underscore-dangle */\n  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__\n    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({\n        // Options: http://extension.remotedev.io/docs/API/Arguments.html\n        actionCreators,\n      })\n    : compose;\n  /* eslint-enable no-underscore-dangle */\n\n  // Apply Middleware & Compose Enhancers\n  enhancers.push(applyMiddleware(...middleware));\n  const enhancer = composeEnhancers(...enhancers);\n\n  // Create Store\n  const store = createStore(rootReducer, initialState, enhancer);\n\n  if (module.hot) {\n    module.hot.accept(\n      '../reducers',\n      // eslint-disable-next-line global-require\n      () => store.replaceReducer(require('../reducers').default)\n    );\n  }\n\n  return store;\n};\n\nexport default {configureStore};\n"
  },
  {
    "path": "desktop-app-legacy/app/store/configureStore.js",
    "content": "// @flow\nimport configureStoreDev from './configureStore.dev';\nimport configureStoreProd from './configureStore.prod';\n\nconst selectedConfigureStore =\n  process.env.NODE_ENV === 'production'\n    ? configureStoreProd\n    : configureStoreDev;\n\nexport const {configureStore} = selectedConfigureStore;\n"
  },
  {
    "path": "desktop-app-legacy/app/store/configureStore.prod.js",
    "content": "// @flow\nimport {createStore, applyMiddleware} from 'redux';\nimport thunk from 'redux-thunk';\nimport createRootReducer from '../reducers';\n\nconst rootReducer = createRootReducer();\nconst heap = () => next => action => {\n  window.requestIdleCallback(() => {\n    if (window.heap) {\n      window.heap.track(`ACTION-${action.type}`, {\n        type: action.type,\n        payload: JSON.stringify(action),\n      });\n    }\n  });\n  return next(action);\n};\nconst enhancer = applyMiddleware(thunk, heap);\n\nfunction configureStore(initialState) {\n  return createStore(rootReducer, initialState, enhancer);\n}\n\nexport default {configureStore};\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/.gitkeep",
    "content": ""
  },
  {
    "path": "desktop-app-legacy/app/utils/TextAreaWithCopyButton.js",
    "content": "import React from 'react';\nimport Button from '@material-ui/core/Button';\nimport Box from '@material-ui/core/Box';\nimport {makeStyles} from '@material-ui/core/styles';\nimport {clipboard} from 'electron';\n\nconst useStyles = makeStyles(theme => ({\n  codeBoxContainer: {\n    overflow: 'auto',\n    width: '30vw',\n    height: '40vh',\n    marginBottom: '10px',\n    marginTop: '10px',\n    border: `1px solid ${theme.palette.border.color}`,\n    padding: '0 10px',\n    borderRadius: '4px',\n  },\n  codeBox: {\n    fontSize: '14px',\n    textAlign: 'left',\n  },\n}));\n\nconst TextAreaWithCopyButton = props => {\n  const classes = useStyles();\n  return (\n    <Box>\n      <div className={classes.codeBoxContainer}>\n        <pre className={classes.codeBox}>\n          {(props.text || '').split('\\\\n ').map((line, idx) => {\n            if (idx === 0) return line;\n            return (\n              <>\n                <br />\n                {line}\n              </>\n            );\n          })}\n        </pre>\n      </div>\n      <Button\n        onClick={() => clipboard.writeText(props.text, 'clipboard')}\n        size=\"small\"\n        variant=\"contained\"\n        color=\"primary\"\n      >\n        Copy\n      </Button>\n    </Box>\n  );\n};\n\nexport default TextAreaWithCopyButton;\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/analytics.js",
    "content": "import {app} from 'electron';\nimport path from 'path';\nimport ua from 'universal-analytics';\nimport {v4 as uuidv4} from 'uuid';\nimport fs from 'fs-extra';\n\nconst isDev = process.env.NODE_ENV !== 'production';\nlet uid = null;\nlet appActive = false;\n\ntry {\n  const filePath = path.join(app.getPath('userData'), 'uid');\n  try {\n    uid = fs.readFileSync(filePath, 'utf-8');\n  } catch (err) {}\n  if (!uid) {\n    uid = uuidv4();\n    fs.writeFileSync(filePath, uid);\n  }\n} catch (err) {\n  console.log('Error initializing analytics', err);\n}\n\nconst visitor = ua('UA-150751006-1', uid);\n\nexport const startSession = () => {\n  appActive = true;\n  if (isDev) {\n    return;\n  }\n  visitor.event({ec: 'App', ea: 'Open', sessionControl: 'start'}).send();\n};\n\nexport const endSession = () => {\n  appActive = false;\n  if (isDev) {\n    return;\n  }\n  visitor.event({ec: 'App', ea: 'Close', sessionControl: 'end'}).send();\n};\n\nexport const sendEvent = (ec, ea) => !isDev && visitor.event({ec, ea}).send();\n\nsetInterval(() => {\n  if (!appActive) {\n    return;\n  }\n  sendEvent('App', 'Ping');\n}, 20000);\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/browserSync.js",
    "content": "const browserSyncEmbed = require('browser-sync').create('embed');\n\nimport {\n  BROWSER_SYNC_VERSION,\n  BROWSER_SYNC_PORT,\n} from '../constants/browserSync';\nimport fs from 'fs';\n\nlet filesWatcher;\nlet cssWatcher;\nexport async function initBrowserSync() {\n  if (!browserSyncEmbed.active) {\n    await initInstance();\n  }\n}\n\nexport function watchFiles(filePath) {\n  if (filePath && fs.existsSync(filePath)) {\n    const fileDir = filePath.substring(0, filePath.lastIndexOf('/'));\n    filesWatcher = browserSyncEmbed\n      .watch([filePath, `${fileDir}/**/**.js`])\n      .on('change', browserSyncEmbed.reload);\n\n    cssWatcher = browserSyncEmbed.watch(\n      `${fileDir}/**/**.css`,\n      (event, file) => {\n        if (event === 'change') {\n          browserSyncEmbed.reload(file);\n        }\n      }\n    );\n  }\n}\n\nexport async function stopWatchFiles() {\n  if (filesWatcher) {\n    await filesWatcher.close();\n  }\n  if (cssWatcher) {\n    await cssWatcher.close();\n  }\n}\n\nexport function getBrowserSyncHost() {\n  return `localhost:${browserSyncEmbed.getOption('port')}`;\n}\n\nexport function getBrowserSyncEmbedScriptURL() {\n  return `https://${getBrowserSyncHost()}/browser-sync/browser-sync-client.js?v=${BROWSER_SYNC_VERSION}`;\n}\n\nasync function initInstance(): Promise<> {\n  return new Promise((resolve, reject) => {\n    browserSyncEmbed.init(\n      {\n        open: false,\n        localOnly: true,\n        https: true,\n        notify: false,\n        ui: false,\n        port: BROWSER_SYNC_PORT,\n      },\n      (err, bs) => {\n        if (err) {\n          return reject(err);\n        }\n        resolve(bs);\n      }\n    );\n  });\n}\n\nexport function closeBrowserSync() {\n  browserSyncEmbed.exit();\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/browserUtils.js",
    "content": "// @flow\n\nimport {MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL} from '../constants';\n\n/**\n * Ensures that the given zoom level stays between MIN_ZOOM_LEVEL and MAX_ZOOM_LEVEL and returns it.\n * @param zoomLevel The zoom level to be normalized.\n */\nexport function normalizeZoomLevel(zoomLevel: number): number {\n  if (zoomLevel < MIN_ZOOM_LEVEL) {\n    return MIN_ZOOM_LEVEL;\n  }\n\n  if (zoomLevel > MAX_ZOOM_LEVEL) {\n    return MAX_ZOOM_LEVEL;\n  }\n\n  return zoomLevel;\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/deviceManagerUtils.js",
    "content": "import os from 'os';\n\nexport const MIN_NUMBER_OF_DEVICES = 2;\n\nexport function getRecommendedMaxNumberOfDevices() {\n  const logicalCpuInfos = os.cpus();\n  const cpuSpeed = logicalCpuInfos[0].speed;\n  const cpuCount = logicalCpuInfos.length;\n\n  const value = Math.max(\n    MIN_NUMBER_OF_DEVICES,\n    Math.trunc(cpuCount * (cpuSpeed / 2000))\n  );\n\n  return value;\n}\n\nexport const recommendedMaxNumberOfDevices = getRecommendedMaxNumberOfDevices();\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/filterUtils.js",
    "content": "import {FILTER_FIELDS} from '../reducers/browser';\n\nexport const isDeviceEligible = (device, filterCriteria) => {\n  if (Object.keys(filterCriteria[FILTER_FIELDS.OS]).length > 0) {\n    if (filterCriteria[FILTER_FIELDS.OS].indexOf(device.os) === -1) {\n      return false;\n    }\n  }\n\n  if (Object.keys(filterCriteria[FILTER_FIELDS.DEVICE_TYPE]).length > 0) {\n    if (filterCriteria[FILTER_FIELDS.DEVICE_TYPE].indexOf(device.type) === -1) {\n      return false;\n    }\n  }\n\n  return true;\n};\n\nexport default {isDeviceEligible};\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/generalUtils.js",
    "content": "import {app} from 'electron';\nimport path from 'path';\nimport fs from 'fs';\nimport os from 'os';\nimport {SSL_ERROR_CODES} from '../constants/values';\n\nexport const getPackageJson = () => {\n  let appPath;\n  if (process.env.NODE_ENV === 'production') appPath = app.getAppPath();\n  else appPath = process.cwd();\n\n  const pkgPath = path.join(appPath, 'package.json');\n  if (fs.existsSync(pkgPath)) {\n    const pkgContent = fs.readFileSync(pkgPath, 'utf-8');\n    return JSON.parse(pkgContent);\n  }\n  console.error(`cant find package.json in: '${appPath}'`);\n  return {};\n};\n\nexport const getEnvironmentInfo = () => {\n  const pkg = getPackageJson();\n  const appVersion = pkg.version || 'Unknown';\n  const electronVersion = process.versions.electron || 'Unknown';\n  const chromeVersion = process.versions.chrome || 'Unknown';\n  const nodeVersion = process.versions.node || 'Unknown';\n  const v8Version = process.versions.v8 || 'Unknown';\n  const osInfo =\n    `${os.type()} ${os.arch()} ${os.release()}`.trim() || 'Unknown';\n\n  return {\n    appVersion,\n    electronVersion,\n    chromeVersion,\n    nodeVersion,\n    v8Version,\n    osInfo,\n  };\n};\n\nexport function isSslValidationFailed(errorCode) {\n  return (\n    errorCode <= SSL_ERROR_CODES.FIRST && errorCode >= SSL_ERROR_CODES.LAST\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/iconUtils.js",
    "content": "import React from 'react';\nimport DesktopIcon from '@material-ui/icons/DesktopWindows';\nimport MobileIcon from '@material-ui/icons/Smartphone';\nimport TabletIcon from '@material-ui/icons/TabletMac';\nimport {DEVICE_TYPE} from '../constants/devices';\n\nexport const getDeviceIcon = deviceType => {\n  const iconProps = {\n    style: {fontSize: 'inherit', paddingRight: 2},\n  };\n  switch (deviceType) {\n    case DEVICE_TYPE.phone:\n      return <MobileIcon {...iconProps} />;\n    case DEVICE_TYPE.tablet:\n      return <TabletIcon {...iconProps} />;\n    case DEVICE_TYPE.desktop:\n      return <DesktopIcon {...iconProps} />;\n    default:\n      return null;\n  }\n};\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/logUtils.js",
    "content": "import * as Sentry from '@sentry/electron';\n\nexport const captureOnSentry = err => {\n  console.log('err', err);\n  if (process.env.NODE_ENV !== 'development') {\n    Sentry.captureException(err);\n  }\n};\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/navigatorUtils.js",
    "content": "import settings from 'electron-settings';\nimport path from 'path';\nimport {STARTUP_PAGE} from '../constants/values';\nimport {USER_PREFERENCES} from '../constants/settingKeys';\n\nconst HOME_PAGE = 'HOME_PAGE';\nconst LAST_OPENED_ADDRESS = 'LAST_OPENED_ADDRESS';\n\nexport function saveHomepage(url) {\n  settings.set(HOME_PAGE, url);\n}\n\nexport function saveLastOpenedAddress(url) {\n  settings.set(LAST_OPENED_ADDRESS, url);\n}\n\nexport function getHomepage() {\n  return settings.get(HOME_PAGE) || 'https://www.google.com/';\n}\n\nexport function getLastOpenedAddress() {\n  return settings.get(LAST_OPENED_ADDRESS) || getStartupPage();\n}\n\nexport function getStartupPage() {\n  const startupPage = settings.get(USER_PREFERENCES).startupPage;\n  if (startupPage === STARTUP_PAGE.BLANK) return 'about:blank';\n  return getHomepage();\n}\n\nexport function saveStartupPage(option: 'BLANK' | 'HOME') {\n  const preferences = settings.get(USER_PREFERENCES);\n  preferences.startupPage = option;\n  settings.set(USER_PREFERENCES, preferences);\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/permissionUtils.js",
    "content": "import pubsub from 'pubsub.js';\nimport {ipcRenderer} from 'electron';\nimport {\n  HIDE_PERMISSION_POPUP_DUE_TO_RELOAD,\n  PERMISSION_MANAGEMENT_PREFERENCE_CHANGED,\n} from '../constants/pubsubEvents';\nimport {PERMISSION_MANAGEMENT_OPTIONS} from '../constants/permissionsManagement';\nimport {USER_PREFERENCES} from '../constants/settingKeys';\nimport settings from 'electron-settings';\n\nconst path = require('path');\n\nexport function getPermissionPageTitle(url) {\n  if (url == null || url.length === 0) return 'The webpage';\n  try {\n    if (url.startsWith('file://')) {\n      return decodeURIComponent(path.basename(url));\n    }\n    return new URL(url).hostname;\n  } catch {\n    return url;\n  }\n}\n\nexport function getDeviceText(device) {\n  if (device === 'audio') return 'microphone';\n  if (device === 'video') return 'camera';\n  return device;\n}\n\n// simulating the behavior of google chrome\nexport function getPermissionRequestMessage(permission, details) {\n  if (permission === 'notifications') return 'wants to show notifications';\n\n  if (permission === 'geolocation') return 'wants to know your location';\n\n  if (permission === 'fullscreen') return 'wants to go to full screen';\n\n  if (permission === 'pointerLock')\n    return 'wants to control your pointer movements';\n\n  //  see https:github.com/electron/electron/pull/23333\n  if (permission.includes('clipboard') || permission === 'unknown')\n    return 'wants to see text and images copied to the clipboard';\n\n  if (permission === 'media') {\n    let mediaTypes = details?.mediaTypes;\n    if (mediaTypes != null && mediaTypes.length !== 0) {\n      mediaTypes = mediaTypes.map(getDeviceText);\n      if (mediaTypes.length === 1) return `wants to use your ${mediaTypes[0]}`;\n      const last = mediaTypes.pop();\n      return `wants to use your ${mediaTypes.join(', ')} and ${last}`;\n    }\n    return 'wants to use some of your media devices';\n  }\n\n  if (permission.includes('midi')) return 'wants to use your MIDI devices';\n\n  return `is requesting permission for \"${permission}\"`;\n}\n\nexport function notifyPermissionToHandleReloadOrNewAddress() {\n  pubsub.publish(HIDE_PERMISSION_POPUP_DUE_TO_RELOAD);\n  ipcRenderer.send('reset-ignored-permissions');\n}\n\nexport function notifyPermissionPreferenceChanged(newPreference) {\n  pubsub.publish(PERMISSION_MANAGEMENT_PREFERENCE_CHANGED, [newPreference]);\n}\n\nexport function getPermissionSettingPreference() {\n  return (\n    settings.get(USER_PREFERENCES)?.permissionManagement ||\n    PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS\n  );\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/proxyUtils.js",
    "content": "export function proxyRuleToString(scheme, rule, defaultRule) {\n  let str = scheme === 'default' ? '' : `${scheme}=`;\n  if (rule.useDefault) return str + defaultRule;\n  if (rule.protocol === 'direct') return `${str}direct://`;\n  str = `${str}${rule.protocol}://${rule.server}`;\n  if (rule.port != null) str = `${str}:${rule.port}`;\n  return defaultRule !== '' ? `${str},${defaultRule}` : str;\n}\n\nexport function convertToProxyConfig(profile) {\n  if (!profile.active) {\n    return {\n      proxyRules: 'direct://',\n    };\n  }\n  const defaultStr = proxyRuleToString('default', profile.default, '');\n  const parts = [\n    proxyRuleToString('http', profile.http, defaultStr),\n    proxyRuleToString('https', profile.https, defaultStr),\n    proxyRuleToString('ftp', profile.ftp, defaultStr),\n  ];\n\n  return {\n    proxyRules: parts.join(';'),\n    proxyBypassRules: (profile.bypassList || []).join(','),\n  };\n}\n\nexport function getEmptyProxySchemeConfig(useDefault = false) {\n  return {\n    protocol: '',\n    server: '',\n    port: '',\n    user: '',\n    password: '',\n    useDefault: !!useDefault,\n  };\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/stringUtils.js",
    "content": "export function isNullOrWhiteSpaces(str: string): boolean {\n  return str == null || /^\\s*$/.test(str);\n}\n"
  },
  {
    "path": "desktop-app-legacy/app/utils/urlUtils.js",
    "content": "import fs from 'fs';\n\nexport function getHostFromURL(url: String) {\n  let host = '';\n  if (url) {\n    const urlObj = new URL(url);\n    host = urlObj.host;\n  }\n  return host;\n}\n\nexport const normalize = (address: string) => {\n  if (address !== 'about:blank' && address.indexOf('://') === -1) {\n    let protocol = 'https://';\n    if (\n      address.startsWith('localhost') ||\n      address.startsWith('127.0.0.1') ||\n      _inferHostname(address).indexOf('.localhost') !== -1\n    ) {\n      protocol = 'http://';\n    } else if (fs.existsSync(address)) {\n      protocol = 'file://';\n    }\n    address = `${protocol}${address}`;\n  }\n  return address;\n};\n\nconst _hostnameCharHints = [':', '/', '#', '?'];\nconst _inferHostname = (address: string) =>\n  _hostnameCharHints.reduce((curr, char) => curr.split(char)[0], address);\n"
  },
  {
    "path": "desktop-app-legacy/appveyor.yml",
    "content": "image: Visual Studio 2017\n\nplatform:\n  - x64\n\nenvironment:\n  matrix:\n    - nodejs_version: 10\n\ncache:\n  - '%LOCALAPPDATA%/Yarn'\n  - node_modules\n  - flow-typed\n  - '%USERPROFILE%\\.electron'\n\nmatrix:\n  fast_finish: true\n\nbuild: off\n\nversion: '{build}'\n\nshallow_clone: true\n\nclone_depth: 1\n\ninstall:\n  - ps: Install-Product node $env:nodejs_version x64\n  - set CI=true\n  - yarn\n\ntest_script:\n  - yarn package-ci\n  - yarn lint\n  # - yarn flow\n  - yarn test\n  - yarn build-e2e\n  - yarn test-e2e\n"
  },
  {
    "path": "desktop-app-legacy/babel.config.js",
    "content": "/* eslint global-require: off */\n\nconst developmentEnvironments = ['development', 'test'];\n\nconst developmentPlugins = [\n  require('@babel/plugin-transform-classes'),\n  require('react-hot-loader/babel'),\n];\n\nconst productionPlugins = [\n  require('babel-plugin-dev-expression'),\n\n  // babel-preset-react-optimize\n  require('@babel/plugin-transform-react-constant-elements'),\n  require('@babel/plugin-transform-react-inline-elements'),\n  require('babel-plugin-transform-react-remove-prop-types'),\n];\n\nmodule.exports = api => {\n  // see docs about api at https://babeljs.io/docs/en/config-files#apicache\n\n  const development = api.env(developmentEnvironments);\n\n  return {\n    presets: [\n      [\n        require('@babel/preset-env'),\n        {\n          targets: {\n            electron: require('electron/package.json').version,\n          },\n          corejs: '2',\n          useBuiltIns: 'usage',\n        },\n      ],\n      require('@babel/preset-flow'),\n      [require('@babel/preset-react'), {development}],\n    ],\n    plugins: [\n      // Stage 0\n      require('@babel/plugin-proposal-function-bind'),\n\n      // Stage 1\n      require('@babel/plugin-proposal-export-default-from'),\n      require('@babel/plugin-proposal-logical-assignment-operators'),\n      [require('@babel/plugin-proposal-optional-chaining'), {loose: false}],\n      [\n        require('@babel/plugin-proposal-pipeline-operator'),\n        {proposal: 'minimal'},\n      ],\n      [\n        require('@babel/plugin-proposal-nullish-coalescing-operator'),\n        {loose: false},\n      ],\n      require('@babel/plugin-proposal-do-expressions'),\n\n      // Stage 2\n      [require('@babel/plugin-proposal-decorators'), {legacy: true}],\n      require('@babel/plugin-proposal-function-sent'),\n      require('@babel/plugin-proposal-export-namespace-from'),\n      require('@babel/plugin-proposal-numeric-separator'),\n      require('@babel/plugin-proposal-throw-expressions'),\n\n      // Stage 3\n      require('@babel/plugin-syntax-dynamic-import'),\n      require('@babel/plugin-syntax-import-meta'),\n      [require('@babel/plugin-proposal-class-properties'), {loose: true}],\n      require('@babel/plugin-proposal-json-strings'),\n\n      ...(development ? developmentPlugins : productionPlugins),\n    ],\n  };\n};\n"
  },
  {
    "path": "desktop-app-legacy/build/entitlements.mac.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <true/>\n  </dict>\n</plist>"
  },
  {
    "path": "desktop-app-legacy/configs/webpack.config.base.js",
    "content": "/**\n * Base webpack config used across other specific configs\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport {dependencies} from '../package.json';\n\nexport default {\n  externals: [...Object.keys(dependencies || {})],\n\n  module: {\n    rules: [\n      {\n        test: /\\.jsx?$/,\n        exclude: /node_modules/,\n        use: {\n          loader: 'babel-loader',\n          options: {\n            cacheDirectory: true,\n          },\n        },\n      },\n    ],\n  },\n\n  output: {\n    path: path.join(__dirname, '..', 'app'),\n    // https://github.com/webpack/webpack/issues/1114\n    libraryTarget: 'commonjs2',\n  },\n\n  /**\n   * Determine the array of extensions that should be used to resolve modules.\n   */\n  resolve: {\n    extensions: ['.js', '.jsx', '.json'],\n  },\n\n  plugins: [\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production',\n    }),\n\n    new webpack.NamedModulesPlugin(),\n  ],\n};\n"
  },
  {
    "path": "desktop-app-legacy/configs/webpack.config.eslint.js",
    "content": "/* eslint import/no-unresolved: off, import/no-self-import: off */\nrequire('@babel/register');\n\nmodule.exports = require('./webpack.config.renderer.dev.babel').default;\n"
  },
  {
    "path": "desktop-app-legacy/configs/webpack.config.main.prod.babel.js",
    "content": "/**\n * Webpack config for production electron main process\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport merge from 'webpack-merge';\nimport TerserPlugin from 'terser-webpack-plugin';\nimport {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer';\nimport baseConfig from './webpack.config.base';\nimport CheckNodeEnv from '../internals/scripts/CheckNodeEnv';\n\nCheckNodeEnv('production');\n\nexport default merge.smart(baseConfig, {\n  devtool: 'source-map',\n\n  mode: 'production',\n\n  target: 'electron-main',\n\n  entry: './app/main.dev',\n\n  output: {\n    path: path.join(__dirname, '..'),\n    filename: './app/main.prod.js',\n  },\n\n  optimization: {\n    minimizer: process.env.E2E_BUILD\n      ? []\n      : [\n          new TerserPlugin({\n            parallel: true,\n            sourceMap: true,\n            cache: true,\n          }),\n        ],\n  },\n\n  plugins: [\n    new BundleAnalyzerPlugin({\n      analyzerMode:\n        process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',\n      openAnalyzer: process.env.OPEN_ANALYZER === 'true',\n    }),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production',\n      DEBUG_PROD: false,\n      START_MINIMIZED: false,\n    }),\n  ],\n\n  /**\n   * Disables webpack processing of __dirname and __filename.\n   * If you run the bundle in node.js it falls back to these values of node.js.\n   * https://github.com/webpack/webpack/issues/2010\n   */\n  node: {\n    __dirname: false,\n    __filename: false,\n  },\n});\n"
  },
  {
    "path": "desktop-app-legacy/configs/webpack.config.renderer.dev.babel.js",
    "content": "/* eslint global-require: off, import/no-dynamic-require: off */\n\n/**\n * Build config for development electron renderer process that uses\n * Hot-Module-Replacement\n *\n * https://webpack.js.org/concepts/hot-module-replacement/\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport webpack from 'webpack';\nimport chalk from 'chalk';\nimport merge from 'webpack-merge';\nimport {spawn, execSync} from 'child_process';\nimport baseConfig from './webpack.config.base';\nimport CheckNodeEnv from '../internals/scripts/CheckNodeEnv';\n\nCheckNodeEnv('development');\n\nconst port = process.env.PORT || 1212;\nconst publicPath = `http://localhost:${port}/dist`;\nconst dll = path.join(__dirname, '..', 'dll');\nconst manifest = path.resolve(dll, 'renderer.json');\nconst requiredByDLLConfig = module.parent.filename.includes(\n  'webpack.config.renderer.dev.dll'\n);\n\n/**\n * Warn if the DLL is not built\n */\nif (!requiredByDLLConfig && !(fs.existsSync(dll) && fs.existsSync(manifest))) {\n  console.log(\n    chalk.black.bgYellow.bold(\n      'The DLL files are missing. Sit back while we build them for you with \"yarn build-dll\"'\n    )\n  );\n  execSync('yarn build-dll');\n}\n\nexport default merge.smart(baseConfig, {\n  devtool: 'inline-source-map',\n\n  mode: 'development',\n\n  target: 'electron-renderer',\n\n  entry: [\n    'react-hot-loader/patch',\n    `webpack-dev-server/client?http://localhost:${port}/`,\n    'webpack/hot/only-dev-server',\n    require.resolve('../app/index'),\n  ],\n\n  output: {\n    publicPath: `http://localhost:${port}/dist/`,\n    filename: 'renderer.dev.js',\n  },\n\n  module: {\n    rules: [\n      {\n        test: /\\.jsx?$/,\n        exclude: /node_modules/,\n        use: {\n          loader: 'babel-loader',\n          options: {\n            cacheDirectory: true,\n          },\n        },\n      },\n      {\n        test: /\\.global\\.css$/,\n        use: [\n          {\n            loader: 'style-loader',\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              sourceMap: true,\n            },\n          },\n        ],\n      },\n      {\n        test: /^((?!\\.global).)*\\.css$/,\n        use: [\n          {\n            loader: 'style-loader',\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              sourceMap: true,\n              importLoaders: 1,\n              localIdentName: '[local]__[hash:base64:5]',\n            },\n          },\n        ],\n      },\n      // SASS support - compile all .global.scss files and pipe it to style.css\n      {\n        test: /\\.global\\.(scss|sass)$/,\n        use: [\n          {\n            loader: 'style-loader',\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              sourceMap: true,\n            },\n          },\n          {\n            loader: 'sass-loader',\n          },\n        ],\n      },\n      // SASS support - compile all other .scss files and pipe it to style.css\n      {\n        test: /^((?!\\.global).)*\\.(scss|sass)$/,\n        use: [\n          {\n            loader: 'style-loader',\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              sourceMap: true,\n              importLoaders: 1,\n              localIdentName: '[local]__[hash:base64:5]',\n            },\n          },\n          {\n            loader: 'sass-loader',\n          },\n        ],\n      },\n      // WOFF Font\n      {\n        test: /\\.woff(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          },\n        },\n      },\n      // WOFF2 Font\n      {\n        test: /\\.woff2(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          },\n        },\n      },\n      // TTF Font\n      {\n        test: /\\.ttf(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/octet-stream',\n          },\n        },\n      },\n      // EOT Font\n      {\n        test: /\\.eot(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: 'file-loader',\n      },\n      // SVG Font\n      {\n        test: /\\.svg(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'image/svg+xml',\n          },\n        },\n      },\n      // Common Image Formats\n      {\n        test: /\\.(?:ico|gif|png|jpg|jpeg|webp)$/,\n        use: 'url-loader',\n      },\n    ],\n  },\n\n  plugins: [\n    requiredByDLLConfig\n      ? null\n      : new webpack.DllReferencePlugin({\n          context: path.join(__dirname, '..', 'dll'),\n          manifest: require(manifest),\n          sourceType: 'var',\n        }),\n\n    new webpack.HotModuleReplacementPlugin({\n      multiStep: true,\n    }),\n\n    new webpack.NoEmitOnErrorsPlugin(),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     *\n     * By default, use 'development' as NODE_ENV. This can be overriden with\n     * 'staging', for example, by changing the ENV variables in the npm scripts\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'development',\n    }),\n\n    new webpack.LoaderOptionsPlugin({\n      debug: true,\n    }),\n  ],\n\n  node: {\n    __dirname: false,\n    __filename: false,\n  },\n\n  devServer: {\n    port,\n    publicPath,\n    compress: true,\n    noInfo: true,\n    stats: 'errors-only',\n    inline: true,\n    lazy: false,\n    hot: true,\n    headers: {'Access-Control-Allow-Origin': '*'},\n    contentBase: path.join(__dirname, 'dist'),\n    watchOptions: {\n      aggregateTimeout: 300,\n      ignored: /node_modules/,\n      poll: 100,\n    },\n    historyApiFallback: {\n      verbose: true,\n      disableDotRule: false,\n    },\n    before() {\n      if (process.env.START_HOT) {\n        console.log('Starting Main Process...');\n        spawn('yarn', ['start-main-dev'], {\n          shell: true,\n          env: process.env,\n          stdio: 'inherit',\n        })\n          .on('close', code => process.exit(code))\n          .on('error', spawnError => console.error(spawnError));\n      }\n    },\n  },\n});\n"
  },
  {
    "path": "desktop-app-legacy/configs/webpack.config.renderer.dev.dll.babel.js",
    "content": "/* eslint global-require: off, import/no-dynamic-require: off */\n\n/**\n * Builds the DLL for development electron renderer process\n */\n\nimport webpack from 'webpack';\nimport path from 'path';\nimport merge from 'webpack-merge';\nimport baseConfig from './webpack.config.base';\nimport {dependencies} from '../package.json';\nimport CheckNodeEnv from '../internals/scripts/CheckNodeEnv';\n\nCheckNodeEnv('development');\n\nconst dist = path.join(__dirname, '..', 'dll');\n\nexport default merge.smart(baseConfig, {\n  context: path.join(__dirname, '..'),\n\n  devtool: 'eval',\n\n  mode: 'development',\n\n  target: 'electron-renderer',\n\n  externals: ['fsevents', 'crypto-browserify'],\n\n  /**\n   * Use `module` from `webpack.config.renderer.dev.js`\n   */\n  module: require('./webpack.config.renderer.dev.babel').default.module,\n\n  entry: {\n    renderer: Object.keys(dependencies || {}),\n  },\n\n  output: {\n    library: 'renderer',\n    path: dist,\n    filename: '[name].dev.dll.js',\n    libraryTarget: 'var',\n  },\n\n  plugins: [\n    new webpack.DllPlugin({\n      path: path.join(dist, '[name].json'),\n      name: '[name]',\n    }),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'development',\n    }),\n\n    new webpack.LoaderOptionsPlugin({\n      debug: true,\n      options: {\n        context: path.join(__dirname, '..', 'app'),\n        output: {\n          path: path.join(__dirname, '..', 'dll'),\n        },\n      },\n    }),\n  ],\n});\n"
  },
  {
    "path": "desktop-app-legacy/configs/webpack.config.renderer.prod.babel.js",
    "content": "/**\n * Build config for electron renderer process\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport MiniCssExtractPlugin from 'mini-css-extract-plugin';\nimport OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin';\nimport {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer';\nimport merge from 'webpack-merge';\nimport TerserPlugin from 'terser-webpack-plugin';\nimport baseConfig from './webpack.config.base';\nimport CheckNodeEnv from '../internals/scripts/CheckNodeEnv';\n\nCheckNodeEnv('production');\nexport default merge.smart(baseConfig, {\n  devtool: 'source-map',\n\n  mode: 'production',\n\n  target: 'electron-renderer',\n\n  entry: path.join(__dirname, '..', 'app/index'),\n\n  output: {\n    path: path.join(__dirname, '..', 'app/dist'),\n    publicPath: './dist/',\n    filename: 'renderer.prod.js',\n  },\n\n  module: {\n    rules: [\n      // Extract all .global.css to style.css as is\n      {\n        test: /\\.global\\.css$/,\n        use: [\n          {\n            loader: MiniCssExtractPlugin.loader,\n            options: {\n              publicPath: './',\n            },\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              sourceMap: true,\n            },\n          },\n        ],\n      },\n      // Pipe other styles through css modules and append to style.css\n      {\n        test: /^((?!\\.global).)*\\.css$/,\n        use: [\n          {\n            loader: MiniCssExtractPlugin.loader,\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              localIdentName: '[name]__[local]__[hash:base64:5]',\n              sourceMap: true,\n            },\n          },\n        ],\n      },\n      // Add SASS support  - compile all .global.scss files and pipe it to style.css\n      {\n        test: /\\.global\\.(scss|sass)$/,\n        use: [\n          {\n            loader: MiniCssExtractPlugin.loader,\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              sourceMap: true,\n              importLoaders: 1,\n            },\n          },\n          {\n            loader: 'sass-loader',\n            options: {\n              sourceMap: true,\n            },\n          },\n        ],\n      },\n      // Add SASS support  - compile all other .scss files and pipe it to style.css\n      {\n        test: /^((?!\\.global).)*\\.(scss|sass)$/,\n        use: [\n          {\n            loader: MiniCssExtractPlugin.loader,\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              importLoaders: 1,\n              localIdentName: '[name]__[local]__[hash:base64:5]',\n              sourceMap: true,\n            },\n          },\n          {\n            loader: 'sass-loader',\n            options: {\n              sourceMap: true,\n            },\n          },\n        ],\n      },\n      // WOFF Font\n      {\n        test: /\\.woff(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          },\n        },\n      },\n      // WOFF2 Font\n      {\n        test: /\\.woff2(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          },\n        },\n      },\n      // TTF Font\n      {\n        test: /\\.ttf(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/octet-stream',\n          },\n        },\n      },\n      // EOT Font\n      {\n        test: /\\.eot(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: 'file-loader',\n      },\n      // SVG Font\n      {\n        test: /\\.svg(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'image/svg+xml',\n          },\n        },\n      },\n      // Common Image Formats\n      {\n        test: /\\.(?:ico|gif|png|jpg|jpeg|webp)$/,\n        use: 'url-loader',\n      },\n    ],\n  },\n\n  optimization: {\n    minimizer: process.env.E2E_BUILD\n      ? []\n      : [\n          new TerserPlugin({\n            parallel: true,\n            sourceMap: true,\n            cache: true,\n          }),\n          new OptimizeCSSAssetsPlugin({\n            cssProcessorOptions: {\n              map: {\n                inline: false,\n                annotation: true,\n              },\n            },\n          }),\n        ],\n  },\n\n  plugins: [\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production',\n    }),\n\n    new MiniCssExtractPlugin({\n      filename: 'style.css',\n    }),\n\n    new BundleAnalyzerPlugin({\n      analyzerMode:\n        process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',\n      openAnalyzer: process.env.OPEN_ANALYZER === 'true',\n    }),\n  ],\n});\n"
  },
  {
    "path": "desktop-app-legacy/flow-typed/electron.js",
    "content": "declare class WebviewElement extends HTMLElement {\n  insertCSS: string => Promise<string>;\n  executeJavaScript: string => Promise<any>;\n  getWebContentsId: () => number;\n  removeInsertedCSS: number => Promise<void>;\n}\n"
  },
  {
    "path": "desktop-app-legacy/flow-typed/module_vx.x.x.js",
    "content": "declare module 'module' {\n  declare module.exports: any;\n}\n"
  },
  {
    "path": "desktop-app-legacy/internals/flow/CSSModule.js.flow",
    "content": "// @flow\n\ndeclare export default { [key: string]: string }"
  },
  {
    "path": "desktop-app-legacy/internals/flow/WebpackAsset.js.flow",
    "content": "// @flow\ndeclare export default string\n"
  },
  {
    "path": "desktop-app-legacy/internals/mocks/fileMock.js",
    "content": "export default 'test-file-stub';\n"
  },
  {
    "path": "desktop-app-legacy/internals/scripts/CheckBuiltsExist.js",
    "content": "// @flow\n// Check if the renderer and main bundles are built\nimport path from 'path';\nimport chalk from 'chalk';\nimport fs from 'fs';\n\nfunction CheckBuildsExist() {\n  const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js');\n  const rendererPath = path.join(\n    __dirname,\n    '..',\n    '..',\n    'app',\n    'dist',\n    'renderer.prod.js'\n  );\n\n  if (!fs.existsSync(mainPath)) {\n    throw new Error(\n      chalk.whiteBright.bgRed.bold(\n        'The main process is not built yet. Build it by running \"yarn build-main\"'\n      )\n    );\n  }\n\n  if (!fs.existsSync(rendererPath)) {\n    throw new Error(\n      chalk.whiteBright.bgRed.bold(\n        'The renderer process is not built yet. Build it by running \"yarn build-renderer\"'\n      )\n    );\n  }\n}\n\nCheckBuildsExist();\n"
  },
  {
    "path": "desktop-app-legacy/internals/scripts/CheckNodeEnv.js",
    "content": "// @flow\nimport chalk from 'chalk';\n\nexport default function CheckNodeEnv(expectedEnv: string) {\n  if (!expectedEnv) {\n    throw new Error('\"expectedEnv\" not set');\n  }\n\n  if (process.env.NODE_ENV !== expectedEnv) {\n    console.log(\n      chalk.whiteBright.bgRed.bold(\n        `\"process.env.NODE_ENV\" must be \"${expectedEnv}\" to use this webpack config`\n      )\n    );\n    process.exit(2);\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/internals/scripts/CheckPortInUse.js",
    "content": "// @flow\nimport chalk from 'chalk';\nimport detectPort from 'detect-port';\n\n(function CheckPortInUse() {\n  const port: string = process.env.PORT || '1212';\n\n  detectPort(port, (err: ?Error, availablePort: number) => {\n    if (port !== String(availablePort)) {\n      throw new Error(\n        chalk.whiteBright.bgRed.bold(\n          `Port \"${port}\" on \"localhost\" is already in use. Please use another port. ex: PORT=4343 yarn dev`\n        )\n      );\n    } else {\n      process.exit(0);\n    }\n  });\n})();\n"
  },
  {
    "path": "desktop-app-legacy/package.json",
    "content": "{\n  \"name\": \"Responsively-App\",\n  \"productName\": \"ResponsivelyApp\",\n  \"version\": \"0.19.1\",\n  \"description\": \"A developer-friendly browser for developing responsive web apps\",\n  \"scripts\": {\n    \"build\": \"concurrently \\\"yarn build-main\\\" \\\"yarn build-renderer\\\"\",\n    \"build-dll\": \"cross-env NODE_ENV=development webpack --config ./configs/webpack.config.renderer.dev.dll.babel.js --colors\",\n    \"build-e2e\": \"cross-env E2E_BUILD=true yarn build\",\n    \"build-main\": \"cross-env NODE_ENV=production webpack --config ./configs/webpack.config.main.prod.babel.js --colors\",\n    \"build-renderer\": \"cross-env NODE_ENV=production webpack --config ./configs/webpack.config.renderer.prod.babel.js --colors\",\n    \"dev\": \"cross-env START_HOT=1 node -r @babel/register ./internals/scripts/CheckPortInUse.js && cross-env START_HOT=1 yarn start-renderer-dev\",\n    \"flow\": \"flow\",\n    \"flow-typed\": \"rimraf flow-typed/npm && flow-typed install --overwrite || true\",\n    \"lint\": \"cross-env NODE_ENV=development eslint --cache --format=pretty .\",\n    \"lint-fix\": \"yarn --silent lint --fix; exit 0\",\n    \"lint-styles\": \"stylelint --ignore-path .eslintignore '**/*.*(css|scss)' --syntax scss\",\n    \"lint-styles-fix\": \"yarn --silent lint-styles --fix; exit 0\",\n    \"package\": \"yarn build && electron-builder build --publish never\",\n    \"package-all\": \"yarn build && electron-builder build -mwl\",\n    \"publish-all\": \"yarn build && electron-builder build -mwl --publish always\",\n    \"package-ci\": \"yarn postinstall && yarn build && electron-builder build -mwl --publish always\",\n    \"package-linux\": \"yarn build && electron-builder build --linux\",\n    \"publish-linux\": \"yarn build && electron-builder build --linux --publish always\",\n    \"package-mac\": \"yarn build && electron-builder build --mac\",\n    \"publish-mac\": \"yarn build && electron-builder build --mac --publish always\",\n    \"package-win\": \"yarn build && electron-builder build --win --x64\",\n    \"publish-win\": \"yarn build && electron-builder build --win --x64 --publish always\",\n    \"postinstall\": \"electron-builder install-app-deps && yarn build-dll\",\n    \"postlint-fix\": \"prettier --ignore-path .eslintignore --single-quote --write '**/*.{*{js,jsx,json},babelrc,eslintrc,prettierrc,stylelintrc}'\",\n    \"postlint-styles-fix\": \"prettier --ignore-path .eslintignore --single-quote --write '**/*.{css,scss}'\",\n    \"prestart\": \"yarn build\",\n    \"start\": \"cross-env NODE_ENV=production electron ./app/main.prod.js\",\n    \"start-main-dev\": \"cross-env HOT=1 NODE_ENV=development electron -r @babel/register ./app/main.dev.js\",\n    \"start-renderer-dev\": \"cross-env NODE_ENV=development webpack-dev-server --config configs/webpack.config.renderer.dev.babel.js\",\n    \"test\": \"cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 jest\",\n    \"test-all\": \"yarn lint && yarn flow && yarn build && yarn test && yarn build-e2e && yarn test-e2e\",\n    \"test-e2e\": \"node -r @babel/register ./internals/scripts/CheckBuiltsExist.js && cross-env NODE_ENV=test testcafe electron:./ ./test/e2e/HomePage.e2e.js\",\n    \"test-e2e-live\": \"node -r @babel/register ./internals/scripts/CheckBuiltsExist.js && cross-env NODE_ENV=test testcafe-live electron:./ ./test/e2e/HomePage.e2e.js\",\n    \"test-watch\": \"yarn test --watch\",\n    \"choco-pkg\": \"node scripts/chocolatey/generateChocoPkg.js\",\n    \"choco-apikey\": \"choco apikey -k %RESPONSIVELY_CHOCO_KEY% -source https://push.chocolatey.org/\",\n    \"choco-publish\": \"cd release/choco && choco push -s https://push.chocolatey.org/\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.{js,jsx}\": [\n      \"cross-env NODE_ENV=development eslint --cache --format=pretty\",\n      \"prettier --ignore-path .eslintignore --single-quote --write\"\n    ],\n    \"{*.json,.{babelrc,eslintrc,prettierrc,stylelintrc}}\": [\n      \"prettier --ignore-path .eslintignore --parser json --write\"\n    ],\n    \"*.{css,scss}\": [\n      \"stylelint --ignore-path .eslintignore --syntax scss --fix\",\n      \"prettier --ignore-path .eslintignore --single-quote --write\"\n    ],\n    \"*.{yml,md}\": [\n      \"prettier --ignore-path .eslintignore --single-quote --write\"\n    ]\n  },\n  \"main\": \"./app/main.prod.js\",\n  \"build\": {\n    \"afterSign\": \"scripts/notarize.js\",\n    \"afterAllArtifactBuild\": \"scripts/extraPublishFiles.js\",\n    \"productName\": \"ResponsivelyApp\",\n    \"appId\": \"app.responsively\",\n    \"files\": [\n      \"app/preload.js\",\n      \"app/imageWorker.js\",\n      \"lib\",\n      \"app/dist/\",\n      \"resources/icons\",\n      \"app/app.html\",\n      \"app/shortcuts.html\",\n      \"app/main.prod.js\",\n      \"app/main.prod.js.map\",\n      \"package.json\"\n    ],\n    \"dmg\": {\n      \"sign\": false,\n      \"contents\": [\n        {\n          \"x\": 130,\n          \"y\": 220\n        },\n        {\n          \"x\": 410,\n          \"y\": 220,\n          \"type\": \"link\",\n          \"path\": \"/Applications\"\n        }\n      ]\n    },\n    \"mac\": {\n      \"hardenedRuntime\": true,\n      \"gatekeeperAssess\": false,\n      \"category\": \"public.app-category.developer-tools\",\n      \"entitlements\": \"build/entitlements.mac.plist\",\n      \"entitlementsInherit\": \"build/entitlements.mac.plist\",\n      \"extendInfo\": {\n        \"CFBundleURLTypes\": [\n          {\n            \"CFBundleURLSchemes\": [\n              \"responsively\"\n            ],\n            \"CFBundleURLName\": \"app.responsively\"\n          }\n        ]\n      },\n      \"icon\": \"resources/icon.icns\",\n      \"target\": [\n        \"dmg\",\n        \"zip\"\n      ],\n      \"fileAssociations\": [\n        {\n          \"ext\": [\n            \"html\",\n            \"htm\"\n          ],\n          \"role\": \"Viewer\"\n        }\n      ]\n    },\n    \"win\": {\n      \"target\": [\n        \"nsis\",\n        \"portable\"\n      ],\n      \"fileAssociations\": [\n        {\n          \"ext\": [\n            \"html\",\n            \"htm\"\n          ],\n          \"role\": \"Viewer\"\n        }\n      ]\n    },\n    \"nsis\": {\n      \"deleteAppDataOnUninstall\": true\n    },\n    \"linux\": {\n      \"target\": [\n        \"AppImage\",\n        \"rpm\"\n      ],\n      \"category\": \"Development\"\n    },\n    \"directories\": {\n      \"buildResources\": \"resources\",\n      \"output\": \"release\"\n    },\n    \"publish\": {\n      \"provider\": \"github\"\n    }\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/responsively-org/responsively-app.git\"\n  },\n  \"author\": {\n    \"name\": \"Responsively\",\n    \"email\": \"p.manoj.vivek@gmail.com\"\n  },\n  \"contributors\": [],\n  \"license\": \"MIT\",\n  \"bugs\": {},\n  \"keywords\": [],\n  \"homepage\": \"\",\n  \"jest\": {\n    \"testURL\": \"http://localhost/\",\n    \"moduleNameMapper\": {\n      \"\\\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$\": \"<rootDir>/internals/mocks/fileMock.js\",\n      \"\\\\.(css|less|sass|scss)$\": \"identity-obj-proxy\"\n    },\n    \"moduleFileExtensions\": [\n      \"js\",\n      \"jsx\",\n      \"json\"\n    ],\n    \"transform\": {\n      \"^.+\\\\.jsx?$\": \"babel-jest\"\n    },\n    \"setupFiles\": [\n      \"./internals/scripts/CheckBuiltsExist.js\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.1.6\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.1.0\",\n    \"@babel/plugin-proposal-decorators\": \"^7.1.6\",\n    \"@babel/plugin-proposal-do-expressions\": \"^7.0.0\",\n    \"@babel/plugin-proposal-export-default-from\": \"^7.0.0\",\n    \"@babel/plugin-proposal-export-namespace-from\": \"^7.0.0\",\n    \"@babel/plugin-proposal-function-bind\": \"^7.0.0\",\n    \"@babel/plugin-proposal-function-sent\": \"^7.1.0\",\n    \"@babel/plugin-proposal-json-strings\": \"^7.0.0\",\n    \"@babel/plugin-proposal-logical-assignment-operators\": \"^7.0.0\",\n    \"@babel/plugin-proposal-nullish-coalescing-operator\": \"^7.0.0\",\n    \"@babel/plugin-proposal-numeric-separator\": \"^7.0.0\",\n    \"@babel/plugin-proposal-optional-chaining\": \"^7.0.0\",\n    \"@babel/plugin-proposal-pipeline-operator\": \"^7.0.0\",\n    \"@babel/plugin-proposal-throw-expressions\": \"^7.0.0\",\n    \"@babel/plugin-syntax-dynamic-import\": \"^7.0.0\",\n    \"@babel/plugin-syntax-import-meta\": \"^7.0.0\",\n    \"@babel/plugin-transform-classes\": \"^7.5.5\",\n    \"@babel/plugin-transform-react-constant-elements\": \"^7.0.0\",\n    \"@babel/plugin-transform-react-inline-elements\": \"^7.0.0\",\n    \"@babel/preset-env\": \"^7.1.6\",\n    \"@babel/preset-flow\": \"^7.0.0\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"@babel/register\": \"^7.0.0\",\n    \"babel-core\": \"7.0.0-bridge.0\",\n    \"babel-eslint\": \"^10.0.1\",\n    \"babel-jest\": \"^26.1.0\",\n    \"babel-loader\": \"^8.0.4\",\n    \"babel-plugin-dev-expression\": \"^0.2.1\",\n    \"babel-plugin-transform-es2015-classes\": \"^6.24.1\",\n    \"babel-plugin-transform-react-remove-prop-types\": \"^0.4.20\",\n    \"chalk\": \"^2.4.1\",\n    \"concurrently\": \"^5.2.0\",\n    \"cross-env\": \"^5.2.0\",\n    \"cross-spawn\": \"^6.0.5\",\n    \"css-loader\": \"^1.0.1\",\n    \"detect-port\": \"^1.3.0\",\n    \"electron\": \"9.3.1\",\n    \"electron-builder\": \"^22.8.0\",\n    \"electron-devtools-installer\": \"^3.1.1\",\n    \"enzyme\": \"^3.7.0\",\n    \"enzyme-adapter-react-16\": \"^1.7.0\",\n    \"enzyme-to-json\": \"^3.3.4\",\n    \"eslint\": \"^5.9.0\",\n    \"eslint-config-airbnb\": \"^17.1.0\",\n    \"eslint-config-prettier\": \"^3.3.0\",\n    \"eslint-formatter-pretty\": \"^2.0.0\",\n    \"eslint-import-resolver-webpack\": \"^0.10.1\",\n    \"eslint-plugin-compat\": \"^2.6.3\",\n    \"eslint-plugin-flowtype\": \"^3.2.0\",\n    \"eslint-plugin-import\": \"^2.14.0\",\n    \"eslint-plugin-jest\": \"^23.18.0\",\n    \"eslint-plugin-jsx-a11y\": \"6.1.2\",\n    \"eslint-plugin-promise\": \"^4.0.1\",\n    \"eslint-plugin-react\": \"^7.11.1\",\n    \"eslint-plugin-testcafe\": \"^0.2.1\",\n    \"fbjs-scripts\": \"^1.0.1\",\n    \"file-loader\": \"^2.0.0\",\n    \"flow-bin\": \"^0.77.0\",\n    \"flow-runtime\": \"^0.17.0\",\n    \"flow-typed\": \"^3.2.0\",\n    \"husky\": \"^4.2.5\",\n    \"identity-obj-proxy\": \"^3.0.0\",\n    \"jest\": \"^23.6.0\",\n    \"lint-staged\": \"^10.2.11\",\n    \"mini-css-extract-plugin\": \"^0.4.4\",\n    \"ncp\": \"^2.0.0\",\n    \"opencollective-postinstall\": \"^2.0.1\",\n    \"optimize-css-assets-webpack-plugin\": \"^5.0.1\",\n    \"prettier\": \"^1.15.2\",\n    \"react-test-renderer\": \"^16.6.3\",\n    \"redux-logger\": \"^3.0.6\",\n    \"replace-in-file\": \"^6.1.0\",\n    \"rimraf\": \"^2.6.2\",\n    \"sass\": \"^1.26.10\",\n    \"sass-loader\": \"^9.0.2\",\n    \"sinon\": \"^7.1.1\",\n    \"style-loader\": \"^0.23.1\",\n    \"stylelint\": \"^15.10.1\",\n    \"stylelint-config-prettier\": \"^4.0.0\",\n    \"stylelint-config-standard\": \"^18.2.0\",\n    \"terser-webpack-plugin\": \"^1.1.0\",\n    \"testcafe\": \"^1.8.8\",\n    \"testcafe-browser-provider-electron\": \"0.0.11\",\n    \"testcafe-live\": \"^0.1.4\",\n    \"testcafe-react-selectors\": \"^3.0.0\",\n    \"url-loader\": \"^1.1.2\",\n    \"webpack\": \"^4.26.0\",\n    \"webpack-bundle-analyzer\": \"^3.4.1\",\n    \"webpack-cli\": \"^3.1.2\",\n    \"webpack-dev-server\": \"^3.8.0\",\n    \"webpack-merge\": \"^4.1.4\"\n  },\n  \"dependencies\": {\n    \"@fortawesome/fontawesome-free\": \"^5.5.0\",\n    \"@material-ui/core\": \"^4.3.2\",\n    \"@material-ui/icons\": \"^4.2.1\",\n    \"@material-ui/lab\": \"^4.0.0-alpha.26\",\n    \"@sentry/electron\": \"^1.5.2\",\n    \"ace-builds\": \"^1.4.12\",\n    \"bluebird\": \"^3.7.2\",\n    \"browser-sync\": \"^2.26.7\",\n    \"classnames\": \"^2.2.6\",\n    \"core-js\": \"2\",\n    \"dotenv\": \"^8.1.0\",\n    \"electron-cookies\": \"heap/electron-cookies\",\n    \"electron-debug\": \"^3.1.0\",\n    \"electron-log\": \"^4.2.1\",\n    \"electron-notarize\": \"^0.3.0\",\n    \"electron-settings\": \"^3.2.0\",\n    \"electron-updater\": \"^4.3.1\",\n    \"electron-util\": \"^0.14.2\",\n    \"framer-motion\": \"^2.2.0\",\n    \"jimp\": \"^0.12.1\",\n    \"jsdom\": \"^16.5.3\",\n    \"lodash\": \"^4.17.19\",\n    \"merge-img\": \"^2.1.3\",\n    \"mousetrap\": \"^1.6.5\",\n    \"mutexify\": \"^1.3.0\",\n    \"promise-worker\": \"^2.0.1\",\n    \"pubsub.js\": \"^1.5.2\",\n    \"re-resizable\": \"^6.4.0\",\n    \"react\": \"^16.13.1\",\n    \"react-ace\": \"^9.1.3\",\n    \"react-beautiful-dnd\": \"^11.0.5\",\n    \"react-dom\": \"^16.13.1\",\n    \"react-hot-loader\": \"^4.8\",\n    \"react-number-format\": \"^4.4.1\",\n    \"react-redux\": \"^7.1.0\",\n    \"react-resizable\": \"^1.10.1\",\n    \"react-rnd\": \"^10.2.2\",\n    \"react-select\": \"^3.1.0\",\n    \"react-switch\": \"^5.0.1\",\n    \"react-tabs\": \"^3.0.0\",\n    \"react-toastify\": \"^5.3.2\",\n    \"react-use\": \"^15.3.3\",\n    \"redux\": \"^4.0.4\",\n    \"redux-thunk\": \"^2.3.0\",\n    \"source-map-support\": \"^0.5.9\",\n    \"universal-analytics\": \"^0.4.23\",\n    \"uuid\": \"^8.3.0\",\n    \"validator\": \"^13.1.1\"\n  },\n  \"devEngines\": {\n    \"node\": \">=7.x\",\n    \"npm\": \">=4.x\",\n    \"yarn\": \">=0.21.3\"\n  },\n  \"browserslist\": \"electron 9.0.0\",\n  \"resolutions\": {\n    \"node-gyp\": \"^6.1.0\",\n    \"node-pre-gyp\": \"^0.14.0\"\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/renovate.json",
    "content": "{\n  \"extends\": [\"config:base\"],\n  \"rangeStrategy\": \"bump\",\n  \"baseBranches\": [\"next\"],\n  \"automerge\": true,\n  \"major\": {\n    \"automerge\": false\n  }\n}\n"
  },
  {
    "path": "desktop-app-legacy/scripts/chocolatey/generateChocoPkg.js",
    "content": "const {version} = require('../../package.json');\nconst path = require('path');\nconst fs = require('fs');\nconst crypto = require('crypto');\nconst replaceInFiles = require('replace-in-file');\nconst {spawn} = require('child_process');\nconst {ncp} = require('ncp');\nconst https = require('https');\n\nncp.limit = 16;\n\nconst checksumFileName = `ResponsivelyApp-Setup-${version}.exe`;\nconst releasePath = path.resolve(__dirname, '../../release');\nconst filesToReplace = [\n  path.resolve(releasePath, 'choco/tools/chocolateyinstall.ps1'),\n  path.resolve(releasePath, 'choco/tools/VERIFICATION.txt'),\n  path.resolve(releasePath, 'choco/responsively.nuspec'),\n];\n\nconst getSha512 = () =>\n  new Promise((resolve, reject) => {\n    const installerPath = path.resolve(releasePath, checksumFileName);\n    console.log(`\\nGetting checksum of '${installerPath}'`);\n    if (fs.existsSync(installerPath)) {\n      const hash = crypto.createHash('sha512');\n      hash.on('error', reject);\n      fs.createReadStream(installerPath, {highWaterMark: 1024 * 1024})\n        .on('error', reject)\n        .on('end', () => {\n          hash.end();\n          const sha = Buffer.from(hash.read())\n            .toString('hex')\n            .toUpperCase();\n          console.log('Success: ', sha);\n          resolve(sha);\n        })\n        .pipe(hash, {\n          end: false,\n        });\n    } else {\n      reject(new Error(`Expected installer file '${installerPath}' not found`));\n    }\n  });\n\nconst replaceTokensInTemplates = async checksum => {\n  console.log('\\nReplacing release info in template files');\n  const options = {\n    files: filesToReplace,\n    from: [/#VERSION#/g, /#CHECKSUM#/g],\n    to: [version, checksum],\n  };\n  const results = await replaceInFiles(options);\n  console.log('Replacing result: ', results);\n};\n\nconst execChocoPack = () =>\n  new Promise((resolve, reject) => {\n    console.log('\\nExecuting choco pack');\n    const destination = path.resolve(releasePath, 'choco');\n    const chocoPkgCmd = spawn('choco', ['pack'], {cwd: destination});\n    chocoPkgCmd.stdout.on('data', data => {\n      console.log(`stdout: ${data}`);\n    });\n\n    chocoPkgCmd.stderr.on('data', data => {\n      console.log(`stderr: ${data}`);\n    });\n\n    chocoPkgCmd.on('error', error => {\n      console.log(`error: ${error.message}`);\n    });\n\n    chocoPkgCmd.on('close', code => {\n      if (code === 0) resolve();\n      else reject();\n    });\n  });\n\nconst ensureInstallerFile = async () => {\n  console.log('\\nEnsure installer file');\n  return new Promise((resolve, reject) => {\n    const installerPath = path.resolve(releasePath, checksumFileName);\n    if (fs.existsSync(installerPath)) {\n      console.log('Removing existing installer file');\n      fs.rmdirSync(installerPath, {recursive: true});\n    }\n\n    const url = `https://github.com/responsively-org/responsively-app/releases/download/v${version}/ResponsivelyApp-Setup-${version}.exe`;\n    console.log(`Downloading '${checksumFileName}' file from github`);\n    const file = fs.createWriteStream(installerPath);\n    https\n      .get(url, response => {\n        response.pipe(file);\n        file.on('finish', () => {\n          file.close(() => {\n            console.log('Download finished');\n            resolve();\n          });\n        });\n      })\n      .on('error', err => {\n        fs.unlink(dest);\n        console.log('Download error!');\n        reject(err.message);\n      });\n  });\n};\n\nconst generateChocoPkg = async () => {\n  console.log(\n    `\\nGenerating Chocolatey package folder for responsively v${version}`\n  );\n  const source = path.resolve(__dirname, 'responsively');\n  const destination = path.resolve(releasePath, 'choco');\n\n  if (!fs.existsSync(source)) {\n    return console.error('Template not found');\n  }\n\n  if (fs.existsSync(destination)) {\n    console.log('Cleaning old version output');\n    fs.rmdirSync(destination, {recursive: true});\n  }\n\n  ncp(source, destination, async err => {\n    console.log('Moving choco package folder to release path');\n    if (err) {\n      return console.error(err);\n    }\n    console.log('done!');\n    try {\n      await ensureInstallerFile();\n      const checksum = await getSha512();\n      await replaceTokensInTemplates(checksum);\n      await execChocoPack();\n    } catch (e) {\n      console.error('Something went wrong', e);\n    }\n  });\n};\n\ngenerateChocoPkg();\n"
  },
  {
    "path": "desktop-app-legacy/scripts/chocolatey/responsively/responsively.nuspec",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Read this before creating packages: https://chocolatey.org/docs/create-packages -->\n<!-- It is especially important to read the above link to understand additional requirements when publishing packages to the community feed aka dot org (https://chocolatey.org/packages). -->\n\n<!-- Test your packages in a test environment: https://github.com/chocolatey/chocolatey-test-environment -->\n\n<!--\nThis is a nuspec. It mostly adheres to https://docs.nuget.org/create/Nuspec-Reference. Chocolatey uses a special version of NuGet.Core that allows us to do more than was initially possible. As such there are certain things to be aware of:\n\n* the package xmlns schema url may cause issues with nuget.exe\n* Any of the following elements can ONLY be used by choco tools - projectSourceUrl, docsUrl, mailingListUrl, bugTrackerUrl, packageSourceUrl, provides, conflicts, replaces\n* nuget.exe can still install packages with those elements but they are ignored. Any authoring tools or commands will error on those elements\n-->\n\n<!-- You can embed software files directly into packages, as long as you are not bound by distribution rights. -->\n<!-- * If you are an organization making private packages, you probably have no issues here -->\n<!-- * If you are releasing to the community feed, you need to consider distribution rights. -->\n<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->\n<package xmlns=\"http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd\">\n  <metadata>\n    <id>responsively</id>\n    <version>#VERSION#</version>\n    <packageSourceUrl>https://github.com/responsively-org/responsively-app</packageSourceUrl>\n    <!-- owners is a poor name for maintainers of the package. It sticks around by this name for compatibility reasons. It basically means you. -->\n    <!--<owners>__REPLACE_YOUR_NAME__</owners>-->\n    <!-- ============================== -->\n\n    <!-- == SOFTWARE SPECIFIC SECTION == -->\n    <!-- This section is about the software itself -->\n    <title>Responsively App</title>\n    <authors>Responsively Org</authors>\n    <!-- projectUrl is required for the community feed -->\n    <projectUrl>https://responsively.app</projectUrl>\n    <iconUrl>https://responsively.app/assets/img/responsively-logo.png</iconUrl>\n    <!-- <copyright>Year Software Vendor</copyright> -->\n    <!-- If there is a license Url available, it is required for the community feed -->\n    <licenseUrl>https://github.com/responsively-org/responsively-app/blob/master/LICENSE</licenseUrl>\n    <requireLicenseAcceptance>true</requireLicenseAcceptance>\n    <projectSourceUrl>https://github.com/responsively-org/responsively-app</projectSourceUrl>\n    <!--<docsUrl>At what url are the software docs located?</docsUrl>-->\n    <!--<mailingListUrl></mailingListUrl>-->\n    <bugTrackerUrl>https://github.com/responsively-org/responsively-app/issues</bugTrackerUrl>\n    <tags>responsively web-development responsive responsive-web-design desktop-app electron developer-tools good-first-issue contributions-welcome react redux javascript opensource-alternative</tags>\n    <summary>A modified web browser that helps in responsive web development. A web developer's must have dev-tool.</summary>\n    <description>\n# Responsively App\n**A must-have devtool for web developers for quicker responsive web development. 🚀**\nSave time by becoming 5x faster!\nA modified browser built using [Electron](https://www.electronjs.org/) that helps in responsive web development.\n\n![Quick Demo](https://responsively.app/assets/img/responsively-app.gif)\n\n## Features\n1. Mirrored User-interactions across all devices.\n2. Customizable preview layout to suit all your needs.\n3. One handy elements inspector for all devices in preview.\n4. 30+ built-in device profiles with option to add custom devices.\n5. One-click screenshot all your devices.\n6. Hot reloading supported for developers.\n\n## Get in touch\n\nPlease visit the website to know more about the application - https://responsively.app\n\nCheck it out our GitHub repo - [![GitHub stars](https://img.shields.io/github/stars/responsively-org/responsively-app.svg?style=social&amp;label=Star&amp;maxAge=2592000)](https://github.com/responsively-org/responsively-app) This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n\nFollow on Twitter for future updates - [![Twitter Follow](https://img.shields.io/twitter/follow/ResponsivelyApp?style=social)](https://x.com/ResponsivelyApp)\n\nSponsor this project on [Open Collective](https://opencollective.com/responsively)\n\nCome say hi to us on [Discord](https://discord.gg/tFG4E7bgkr)!\n  </description>\n  <releaseNotes>https://github.com/responsively-org/responsively-app/releases/tag/v#VERSION#</releaseNotes>\n    <!-- =============================== -->\n\n    <!-- Specifying dependencies and version ranges? https://docs.nuget.org/create/versioning#specifying-version-ranges-in-.nuspec-files -->\n    <!--<dependencies>\n      <dependency id=\"\" version=\"__MINIMUM_VERSION__\" />\n      <dependency id=\"\" version=\"[__EXACT_VERSION__]\" />\n      <dependency id=\"\" version=\"[_MIN_VERSION_INCLUSIVE, MAX_VERSION_INCLUSIVE]\" />\n      <dependency id=\"\" version=\"[_MIN_VERSION_INCLUSIVE, MAX_VERSION_EXCLUSIVE)\" />\n      <dependency id=\"\" />\n      <dependency id=\"chocolatey-core.extension\" version=\"1.1.0\" />\n    </dependencies>-->\n    <!-- chocolatey-core.extension - https://chocolatey.org/packages/chocolatey-core.extension\n         - You want to use Get-UninstallRegistryKey on less than 0.9.10 (in chocolateyUninstall.ps1)\n         - You want to use Get-PackageParameters and on less than 0.11.0\n         - You want to take advantage of other functions in the core community maintainer's team extension package\n    -->\n\n    <!--<provides>NOT YET IMPLEMENTED</provides>-->\n    <!--<conflicts>NOT YET IMPLEMENTED</conflicts>-->\n    <!--<replaces>NOT YET IMPLEMENTED</replaces>-->\n  </metadata>\n  <files>\n    <!-- this section controls what actually gets packaged into the Chocolatey package -->\n    <file src=\"tools\\**\" target=\"tools\" />\n    <!--Building from Linux? You may need this instead: <file src=\"tools/**\" target=\"tools\" />-->\n  </files>\n</package>\n"
  },
  {
    "path": "desktop-app-legacy/scripts/chocolatey/responsively/tools/LICENSE.txt",
    "content": "﻿\n\nFrom: https://github.com/responsively-org/responsively-app/edit/master/LICENSE\n\nLICENSE\n\n                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n\n"
  },
  {
    "path": "desktop-app-legacy/scripts/chocolatey/responsively/tools/VERIFICATION.txt",
    "content": "﻿\n\n\nVERIFICATION\nI'm the author\n\npowershell > (Get-FileHash -Path .\\ResponsivelyApp-Setup-#VERSION#.exe -Algorithm 'sha512').Hash"
  },
  {
    "path": "desktop-app-legacy/scripts/chocolatey/responsively/tools/chocolateyinstall.ps1",
    "content": "﻿$ErrorActionPreference = 'Stop';\n\n$toolsDir   = \"$(Split-Path -parent $MyInvocation.MyCommand.Definition)\"\n$url        = 'https://github.com/responsively-org/responsively-app/releases/download/v#VERSION#/ResponsivelyApp-Setup-#VERSION#.exe'\n$url64      = 'https://github.com/responsively-org/responsively-app/releases/download/v#VERSION#/ResponsivelyApp-Setup-#VERSION#.exe'\n\n$packageArgs = @{\n  packageName   = $env:ChocolateyPackageName\n  unzipLocation = $toolsDir\n  fileType      = 'exe'\n  url           = $url\n  url64bit      = $url64\n\n  softwareName  = 'responsively*'\n  checksum      = '#CHECKSUM#'\n  checksumType  = 'sha512'\n  checksum64    = '#CHECKSUM#'\n  checksumType64= 'sha512'\n\n  silentArgs    = \"/S\"\n  validExitCodes= @(0, 3010, 1641)\n}\n\nInstall-ChocolateyPackage @packageArgs"
  },
  {
    "path": "desktop-app-legacy/scripts/chocolatey/responsively/tools/chocolateyuninstall.ps1",
    "content": "﻿$ErrorActionPreference = 'Stop';\n$packageArgs = @{\n  packageName   = $env:ChocolateyPackageName\n  softwareName  = 'responsively*'\n  fileType      = 'EXE'\n  silentArgs    = \"/S\"\n  validExitCodes= @(0, 3010, 1605, 1614, 1641)\n}\n\n$uninstalled = $false\n[array]$key = Get-UninstallRegistryKey -SoftwareName $packageArgs['softwareName']\n\nif ($key.Count -eq 1) {\n  $key | % { \n    $packageArgs['file'] = \"$($_.UninstallString)\"\n    \n    if ($packageArgs['fileType'] -eq 'MSI') {\n      $packageArgs['silentArgs'] = \"$($_.PSChildName) $($packageArgs['silentArgs'])\"\n      $packageArgs['file'] = ''\n    } else {\n\n    }\n\n    Uninstall-ChocolateyPackage @packageArgs\n  }\n} elseif ($key.Count -eq 0) {\n  Write-Warning \"$packageName has already been uninstalled by other means.\"\n} elseif ($key.Count -gt 1) {\n  Write-Warning \"$($key.Count) matches found!\"\n  Write-Warning \"To prevent accidental data loss, no programs will be uninstalled.\"\n  Write-Warning \"Please alert package maintainer the following keys were matched:\"\n  $key | % {Write-Warning \"- $($_.DisplayName)\"}\n}\n"
  },
  {
    "path": "desktop-app-legacy/scripts/extraPublishFiles.js",
    "content": "const {generateChecksums} = require('./generate-checksums');\nconst {version, build, productName} = require('../package.json');\nconst path = require('path');\nconst fs = require('fs');\n\nconst getExtraPublishFiles = () =>\n  generateChecksums().then(files => {\n    console.log(`\\nExtra Files Included:\\n${files.join('\\n')}`);\n    return files;\n  });\n\nexports.default = getExtraPublishFiles;\n"
  },
  {
    "path": "desktop-app-legacy/scripts/generate-checksums.js",
    "content": "const path = require('path');\nconst fs = require('fs');\nconst crypto = require('crypto');\nconst pkg = require('../package.json');\n\nconst version = pkg.version;\nconst requiredFiles = [\n  `Responsively-App-${version}.x86_64.rpm`,\n  `ResponsivelyApp-${version}-mac.zip`,\n  `ResponsivelyApp-${version}.AppImage`,\n  `ResponsivelyApp-${version}.dmg`,\n  `ResponsivelyApp ${version}.exe`,\n  `ResponsivelyApp Setup ${version}.exe`,\n];\n\nfunction hashFile(file, algorithm = 'sha512', encoding = 'hex', options = {}) {\n  return new Promise((resolve, reject) => {\n    const hash = crypto.createHash(algorithm);\n    hash.on('error', reject).setEncoding(encoding);\n    fs.createReadStream(\n      file,\n      Object.assign({}, options, {\n        highWaterMark: 1024 * 1024,\n        /* better to use more memory but hash faster */\n      })\n    )\n      .on('error', reject)\n      .on('end', () => {\n        hash.end();\n        resolve(hash.read());\n      })\n      .pipe(hash, {\n        end: false,\n      });\n  });\n}\n\nconst RELATIVE_FOLDER_PATH = '../release';\nconst CHECKSUM_SUFFIX = '.checksum.sha512';\nconst SKIP_SUFFIX_LIST = [CHECKSUM_SUFFIX, '.yml', '.yaml', '.txt'];\n\nconst generateChecksums = async () => {\n  const result = [];\n  const installerPath = path.resolve(__dirname, RELATIVE_FOLDER_PATH);\n  console.log(\"\\nGenerating checksum files for files in: '%s'\", installerPath);\n\n  for (const file of requiredFiles) {\n    if (SKIP_SUFFIX_LIST.some(s => file.endsWith(s))) continue;\n\n    const filePath = path.join(installerPath, file);\n    const stat = await fs.promises.stat(filePath);\n\n    if (stat.isFile()) {\n      const checksumFile = `${file.replace(/ /g, '-')}${CHECKSUM_SUFFIX}`;\n      const checksumFilePath = path.join(installerPath, checksumFile);\n      const checksum = await hashFile(filePath);\n      await fs.promises.writeFile(checksumFilePath, checksum);\n      console.log(\"'%s' -> '%s'\", checksum, checksumFile);\n      result.push(checksumFilePath);\n    }\n  }\n  return result;\n};\n\nmodule.exports = {generateChecksums};\n"
  },
  {
    "path": "desktop-app-legacy/scripts/notarize.js",
    "content": "require('dotenv').config();\nconst {notarize} = require('electron-notarize');\n\nexports.default = async function notarizing(context) {\n  const {electronPlatformName, appOutDir} = context;\n  if (electronPlatformName !== 'darwin') {\n    return;\n  }\n\n  const appName = context.packager.appInfo.productFilename;\n\n  return notarize({\n    appBundleId: 'app.responsively',\n    appPath: `${appOutDir}/${appName}.app`,\n    appleId: process.env.APPLEID,\n    appleIdPassword: process.env.APPLEIDPASS,\n  });\n};\n"
  },
  {
    "path": "dev.code-workspace",
    "content": "{\n\t\"folders\": [\n\t\t{\n\t\t\t\"path\": \"desktop-app\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"browser-extension\"\n\t\t}\n\t],\n\t\"settings\": {\n\t\t\"files.associations\": {\n\t\t\t\".babelrc\": \"jsonc\",\n\t\t\t\".eslintrc\": \"jsonc\",\n\t\t\t\".prettierrc\": \"jsonc\",\n\t\t\t\".stylelintrc\": \"json\",\n\t\t\t\".dockerignore\": \"ignore\",\n\t\t\t\".eslintignore\": \"ignore\",\n\t\t\t\".flowconfig\": \"ignore\"\n\t\t},\n\t\t\"javascript.validate.enable\": false,\n\t\t\"javascript.format.enable\": false,\n\t\t\"typescript.validate.enable\": false,\n\t\t\"typescript.format.enable\": false\n\t}\n}"
  }
]