Timeline performance improvements
This commit is contained in:
parent
92aff32155
commit
1dad31efab
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUTW/bOhD8KwLPpsIvUaJugdPiGXltjNZoi94o7qoholAGSaUNAv/3grKLNKeHdyGIFWc5OzPUCwn2EUlPro/Hd08YciIbkv1aEkxoyjRl4sBN38iet7XkmstWfCcb4m/xmfSkVc5xZ5BK1EgVsIYOcnQUAA1nHJhoVOlpfyTSvxDra8An77Ce0xeMyc+B9OSrDzD/LHdbX7tpXqCO84S7kLINrpD5uNve0n+v94e7/flUwlTAtYdCFUTHJHR0YK2iCnVLDQdFXWMG7Zi0QpgzzIeMMdipTvDwej/MOWB2vaiFrBkVhpw2BGy2hfJgEx6ej4XFKtFNqW/W8rrtX8gTRtKLzR8xz+2u3OSvHtOw+Amuzmu26SGt62hdnuMz2ZBjnI8Ys8dVn+uU8HGYng82Pbw/nynb9O4XuiUjbOclZNITrjjZkFWgabLZz6G6cNxOHkNeP+boQ/Luv3oVf+4+V/vJ5nGOj2/8+HyWefc/VP5g3b0PWO1uSE8YRwGyQzoKrqnqwFLbSEO1lnpE7kwryiDbOWQflnlJ1S5k/BHXkUhP3tsp4ZnfdXT3PqPLSyxzftOF9z7OsLhcvXrJWc1qyVgBLfm45OoTgo/oMpYhDnG59PvyFiK0WEHbJUYMudrbfF/9Y9M96YmxnePWWGOtdm7EhuHIgFnNWkANGkEJaEEZBVoZ5rSyA1dgGz6MpUI2BEt0qjWtbByN0cxRK1tGVdc6OnSOU9N2nInOgjAlYK86VndTwVnXMYUgqcERqDLcUqOkpCAb2wo9tq0Y/5LkkgfOisCAT+d3tzLQAB2C5lQo3lBlDKedaVo6tN1gBt1h4wrnW4wBp7+E+uBdnNM85uqSkOqNdJ+WUP4d1ZqWnz7QX6tHN7N7wFgVj60P5alcbD2dTqffAAAA//8DAH1Y7jeCBAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTTW/cOAyG/4qhs+Xoy7LlWzDZRQdpN4M2aIveaJHuCHXkgS2nLYL57wt5pki7t70JlEg+fF/qhUV4Itax29Ppr2eKaWElS2ELKaEsF5YL9ShdV+tONpVW2igjv7CShXv6yTrWGO+ld8Q1WeIGRc17PXiOSE4KiULVJteErwvrXhiECuk5eKqm5SPNS5gi69inEHH6nntDqPw4rVjN00j7uCSIPsP8s9/d87e3h8eHw+XVQktOrgJmVFSt0NjyXjSGG7INdxIN97XrrRcalHKXtBATzRHGasFvr/1xSpGS71SldCW4cuxcMoQEGbmHhR5/njLFJtFdjpdbeDt2L+yZZtap8peYl3I3fgw3fnp6gog3Q4hhObKSnebpRHMKtMmxTTiOkMIUi2uT3RgoJlayhw/FYYQ0TPPTHxp9uIy+/x+TvwN/DJGK/R3rmJCkULfEByUtNy0Ch1o7bq22A0nvGiVZyXZTTCGu07oU+5jo67xRso79DeNCF77b2R9DIp/WOaN/ttlr+hHSbsIcEKxkh3nC1afiVW4pKlFpkS8f1nRaU/GeMMzkE+WZHuf1Wv4/KcqqLWm3zjPFVBwgHYs3sBxZxxy0XoIDB2C9H6gWNAgUYEWDZNESGoUNGmfQGie8NdBLg1DLfsiRzJ3dLbaFGtABoTC8h6blpjaSO6uBSwutVVobkBnkVdbiYcx54FthCDV3NCA3TgJ3RmuOuoZG2aFp1PCbJFfHpch6Iz1fvsZGYBFbQiu5MrLmxjnJW1c3vG/a3vW2pdpn5nuaI42/CfUu+HlapiEV14Up/pDu/Rrz9y625fkeIv+xWXY3+W80F9lyCDFv89Xl8/l8/hcAAP//AwCN39GdJQQAAA==
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS6Som2G3qOGmWcRGW/Q25IxiIVppQVGbBob/e0HtFol7640YcIYvn3lnXtkMR2I9uz2dfjrTnFdWszzuISWU5cJyoZ6l71vXy7ZxznSik3+xmo2P9I31zJkYZfTENVniBkXLgx4iRyQvhUShWlNqwueV9a8MxgbpPEZqlvV3Suu4zKxnf4wzLl/L2zA2cVo2bNIy0cO8ZphjEfPbw90j//X28PzxcLm10lqSmxFZz8AHMN1gOanguAEgHlwAThGJWgKpTbykjXOmNMPUrPjl+/u45Jly7FWjdCO48uytZggZiuQAKz1/OxUVO6L7Eq/38H7sX9mZEutV/S/MS7mbOI036xYmOtN0grRSuonL8Qgzspqd0nKilEfasZwphWUdcyEao4OukxI7cKZ12OlWaYdaEHbCtejRgzV2ULFtBwydd2YwqKRBS8YEx2q2g5smyOMyV1ftd9NIc2Y1+/hUHSbIw5KO79A/XYg+/A+gRTbrmRatts6R0VqDt0EJQmejtSpEEG4IimQk0XrjvHRCaq+E1611nUMQvlT6APFlnKl6uGc9E5IU6o74oKTlpkPg0GrPrdV2IBm9U5LV7G6Z8zhvy7ZWD3Omz2n/L+vZzzCtdPnpbYovY6aYt1Qg/GmLGQ9pwS3m6rsBpGhEo4UoSVs+bbn6RDgmipkKjue0Xev9J0VZtSfdbSnRnKsD5JfqF1hfWM88dFGCBw9gYxyoFTQIFGCFQ7JoCY1Ch8YbtMaLaA0EaRBaGYYSYTWj4rdqtziBH2hAy8Fp4qZTinchOC6UNcHpIAWGdxyrj9PeydgJQ6i5pwG58RK4N1pz1C04ZQfn1PADkqtZpCiAkc6XYd0VWMSO0EqujGy58V7yzreOB9cFH2xHbSyaHynNNP0A6sMY07IuQ66uXqveofu0zWXhVLvvvo4z/3vv0f0Sv1CqSo9hnMt8Xdv6VrMjwbolOu77qn9lhzJc1fO+trRopNK6Zk8ZUt5O17DVjZGqfXt7e/sHAAD//wMAlFq6YfQEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS6Som2G3qOumXsSLtOhtSI5sIVpqQVGbBsb+94DaDRL31psw0DszfOadeSMRDkh6cns8/nLCmBdSkzxuIcGEpkxTJvbc9q3pedsYqXkn+T+kJuMjfiU9Mcp77i1SiRqpCqylTg6ehoCWMx6YaFXJCS8L6d8IjE3A0+ixmZdPmJZxjqQnf40xzF9KbRgbP81raNI84UNcMkRfmvnz4e6R/nG72z/tLn8tuBRxMwbSE7AOVDdoisIZqgCQOuOAog+ILQKXyl9kY8yYIkzNEj7/qB/mHDH7XjRCNowKS841CZChtOxgwf3XY+liQ3Rf4vUW3j77N3LCRHpRf4d5SXfjp/Emz8cJTzgdIS2Ybvx8OEAMpCbHNB8x5RE3LNtLpwnyOMfqWuxuGjFmUpOn52o3QR7mdHjH6vmC4OF/EDhhcqQnkrVSG4NKSglWO8EwGO21Fs4DM4MTyD2y1ipjuWFcWsGsbLXpTABmS6aXaXYw/b5s/KxgrgvMCC1QoRVKa+sZyg6H1iALoLXTnbdBdkaoLnQSLAC3Do232heDfAD/OkasHu5JTxhHEWSHdBBcU9UFoNBKS7WWekDurRGc1ORujnmM67wu1UPM+JI2fqQnv8K04IXcbfKvY0af11Sg/q1LsV2aw+pz9cMBnDWskYwV0ZqPa64+YhgT+owF7z6t13z/kQgtNtHdmhLGXO0gv1a/wfJaqEDnOdjyVO39gC3DgQUGmpmAOmgMSgQTlFVBK8u8VuC4CtByN5QIqQkWw1WbxwdoUXZgqJC+parVnlqwnHZdkIDGO8b9O47V07Q5w3dMYZDU4hCoshyoVVLSIFswQg/GiOEnJFfzcVYABzxdtnXrQIfQYdCcCsVbqmwpbltDnemcdbrD1peeHzFFnH4C9WH0aV7mIVdX71bv0H1cY7k41ebjL2Ok/24zup/9Z0xVmTGMsSzYdaznmhwQljXhYTtY/RvZle2q9tvdkqzhQsqaPGdIeT1ew1o2iov2fD6fvwEAAP//AwBUXko/9QQAAA==
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUzW7jNhSFX0Xg2lRIiqJE7VKngxqZTIxJOi26uyKvHCIyaVBUpkGQdx9QdhBnBl10J1zx/n3nkC/Ewx5JRy4Ph9+f0KeJrEhyS0gwoShTlIl7rru66XhdtqzmrFL/kBVx1/hMOtJIY7jRSCtUSKVlNe2rwVBrUXPGLRO1zDVhN5HuhYArLT45g2WYvmGcXPCkI385b8P33BtcacYw2zKGETd+SuBNHubLZn1NP19u72+3x1MTTjm5dJZ0BHQPsh0URdE3VAIg7ZseKBqLWCPwSppjmvMJo4exnOzje38bksdkOlGKqmRUaPK6IhYS5JF7mPD++ZCnWBBd5fhqCS+f3Qt5wkg6sXqDeSx3YUZ3sZ/62Y32Ygy7nfO7dfCD280RUm68IocYDhiTwwXOb85DfP4cdrtcj3yCcUKyIvcY987DePxxhQPMY7oLc1zITPbxv85kMnMKZEUWkuO4tC1Oy6xHhz79knvW+Pau2I6QhhD3H0S6O7Lf/A/0N2AenMdic0U6wjgKW7VIB8EVla0FCnWlqVKVGpAb3QhOVmQdfHJ+DvNUbHzC3Ynah/kuo3lwCU2aY97pb5XNto3BziYV7wJzVrKyYiwnzekwp+IrWhfRJMxL3Mf5VO+nFKHEkvTJjfgLnvUcI/pUbCE9FH/A9EA6oqE1HDRoAGXMgDXDgVkGijUWlVVopbCNlVpaJTUzSkLPpYWa90OOkBXBbLJi8TUfBGhAQ3mLNZVVjxS0MLSqGylkpfUwDB/gFrfjIoppmURbUY2DpVJzoFpWFbVVDY1QQ9OI4YzTyRCcZernJvxzQnvy0pejtd92t/h0vMfLnMraFq3iVEheU6k1p62uG9o3ba971WJt8mbr4KfwBvIbxj5MLuVH5MZ5t4fx5yPvylxj9DieqXPjTAxTGFJxsmXxQa+vs8+vWLFY9Lvz9N/FGFfBPGIssrHA+TMxX19fX38AAAD//wMAr++TXgwFAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUTW/bOBD9KwLPokJS1AcF7MFwWqyRpjESd3exN4ocxkRk0aAop0GR/76gJNdO0cPehNF8vHnvDX+gXh4ANWh1PH46QR8GlKJgpxAjrMSkxITtqGiKqqFlxipOKaP/ohTZO3hDDaq4UlQJwDmUgLkmBW5zo7DWICihmrCCx57yeUDNDyRtpuFkFWRu+Av8YF2PGvS37bV7jbOlzVTnRp1518GmH4LsVQTzdbO+w19W293Dds4aYIjFmdWoQVK0ktemxMDaCnMpAbdVKzEoDVCApDlXc5ntA/hedtmgXy7ztQs9BNWwjOUZwUyg9xRpGWSE3MoBdm/HiGKi6DbG0yk8fTY/0Ak8alh6JnNud6M6e3MY2tF2+iZI/wzBeHmAV+df4CQ7lKKjd0fwwcLEzcNTcoFESUYyVjJCUIrupdrbHpLNbfLQTQurmnDQORZgNOaCSix4nmOdF7JipakqZlCK7sD30F11vbfKu8GZkCyUJx/mrF0fbD+6cUg2fYBnL8Nc91l2A6AUbce2s8P+EaR+27nHMf7rxy7ush69hz4kWxn2yZ9y2KMGCVkrKoUUUpZKGSgIGKKJLEmlodQlaM50pbnguuSCqJLLlnItC9qaGEEpehjDcQxn/r9fgdh5eziAviDYTRxvOxmM84eNhj5YY6M054zJUF03bZUsPdedhT58IBk1iFBgOq8BG0ZLzGstsSxygcsyLw1QJSpGZ8KMfR5/8kSM0Tw3RV2XrWgpAWaqmpPKtEQrkyuhixYqVpGqhlooQtqC07oVimigBddxO+/0qMKvVsgngR7HPl5nsolrv9oefy/5hZGtd8Ep10XBbAdPb0OAw+9UW7vD0Q02xP3NIu23Ae5dH/8u979wthSvXLjEIN5BMp2eriSYghdYVBIwNzVgCdzgGog2NSeEqAKl6NapF/BJNJi0/aTJ2VMPT8nKq70NoMLo4+R/pqVmOT+fT+bCR/b1027tPKyOx3SJ/nGKJM3Nzgb48K58G2L63g1xi+DHKx89QWfOuK7cpOE0v1Q2Bkuta9AlxYzTAnMhKK5FUeG2qlvRljUU6uLW5BG09aDC1G83T1uI/Z0tVz5YI1UY4u18cWpy02LPJeVXkFeq/ayeh18ptzhp6URJdOzT/G5OBvpfz+b7+/v7fwAAAP//AwB0fDGlJwYAAA==
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTTW/cNhD9K8KcRZmkKErUzVin6MJNvEgWadHbiBzFhGVqQVFOAmP/e0DtBk5664UgBnwz72P4CgGfCXq4PZ3evVBIC5SQ/FaSXGrGNePyKEzftL2sq7btlNT1v1CCv6fv0EOrrBXWEKtJE1OON2yoR8ucIyO4cFw2KvfELwv0r4C+cvTiLVXz8pni4ucAPfztg5u/5tnoKzvNq6viPNE+LAmDzWQ+7Hf37K/bw/HhcHm10JLBlXfQA5oBVTdqRnJomUIkNrQDMrKOqCEUtbIXmA+JYsCpWtzT23w3p0DJ9rKSdcWZNHAuwWHCTHnAhY7fT5nFZtFdrpdbebv2r/BCEXpZ/jTz0u7GTv7meRlWP7mby5lwecoqT3E+UUyeNk82mdOEyc+huE7aTZ5CghIePhWHCdM4x+ffjPp00b//H/KPefq7b2TXRG43ryFBD0I1UMJ7tI8+ULG/gx64IOnqjtgohWaqc8iwqQ3TutYjCWtaKaCE3RySD+u8LsU+JPoSNwHQwx84LXShfhvto09k0xqzqn903oVDnN1qU/Hmv+AVr2rOM2hNpzUVH8n5SDZR1neM67XffyBSyw20W2OkkIoDpsfiT1weoQeDnRVo0CBqa0dqOI3ccdS8daSdJqeka50yymlluNUKB6EcNmIYcwVKoBx3sW2YNkRcaMtka5EprQc2KNuxQQ1167BrrMzS3nwsHqYtGttxRa5mhkbHlBHIjKpr5uoGW6nHtpXjL5Zc0xc8G+zo5fJXLgyc68hpwaQSDVPGCNaZpmVD2w1m0B01NnO+pxho+sWo997GeZnHVFyXp/jNuo9ryP+92Bbpqw/s25bR3WyfKBY5Y/Qhr/c11vP5fP4BAAD//wMA//ibKjYEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTwW7bOBD9FYFnUSEpihJ1C5xd1Ei7MdqgLXobcUY1UYUyKCptEfjfF5RdpN3b3ohHvpk37w1fWIAnYj27PZ3+eqaQFlay5DdICWW4MFyoR2n7pu1VXbWd6FohvrCS+Xv6yXrWaueks8RrMsQ1ioYP9eg4IlkpJArV6FwTvi6sf2HgK6Rn76ial48UFz8H1rNPPuD8PfcGX7lpXrGK80T7sCQILov5Z7+7529vD48Ph8urhZZMrjyynoEdQHej4aSGlmsA4kM7ACeHRA2BrLW70HxIFANM1YLfXvvjnAIl16tK1ZXgyrJzyRASZMkDLPT485RVbBbdZbzc4O3Yv7BniqxX5S8zL+Vu3ORv3Pz0BAFvRh/8cmQlO8X5RDF52uzYJpwmSH4OxbXJbvIUEivZw4fiMEEa5/j0h0cfLqPv/8fk78AdfaBif8d6JiQprDvio5KG6w6BQ1NbbkxtRpLOtkqyku3mkHxY53Up9iHR17ipZD37G6aFLvpuozv6RC6tMUv/bHLW9MOn3YwZEKxkhzjj6lLxarcUlahqkS8f1nRaU/Ge0EdyifJMj3G9lv8PRRm1kXZrjBRScYB0LN7AcmQ9s9A5CRYsgHFupEbQKFCAES2SQUOoFbaorUajrXBGwyA1QiOHMSNZd0632BYKjdC21cglNQPXUozcggROgMKOrXCmzYxXW4uHaYvDdUIT1tzSiFxbCdzquuZYN9AqM7atGn+z5Jq4FNlvpOfL19gUGMSO0EiutGy4tlbyzjYtH9pusIPpqHFZwT3FQNNvRr3zLs7LPKbiujDFH9a9X0P+3sW2PN994D+2yO5m941ikSMHH/I2X1M+n8/nfwEAAP//AwAfRvRKJQQAAA==
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUwW7cNhCGX0XgeSmTFEWJuhl2gi7c1IvESIveRpxRTFimFiTlxDD23Qtqt0h8KnohiBFn+M/3D/XGAjwTG9j18fjhhUJObMey30JKKMOF4UI9SDu03aCauuta06j2b7Zj/o5e2cA67Zx0lnhDhrhG0fKxmRxHJCuFRKFaXWrCt8SGNwa+RnrxjuolfaWY/BLYwP70AZfv5W7wtZuXFeu4zLQPKUNwRcwf+5s7/vv14eH+cD6VKJXk2iMbGNgRdD8ZTmrsuAYgPnYjcHJI1BLIRrtzmg+ZYoC5Tvj0835ccqDsBlWrphZcWXbaMYQMRfIIiR5ej0XFhui2xHdbeNsOb+yFIhvU7l+Y53JXbvZXz2lc/YxX5zVDekrbOoHLS3xlO3aMy5Fi9rTxuU6Jnsf59QHS08fzmbJNH36QWzPhzbKGzAYmtWQ7tgGaZ8h+CdVF483sKeTtY44+JO/+q1bx5/5LdZghT0t8fufHlzPm/f+g/Ancow9U7W/ZwIQkhU1PfFLScN0jcGgby41pzETS2U6VRm6WkH1YlzVV+5DpW9xaYgP7CHOis77r6B59JpfXWPr8yxTdh7jg6nL100spalE3QpSkNR/XXH0m9JFcptLEQ1wv9b6+T1FGbUk3a4wUcnWA/Fj9BumRDcxC7yRYsADGuYlaQZNAAUZ0SAYNoVbYobYajbbCGQ2j1AitHKcSYTtGZXSqbVrbroHWTJr3I3VcS7QcJBreCbI4WWnHtn/HsbqfN/6uF5qw4ZYm5NpK4FY3DcemhU6ZqevU9AuSyzxIUQAjvZzf3abAIPaERnKlZcu1tZL3tu342PWjHU1PrSua7ygGmn8B9cm7uKRlytVlQqp36D6vofw7qm1avvvAf2we3S7uiWJVPAYfylO52Ho6nU7/AAAA//8DAIN+pxyCBAAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTwY7cKBCGX8XibDyAMTa+jXp2ldYkO61klKz2hqkijeKBFsaTRKN+9xXujpLJLTdUUFV/fX/xQoJ5QjKS29Ppr2cMeSE1yX4LCSYUZYoy8SjYyMXY6UawXnWD+I/UxN/jdzKSXlrLrUbaokIqgXV0ap2lAKg548BEJ0tN83kh4wsxvgF89habuHzEtPgYyEg++QDxa+ltfGPnuEKT4oz7sGQTbBHzz353T9/eHh4fDpdXCy4lufFARtK1YHopGDWDslQ6ZHSYnKNKaQlKdpINcEnzIWMKZm4W+PKzP8QcMNtRNKJtGBWanGsCJpsieTILPn4/FRUborsSr7fwdhxfyDMmMor6B8xLuRs7+xsbn55MgBvng1+OpCanFE+YsscNxzbhPJvsY6iuTXazx5BJTR4+VIfZZBfT0ytGHy6j7/9g8nfGHn3Aan9HRsI4CmgHpE5wReUAhpqu1VSpVjnkVveCk5rsYsg+rHFdqn3I+DltKslI/jbzghd9t8kefUab11Sk/6uK1/jN512EEmCkJocUYbW5+ombs4Y1LSuXD2s+rbl6j+AT2oxlpse0Xsv/liKU2JJ2a0oYcnUw+Vi9McuRjESbwXKjjTZGWeuwY+gYMKNYD6hAIUgBPcgNi2ZWSTNxCabjkyuRoru4W20LNYAQnUNNBQhOJWigRrUTVQxagMG5blCvsFYPc8kzdmASoaUaHVCpuaFati2FtjO9UK7vhfsFydVxzgpvwOfL19gUKIABQXEqJO+o1JrTQXc9nfph0pMasLNF8z2mgPMvoN55m+ISXa6uC1O9Qvd+DeV7V9vyfPWBftssu4v2C6aqWG58KNt8dfl8Pp//BwAA//8DAJt2xo0lBAAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTQW/cNhCF/4owZ1EmKYoSdTPWKbpwEy+SRVr0RnFGMWGZWlCUk8DY/x5Qu0Hi3noRhAEf571vhq8Q7DNBD7en07sXCmmBEpLfSpJLzbhmXB4l74XsG1NJrnSnzb9Qgr+n79BDq5wTzhCrSRNTyBs21KNjiGQEF8hlo/Kd9ssC/StYXyG9eEfVvHymuPg5QA9/+4Dz19zb+spN84pVnCfahyXZ4LKZD/vdPfvr9nB8OFxOLbRkceURemhqtK2SnNlOO6ZG4qwbxpFpbRRq1Sje4UXmQ6IY7FQt+PSrP84pUHK9rGRdcSYNnEtAm2y2PNiFjt9P2cWG6C7Xy628/fav8EIReln+hHm57sZN/uZ5GVY/4c3lm+zylFOe4nyimDxtTLaY02STn0Nx7bSbPIUEJTx8Kg6TTeMcn9+A+nTJv/8f8Y+5+7tv5NZEuJvXkKAHoTSU8N66Rx+o2N9BD1yQxLojNkqhmerQMtvUhmld65GEM60UUMJuDsmHdV6XYh8SfYlbAOjhDzstdLF+G92jT+TSGnOqf3TehUOccXWp+MVf8IpXNedZtKbTmoqPhD6SS5TzHeN6ve8/EqnlJtqtMVJIxcGmx+JPuzxCD8Z2TlhjjbXauZEaTiNHbjVvkTRqQiWxRbUhMtxpZQeh0DZiGHMFSqA87mLbMMt1I0Sj2EiNY6rtJDMGFaPREklXt13TvuFYPEybznVcEdbM0IhMGWGZUXXNsG5sK/XYtnL8Dcl1+oJnwEgvl7eyOdCIHaEWTCrRMGWMYJ1pWja03WAG3VHjsud7ioGm30C99y7Oyzym4ro8xRt0H9eQ33uxLdJXH9i3bUZ3s3uiWOQZWx/yel/Hej6fzz8AAAD//wMAR26UADYEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUW2/cNhCF/4rAZ1Hm/aI3w25Rw02ziI226NuIM4qFaKWFRG0aGP7vBbVbJO5b34SBzvDwmzN8ZRMcibXs9nT66UxTXlnN8rCXlFCOC8eFelailbq1sfFGhxjsX6xmwyN9Yy3zJiWZInFNjrhBYXmn+8QRKUohUShrSk/4vLL2lcHQIJ2HRM28/k7LOswTa9kfw4Tz13I2DE0a5w2bZR7pYVozTKmY+e3h7pH/ent4/ni4/LXSWsTNgMWqtjH2aHgM0HPjlePBoeGKgk0gAgQDF9kwZVomGJsVv3w/H+c8UU6tapRuBFeRvdUMIUOx3MFKz99OxcWO6L7U6728f7av7EwLa1X9L8xLu5s0Djfr1o10pvEEy0rLTZqPR5iQ1ey0zCda8kA7ljMt3bwOuRBNyUMIUmIAb6zHoK3SHrUgDMJbjBjBGderZG2PXYje9AaVNOjImM6zmu3gxhHyME/V1fvdONCUWc0+PlWHEXI/L8d36J8uRB/+B9Bim7VMC6ud92S01hBdpwShd8k51SUQvu8UyUTCRuOj9ELqqETU1vngEURMrGYfIL0ME1UP96xlQpJCHYj3SjpuAgIHqyN3TrueZIpeSVazu3nKw7TN21o9TJk+L/t9Wct+hnGly01vl/QyZEp5WwqEP10J42GZcUu5+h4AKRrRaCGKaMunLVefCIeFUqaC43nZrv3+I1FO7aK7bVloytUB8kv1C6wvrGURQpIQIQK4lHqygnqBApzwSA4doVHo0USDzkSRnIFOGgQru75UWM2o5K3aI05Sd0II5N75npvO9DwEQh5sTFo4DWD9O47Vx7HoIAVhCDWP1CM3UQKPRmuO2oJXrvde9T8guYZFigIY6XxZ1t2BQwyETnJlpOUmRslDtJ53PnSxc4FsKp4faZlo/AHUhyEt8zr3ubpmrXqH7tM2lQen2nP3dZj43/uM7uf0hZaqzBiGqezXdaxvNTsSrNtCx/29al/ZoSxX9bw/W8o3ToZQs6cMS95O17IJjXXGvr29vf0DAAD//wMARwDop/QEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjz+0M3w25R1029iBdp0duIHNlCtNKCojYNjP3vAbUbJO6tF0EYcGZePvMO38gEByQtuT0efznhlBdSkzxsIcGEocxQJvaCtVy22jeWScO4/ofUZHjEr6QlVoXAg0cq0SBVkWnayT7QGNFzxiMTWpWa8LKQ9o3A0EQ8DQGbefmEaRnmibTkr2GK85fSG4YmjPMamzSP+DAtGaZQxPz5cPdI/7jd7Z92l1MLLiW5GWKRKrX3fVTUO+ipssJQZ6KiAp0OwBw4BZe0YcqYJhibJX7+0T/OecIcWtEI2TAqPDnXJEKGIrmDBfdfj0XFhui+xOstvP22b+SEibSi/g7zUu4mjMNNno8jnnA8Qlow3YT5cIApkpoc03zElAfcsGw3HUfIwzxV12Z344BTJjV5eq52I+R+Tod3rJ4vCB7+B4ETpo60RDItjbWopJTgTScYRmuCMaILwGzfCeQBmfbKem4Zl14wL7WxzkZgPpCavIxzB+Pvy8bPC9a5yKwwAhV6oYzxgaF02GuLLIIxnXHBR+msUC46CR6A+w5t8CYUg3yA8DpMWD3ck5YwjiJKh7QX3FDlIlDQ0lNjpOmRB28FJzW5m6c8TOu8LtXDlPElbfxIS36FccELudsUXoeMIa+pQP3blGa7NMc15OqHAzhrWCMZK0lrPq65+ohxSBgyFrz7tF7r/SdFGLEl3a0p4ZSrHeTX6jdYXgsVcIGDL1c1IfSoGfYsMjDMRjTRYFQi2qi8ikZ5FoyCjqsImnd9iZCaYDFctXncg+5602uqZPkYgdRxzqhF7kSURnbaveNYPY0lD4JjCqOkHvtIledAvZKSRqnBCtNbK/qfkFzNx1kBHPF02dZNgYnRYTScCsU1Vd5z6ry2tLOu851xqEPR/IhpwvEnUB+GkOZl7nN19W71Dt3HdSovTrX5+Msw0X+3Gd3P4TOmqswYhqks2HWs55ocEJY14WF7sNo3sivbVe23d0vYxnDnavKcIeX1eA0r12ij9Pl8Pn8DAAD//wMAZQmTEfUEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/jNhCG/4rAs6hQFEWJuqVOFzWy2Rgbd1v0NiJHDhGZNCgq2yDwf19QdhBnFz30Joz4zsczL/lKHOyRdOT6cPj9GV2cSE6iXUKccUmZpIxvOevKqqtV0TR1xcr2H5ITe4svpCON0LrUCmmFEqkwrKZ9NWhqDKqSlYbxWqScsJtI90rAFgafrcbCT98wTNY70pG/rDP+e6oNttCjn00R/IhrN0VwOjXzZb26pZ+vN9v7zenUhFMSF9akVqtaqcEIqloYqGi4pK00gnJsaw2shVbASWZdxOBgLCbz9F7f+Ogw6o4XvCoY5Yocc2IgQmq5hwm3L4fUxYLoJsXzJbx8dq/kGQPpeP4G85TuSo/2aj/1sx3N1eh3O+t2K+8Gu5sDxFQ4J4fgDxiixQXOb9ZBePnsd7uUj3yCcUKSky2GvXUwnn7c4ADzGB/8HBYyk3n6rzOkIzBHT3KykBzHpWx2HmY1WnTxF+1F4fuHbDNCHHzYf1jSw4n9+n+gvwP9aB1m6xvSEVYiN1WLdOClpKI1QKGuFJWykgOWWjW8JDlZeRetm/08ZWsXcXem9qG/66AfbUQd55Bm+lsms22CN7OO2fuCS1awomIsieZ4mGP2FY0NqCOmIbZhPuf7ScIlX0Sf7Ii/4FnNIaCL2QbiY/YHTI+kIwpaXYICBSC1HrBmODDDQLLGoDQSjeCmMUIJI4ViWgroS2GgLvshRUhOMJksW3zNjBiMrBQFUTMq+NDTViOjptZVrRhP4Q9ws/sx6UC3TKCpqMLBUKFKoEpUFTVVDQ2XQ9Pw4YLT2RAlS9QvTfjnhObspS8na7/NbvD5dI+XPqUxLRpZUi7KmgqlStqquqF90/aqly3WOk228m7ybyC/Yej9ZGN6RO6ss3sYfz7yvplbDA7Hi+3cWR385IeYnW2ZfdjX19mlVyxbLPrdOvrvYowbr58wZMlYYN3FMo/H4/EHAAAA//8DAHDfcRcMBQAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTwW7UMBD9lcjnOLUdx4lzq7YgVgW6ohUgbrOeCWuROivHKaBq/x05uwjKjZv17Dfz5r3xMwvwSKxn18fjqycKaWYlS36FlFCGC8OFelCil7oXorJS61qpL6xk/pZ+sp612jnpLPGaDHGNouH7enAckawUEoVqdK4JX2fWPzPwFdKTd1RN80eKs58C69knH3D6nnuDr9w4LVjFaaRtmBMEl8W8325u+dvr3cPd7vxqpjmTK49Zat1YO6DmtoOB61YZ3hnUXFHXOBAddBrONB8SxQBjNeO3P/1xSoGS61Wl6kpwZdmpZAgJsuQ9zPTw85hVrBbdZLxc4fXYP7MniqxX5W8zz+Wu3Oiv3PT4CAGvBh/8fGAlO8bpSDF5Wu1YJxxHSH4KxaXJZvQUEivZ3X2xGyENU3x84dH9efTtf0z+DtzBByq2N6xnQpLCuiM+KGm47hA4NLXlxtRmIOlsqyQr2WYKyYdlWuZiGxJ9jatK1rPXMM501ncd3cEncmmJWfpnk7OmHz5tJsyAYCXbxQkXl4o/dktRiaoW+fJuScclFR8IfSSXKM/0EJdL+X8oyqiVtFlipJCKHaRD8QbmA+uZhc5JsGABjHMDNYIGgQKMaJEMGkKtsEVtNRpthTMa9lIjNHI/ZCTrzukW60KBA4GmG7hqasW1dIJ3lgyXRlmwlgwYfGFrcTeeeZ3QhDW3NCDXVgK3uq451g20ygxtq4a/LLkkLkX2G+np/DVWBQaxIzSSKy0brq2VvLNNy/dtt7d701HjsuZbioHGv4x6512c5mlIxWVhihfWfVhC/t7FujzffeA/1shuJveNYpEjBx/yNl9SPp1Op18AAAD//wMATZ4i4iUEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUwW7bOhD8FYFnU6EoihJ1C5wWz8hrY7RGW/S24q4aIgplkFTaIPC/F5RdpDk9vAtBrDjL2ZmhXpiHR2I9uz4e3z2RT5FtWHJrSQqpudBcyIMUfaV6IUojtGpq8Z1tmLulZ9azVllbWUO8Jk1coWj4UI+WI5KpRIVCNir3hB+R9S8MXIn05CyVc/xCIbrZs559dR7nn/lucKWd5gXLME+08zGBt5nMx932lv97vT/c7c+nIsUMLh1mqnVjzIiKmw5GrlqpeadRcUldY0F00Ck4w5xPFDxMZcSH1/txTp6S7WUp61JwadhpwxASZMoDRDo8HzOLVaKbXN+s5XXbv7AnCqyXmz9inttd2cldPcZhcRNendcE8SGu6wg2zeGZbdgxzEcKydGqz3WM9DhMzweID+/PZ/I2vvtFdkmE23nxifWsqg3bsFWgaYLkZl9cOG4nRz6tH1NwPjr7X72yP3efi/0EaZzD4xs/Pp9l3v0PlT+AvXeeit0N65moSGLdER9lpbnqEDg0teFa13qkyppWVmzDtrNPzi/zEoudT/QjrCOxnr2HKdKZ33Ww9y6RTUvIc37Tmfc+zLjYVLx6WYlSlLUQGbSk45KKT4QukE2UhziE5dLvy1uI1HIFbZcQyKdiD+m++AfiPeuZgc5WYMAAaGtHagSNAgVo0SJp1IRKYovKKNTKCKsVDJVCaKphzBW2YZSjU6xpbQ3JVirBqTaKq7EZuaG25dQhiU4rlKu5rzoWd1PGge2EIqy5oRG5MhVwo+qaY91AK/XYtnL8S5JLHiqRBUZ6Or+7lYFG7Ah1xaWqGq6MqXhnmpYPbTeYQXfU2MzgloKn6S+hPjgb5jiPqbgkpHgj3afF539Hsablp/P81+rRzWwfKBTZY3A+P5WLrafT6fQbAAD//wMAYoXgq4IEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUXU/lNhD9K5FfGwd/xYnzRoFVrygf2nvVVn2b2BNwCc6V48AixH9fORcK7KpSX6JobM85c+bMPJMA90g6crzfnz1gSDMpSfJrSDChKdOUiZ1gHVcdY5XhSnFp/iYl8ef4RDrSKGu5NUglaqTKsZr2crDUOTScccdErXJOuJlJ90zAVw4fvMVqmv/AOPspkI786YObHjM2+MqO0+KqOI24CXOCYDOZy83JOf39+Hp3dX24NeOcH1feZaqyNmZwipoWBqoaoWmrnaIC29oCa6FVcHjmQ8IYYKxmd/eO76YUMNlOVEJWjApDXkriIEGm3MOMu6d9ZrFKdJrj5Rpef7tn8oCRdKJ8E/OQ7siO/uh+7hc/uqP1S0qyj9MeY/K4qnG1Ld5JcFaxSmjBGCnJBdhbH7DYnBZXYy4RbMsUOkkNDo4qw4EaJSV1soZG6KFpxEBKco4x4Pgh64W3cZqnIRWvIhefcLbHJ2cB+hEzxhcYZyQl2czbBMHBOAU8+4Z2SYdcu7jk45MpJB+WaZmLTUh4E+H1/O39yRIjhlRcQ7otfoP5lnTEQGs5GDAA2toBa4YDcww0axxqp9Ep4RqnjHJaGWa1gp4rBzXvhxwhJfk1a7hdrMV5fmezmmQcVw7Fa59ORo8hfZKRdIRxFE62SAfBNVWtAwq1NFRrqQfk1jSCv8HsIN5gIh0Z2r7vmbW9UIZbEAakG5h0ejBCNA2oQdaNElrXrAHrmkZw5WSDFuQgWStISa7j5Babfmy1XBtwHad/0KYsFemIrblpWl5LUdeybViPqm6g1oyJRjPkRtZONtK5QbWmbWBwChrUqh14z4Rwb/TPwo0PeOrn/QhPH3DbSlfyFwvQ8gHeRT1c/xLhHh+neHd5sHF1ebYjJfm6hLwRik22yKMP9JvOE32xjMnvbiOCQ3cxOfzZR5jHpVgnVOZmS4V0kMJRJaSggKDpgLbFFq1xLJM/newdxiI7DHzIU/VvsqttcRztrU9o0xIzv79WHlfb4nqENEzx/tMm+VDYjwJUQnKWO+Pw4bCNVo7auRad5lQoXlNlDKetqRvaN21vet1ibbNiV0vaL6n4is5HtGmt+NWLK+bJLdq7n8V4c8GrRTnLXtsettgq7f9aYi8luUeYl4j367bunskmBIwr8ulyGMVNuPDj6Ge0U3Az6ThXuhLC6FeG/3lPMFYxWdcvLy8v3wEAAP//AwC4eGIfGwYAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUTW/bOBD9KwLPokJRFCUK2IPhtFgjTWMk7u5ibxQ5jInIokFRToMi/31BSa6dooe9CaP5eO/NG/5AvTwAatDqePx0gj4MKEXBTiFKKMeEY0J3lDQ5awjJaJGXlNN/UYrsHbyhBlVMqVwJwAVwwEyTEreFUVhrEDnJNaEliz3l84CaH0jaTMPJKsjc8Bf4wboeNehv22v3GmdLm6nOjTrzroNNPwTZqwjm62Z9h7+struH7Zw1wBCLM6sj1KIUwmiGRS0NZhXluOaaYQp1qSSpZc3kXGb7AL6XXTbol8t87UIPQTU0o0VGMBXoPUVaBhkht3KA3dsxopgkuo3xdApPn80PdAKPGpqexZzb3ajO3hyGdrSdvgnSP0MwXh7g1fkXOMkOpejo3RF8sDBp8/CUXCDlJCMZ5ZQQlKJ7qfa2h2Rzmzx0kbBUNWGgCyzAaMxELrFgRYF1UcqKclNV1KAU3YHvobvqem+Vd4MzIVkkTz7MWbs+2H5045Bs+gDPXoa57rPsBkAp2o5tZ4f9I0j9tnOPY/zXj13ksh69hz4kWxn2yZ9y2KMGCVmrXAoppORKGSgJGKKJ5KTSwDUHzaiuNBNMcyaI4ky2OdOyzFsTIyhFD2M4juGs//crEDtvDwfQFwS7SeNtJ4Nx/rDR0AdrbFzNOWMyVNdNrJKl57qz0IcPIqMGkRyoLmrAhuYcs1pLLMtCYM4LbiBXoqL5LJixz+NPnYgxmhWmrGveijYnQE1VM1KZlmhlCiV02UJFK1LVUAtFSFuyvG6FIhrykunIzjs9qvCrFYppQY9jH68z2UTar7bH3zm7KLL1Ljjlurgw28HT2xDg8Lutrd3h6AYbIn+zrPbbAPeuj3+X+180W4pXLlxiEO8gmU5PStnSquW4NabFDEyBa2grXFaUGVrTquaR1a1TL+CTaDBp+2knZ089PCUrr/Y2gAqjj5P/mUjN6/x8PpmLHtnXT7u187A6HtMl+scpijQ3Oxvgw7vybYjpezdEFsGPVz56gs6ccV25ScNpfqlsDHKta9A8x5TlJWZC5LgWZYXbqm5Fy2so1cWtySNo60GFqd9unrYI+ztbrnywRqowxNv54tTkpsWeS8qvIK+29rN6Hn61ucVJS6ecRMc+ze/mZKD/9Wy+v7+//wcAAP//AwAXRjOgJwYAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTTW/cNhD9KwLPokxSFCXqZqxTdOEmXiSLtOhtxBnFhGVqQVFOAmP/e0DtBk5664UgBnwz72P4ygI8E+vZ7en07oVCWljJkt9KSijDheFCHZXope6FqKzotLLNv6xk/p6+s5612jnpLPGaDHGNouFDPTqOSFYKiUI1OveELwvrXxn4CunFO6rm5TPFxc+B9exvH3D+mmeDr9w0r1jFeaJ9WBIEl8l82O/u+V+3h+PD4fJqoSWDK4+Zat1YO6LmtoOR61YZ3hnUXFHXOBAddBouMB8SxQBTteDT23ycU6DkelWpuhJcWXYuGUKCTHmAhY7fT5nFZtFdrpdbebv2r+yFIutV+dPMS7sbN/mb52VY/YQ3lzPB8pRVnuJ8opg8bZ5sMqcJkp9DcZ20mzyFxEr28Kk4TJDGOT7/ZtSni/79/5B/zNPffSO3JsLdvIbEeiZ1zUr2HtyjD1Ts71jPhCSFdUd8VNJw3SFwaGrLjanNSNLZVklWst0ckg/rvC7FPiT6EjcBrGd/wLTQhfptdI8+kUtrzKr+MXkXDnHG1aXizX8pKlHVQmTQmk5rKj4S+kguUdZ3jOu1338gyqgNtFtjpJCKA6TH4k9YHlnPLHROggULYJwbqRE0ChRgRItk0BBqhS1qq9FoK5zRMEiN0MhhzBVWMspxF9uGDSMSNcJy0cqRa6kUt7JtuGxtO1jROqkzkTcfi4cp48B1QhPW3NKIXFsJ3Oq65lg30Coztq0af7Hkmr4U2WCkl8tf2RgYxI7QSK60bLi2VvLONi0f2m6wg+mocZnzPcVA0y9Gvfcuzss8puK6PMVv1n1cQ/7vxbZIX33g37aM7mb3RLHIGYMPeb2vsZ7P5/MPAAAA//8DANBOlYk2BAAA
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
@ -881,7 +882,8 @@ public sealed class TimelineService(
|
||||
IPlotRepository plots,
|
||||
ILookupRepository lookups,
|
||||
IWarningRepository warnings,
|
||||
ISceneDependencyRepository sceneDependencies) : ITimelineService
|
||||
ISceneDependencyRepository sceneDependencies,
|
||||
ILogger<TimelineService> logger) : ITimelineService
|
||||
{
|
||||
private static readonly string[] DefaultGraphMetrics = ["Overall Intensity", "Tension", "Emotional Weight", "Action"];
|
||||
|
||||
@ -890,29 +892,29 @@ public sealed class TimelineService(
|
||||
var projectId = filter.ProjectID;
|
||||
var bookId = filter.BookID;
|
||||
var selectedSceneId = filter.SelectedSceneID;
|
||||
var timeline = await timelineRepository.GetByProjectAsync(projectId, bookId);
|
||||
var timeline = await TimeTimelineLoadAsync("dbo.Timeline_GetByProject", () => timelineRepository.GetByProjectAsync(projectId, bookId), projectId, bookId);
|
||||
if (timeline.Project is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var allBooks = await books.ListByProjectAsync(projectId);
|
||||
var lookupData = await lookups.GetAllAsync();
|
||||
var assetLookupData = await assets.GetLookupsAsync();
|
||||
var characterLookupData = await characters.GetLookupsAsync();
|
||||
var locationLookupData = await locations.GetLookupsAsync();
|
||||
var plotLines = await plots.ListPlotLinesAsync(projectId);
|
||||
var plotThreads = await plots.ListPlotThreadsByProjectAsync(projectId);
|
||||
var allAssets = await assets.ListAssetsAsync(projectId);
|
||||
var allCharacters = await characters.ListCharactersAsync(projectId);
|
||||
var allLocations = await locations.ListByProjectAsync(projectId);
|
||||
var warningLookups = await warnings.GetLookupsAsync();
|
||||
var allBooks = await TimeTimelineLoadAsync("dbo.Book_ListByProject", () => books.ListByProjectAsync(projectId), projectId, bookId);
|
||||
var lookupData = await TimeTimelineLoadAsync("dbo.Lookup_All", lookups.GetAllAsync, projectId, bookId);
|
||||
var assetLookupData = await TimeTimelineLoadAsync("dbo.AssetLookup_All", assets.GetLookupsAsync, projectId, bookId);
|
||||
var characterLookupData = await TimeTimelineLoadAsync("dbo.CharacterLookup_All", characters.GetLookupsAsync, projectId, bookId);
|
||||
var locationLookupData = await TimeTimelineLoadAsync("dbo.LocationLookup_All", locations.GetLookupsAsync, projectId, bookId);
|
||||
var plotLines = await TimeTimelineLoadAsync("dbo.PlotLine_ListByProject", () => plots.ListPlotLinesAsync(projectId), projectId, bookId);
|
||||
var plotThreads = await TimeTimelineLoadAsync("dbo.PlotThread_ListByProject", () => plots.ListPlotThreadsByProjectAsync(projectId), projectId, bookId);
|
||||
var allAssets = await TimeTimelineLoadAsync("dbo.StoryAsset_ListByProject", () => assets.ListAssetsAsync(projectId), projectId, bookId);
|
||||
var allCharacters = await TimeTimelineLoadAsync("dbo.Character_ListByProject", () => characters.ListCharactersAsync(projectId), projectId, bookId);
|
||||
var allLocations = await TimeTimelineLoadAsync("dbo.Location_ListByProject", () => locations.ListByProjectAsync(projectId), projectId, bookId);
|
||||
var warningLookups = await TimeTimelineLoadAsync("dbo.WarningLookup_All", warnings.GetLookupsAsync, projectId, bookId);
|
||||
var chaptersByBook = timeline.Chapters.GroupBy(x => x.BookID).ToDictionary(x => x.Key, x => x.ToList());
|
||||
var scenesByChapter = timeline.Scenes.GroupBy(x => x.ChapterID).ToDictionary(x => x.Key, x => x.ToList());
|
||||
var purposeLabelsByScene = timeline.PurposeLabels.GroupBy(x => x.SceneID).ToDictionary(x => x.Key, x => x.OrderBy(p => p.SortOrder).ToList());
|
||||
var orderedScenes = timeline.Scenes.ToList();
|
||||
var warningCounts = (await warnings.GetSceneCountsAsync(projectId, bookId)).ToDictionary(x => x.SceneID, x => x.WarningCount);
|
||||
var dependencyCounts = (await sceneDependencies.GetCountsAsync(projectId, bookId)).ToDictionary(x => x.SceneID, x => x.DependencyCount);
|
||||
var warningCounts = (await TimeTimelineLoadAsync("dbo.ContinuityWarning_SceneCounts", () => warnings.GetSceneCountsAsync(projectId, bookId), projectId, bookId)).ToDictionary(x => x.SceneID, x => x.WarningCount);
|
||||
var dependencyCounts = (await TimeTimelineLoadAsync("dbo.SceneDependency_Counts", () => sceneDependencies.GetCountsAsync(projectId, bookId), projectId, bookId)).ToDictionary(x => x.SceneID, x => x.DependencyCount);
|
||||
|
||||
foreach (var scene in orderedScenes)
|
||||
{
|
||||
@ -921,16 +923,18 @@ public sealed class TimelineService(
|
||||
scene.DependencyCount = dependencyCounts.GetValueOrDefault(scene.SceneID);
|
||||
}
|
||||
|
||||
var assetTimeline = await assets.GetTimelineAsync(projectId, bookId);
|
||||
var characterTimeline = await characters.GetTimelineAsync(projectId, bookId);
|
||||
var assetTimeline = await TimeTimelineLoadAsync("dbo.AssetTimeline_GetByProject", () => assets.GetTimelineAsync(projectId, bookId), projectId, bookId);
|
||||
var characterTimeline = await TimeTimelineLoadAsync("dbo.CharacterTimeline_GetByProject", () => characters.GetTimelineAsync(projectId, bookId), projectId, bookId);
|
||||
var scenePovLabels = characterTimeline.Appearances
|
||||
.Where(x => x.RoleInSceneTypeName?.Contains("POV", StringComparison.OrdinalIgnoreCase) == true)
|
||||
.GroupBy(x => x.SceneID)
|
||||
.ToDictionary(x => x.Key, x => x.First().CharacterName);
|
||||
var matchingSceneIds = await BuildMatchingSceneIdsAsync(filter, orderedScenes, timeline.ThreadEvents, assetTimeline.Events, characterTimeline.Appearances, projectId, bookId);
|
||||
var focusSceneIds = await BuildFocusSceneIdsAsync(filter, orderedScenes, timeline.ThreadEvents, assetTimeline.Events, characterTimeline.Appearances, projectId, bookId);
|
||||
var matchingSceneIds = await TimeTimelineLoadAsync("TimelineService.BuildMatchingSceneIds", () => BuildMatchingSceneIdsAsync(filter, orderedScenes, timeline.ThreadEvents, assetTimeline.Events, characterTimeline.Appearances, projectId, bookId), projectId, bookId);
|
||||
var focusSceneIds = await TimeTimelineLoadAsync("TimelineService.BuildFocusSceneIds", () => BuildFocusSceneIdsAsync(filter, orderedScenes, timeline.ThreadEvents, assetTimeline.Events, characterTimeline.Appearances, projectId, bookId), projectId, bookId);
|
||||
var focusLabel = BuildFocusLabel(filter, plotLines, plotThreads, allAssets, allCharacters, allLocations);
|
||||
var selectedScene = selectedSceneId.HasValue ? await scenes.GetEditAsync(selectedSceneId.Value) : null;
|
||||
var selectedScene = selectedSceneId.HasValue
|
||||
? await TimeTimelineLoadAsync("SceneService.GetEditAsync", () => scenes.GetEditAsync(selectedSceneId.Value), projectId, bookId)
|
||||
: null;
|
||||
if (selectedScene is not null)
|
||||
{
|
||||
selectedScene.ReturnProjectID = projectId;
|
||||
@ -974,7 +978,7 @@ public sealed class TimelineService(
|
||||
WarningTypeOptions = warningLookups.WarningTypes.Select(x => new SelectListItem(x.TypeName, x.WarningTypeID.ToString(), x.WarningTypeID == filter.WarningTypeID)).ToList(),
|
||||
RevisionStatusOptions = lookupData.RevisionStatuses.Select(x => new SelectListItem(x.StatusName, x.RevisionStatusID.ToString(), x.RevisionStatusID == filter.RevisionStatusID)).ToList(),
|
||||
ScenePurposeOptions = lookupData.ScenePurposeTypes.Select(x => new SelectListItem(x.PurposeName, x.ScenePurposeTypeID.ToString(), x.ScenePurposeTypeID == filter.ScenePurposeTypeID)).ToList(),
|
||||
ViewPresets = await presets.ListAsync(projectId, bookId),
|
||||
ViewPresets = await TimeTimelineLoadAsync("dbo.TimelineViewPreset_List", () => presets.ListAsync(projectId, bookId), projectId, bookId),
|
||||
Books = timeline.Books.Select(book => new BookTimelineViewModel
|
||||
{
|
||||
Book = book,
|
||||
@ -1002,6 +1006,25 @@ public sealed class TimelineService(
|
||||
|
||||
public Task ArchivePresetAsync(int timelineViewPresetId) => presets.ArchiveAsync(timelineViewPresetId);
|
||||
|
||||
private async Task<T> TimeTimelineLoadAsync<T>(string operationName, Func<Task<T>> operation, int projectId, int? bookId)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
return await operation();
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
logger.LogInformation(
|
||||
"Timeline load operation {OperationName} completed in {ElapsedMilliseconds} ms for ProjectID {ProjectID}, BookID {BookID}.",
|
||||
operationName,
|
||||
stopwatch.ElapsedMilliseconds,
|
||||
projectId,
|
||||
bookId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ContinuityValidationSummary?> MoveSceneByDropAsync(TimelineSceneDropMoveViewModel model)
|
||||
{
|
||||
if (model.SceneID == model.AnchorSceneID)
|
||||
@ -1188,14 +1211,14 @@ public sealed class TimelineService(
|
||||
if (filter.CharacterID.HasValue) Intersect(characterAppearances.Where(x => x.CharacterID == filter.CharacterID).Select(x => x.SceneID));
|
||||
if (filter.LocationID.HasValue)
|
||||
{
|
||||
var locationScenes = (await locations.ListScenesByLocationAsync(filter.LocationID.Value)).Select(x => x.SceneID);
|
||||
var characterLocationScenes = (await locations.ListCharactersByLocationAsync(filter.LocationID.Value)).Select(x => x.SceneID);
|
||||
var assetLocationScenes = (await locations.ListAssetsByLocationAsync(filter.LocationID.Value)).Select(x => x.SceneID);
|
||||
var locationScenes = (await TimeTimelineLoadAsync("dbo.LocationScene_ListByLocation", () => locations.ListScenesByLocationAsync(filter.LocationID.Value), projectId, bookId)).Select(x => x.SceneID);
|
||||
var characterLocationScenes = (await TimeTimelineLoadAsync("dbo.LocationCharacter_ListByLocation", () => locations.ListCharactersByLocationAsync(filter.LocationID.Value), projectId, bookId)).Select(x => x.SceneID);
|
||||
var assetLocationScenes = (await TimeTimelineLoadAsync("dbo.SceneAssetLocation_ListByLocation", () => locations.ListAssetsByLocationAsync(filter.LocationID.Value), projectId, bookId)).Select(x => x.SceneID);
|
||||
Intersect(locationScenes.Concat(characterLocationScenes).Concat(assetLocationScenes));
|
||||
}
|
||||
if (filter.WarningSeverityID.HasValue || filter.WarningTypeID.HasValue || filter.ShowWarningsOnly)
|
||||
{
|
||||
var warningScenes = (await warnings.ListAsync(projectId, bookId, null, filter.WarningTypeID, filter.WarningSeverityID, null, filter.IncludeDismissedWarnings, filter.IncludeIntentionalWarnings))
|
||||
var warningScenes = (await TimeTimelineLoadAsync("dbo.ContinuityWarning_List", () => warnings.ListAsync(projectId, bookId, null, filter.WarningTypeID, filter.WarningSeverityID, null, filter.IncludeDismissedWarnings, filter.IncludeIntentionalWarnings), projectId, bookId))
|
||||
.Where(x => x.SceneID.HasValue)
|
||||
.Select(x => x.SceneID!.Value);
|
||||
Intersect(warningScenes);
|
||||
@ -1225,9 +1248,9 @@ public sealed class TimelineService(
|
||||
"asset" => assetEvents.Where(x => x.StoryAssetID == filter.FocusID).Select(x => x.SceneID).ToHashSet(),
|
||||
"plotline" => threadEvents.Where(x => x.PlotLineID == filter.FocusID).Select(x => x.SceneID).ToHashSet(),
|
||||
"plotthread" => threadEvents.Where(x => x.PlotThreadID == filter.FocusID).Select(x => x.SceneID).ToHashSet(),
|
||||
"location" => (await locations.ListScenesByLocationAsync(filter.FocusID.Value)).Select(x => x.SceneID)
|
||||
.Concat((await locations.ListCharactersByLocationAsync(filter.FocusID.Value)).Select(x => x.SceneID))
|
||||
.Concat((await locations.ListAssetsByLocationAsync(filter.FocusID.Value)).Select(x => x.SceneID))
|
||||
"location" => (await TimeTimelineLoadAsync("dbo.LocationScene_ListByLocation", () => locations.ListScenesByLocationAsync(filter.FocusID.Value), projectId, bookId)).Select(x => x.SceneID)
|
||||
.Concat((await TimeTimelineLoadAsync("dbo.LocationCharacter_ListByLocation", () => locations.ListCharactersByLocationAsync(filter.FocusID.Value), projectId, bookId)).Select(x => x.SceneID))
|
||||
.Concat((await TimeTimelineLoadAsync("dbo.SceneAssetLocation_ListByLocation", () => locations.ListAssetsByLocationAsync(filter.FocusID.Value), projectId, bookId)).Select(x => x.SceneID))
|
||||
.ToHashSet(),
|
||||
"scene" => orderedScenes.Where(x => x.SceneID == filter.FocusID).Select(x => x.SceneID).ToHashSet(),
|
||||
_ => new HashSet<int>()
|
||||
|
||||
142
PlotLine/Sql/023_TimelinePerformanceIndexes.sql
Normal file
142
PlotLine/Sql/023_TimelinePerformanceIndexes.sql
Normal file
@ -0,0 +1,142 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
/*
|
||||
Timeline performance pass for imported/large projects.
|
||||
|
||||
Focus:
|
||||
- Keep indexes narrow.
|
||||
- Support the actual timeline/project load path.
|
||||
- Avoid duplicating existing core indexes on Books, Chapters, Scenes,
|
||||
PlotLines, StoryAssets, SceneCharacters(SceneID), AssetEvents(SceneID),
|
||||
and ContinuityWarnings(ProjectID, BookID, SceneID...).
|
||||
*/
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ThreadEvents_PlotThreadID_SceneID' AND object_id = OBJECT_ID(N'dbo.ThreadEvents'))
|
||||
BEGIN
|
||||
-- Supports plot-thread filters and thread-event reads starting from a thread.
|
||||
CREATE INDEX IX_ThreadEvents_PlotThreadID_SceneID
|
||||
ON dbo.ThreadEvents(PlotThreadID, SceneID, EventTypeID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AssetEvents_StoryAssetID_SceneID' AND object_id = OBJECT_ID(N'dbo.AssetEvents'))
|
||||
BEGIN
|
||||
-- Supports asset focus/filter lanes and asset-event reads starting from an asset.
|
||||
CREATE INDEX IX_AssetEvents_StoryAssetID_SceneID
|
||||
ON dbo.AssetEvents(StoryAssetID, SceneID, AssetEventTypeID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneCharacters_CharacterID_SceneID' AND object_id = OBJECT_ID(N'dbo.SceneCharacters'))
|
||||
BEGIN
|
||||
-- Supports character focus/filter lanes and character detail appearance lookups.
|
||||
CREATE INDEX IX_SceneCharacters_CharacterID_SceneID
|
||||
ON dbo.SceneCharacters(CharacterID, SceneID, RoleInSceneTypeID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Characters_ProjectID_Importance_Name' AND object_id = OBJECT_ID(N'dbo.Characters'))
|
||||
BEGIN
|
||||
-- Supports CharacterTimeline_GetByProject ordering by importance, then name.
|
||||
CREATE INDEX IX_Characters_ProjectID_Importance_Name
|
||||
ON dbo.Characters(ProjectID, IsArchived, CharacterImportance DESC, CharacterName);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Locations_Project_Parent_Name' AND object_id = OBJECT_ID(N'dbo.Locations'))
|
||||
BEGIN
|
||||
-- Supports project location lists and LocationPaths roots by project.
|
||||
CREATE INDEX IX_Locations_Project_Parent_Name
|
||||
ON dbo.Locations(ProjectID, IsArchived, ParentLocationID, LocationName);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Locations_ParentLocationID' AND object_id = OBJECT_ID(N'dbo.Locations'))
|
||||
BEGIN
|
||||
-- Supports recursive LocationPaths expansion from parent to child locations.
|
||||
CREATE INDEX IX_Locations_ParentLocationID
|
||||
ON dbo.Locations(ParentLocationID, IsArchived, LocationName);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneCharacters_LocationID_SceneID' AND object_id = OBJECT_ID(N'dbo.SceneCharacters'))
|
||||
BEGIN
|
||||
-- Supports timeline location filters that include character appearances at a location.
|
||||
CREATE INDEX IX_SceneCharacters_LocationID_SceneID
|
||||
ON dbo.SceneCharacters(LocationID, SceneID, CharacterID)
|
||||
WHERE LocationID IS NOT NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneAssetLocations_LocationID_SceneID' AND object_id = OBJECT_ID(N'dbo.SceneAssetLocations'))
|
||||
BEGIN
|
||||
-- Supports timeline location filters that include assets placed at a location.
|
||||
CREATE INDEX IX_SceneAssetLocations_LocationID_SceneID
|
||||
ON dbo.SceneAssetLocations(LocationID, SceneID, StoryAssetID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneAssetLocations_SceneID_LocationID' AND object_id = OBJECT_ID(N'dbo.SceneAssetLocations'))
|
||||
BEGIN
|
||||
-- Supports scene-level location/asset reads from the scene inspector and timeline focus.
|
||||
CREATE INDEX IX_SceneAssetLocations_SceneID_LocationID
|
||||
ON dbo.SceneAssetLocations(SceneID, LocationID, StoryAssetID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ContinuityWarnings_ActiveSceneCounts' AND object_id = OBJECT_ID(N'dbo.ContinuityWarnings'))
|
||||
BEGIN
|
||||
-- Supports active warning counts shown on timeline scene cards.
|
||||
CREATE INDEX IX_ContinuityWarnings_ActiveSceneCounts
|
||||
ON dbo.ContinuityWarnings(ProjectID, BookID, SceneID)
|
||||
WHERE SceneID IS NOT NULL AND IsDismissed = 0 AND IsIntentional = 0;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneDependencies_Project_SourceScene' AND object_id = OBJECT_ID(N'dbo.SceneDependencies'))
|
||||
BEGIN
|
||||
-- Supports dependency counts and source-scene dependency lookups.
|
||||
CREATE INDEX IX_SceneDependencies_Project_SourceScene
|
||||
ON dbo.SceneDependencies(ProjectID, IsArchived, SourceSceneID, TargetSceneID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneDependencies_Project_TargetScene' AND object_id = OBJECT_ID(N'dbo.SceneDependencies'))
|
||||
BEGIN
|
||||
-- Supports dependency counts and target-scene dependency lookups.
|
||||
CREATE INDEX IX_SceneDependencies_Project_TargetScene
|
||||
ON dbo.SceneDependencies(ProjectID, IsArchived, TargetSceneID, SourceSceneID);
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SceneDependency_Counts
|
||||
@ProjectID int,
|
||||
@BookID int = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
;WITH DependencyScenes AS
|
||||
(
|
||||
SELECT sd.SceneDependencyID, sd.SourceSceneID AS SceneID
|
||||
FROM dbo.SceneDependencies sd
|
||||
WHERE sd.ProjectID = @ProjectID AND sd.IsArchived = 0
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT sd.SceneDependencyID, sd.TargetSceneID AS SceneID
|
||||
FROM dbo.SceneDependencies sd
|
||||
WHERE sd.ProjectID = @ProjectID
|
||||
AND sd.IsArchived = 0
|
||||
AND sd.TargetSceneID <> sd.SourceSceneID
|
||||
)
|
||||
SELECT scene.SceneID, COUNT(*) AS DependencyCount
|
||||
FROM dbo.NarrativeSceneOrder scene
|
||||
INNER JOIN DependencyScenes dependencyScene ON dependencyScene.SceneID = scene.SceneID
|
||||
WHERE scene.ProjectID = @ProjectID AND (@BookID IS NULL OR scene.BookID = @BookID)
|
||||
GROUP BY scene.SceneID;
|
||||
END;
|
||||
GO
|
||||
151
PlotLine/Sql/024_CharacterTimelinePerformance.sql
Normal file
151
PlotLine/Sql/024_CharacterTimelinePerformance.sql
Normal file
@ -0,0 +1,151 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
/*
|
||||
Focused optimisation for dbo.CharacterTimeline_GetByProject.
|
||||
|
||||
Diagnosis:
|
||||
- The timeline timing showed this procedure as the clear bottleneck.
|
||||
- The original appearance query joined SceneCharacters to Characters, Scenes,
|
||||
Chapters, Books, and the recursive LocationPaths view three times.
|
||||
- Filtering the project/book scene set once gives SQL Server a small driving
|
||||
set before joining appearances.
|
||||
- Materialising project location paths once avoids repeated recursive view
|
||||
expansion for location, entry location, and exit location labels.
|
||||
*/
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Characters_ProjectID_Importance_Name' AND object_id = OBJECT_ID(N'dbo.Characters'))
|
||||
BEGIN
|
||||
-- Supports CharacterTimeline_GetByProject character list ordering.
|
||||
CREATE INDEX IX_Characters_ProjectID_Importance_Name
|
||||
ON dbo.Characters(ProjectID, IsArchived, CharacterImportance DESC, CharacterName);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneCharacters_CharacterID_SceneID' AND object_id = OBJECT_ID(N'dbo.SceneCharacters'))
|
||||
BEGIN
|
||||
-- Supports appearance lookups when the optimizer starts from project characters.
|
||||
CREATE INDEX IX_SceneCharacters_CharacterID_SceneID
|
||||
ON dbo.SceneCharacters(CharacterID, SceneID, RoleInSceneTypeID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Locations_Project_Parent_Name' AND object_id = OBJECT_ID(N'dbo.Locations'))
|
||||
BEGIN
|
||||
-- Supports project-scoped LocationPaths materialisation.
|
||||
CREATE INDEX IX_Locations_Project_Parent_Name
|
||||
ON dbo.Locations(ProjectID, IsArchived, ParentLocationID, LocationName);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Locations_ParentLocationID' AND object_id = OBJECT_ID(N'dbo.Locations'))
|
||||
BEGIN
|
||||
-- Supports recursive LocationPaths expansion from parent to child locations.
|
||||
CREATE INDEX IX_Locations_ParentLocationID
|
||||
ON dbo.Locations(ParentLocationID, IsArchived, LocationName);
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.CharacterTimeline_GetByProject
|
||||
@ProjectID int,
|
||||
@BookID int = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
CREATE TABLE #TimelineScenes
|
||||
(
|
||||
SceneID int NOT NULL PRIMARY KEY,
|
||||
SceneNumber decimal(9,2) NOT NULL,
|
||||
SceneTitle nvarchar(200) NOT NULL,
|
||||
ChapterNumber decimal(9,2) NOT NULL,
|
||||
BookTitle nvarchar(200) NOT NULL,
|
||||
BookSortOrder int NOT NULL,
|
||||
ChapterSortOrder int NOT NULL,
|
||||
SceneSortOrder int NOT NULL
|
||||
);
|
||||
|
||||
INSERT #TimelineScenes (SceneID, SceneNumber, SceneTitle, ChapterNumber, BookTitle, BookSortOrder, ChapterSortOrder, SceneSortOrder)
|
||||
SELECT s.SceneID, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle, b.SortOrder, ch.SortOrder, s.SortOrder
|
||||
FROM dbo.Books b
|
||||
INNER JOIN dbo.Chapters ch ON ch.BookID = b.BookID
|
||||
INNER JOIN dbo.Scenes s ON s.ChapterID = ch.ChapterID
|
||||
WHERE b.ProjectID = @ProjectID
|
||||
AND b.IsArchived = 0
|
||||
AND ch.IsArchived = 0
|
||||
AND s.IsArchived = 0
|
||||
AND (@BookID IS NULL OR b.BookID = @BookID);
|
||||
|
||||
CREATE TABLE #TimelineCharacters
|
||||
(
|
||||
CharacterID int NOT NULL PRIMARY KEY,
|
||||
ProjectID int NOT NULL,
|
||||
CharacterName nvarchar(200) NOT NULL,
|
||||
ShortName nvarchar(100) NULL,
|
||||
Sex nvarchar(50) NULL,
|
||||
BirthDate date NULL,
|
||||
AgeAtSeriesStart int NULL,
|
||||
AgeReferenceSceneID int NULL,
|
||||
Height nvarchar(50) NULL,
|
||||
EyeColour nvarchar(50) NULL,
|
||||
CharacterImportance int NULL,
|
||||
DefaultDescription nvarchar(max) NULL,
|
||||
CreatedDate datetime2 NOT NULL,
|
||||
UpdatedDate datetime2 NOT NULL,
|
||||
IsArchived bit NOT NULL
|
||||
);
|
||||
|
||||
INSERT #TimelineCharacters (CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID, Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived)
|
||||
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||
Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||
FROM dbo.Characters
|
||||
WHERE ProjectID = @ProjectID AND IsArchived = 0;
|
||||
|
||||
CREATE TABLE #ProjectLocationPaths
|
||||
(
|
||||
LocationID int NOT NULL PRIMARY KEY,
|
||||
LocationName nvarchar(200) NOT NULL,
|
||||
LocationPath nvarchar(max) NOT NULL
|
||||
);
|
||||
|
||||
;WITH ProjectLocationTree AS
|
||||
(
|
||||
SELECT LocationID, LocationName, CAST(LocationName AS nvarchar(max)) AS LocationPath
|
||||
FROM dbo.Locations
|
||||
WHERE ProjectID = @ProjectID AND ParentLocationID IS NULL AND IsArchived = 0
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT child.LocationID, child.LocationName, CAST(parent.LocationPath + N' > ' + child.LocationName AS nvarchar(max)) AS LocationPath
|
||||
FROM dbo.Locations child
|
||||
INNER JOIN ProjectLocationTree parent ON parent.LocationID = child.ParentLocationID
|
||||
WHERE child.ProjectID = @ProjectID AND child.IsArchived = 0
|
||||
)
|
||||
INSERT #ProjectLocationPaths (LocationID, LocationName, LocationPath)
|
||||
SELECT LocationID, LocationName, LocationPath
|
||||
FROM ProjectLocationTree;
|
||||
|
||||
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||
Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||
FROM #TimelineCharacters
|
||||
ORDER BY ISNULL(CharacterImportance, 0) DESC, CharacterName;
|
||||
|
||||
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
|
||||
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
|
||||
sc.LocationID, location.LocationName, location.LocationPath, sc.EntryLocationID, entry.LocationName AS EntryLocationName,
|
||||
sc.ExitLocationID, exitLocation.LocationName AS ExitLocationName, sc.AppearanceNotes, sc.OutfitDescription,
|
||||
sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes, s.SceneNumber, s.SceneTitle, s.ChapterNumber, s.BookTitle,
|
||||
sc.CreatedDate, sc.UpdatedDate
|
||||
FROM #TimelineScenes s
|
||||
INNER JOIN dbo.SceneCharacters sc ON sc.SceneID = s.SceneID
|
||||
INNER JOIN #TimelineCharacters c ON c.CharacterID = sc.CharacterID
|
||||
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
|
||||
LEFT JOIN #ProjectLocationPaths location ON location.LocationID = sc.LocationID
|
||||
LEFT JOIN #ProjectLocationPaths entry ON entry.LocationID = sc.EntryLocationID
|
||||
LEFT JOIN #ProjectLocationPaths exitLocation ON exitLocation.LocationID = sc.ExitLocationID
|
||||
ORDER BY s.BookSortOrder, s.ChapterSortOrder, s.SceneSortOrder, c.CharacterName;
|
||||
END;
|
||||
GO
|
||||
@ -1,9 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="light" data-bs-theme="light">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - PlotLine</title>
|
||||
<script>
|
||||
(() => {
|
||||
let savedTheme = null;
|
||||
try {
|
||||
savedTheme = localStorage.getItem("plotline.theme");
|
||||
} catch {
|
||||
savedTheme = null;
|
||||
}
|
||||
const theme = savedTheme === "dark" ? "dark" : "light";
|
||||
document.documentElement.dataset.theme = theme;
|
||||
document.documentElement.dataset.bsTheme = theme;
|
||||
})();
|
||||
</script>
|
||||
<script type="importmap"></script>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
@ -12,7 +25,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom plotline-nav mb-3">
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm border-bottom plotline-nav mb-3">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fw-semibold" asp-area="" asp-controller="Projects" asp-action="Index">PlotLine</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
@ -34,6 +47,10 @@
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="theme-switcher btn-group btn-group-sm" role="group" aria-label="Theme selection">
|
||||
<button type="button" class="btn btn-outline-secondary" data-theme-choice="light" aria-pressed="true">Light</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-theme-choice="dark" aria-pressed="false">Dark</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -27,6 +27,34 @@
|
||||
--plotline-soft: var(--plotline-accent-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--plotline-bg: #14110f;
|
||||
--plotline-bg-warm: #1c1714;
|
||||
--plotline-surface: rgba(35, 29, 25, 0.96);
|
||||
--plotline-surface-elevated: #2a231e;
|
||||
--plotline-border-soft: rgba(231, 214, 190, 0.16);
|
||||
--plotline-text: #f1e7d8;
|
||||
--plotline-text-muted: #b9aa99;
|
||||
--plotline-accent: #d49a62;
|
||||
--plotline-accent-dark: #f0c99d;
|
||||
--plotline-accent-soft: #3a2a20;
|
||||
--plotline-secondary: #78b9c2;
|
||||
--plotline-secondary-soft: #21383d;
|
||||
--plotline-danger: #ee8e83;
|
||||
--plotline-warning: #e0ad65;
|
||||
--plotline-success: #91c99a;
|
||||
--plotline-timeline-bg: #211b17;
|
||||
--plotline-shadow-card: 0 16px 38px rgba(0, 0, 0, 0.34);
|
||||
--plotline-shadow-subtle: 0 10px 26px rgba(0, 0, 0, 0.24);
|
||||
--plotline-ink: var(--plotline-text);
|
||||
--plotline-muted: var(--plotline-text-muted);
|
||||
--plotline-paper: var(--plotline-bg-warm);
|
||||
--plotline-panel: var(--plotline-surface);
|
||||
--plotline-line: var(--plotline-border-soft);
|
||||
--plotline-soft: var(--plotline-accent-soft);
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--plotline-text);
|
||||
background:
|
||||
@ -851,6 +879,237 @@ a:focus-visible {
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.theme-switcher .btn {
|
||||
--bs-btn-color: var(--plotline-text-muted);
|
||||
--bs-btn-border-color: var(--plotline-border-soft);
|
||||
--bs-btn-hover-color: var(--plotline-accent-dark);
|
||||
--bs-btn-hover-bg: rgba(122, 77, 44, 0.1);
|
||||
--bs-btn-hover-border-color: rgba(122, 77, 44, 0.28);
|
||||
--bs-btn-active-color: #ffffff;
|
||||
--bs-btn-active-bg: var(--plotline-accent);
|
||||
--bs-btn-active-border-color: var(--plotline-accent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
[data-theme="dark"] body {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(212, 154, 98, 0.12), transparent 34rem),
|
||||
linear-gradient(135deg, var(--plotline-bg-warm), var(--plotline-bg) 52%, #0f0d0c);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .plotline-nav {
|
||||
background: rgba(29, 23, 20, 0.92) !important;
|
||||
border-bottom-color: var(--plotline-border-soft) !important;
|
||||
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .navbar-toggler {
|
||||
border-color: var(--plotline-border-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .page-heading h1,
|
||||
[data-theme="dark"] .workspace-heading h1,
|
||||
[data-theme="dark"] .timeline-scene-card strong,
|
||||
[data-theme="dark"] .timeline-chapter-title strong,
|
||||
[data-theme="dark"] .timeline-book-title strong,
|
||||
[data-theme="dark"] .plain-card h2,
|
||||
[data-theme="dark"] .plain-card h3,
|
||||
[data-theme="dark"] .context-title strong,
|
||||
[data-theme="dark"] .asset-card h2,
|
||||
[data-theme="dark"] .character-card h2 {
|
||||
color: var(--plotline-text);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .plain-card,
|
||||
[data-theme="dark"] .empty-panel,
|
||||
[data-theme="dark"] .edit-panel,
|
||||
[data-theme="dark"] .list-section,
|
||||
[data-theme="dark"] .story-bible-section,
|
||||
[data-theme="dark"] .story-bible-card,
|
||||
[data-theme="dark"] .archive-item-card,
|
||||
[data-theme="dark"] .warning-card,
|
||||
[data-theme="dark"] .warning-mini-card,
|
||||
[data-theme="dark"] .scenario-chapter,
|
||||
[data-theme="dark"] .drag-drop-panel,
|
||||
[data-theme="dark"] .import-tree-book,
|
||||
[data-theme="dark"] .import-confirmation,
|
||||
[data-theme="dark"] .import-docs details,
|
||||
[data-theme="dark"] .scene-inspector-panel,
|
||||
[data-theme="dark"] .pacing-panel,
|
||||
[data-theme="dark"] .timeline-control-panel,
|
||||
[data-theme="dark"] .timeline-drag-palette,
|
||||
[data-theme="dark"] .timeline-filter-drawer,
|
||||
[data-theme="dark"] .timeline-control-group,
|
||||
[data-theme="dark"] .timeline-visibility-panel,
|
||||
[data-theme="dark"] .timeline-jump-panel,
|
||||
[data-theme="dark"] .timeline-preset-panel,
|
||||
[data-theme="dark"] .attachment-card,
|
||||
[data-theme="dark"] .attachment-preview-row,
|
||||
[data-theme="dark"] .asset-mini-item,
|
||||
[data-theme="dark"] .archive-empty {
|
||||
color: var(--plotline-text);
|
||||
background: var(--plotline-surface);
|
||||
border-color: var(--plotline-border-soft);
|
||||
box-shadow: var(--plotline-shadow-subtle);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .timeline-workspace,
|
||||
[data-theme="dark"] .timeline-shell {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(42, 35, 30, 0.96), rgba(29, 24, 21, 0.96)),
|
||||
var(--plotline-timeline-bg);
|
||||
border-color: var(--plotline-border-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .timeline-section-header,
|
||||
[data-theme="dark"] .lane-board-heading,
|
||||
[data-theme="dark"] .plot-lane-board-heading,
|
||||
[data-theme="dark"] .pacing-heading,
|
||||
[data-theme="dark"] .lane-group-heading,
|
||||
[data-theme="dark"] .timeline-chapter-title,
|
||||
[data-theme="dark"] .timeline-book-title {
|
||||
color: var(--plotline-accent-dark);
|
||||
background: rgba(37, 31, 27, 0.96);
|
||||
border-color: var(--plotline-border-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .timeline-project-title {
|
||||
background: #352318;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .timeline-chapter,
|
||||
[data-theme="dark"] .timeline-scene-card,
|
||||
[data-theme="dark"] .timeline-empty-scene,
|
||||
[data-theme="dark"] .plot-lane-label,
|
||||
[data-theme="dark"] .asset-lane-label,
|
||||
[data-theme="dark"] .character-lane-label,
|
||||
[data-theme="dark"] .plot-lane-slot,
|
||||
[data-theme="dark"] .asset-lane-slot,
|
||||
[data-theme="dark"] .character-lane-slot,
|
||||
[data-theme="dark"] .timeline-placeholder-scene,
|
||||
[data-theme="dark"] .timeline-placeholder-cell,
|
||||
[data-theme="dark"] .metric-graph-points,
|
||||
[data-theme="dark"] .timeline-minimap {
|
||||
color: var(--plotline-text);
|
||||
background: rgba(42, 35, 30, 0.9);
|
||||
border-color: var(--plotline-border-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .timeline-scene-card,
|
||||
[data-theme="dark"] .timeline-empty-scene {
|
||||
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .timeline-scene-card:hover,
|
||||
[data-theme="dark"] .timeline-scene-card.selected,
|
||||
[data-theme="dark"] .timeline-scene-card.focus-match,
|
||||
[data-theme="dark"] .timeline-scene-card.keyboard-focus,
|
||||
[data-theme="dark"] .timeline-scene-card.search-hit {
|
||||
border-color: rgba(212, 154, 98, 0.62);
|
||||
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .timeline-minimap button {
|
||||
color: var(--plotline-accent-dark);
|
||||
background: linear-gradient(180deg, #332920 0%, #241d18 100%);
|
||||
border-color: var(--plotline-border-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .timeline-minimap button.is-active {
|
||||
color: #1c120d;
|
||||
background: linear-gradient(180deg, var(--plotline-accent-dark) 0%, var(--plotline-accent) 100%);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .drag-chip,
|
||||
[data-theme="dark"] .purpose-chip,
|
||||
[data-theme="dark"] .metric-mini-summary,
|
||||
[data-theme="dark"] .scene-pov-label,
|
||||
[data-theme="dark"] .location-pill,
|
||||
[data-theme="dark"] .archive-pill,
|
||||
[data-theme="dark"] .badge,
|
||||
[data-theme="dark"] .status-pill,
|
||||
[data-theme="dark"] .scenario-tag,
|
||||
[data-theme="dark"] .plain-preview {
|
||||
color: var(--plotline-text);
|
||||
background: rgba(212, 154, 98, 0.14);
|
||||
border-color: rgba(212, 154, 98, 0.24);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .form-control,
|
||||
[data-theme="dark"] .form-select,
|
||||
[data-theme="dark"] textarea,
|
||||
[data-theme="dark"] input,
|
||||
[data-theme="dark"] select {
|
||||
color: var(--plotline-text);
|
||||
background-color: #211b17;
|
||||
border-color: var(--plotline-border-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .form-control::placeholder,
|
||||
[data-theme="dark"] textarea::placeholder,
|
||||
[data-theme="dark"] input::placeholder {
|
||||
color: #8f8172;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .form-control:focus,
|
||||
[data-theme="dark"] .form-select:focus,
|
||||
[data-theme="dark"] .form-check-input:focus {
|
||||
border-color: var(--plotline-accent);
|
||||
box-shadow: 0 0 0 0.2rem rgba(212, 154, 98, 0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .table {
|
||||
--bs-table-color: var(--plotline-text);
|
||||
--bs-table-bg: transparent;
|
||||
--bs-table-border-color: var(--plotline-border-soft);
|
||||
--bs-table-striped-color: var(--plotline-text);
|
||||
--bs-table-striped-bg: rgba(255, 255, 255, 0.035);
|
||||
--bs-table-hover-color: var(--plotline-text);
|
||||
--bs-table-hover-bg: rgba(212, 154, 98, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .modal-content,
|
||||
[data-theme="dark"] .plotline-confirm-modal {
|
||||
color: var(--plotline-text);
|
||||
background: var(--plotline-surface-elevated);
|
||||
border-color: var(--plotline-border-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .modal-header,
|
||||
[data-theme="dark"] .modal-footer {
|
||||
border-color: var(--plotline-border-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .btn-close {
|
||||
filter: invert(1) grayscale(1) brightness(1.6);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .dropdown-menu {
|
||||
--bs-dropdown-color: var(--plotline-text);
|
||||
--bs-dropdown-bg: var(--plotline-surface-elevated);
|
||||
--bs-dropdown-border-color: var(--plotline-border-soft);
|
||||
--bs-dropdown-link-color: var(--plotline-text);
|
||||
--bs-dropdown-link-hover-color: var(--plotline-accent-dark);
|
||||
--bs-dropdown-link-hover-bg: rgba(212, 154, 98, 0.12);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .alert-info {
|
||||
--bs-alert-color: #cceaf0;
|
||||
--bs-alert-bg: #17343a;
|
||||
--bs-alert-border-color: #285c66;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .alert-warning,
|
||||
[data-theme="dark"] .validation-summary-errors {
|
||||
--bs-alert-color: #ffe3ad;
|
||||
--bs-alert-bg: #3b2a16;
|
||||
--bs-alert-border-color: #705229;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.timeline-filter-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
|
||||
@ -1,3 +1,42 @@
|
||||
(() => {
|
||||
const storageKey = "plotline.theme";
|
||||
const choices = document.querySelectorAll("[data-theme-choice]");
|
||||
|
||||
const readTheme = () => {
|
||||
try {
|
||||
return localStorage.getItem(storageKey);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const saveTheme = (theme) => {
|
||||
try {
|
||||
localStorage.setItem(storageKey, theme);
|
||||
} catch {
|
||||
// Theme still applies for the current page if persistence is unavailable.
|
||||
}
|
||||
};
|
||||
|
||||
const applyTheme = (theme) => {
|
||||
const nextTheme = theme === "dark" ? "dark" : "light";
|
||||
document.documentElement.dataset.theme = nextTheme;
|
||||
document.documentElement.dataset.bsTheme = nextTheme;
|
||||
saveTheme(nextTheme);
|
||||
choices.forEach((button) => {
|
||||
const isActive = button.dataset.themeChoice === nextTheme;
|
||||
button.classList.toggle("active", isActive);
|
||||
button.setAttribute("aria-pressed", String(isActive));
|
||||
});
|
||||
};
|
||||
|
||||
choices.forEach((button) => {
|
||||
button.addEventListener("click", () => applyTheme(button.dataset.themeChoice));
|
||||
});
|
||||
|
||||
applyTheme(readTheme() || "light");
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const modalElement = document.getElementById("plotlineConfirmModal");
|
||||
const modal = modalElement && window.bootstrap ? new bootstrap.Modal(modalElement) : null;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user