This commit is contained in:
Nick Beckley 2026-06-01 15:42:44 +01:00
parent 480b557f81
commit 04a41e81b0
80 changed files with 1184 additions and 148 deletions

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS/zQzbBb1HDTLGKjLXobcUaxEK20oKhNA2P/e0F5i8S99UYMOMOXz7wzr2yGI7GO3Z5OP51pziurWR73kBLKcmG5kM9Sd0Z22jVBt0p5+xer2fhI31jHnIlRxkBckyVuULS810PkiBSkkChUa0pN+Lyy7pXB2CCdx0jNsv5OaR2XmXXsj3HG5Wt5G8YmTsuGTVomepjXDHMsYn57uHvkv94enj8e3m6ttJbkZkTWMe+dbTFaboIibgwoDrEduJM6RDMIGkIRAWMzzpnSDFOz4pfv7+OSZ8qxU43SjeAqsEvNEDIUyT2s9PztVFTsiO5LvN7D+7F7ZWdKrFP1vzDfyt3EabxZt36iM00nSCulm7gcjzAjq9kpLSdKeaQdy5lSv6xjLkRjdOC9lOjBmdah163SDrUg9MK1GDCANXZQsW0H7H1wZjCopEFLxvSO1WwHN02Qx2WurtrvppHmzGr28ak6TJCHJR3foX96I/rwP4AW2axjWrTaOkdGaw3B9koQOhutVX0E4YZekYwk2mBckE5IHZQIurXOOwQRIqvZB4gv40zVwz3rmJCkUHvig5KWG4/AodWBW6vtQDIGpySr2d0y53Helm2tHuZMn9P+X9axn2Fa6e2ntym+jJli3lKB8Kctsg9pwS3m6rsBpGhEo4UoSVs+bbn6RDgmipkKjue0Xev9J0VZtSfdbSnRnKsD5JfqF1hfWMcC+CghQACwMQ7UChoECrDCIVm0hEahQxMMWhNEtAZ6aRBa2Q8lwmpGxW/VbnHprAERFMdge24iIPeD1Vy23htpBGjh33GsPk4lD6IXhlDzQANyEyTwYLTmqFtwyg7OqeEHJFezSFEAI53fhnVXYBE9oZVcGdlyE4LkPrSO9873obee2lg0P1KaafoB1IcxpmVdhlxdvVa9Q/dpm8vCqXbffR1n/vfeo/slfqFUlR7DOJf5urb1UrMjwbolOu77qntlhzJc1fO+tpRrrBehZk8ZUt5O17AOjQxBXy6Xyz8AAAD//wMA9VbUEPQEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUzW7bOBB+FYFnUSElihIF7MFwWtRI0xiJu7vojSKHMRFZNCjKaRD43ReU5dopetibMCJnvr/hO+rlDlCDFvv9pwP0YUApCnYq5STnmHBM6IYWDaNNUWdFWRLCqx8oRfYO3lCDKqYUVQJwARww06TEbWEU1hoEJVSTvGSxp3weUPOOpM00HKyCzA1/gx+s61GD/rG9dq9xtrSZ6tyoM+86WPVDkL2KYL6tlnf462K9eVifTg0wxMuZ1ahBdV3xUiuOmcgBMyZzLFVpcEULoZghYEQEIW1m+wC+l1026JfLfO1CD0E1eZYXGcG5QMcUaRlkhNzKATZv+4hikug21tOpPH027+gAHjV5ehbz1O5GdfZmN7Sj7fRNkP4ZgvFyB6/Ov8BBdihFe+/24IOFSZuHp+QCiZKMZDnPCUEpupdqa3tIVrfJQxcJS1UTBrrAAozGTFCJBSsKrItSVjk3VZUblKI78D10V13vrfJucCYks+TJhzlL1wfbj24cklUf4NnLcLr3WXYDoBStx7azw/YRpH7buMcx/uvHLnJZjt5DH5K1DNvkixy2qEFC1opKIYWUXCkDJQFDNJGcVBq45qBZrivNBNOcCaI4ky1lWpa0NbGCUvQwhv0Yzvr/vAKx8Xa3A31BsJk0XncyGOd3Kw19sMZGa84npkB13cQqmXsuOwt9+CAyahChkOuiBmxyyjGrtcSyLATmvOAGqBJVTk+CGfs8/tKJGKNZYcq65q1oKYHcVDUjlWmJVqZQQpctVHlFqhpqoQhpS0brViiigZZMR3be6VGF36NQTAY9jn3czmQVab/aHv/k7KLI2rvglOuiYbaDp7chwO5Pri3dbu8GGyJ/M1v7fYB718e/8/7Pms2XFy5cahD3IJlWj1U0B8E5JlLVmGlJcVtrgxUtSpkXpKW6RCm6deoFfBIDJm0/eXLO1MNTsvBqawOoMPo4+d+J1MnOz+eVueiRffu0WToPi/0+nat/HaJIp2bnAHx4V74P8fjWDeGK8kztCTpzBnYVJw2H01NlY5FrXYPmFOeMlpgJQXEtygq3Vd2KltdQqktck0fQ1oMKU7+NH+O0Wdk/5XLhgzVShSEuz1enpjjN+ZyP/A7yyrZft0/Dr6ybozR3oiRG9un0cE4J+l/v5vF4PP4HAAD//wMABRuhhygGAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSTwY7cKBCGX8XibDyAARvfRj27SmuSnVYySlZ7w1Q5jeKBFsaTRKN+9xXujpLJLTf0Q1X99VXxQoJ9QjKQ29Ppr2cMeSE1yX6TBBOaMk0Zf+TtIPkgZSM4U8zw/0hN/D1+JwPppHPcGaQtaqQSmKJjOzkKgIYzDkwoWXLazwsZXoj1DeCzd9jE5SOmxcdABvLJB4hfS23rGzfHFZoUZ9yHJdvgipl/9rt7+vb28PhwuLxacCnBjQcykL7vtAKnqTQCqZRWUOvURDveGicnhpMpJqxvfMiYgp2bBb78rA8xB8xuEI1oG0aFIeeagM22WB7tgo/fT8XFhuiu6PUmb8fhhTxjIoOof8C8pLtxs79x8enJBriZfPDLkdTklOIJU/a44dg6nGebfQzVtchu9hgyqcnDh+ow2zzF9PSK0YdL6/s/6PyddUcfsNrfkYEwjgLaHukkuKayB0utag3VutUTcmc6wUlNdjFkH9a4LtU+ZPycNpdkIH/becGLv9vkjj6jy2sq1v/VpRh+83kXoQiM1OSQIqwuVz9xc9awpmXl8mHNpzVX7xF8Qpex9PSY1mv630KEFlvQbk0JQ64ONh+rN3Y5koEY2ztujTXWaucmVAwnBsxq1gFq0AhSQAfSSNDSMKelHbkEq/g4FaX4LtOttoVCkK1QI9IRlaKyd46OvFO04+U971Q7ja+wVg9zibOuZxKhpQYnoNJwS41sWwqtsp3QU9eJ6Rck14lzVngDPl++xuZAA/QImlMhuaLSGE57ozo6dv1oRt2jcsXzPaaA8y+g3nmX4hKnXF0XpnqF7v0ayveutuX56gP9to3sLrovmKoycutD2ebrlM/n8/l/AAAA//8DAO33rqElBAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUUW/cKBDHv4rFs3EAY2z8Fm1a3SrXy6pd9U73NoZxg+LACnDaKNrvfsK7pzZPp3tBaGBm/vObgTfi4RnJSG5Ppw8v6HMiNcluMwkmFGWKMn7k7Sj5KGUjWNdxrf8mNXH3+EpG0ktjuNFIW1RIpWUdndrZUGtRc8YtE50sMeFbIuMbAddYfHEGm5C+YkwueDKSP5234XvJDa4xS1htE8OCe58yeFPE/LHf3dPfbw/Hh8PlVsJUnBtnyUiGoVedNYpKLZBKCYKC6Wba81YbOTOcdREBrnE+Y/SwNMk+/cxvQ/aYzSga0TaMCk3ONbGQoUieIOHx9VRUbIjuir3ezNt2fCMvGMko6n9hXsLdmMXdPKdpdYu9uawZ0lPa1hlMDvGV1OQUwwljdrjxuU0Jn6fl9Qjp6ePlTtmmDz/QrBntLqw+k5HwVpGabICWBbILvrpq3C0Ofd4Oc3Q+OfNfsVpSk4cv1WGBPIf4/K4fXy6Y9/+D8icwj85jtb8jI2EchW0HpLPgisrBAoWu1VSpVs3Ije4FJzXZBZ+dX8Oaqr3P+C1uJZGRfIQl4UXfbTSPLqPJayx1/qVKskMMdjW5+tlLzhrWtIwVpzWf1lx9RusimoyliGNcr/G+vncRSmxOuzVG9Lk6QH6sfoP0SEaiYTAcNGgAZcyMHcOZWQaK9RaVVWilsL2VWlolNTNKwsSlhY5Pc7GQmmAZnWqbVrRopIGOzgonKqeuoxMOQGHgyGduWC/ec6weluIHZmASbUs1zpZKzYFq2bbUth30Qs19L+ZfkFzngbMC2OLL5d1tCpS1A1rFqZC8o1JrTgfd9XTqh0lPasDOFM33GD0uv4D65EwMKcy5uk5I9Q7d59WXv6PapuW78/TH1qO7YJ4wVqXH4Hx5Kte2ns/n8z8AAAD//wMAE1/rSoIEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTS2/cNhD+KwLPosyXKFE3Y52iCzfxIlmkRW8jchgTlqkFRTkJjP3vAbUbJO6tF4IY8Jv5HsNXEuEZyUBuT6d3LxjzQmqSw1YSTGjKNGX8yOWg+KBUI1jPW8X/JTUJ9/idDKRT1nJrkErUSJVjLR2lt9Q5NJxxx0SrSk/4spDhlUBoHL4Ei828fMa0hDmSgfwdopu/ltkQGjvNq2vSPOE+LhmiLWQ+7Hf39K/bw/HhcHm14FLATXBkIH3f6dZZTZURSJUCQcG2nnZcGqs8Q28KCQhNiBlThKlZ3NOv+W7OEbMdRCNkw6gw5FwTBxkK5REWPH4/FRabRXelXm/l7Tq8khdMZBD1TzMv7W7sFG6el3ENk7u5nBmWp6LylOYTphxw82STOU2Qwxyr66TdFDBmUpOHT9Vhguzn9PzGqE8X/fv/If9Ypr/7hnbN6HbzGjMZCJeG1OQ92McQsdrfkYEwjsLJHqkXXFPVO6DQSkO1ltojt6YTnNRkN8cc4jqvS7WPGb+kTQAZyB8wLXihfpvsY8ho85qKqn904XFIs1ttrn75z1nDGslYAa35tObqI7qQ0GYs+o5pvfb7D0RosYF2a0oYc3WA/Fj9CcsjGYiB3nIwYAC0tR5bhp45Bpp1DrXT6JRwnVNGOa0Ms1rByJWDlo++VEhNsMRdbRsmZQe9B0k77wRVnUDaOwTq1WgFiHZUKN/4WD1MBQe2ZwqdpAa9o8pwoEZJSZ1soRPad53wv1lyTZ+zYrDDl8tf2Rho53p0mlOheEuVMZz2pu3o2PWjGXWPrS2c7zFFnH4z6n2waV5mn6vr8lRvrPu4xvLfq22RvoZIv20Z3c32CVNVMoYQy3pfYz2fz+cfAAAA//8DANVoRU82BAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUXW/jKBSG/4rFtXEBY2x81013tFGn02ianV3tHYbjFNWBCHBnqyr/fYSTqumM9mLvrGPe8/GcF16RU3tAPbo+HH5/BpciKlGyS4gRJjARmNAtrXvO+rqpBCed5OIfVCJ7Cy+oRy3XmmoJuAYBmBvS4KEeNTYGJCXUENbwnFPtIupfkbKVgWerofLxG4RovUM9+ss647/n2spWevKzqYKfYO1iUk7nZr6sV7f48/Vme785nYoQs7iyBvVoGLnhghisBRsxJx3BHSiBFVDgpqMSWHOSWZcgODVV0Ty91zc+OUi6ZxWrK4KZRMcSGZVUbnlQEbYvh9zFgugmx8slvHz2r+gZAupZ+QbzlO5KT/ZqH4fZTuZq8ruddbuVd6PdzUGlXLhEh+APEJKFBc5v1qnw8tnvdjkf+qSmCKhEWwh769R0+nEDo5qn9ODnsJCJ5um/zqAeqTl5VKKF5DQtZYvzMKvJgku/aC8K3z8Um0ml0Yf9hyU9nNiv/wf6O6UfrYNifYN6RCgwU3eAR0YF5p1RWDW1xELUYgSqZcsoKtHKu2Td7OdYrF2C3Znah/6ug360CXSaQ57pb5HNtgnezDoV7wumpCJVTUgWzekwp+IrGBtAJ8hDbMN8zveThAm2iD7ZCX7Bs5pDAJeKjUqPxR8qPqIeSdVpqqSSSgmtR2gIjMQQJUhrQBgBhjPTGi65EVwSLbgaKDeqocOYI6hEkE1WLL4momasIxLX1HSY12ODpaoHzIWghOpRctZ+gFvcT1mndEc4mBpLGA3mkioseV1jUzeqZWJsWzZecDobgpJM/dKEf0YwZy99OVn7bXYDz6d7vPQpjOnACIoZpw3mUlLcyabFQ9sNchAdNDpPtvIu+jeQ3yAMPtqUH5E76+xeTT8fed/MLQQH08V27qwOPvoxFWdbFh/29XV2+RUrFot+tw7/uxjjxusnCEU2lrLuYpnH4/H4AwAA//8DACOBIt4MBQAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUW2/cNhCF/4rAZ1Hm/aI3w27RhZt6ES/aoG8UZxQL0UoLito0MPa/F9RukbhveRMGOsPDb87wjUzhiKQl96fTL2ec8kJqkoetJJgwlBnK+IHLVolW6sYw77TWf5OaDE/4jbTEqhh59EglGqQKmKad7CMFQM8ZBya0Kj3D54W0byQMDeB5iNjMy5+YlmGeSEv+GiaYv5azw9DEcV6hSfOIu2nJYYrFzB+7hyf6+/3+8Ly//rXgUsTNAKQlXa9AGQY0GtFTxRyjDoOhATkqcNyj0FfZMGVMUxibBb58Px/mPGGOrWiEbBgVnlxqAiGHYrkLCx6+nYqLDdFjqddbefts38gZE2lF/R/Ma7u7OA53y9qNeMbxFNKC6S7Ox2OYgNTklOYTpjzghuWMqZuXIReiMdrgHOfgglXagpNaSAuSIThmNXjwwSjTi6h1D53zVvUKBFdgUKnOkpps4MYx5GGeqpv3h3HAKZOaPL9U+zHkfk7Hd+hfrkR3PwG02CYtkUxLYy0qKWXwphMMwZpojOhiYLbvBPKITHtlPbeMSy+Yl9pYZyEwH0lNPoT4OkxY7R5JSxhHAdIh7QU3VDkINGjpqTHS9Mijt4KTmjzMUx6mdV6Xajdl/Jy2+5KW/BrGBa83vU/xdcgY85oKhE+mhHGfZlhjrr4HgLOGNZKxIlrzac3VR4QhYcxYcBzSeuv3P4kwYhM9rCnhlKt9yK/Vb2F5JS3xwUUefPAhmBh71Ax7BiwYZgENGAQlwILyCozyLBoVOq4gaN71pUJqgiVv1RZxzXoudadpj6ipYr2gnjFHey6CdMZaUPIdx+p5LLoQHVMIknrsgSrPA/VKSgpSBytMb63of0ByCwtnBTDg+bqsmwMD4BAMp0JxTZX3nDqvLe2s63xnHOpYPD9hmnD8AdSHIaZ5mftc3bJWvUP3cZ3Kg1Ntufs6TPSfbUaPc/yCqSozDsNU9us21ktNjhiWNeFxe6/aN7Ivy1UdtmdL6IZ5J2rykkPK6+lW1rKxzrjL5XL5FwAA//8DAPYEweL0BAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS/zQzbBb1HVTL+JFWvQ2Ike2EC21oKhNA2P/e0DtBol7600Y6J0ZPvPOvJEIByQduT0efzlhzAupSR63kGBCU6Yp43suOyU62Tat4cpq9w+pyfiIX0lHjPKee4dUokaqAmtpLwdPQ0DHGQ9MtKrkhJeFdG8ExibgafTYzMsnTMs4R9KRv8YY5i+lNoyNn+Y1NGme8CEuGaIvzfz5cPdI/7jd7Z92l78WXIq4GQPpSD+ooDQL1GsxUMUsoxZBU0COKljuULQX2RgzpghTs4TPP+qHOUfMvhONkA2jwpFzTQJkKC33sOD+67F0sSG6L/F6C2+f3Rs5YSKdqL/DvKS78dN4k+fjhCecjpAWTDd+PhwgBlKTY5qPmPKIG5btpdMEeZxjdS12N40YM6nJ03O1myAPczq8Y/V8QfDwPwicMPWkI5K1UhuDSkoJTveCYTDaay16D8wMvUDukbVOGccN49IJ5mSrjTUBmPOkJi/T3MP0+7Lxc4L1NjAjtECFTiitnWcoLQ6tQRZA615b74K0RigbrAQHwF2Pxjvti0E+gH8dI1YP96QjjKMI0iIdBNdU2QAUWumo1lIPyL0zgpOa3M0xj3Gd16V6iBlf0saPdORXmBa8kLtN/nXM6POaCtS/dSm2S3NYfa5+OICzhjWSsSJa83HN1UcMY0KfseDdp/Wa7z8SocUmultTwpirHeTX6jdYXgsVsJ6DK0/V3g/YMhxYYKCZCaiDxqBEMEE5FbRyzGsFPVcBWt4PJUJqgsVw1eZxqSQGNgBtfQtUoeYUmDXUh9aI4EQ/wHuO1dNUdOAtUxgkdTgEqhwH6pSUNMgWjNCDMWL4CcnVfJwVwAFPl23dOtAhWAyaU6F4S5VznFrXGtob27teW2x96fkRU8TpJ1AfRp/mZR5ydfVu9Q7dxzWWi1NtPv4yRvrvNqP72X/GVJUZwxjLgl3Heq7JAWFZEx62g9W9kV3Zrmq/3S3RNsxZUZPnDCmvx2u4lY2x2p7P5/M3AAAA//8DADaVhNr1BAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUS2/bOBD+KwLPpkJSFCUK2IPhtFgjTWMk7u5ibxQ5jInIokFRToMi/31BWa6dooe9CaN5fY/hD9SrPaAGLQ+HT0fo44AWKLopxAgTmAhM6JYWDWdNIXJS0IrW7F+0QO4O3lCDKq411RJwAQIwN6TEbWE1NgYkJdQQVvLUUz0PqPmBlMsNHJ2G3A9/QRic71GD/na98a9ptnK57vxo8uA7WPdDVL1Oy3xdr+7wl+Vm+7A5ZQ0wpOLcGdSg1nLDBTFYC2YxJzXBNSiBFVDgpqYSWHkqc32E0KsuH8zLZb7xsYeoG5azIieYSfS+QEZFlVZu1QDbt0PaYqLoNsUXU3j6bH6gIwTUsMWZzFO7G925m/3Qjq4zN1GFZ4g2qD28+vACR9WhBToEf4AQHUzcPDxll5UoyUnOBCMELdC90jvXQ7a+zR66BFjpmnAwBZZgDeaSKix5UWBTlKpiwlYVs2iB7iD00F11vXc6+MHbmM2UZx/mrHwfXT/6ccjWfYTnoOKp7rPqBkALtBnbzg27R1Dmbesfx/SvH7uEZTWGAH3MNirusj/VsEMNkqrWVEkllRJaWygJWGKIEqQyIIwAw5mpDJfcCC6JFly1lBtV0tamCFqghzEexnjm//vVEtvg9nswlw22E8ebTkXrw35toI/OuiTNOWMyVNdNqLK556pz0McPJKMGEQrMFDVgy6jAvDYKq7KQWIhCWKBaVoyeCLPuefzJE7HW8MKWdS1a2VICzFY1J5VtidG20NKULVSsIlUNtdSEtCWndSs1MUBLbhK64M2o469WKCaBHsc+XWe2TrBfXY+/C35hZBN89Np3STDXwdPbEGH/O9VWfn/wg4sJv52l/TbAve/T3/n+Z87m4qWPlxikO8im0yuZopYwiRWnBea20LjmdYsVU9TQtpVGJxlvvX6BkCWDKddPmpw99fCULYPeuQg6jiFN/mcCdZLz8/lkLnzkXz9tVz7A8nBYzNE/jomkU7OzAT68K9+GlL7zQ7yCPEN7gs6eF7uyk4Hj6alyKSiMqcEIihmnJeZSUlzLssJtVbeyFTWUE86TXbNHMC6AjlO/bRjTtJnZ3/lyGaKzSschHc8Xryc7zf6cU35d8kq2n9Wn4VfSzVaaO1GSLPt0ejgnB/2vd/P9/f39PwAAAP//AwC2uuIgKAYAAA==

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRJF6c20HXaSOjeyiLfo2Ikc2a5laUJSTwPC/F9TauaJAXwRhSM45c+bMPJEID0h6cnY8Xj5izAupSQ5bSDChKdOU8QOXvRK94o0xgivd/U1qEt7hZ9KTTjnHnUUqUSNVnrV0kKOj3qPljHsmWlVywu1C+icCofH4GBw28/IHpiXMkfTkzxD9/LFgQ2jcNK++SfOEu7hkiK6Qeb87f0d/P7s5XN+cbi24lMdN8KQnw6i80sxTp8VIFTOMGgRNATkqb7hF0Z6ehZgxRZiaxd9/xfdzjphdLxohG0aFJc818ZChUB5gwcPnY2GxSXRR4vUW3n77J/KIifSifhXzlO6Nm8Kbh2VYw+TfbF9Sk2Oaj5hywE2N6331lQRnDWuEFoyRmlyBuwsRq91FdT2VEsEZptBLanH0VFkO1CopqZctdEKPXSdGUpN3mCJO32S9Ci7Nyzzm6kXk6juc/dn5ZYRhwoLxFqYFSU12yz5D9DDNES8/oVvzKdchreX4fI45xHVel2oXM94meDl/fX++poQxVzeQ76rfYLkjPbFgHAcLFkA7N2LLcGSegWadR+01eiV855VVXivLnFYwcOWh5cNYIqQmvxYN96tzuCxf2WwmmaaNQ/XSp/MpYMzfyUh6wjgKLw3SUXBNlfFAoZWWai31iNzZTvBXmAOkW8ykJ6MZhoE5NwhluQNhQfqRSa9HK0TXgRpl2ymhdcs6cL7rBFdeduhAjpIZQWpyk2a/uvxjq+XWgJs0/4MuF6lITzRrW+NgGIRW3IA1qLhWRlvsrBqBKekG4K2BYeAMWuasEQ64tqAG345fVLqMtyHiRViOE3z+Btc0upG/OADDR1A/XH+b4AE/zun+/cnGzfvLA6nJhzWWjVDtikU+hkg/6TLRV+uUw+EuIXj0V7PHn32EZVyqbUJH27pRGks96DKhbKAwGEalYcC1VErpov3F7O4xVcVhEGKZqi/JrvfVWXJ3IaPLayr8/tp4XO+rmwnyOKeH7zbJN4X9KEAjJGelMx4fT9to46i9N+g1p0LxliprOTW27ejQmcEO2mDrimLXaz6uufqAPiR0eav4xYsb5vkduvufxXh1wYtFOSv17k9bbJP2fy2x55o8ICxrwodtW/dPZBcjpg35Yj2N4i5ehWkKC7o5+oX0WijTdK1WLwz/855kvOmkUs/Pz8//AgAA//8DAEDMGTMbBgAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTTW/cNhD9K8KcRZmkKErUzVin6MJNvEgWadEbxRnFhGVqQVFOAmP/e0DtBk5664UgBnwz72P4CsE+E/Rwezq9e6GQFigh+a0kudSMa8bFUdS9kr0SVdd2XKruXyjB39N36KFVzglniNWkiSnkDRvq0TFEMoIL5LJRuaf9skD/CtZXSC/eUTUvnykufg7Qw98+4Pw1z7a+ctO8YhXnifZhSTa4TObDfnfP/ro9HB8Ol1cLLRlceYQehlGh0hyZ03JkinecdWQ1syRIYScMyeYC8yFRDHaqFnx6m49zCpRcLytZV5xJA+cS0CabKQ92oeP3U2axWXSX6+VW3q79K7xQhF6WP828tLtxk795XobVT3hzOZNdnrLKU5xPFJOnzZNN5jTZ5OdQXCftJk8hQQkPn4rDZNM4x+ffjPp00b//H/KPefq7b+TWRLib15CgB1EbKOG9dY8+ULG/gx64IIl1R2yUQjPVoWW2qQ3TutYjCWdaKaCE3RySD+u8LsU+JPoSNwHQwx92WuhC/Ta6R5/IpTVmVf/ovAuHOOPqUvHmv+AVr2rOM2hNpzUVHwl9JJco6zvG9drvPxCp5QbarTFSSMXBpsfiT7s8Qg/Gdk5YY4212rmRGk4jR241b5E0akIlsUVlFGpluNPKDkKhbcQw5gqUQDnuYtswIdRQu4GYbCRnCp1idqCWNdia0TnpBpspvvlYPEwZZ13HFWHNDI3IlBGWGVXXDOvGtlKPbSvHXyy5pi94Nhjp5fJXNgYasSPUgkklGqaMEawzTcuGthvMoDtqXOZ8TzHQ9ItR772L8zKPqbguT/GbdR/XkP97sS3SVx/Yty2ju9k9USxyxtaHvN7XWM/n8/kHAAAA//8DAOoNqD02BAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUwW7cNhD9FWHOokxSFCXqZqwTdOGmXiSLtOiN4oxiwjK1ICknhrH/Xki7ReJT0QtBjPiGb9571BsE+0zQw+3p9OGFQk5QQvZbSXKpGdeMi6OoeyV7JaquVa1q6r+hBH9Pr9BDq5wTzhCrSRNTyBs21KNjiGQEF8hlo9ae9luC/g2sr5BevKNqTl8pJj8H6OFPH3D+vt5tfeWmecEqzhPtQ8o2uJXMH/vdPfv99nB8OFxOJUoruPIIPQyjQqU5MqflyBTvOOvIamZJkMJOGJLNBeZDphjsVCV8+nk/zjlQdr2sZF1xJg2cS0Cb7Up5sImOr6eVxSbR3Vovt/K27d/ghSL0svxXzEu7Gzf5m+c0LH7Cm8uabXpK2zpal+f4CiWc4nyimD1t+tymRM/D9Hq06enj5cy6TR9+kFsy4W5eQoYeRK2hhE2gabLZz6G4ctxNnkLePuboQ/Luv3rVUMLDl+Iw2TzO8fmdH18uMu//h8qfrHv0gYr9HfTABUmsO2KjFJqpDi2zTW2Y1rUeSTjTSgEl7OaQfVjmJRX7kOlb3EaCHj7aKdGF3210jz6Ty0tc5/xLr7k6xBkXl4ufXgpe8armfAUt+bTk4jOhj+QyrUMc43Lt9/U9RGq5gXZLjBRycbD5sfjNpkfowdjOCWussVY7N1LDaeTIreYtkkZNqCS2qIxCrQx3WtlBKLSNGMa1AiXQGp1iS6uVjZAtR9Y5NTJFamRdKw3ruNNScMnR4Dsdi4dpw7mOK8KaGRqRKSMsM6quGdaNbaUe21aOv0hyzYPgq8BIL5d3tzHQiB2hFkwq0TBljGCdaVo2tN1gBt1R41bO9xQDTb8I9cm7OKd5zMU1IcU76T4vYf13FFtavvvAfmwe3c3uiWKxemx9WJ/K1dbz+Xz+BwAA//8DAELILvyCBAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUW2/cNhCF/4rAZ1Hm/aI3w27RhZt6ES/aoG8UZxQL0UoLito0MPa/F9RukLpveRMGOjOH3xzyjUzhiKQl96fTL2ec8kJqkoetJJgwlBnK+IHLVslW8EZK75R2f5OaDE/4jbTEqhh59EglGqQKmKad7CMFQM8ZBya0Kj3D54W0byQMDeB5iNjMy5+YlmGeSEv+GiaYv5bZYWjiOK/QpHnE3bTkMMVi5o/dwxP9/X5/eN5f/1pwKeJmANIS1wsTQtdR77tAFaCjgXtOueaopFYBFb/KhiljmsLYLPDlx3yY84Q5tqIRsmFUeHKpCYQciuUuLHj4diouNkSPpV5v5e2zfSNnTKQV9XeY13Z3cRzulrUb8YzjKaQF012cj8cwAanJKc0nTHnADcsZUzcvQy5EY7TBOc7BBau0BSe1kBYkQ3DMavDgg1GmF1HrHjrnreoVCK7AoFKdJTXZwI1jyMM8VTfvD+OAUyY1eX6p9mPI/ZyO79C/XInufgJosU1aIpmWxlpUUsrgTScYgjXRGNHFwGzfCeQRmfbKem4Zl14wL7WxzkJgPpKafAjxdZiw2j2SljCOAqRD2gtuqHIQaNDSU2Ok6ZFHb0WZ/jBPeZjWeV2q3ZTxc9rOS1ryaxgXvJ70PsXXIWPMayoQPpkSxn2aYY25+hEAzhrWSMaKaM2nNVcfEYaEMWPBcUjrrd//JMKITfSwpoRTrvYhv1a/heWVtMQHF3nwwYdgYuxRM+wZsGCYBTRgEJQAC8orMMqzaFTouIKgedeXCqkJlrxVW8SNFyxy1VOuvKLKR0+d95zqTisP6KXD/h3H6nksuhAdUwiSeuyBKs8D9UpKClIHK0xvrSi670huYeGsAAY8Xy/r1QGAQzCcCsU1VWW489rSzrrOd8ahjsXzE6YJx/+A+jDENC9zn6tb1qp36D6uU3lwqi13X4eJ/rPt6HGOXzBVZcdhmMr9uq31UpMjhmVNeNzeq/aN7Mvlqg7bsyV0Y73zNXnJIeX1dCtb2Xjv3OVyufwLAAD//wMAMacs9fQEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUS2/bOBD+KwLPpkJKFCUK2IPhtFgjTWMk7u5ibxQ5jInIpEFRToMi/31BWa6dooe9CaN5fY/hD+TkHlCLlofDpyO4OKAFinYKFaTgmHBM6JaWLSvbguZNwTgvxL9ogewdvKEW1UwpqgTgEjhgpkmFu9IorDUISqgmRcVST/k8oPYHkjbXcLQKcj/8BWGw3qEW/W2d9q9ptrS56v2o8+B7WLshSqfSMl/Xqzv8ZbnZPmxOWQMMqTi3GrWoMQWXsuuwEJ3ETEODJRUU04oCKysmgdFTmXURgpN9PuiXy3zto4Oo2iIvypzgQqD3BdIyyrRyJwfYvh3SFhNFtym+mMLTZ/sDHSGgtlicyTy1u1G9vdkP3Wh7fRNleIZogtzDqw8vcJQ9WqBD8AcI0cLEzcNTdlmJkpzkBS8IQQt0L9XOOsjWt9lDnwBL1RAGusQCjMZMUIkFK0usy0rWBTd1XRi0QHcQHPRXXe+tCn7wJmYz5dmHOSvvonWjH4ds7SI8BxlPdZ9lPwBaoM3Y9XbYPYLUb1v/OKZ/buwTltUYAriYbWTcZX/KYYdaJGSjqBRSSMmVMlARMEQTyUmtgWsOmhW61kwwzZkgijPZUaZlRTuTImiBHsZ4GOOZ/+9XS2yD3e9BXzbYThxvehmND/u1BhetsUmac8ZkqL6fUGVzz1VvwcUPJKMWEQqFLhvApqAcs0ZLLKtSYM5LboAqURfJUSvvjH0ef/JEjNGsNFXT8E50lEBh6oaR2nREK1MqoasO6qImdQONUIR0FaNNJxTRQCumE7rg9ajir1YoJ4EeR5euM1sn2K/W4e+cXRjZBB+98n0SzPbw9DZE2P9OtZXfH/xgY8JvZmm/DXDvXfo73//M2Vy89PESg3QH2XR6siLSlCXHomQCM91JLIhssDGkNgZ0WUOJFujWqxcIWTKYtG7S5Oyph6dsGdTORlBxDGnyPxOok5yfzydz4SP/+mm78gGWh8Nijv5xTCSdmp0N8OFd+Tak9J0f4hXkGdoT9Oa82JWdNBxPT5VNQa51A5pTXDBaYSYExY2oatzVTSc63kClLnbNHkHbACpO/bZhTNNmZn/ny2WI1kgVh3Q8X7ya7DT7c075dckr2X5Wn4ZfSTdbae5ESbLs0+nhnBz0v97N9/f39/8AAAD//wMAM2qugCgGAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSU227cNhCGX0XgtSjzJB50Z9gt6rqpF/EiLXo3JEe2EK20oKhNA2PfPaB2g8S9650w0D+Hb/7hG5nggKQjt8fjLyec8kJqkoctJJjQlGnK+J7LTslO8EY4boXl/5CaDI/4lXTEqBB4cEglaqQqspZ62QcaIzrOeGSiVSUnvCykeyMwNBFPQ8BmXj5hWoZ5Ih35a5ji/KXUhqEJ47zGJs0jPkxLhimUZv58uHukf9zu9k+7y18LLkXcDJF0xPZCA3hPnfNAVURLgTtOectRyVYBKn6RDVPGNMHYLPHzj/pxzhPm0IlGyIZR4ci5JhEylJY9LLj/eixdbIjuS7zewttn90ZOmEgn6u8wL+luwjjc5Pk44gnHI6QF002YDweYIqnJMc1HTHnADcs26ThCHuapuha7GwecMqnJ03O1GyH3czq8Y/V8QfDwPwicMHnSEclaqY1BJaUEp71gGI0OWgsfgJneC+QBWeuUcdwwLp1gTrbaWBOBuUBq8jLOHsbfl42fE8zbyIzQAhU6obR2gaG02LcGWQStvbbBRWmNUDZaCQ6AO48mOB2KQT5AeB0mrB7uSUcYRxGlRdoLrqmyESi00lGtpe6RB2dEmeZunvIwrfO6VA9Txpe08SMd+RXGBS/kblN4HTKGvKYC9W9diu3SHNeQqx8O4KxhjWSsiNZ8XHP1EeOQMGQsePdpveb7j0RosYnu1pRwytUO8mv1GyyvhQrYwMGVUXUIPbYMexYZaGYi6qgxKhFNVE5FrRwLWoHnKkLLfV8ipCZYDFdtHjcsGghtT73RLVWtZ9RxISj4wGPfC2Xb9h3H6mksOgiWKYySOuwjVY4DdUpKGmULRujeGNH/hORqPs4K4Iiny7VuHegYLUbNqVC8pco5Tq1rDfXGeue1xTaUnh8xTTj+BOrDENK8zH2urt6t3qH7uE7lxak2H38ZJvrvtqP7OXzGVJUdwzCVA7uu9VyTA8KyJjxsD1b3Rnbluqr99m6JtjHOupo8Z0h5PV7DRjbOWXs+n8/fAAAA//8DALmMltD1BAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/jNhCG/4rAs6mQFEWZuqVOFzWy2Rgbd1v0NiJHDhGZNCgq2yDwf19QdhBnFz30Joz4zsczL/lKPOyRtOT6cPj9GX0ayYIkN4cEE4oyRRnf8qqVVSt4WSnBlBD/kAVxt/hCWtJIY7jRSCtUSKVlNe2q3lBrUXPGLRO1zDlhN5L2lYArLT47g2UYv2EcXfCkJX85b8P3XBtcaYYw2TKGAdd+TOBNbubLenVLP19vtveb06kRxywunSUtWfZCAXQd1boDKi0uKXDNKa85yqqWgJKfZM4njB6GcrRP7/VtSB6TaUUpqpJRoclxQSwkyC13MOL25ZC7mBHd5PhiDs+f7St5xkhasXiDeUp3ZQZ3tR+7yQ32agi7nfO7VfC9200RUi68IIcYDhiTwxnOb85DfPkcdrucj3yCYUSyIFuMe+dhOP24wR6mIT2EKc5kRvv0X2dIS2BKgSzITHIY5rLFeZjV4NCnX7QXhe8fis0AqQ9x/2FJDyf26/+B/g7Mo/NYrG9ISxhHYasl0l5wReXSAoW60lSpSvXIjW5E1qyCT85PYRqLtU+4O1P70N91NI8uoUlTzDP9rbLZNjHYyaTifcGclaysGMuiKR2mVHxF6yKahHmIbZzO+X6SCCVm0Sc34C94VlOM6FOxgfRY/AHjI2mJhqXhoEEDKGN6rBn2zDJQrLGorEIrhW2s1NIqqZlREjouLdS863OELAhmkxWzr1HpjlVNTbUGQWUvO6pr2dAKGmGENpxVH+EW90PWgVkyibaiGntLpeZAtawqaqsaGqH6phH9BaezITjLyS5N+OeI9uylLydrv81u8fl0j+c+lbVLtIpTIXlNpdacLnXd0K5ZdrpTS6xNnmwV/BjeQH7D2IXRpfyI3Dnv9jD8fOR9M7cYPQ4X27lzJoYx9Kk427L4sK+vk8+vWDFb9Lvz9N/ZGDfBPGEssrHA+YtlHo/H4w8AAAD//wMA2IvlogwFAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTwW7UMBD9lcjndWo7jhPnVm1BrAp0RStA3CaeCWuROivHKaBq/x05uwjKjZv17Dfz5r3xMwvwSKxj18fjqycKaWYblvwKKaEMF4YL+SCrTledsmUjhGis+sI2zN/ST9axRjsnnSVekSGuUdS8rwbHEclKIVGoWuea8HVm3TMDXyI9eUflNH+kOPspsI598gGn77k3+NKN04JlnEbahTlBcFnM+932lr+93j/c7c+vZpozufTIOtYOygD0Pbe2B66RWg7SSi5rSbqqNZCWZ5oPiWKAsZzx25/+OKVAyXWqVFUpuLLstGEICbLkHmZ6+HnMKlaLbjK+WeH12D2zJ4qsU5vfZp7LXbnRX7np8RECXg0++PnANuwYpyPF5Gm1Y51wHCH5KRSXJtvRU0hsw+7ui/0IaZji4wuP7s+j7/5j8nfgDj5QsbthHROSFFYt8UFJw3WLwKGuLDemMgNJZxuVOdspJB+WaZmLXUj0Na4qWcdewzjTWd91dAefyKUlZumfTc6afvi0nTADgm3YPk64uFT8sVuKUpSVyJd3SzouqfhA6CO5RHmmh7hcyv9DUUatpO0SI4VU7CEdijcwH1jHLLROggULYJwbqBY0CBRgRINk0BBqhQ1qq9FoK5zR0EuNUMt+yEjWndMt1oXSUuKAdcO1A8216DXvexq4rYwZemtb0eMLW4u7MfPAtUITVtzSgFxbCdzqquJY1dAoMzSNGv6y5JK4FNlvpKfz11gVGMSW0EiutKy5tlby1tYN75u2t71pqXZZ8y3FQONfRr3zLk7zNKTisjDFC+s+LCF/72Jdnu8+8B9rZDeT+0axyJGDD3mbLymfTqfTLwAAAP//AwAqQNGLJQQAAA==

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRUTW/cNhT8KwLPS5lfokTdjHWCLtzUi2SRFr09kU8xYZlakJQTw9j/XlDaIPWp6IUgnjiP82aGeiMBnpH05PZ8/vCCISeyI9mvJcGEpkxTxk9c9kr2wtTaKK2N+JvsiL/HV9KTVlnLrUEqUSNVjjV0kKOlzqHhjDsmGlV6wrdE+jcCvnb44i3Wc/qKMfk5kJ786YObv5e7wdd2mhdXx3nCQ0gZgi1k/jjs7+nvt8fTw3E7lTAVcO0d6Uk3Cg0wDNSYAahy2FHghlPecFSyUYCKbzAfMsYAU53c06/73ZwDZtuLWsiaUWHIZUccZCiUB0h4ej0XFqtEd6W+W8vrtn8jLxhJL3Y/xdza3djJ3zynYfGTu9nWDOkpresINs/xlezIOc5njNnjqs9tSvg8TK8nSE8ftzNlmz78QLtkdPt5CZn0hEtNdmQVaJog+zlUV477yWPI68ccfUje/lcvSXbk4Ut1nCCPc3x+58eXTebD/1D5E9hHH7A63JGeMI7CyQ7pKLimqnNAoZGGai31iNyaVhTMfg7Zh2VeUnUIGb/FdSTSk48wJdz43Ub76DPavMQy51+65OoYZ7fYXP3ykrOa1ZKxAlryecnVZ3Q+os1YhjjF5drv63uI0GIF7ZcYMeTqCPmx+g3SI+mJgc5yMGAAtLUjNgxH5hho1jrUTqNTwrVOGeW0MsxqBQNXDho+jKVCdgRLdKotrUK2jbSKIraWKmY5NdIyqgE1ArDOmPGdjtXDVHBgO6bQSWpwdFQZDtQoKamTDbRCj20rCu6nJNc8cFYEdviyvbuVgXauQ6c5FYo3VBnDaWealg5tN5hBd9jYwvkeY8DpX0J98jbOaR5zdU1I9U66z0so/45qTct3H+iP1aO72T5hrIrH4EN5KldbL5fL5R8AAAD//wMAoRyWV4IEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTS2/cNhD+KwLPS5kvUaJuxjpFF27qRbJoi95GnFFMWKYWFOUkMPzfC2o3SN1bLwQx4DfzPYavLMIzsZ7dns8fXijmhe1YDltJCWW5sFzIk9S90b1ytXVtq1X3N9uxcE/fWc9a4730jrgmS9ygaPigR88RyUkhUajGlJ7wZWH9K4NQI70ET/W8/EFpCXNkPfszRJy/ltkQaj/NK9ZpnugQlwzRFzK/H/b3/Lfb4+nheHm10FLAdUDWs25UFmAYuHMDcIPUcZBOctlIMroxQEZeYCFmShGmesGnn/NxzpGy71WtdC24cuxtxxAyFMoDLHT6fi4sNovuSn23lbdr/8peKLFe7X6YeWl346dw87wMa5jw5nJmWJ6KynOaz5RyoM2TTeY0QQ5zrK6T9lOgmNmOPXyujhPkcU7P74z6fNF/+B/yT2X6h2/k10y4n9eYWc+kdmzHPoJ/DJGqwx3rmZCkUHfERyUtNx0Ch0Y7bq22I0nvWlXa7eeYQ1zndakOMdOXtAlgPfsFpoUu1G+TfwyZfF5TUfWXLbtwTDOuPlc//ZeiFrUWooDWfF5z9YkwJPKZir5TWq/9/gNRVm2g/ZoSxVwdIT9Wv8LyyHrmoPMSHDgA6/1IjaBRoAArWiSLltAobNE4g9Y44a2BQRqERg5jqbAdoxJ3tW0YaAckoOPK6ZabDog77DQfpDANYmPGcXznY/UwbTjfCUOouaMRuXESuDNac9QNtMqObasK7ocl1/SlKAYjvVz+ysbAInaEVnJlZMONc5J3rmn50HaDG2xHjS+c7ylFmv5l1Mfg07zMY66uy1O9s+7TGst/r7ZF+hoi/7ZldDf7J0pVyRhCLOt9jfXt7e3tHwAAAP//AwDn5IjHNgQAAA==

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxU227jNhD9FYGvFRXeRIl6S50saqRJjLXRFn0bkaOYjUwZFJXsIsi/F5KTzWVRoC+CMCTnnDlzZp5IgAOShpwfj5cPGNJIcpL8EhJMaMo0ZXzHZaNkI0xRMc60qP4mOfFX+J00pFLWcmuQStRIlWMlbWVnqXNoOOOOiVLNOeFuJM0TAV84fPAWi2H8A+Poh0Aa8qcPbnicscEXth8mV8Shx3UYEwQ7k7lZr67o7+eb3e3mdGvEcX5ceEcaUndCA7QtNaYFqhzWFLjhlJcclSwVoOKnZz4kjAH6YnT3b/huSAGTbUQhZMGoMOQ5Jw4SzJRbGHH3/TizWCS6mOP5El5+myfygJE0In8V85TuzPb+7DC2k+/d2fIlOTnG4YgxeVzUuN1mbyQ4K1ghtGCM5OQa7N4HzNYX2W0/lwi2ZgqdpAY7R5XhQI2SkjpZQiV0V1WiIzm5whiwf5f12ts4jEOXsheRsw842/PVZYC2xxnjC/Qjkpysx22C4KAfAl5+QzulU65dnObj1RCSD9Mwjdk6JLyL8HL++n41xYghZRtI++w3GPekIQZqy8GAAdDWdlgy7JhjoFnlUDuNTglXOWWU08owqxW0XDkoedvNEZKTX2cNt5O1OI5vbBaT9P3CIXvp06r3GNIHGUlDGEfhZI20E1xTVTugUEpDtZa6Q25NJfgrzA7iHSbSkK5u25ZZ2wpluAVhQLqOSac7I0RVgepkWSmhdckqsK6qBFdOVmhBdpLVguRkEwc32fS51XJpwCYO/6BNs1SkIZqVZW2hbYVWvAZTo+Ja1dpgZVQHTEnbAi9raFvOoGTW1MIC1wZU68ruh0qX4c4HvPDjsYfv73DrQhfyFwtQ8w7Up+tfIhzwcYj3NycbFzeXO5KTr1OYN0K2ni3y6AP9pueJvp765Hf7iODQXQ8Of/YRzuOSLRNaCle7sgTKVN1RBVpSkAopZ2WHyuqKYUVycjHYe4zZ7DDwYZ6qH8lut9l5tHuf0KYpzvz+WnjcbrNND6kb4uHDJnlX2GcBCiE5mzvj8OG0jRaO2rkaneZUKF5SZQyntSkr2lZ1a1pdY2lnxW6ndJxS9hWdj2jTUvGLFxfM1R7t/c9ivLrgxaKczV7bnrbYIu3/WmLPOTkgjFPEw7KtmyeyDgHjgnwxnUZxHa593/sR7RDcSJpaqrqodGVeGP7nPcVEURrGn5+fn/8FAAD//wMAys86yBsGAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUW2/cNhCF/4rAZ1HmTbzozbBbdOGmXsSLNugbxRnGQrTSgqI2DYz97wW1G6TuW96Egc7w8JszfCOTPyLpyP3p9MsZp7yQmuRhKwkmNGWaMn7gslOqY65h1iij2d+kJsMTfiMdMSoEHhxSiRqpAtbSXsZAAdBxxoGJVpWe/vNCujfihwbwPARs5uVPTMswT6Qjfw0TzF/L2X5owjiv0KR5xN20ZD+FYuaP3cMT/f1+f3jeX/9acCniZgDSERukQotIdXSRKt621FkZKbjeCS1chBivsmHKmCY/Ngt8+XE+zHnCHDrRCNkwKhy51AR89sVy7xc8fDsVFxuix1Kvt/L22b2RMybSifo7zGu7uzAOd8vaj3jG8eTTgukuzMejn4DU5JTmE6Y84IbljKmflyEXoiEYby3nYL1RrQErWyENSIZgmWnBgfNa6ShC20borTMqKhBcgUalekNqsoEbR5+Heapu3h/GAadMavL8Uu1Hn+Ocju/Qv1yJ7n4CaLFNOiJZK7UxqKSU3uleMASjg9aiD56Z2AvkAVnrlHHcMC6dYE622lgDnrlAavLBh9dhwmr3SDrCOAqQFmkUXFNlwVPfSke1ljoiD84ITmryME95mNZ5XardlPFz2u5LOvKrHxe83vQ+hdchY8hrKhA+6RLGfZphDbn6EQDOGtZIxopozac1Vx8RhoQhY8FxSOut3/8kQotN9LCmhFOu9j6/Vr/55ZV0xHkbuHfeea9DiNgyjAyY18wAatAISoAB5RRo5VjQyvdcgW95H0uF1ARL3qot4kE538uoKVihqWLcUseNp4Frbr0B6eR7jtXzWHQ+WKYQJHUYgSrHPXVKSgqy9UboaIwok/yO5BYWzgpgwPN1WTcHGsAiaE6F4i1VznFqXWtob2zvem2xDcXzE6YJx/+A+jCENC9zzNUta9U7dB/XqTw41Za7r8NE/9lm9DiHL5iqMmM/TGW/bmO91OSIflkTHrf3qnsj+7Jc1WF7tiRrHBemJi/Zp7yebmXjGqWFuVwul38BAAD//wMACyupKPQEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUXW/jKBSG/4rFtXEBY2x81013tFGn02ianV3tHYZDiupAhHFnqyr/fYSTqumM9mLvrGPe8/GcF16RV3tAPbo+HH5/Bp8mVKLklhAjTGAiMKFbWvec90RWlJG2IfwfVCJ3Cy+oRy3XmmoJuAYBmBvS4KG2GhsDkhJqCGt4zql2E+pfkXKVgWenoQrTN4iTCx716C/nTfieaytX6THMpophhLWfkvI6N/NlvbrFn6832/vN6dQEUxZXzqAedbrm0AFgYaXFnDYNll1tsZGDZIJJa6w9yZxPEL0aq8k8vdc3IXlIumcVqyuCmUTHEhmVVG55UBNsXw65iwXRTY6XS3j57F/RM0TUs/IN5indlR7d1X4aZjeaqzHsds7vVsFbt5ujSrlwiQ4xHCAmBwuc35xX8eVz2O1yPvRJjROgEm0h7p1X4+nHDVg1j+khzHEhM5mn/zqDeqTmFFCJFpLjuJQtzsOsRgc+/aK9KHz/UGxGlWyI+w9LejixX/8P9HdKPzoPxfoG9YhQYKbuAFtGBeadUVg1tcRC1MIC1bJlFJVoFXxyfg7zVKx9gt2Z2of+rqN+dAl0mmOe6W+RzbaJwcw6Fe8LpqQiVU1IFs3pMKfiKxgXQSfIQ2zjfM73k4QJtog+uRF+wbOaYwSfio1Kj8UfanpEPZKq01RJJZUSWltoCFhiiBKkNSCMAMOZaQ2X3AguiRZcDZQb1dDB5ggqEWSTFYuv684CpR3BkrUK8xYkHngjMWmM0o1iurbtB7jF/Zh1SneEg6mxBGswl1Rhyesam7pRLRO2bZm94HQ2BCWZ+qUJ/5zAnL305WTtt9kNPJ/u8dKnMKYDIyhmnDaYS0lxJ5sWD203yEF00Og82Sr4KbyB/AZxCJNL+RG5c97t1fjzkffN3EL0MF5s587pGKZgU3G2ZfFhX19nn1+xYrHod+fxv4sxboJ+glhkYynnL5Z5PB6PPwAAAP//AwBHon9jDAUAAA==

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS/zQzbBb1HVTL+JFWvRGcUa2EC21oKhNA2P/e0DtBol7600Y6J0ZPvPOvJHoD0g6cns8/nLCmBdSkzxuIcGEpkxTxvdcdkp1zDVMOWmF+YfUZHzEr6QjRoXAg0MqUSNVwFrayyFQAHSccWCiVSWnf1lI90b82ACexoDNvHzCtIxzJB35a4wwfym1/diEaV6hSfOED3HJPobSzJ8Pd4/0j9vd/ml3+WvBpYibEUhHbJAKLSLVgxuo4m1LnZUDBdc7oYUbYBgusjFmTNFPzQKff9SHOUfMoRONkA2jwpFzTcBnX1ru/YL7r8fSxYbovsTrLbx9dm/khIl0ov4O85LuJkzjTZ6PE55wOvq0YLoJ8+HgI5CaHNN8xJRH3LBsL50mn8c5Vtdid9OIMZOaPD1Xu8nnYU6Hd6yeLwge/geBE6aedESyVmpjUEkpvdO9YAhGB61FHzwzQy+QB2StU8Zxw7h0gjnZamMNeOYCqcnLNPd++n3Z+DnBegvMCC1QoRNKaxcYSotDa5CB17rXNjiQ1ghlwUrvvOeuRxOcDsUgH3x4HSNWD/ekI4yjAGmRDoJrqix46lvpqNZSD8iDM4KTmtzNMY9xndeleogZX9LGj3TkVz8teCF3m8LrmDHkNRWof+tSbJdmWEOufjiAs4Y1krEiWvNxzdVHhDFhyFjw7tN6zfcfidBiE92tKWHM1c7n1+o3v7wWKt4G7l15qg5hwJbhwIB5zQygBo2gBBhQToFWjgWtfM8V+Jb3Q4mQmmAxXLV5nPUoglWCGi0GqqyQ1AcItDUBNXBtPLB3HKunqeh8sEwhSOpwAKoc99QpKSnI1huhB2NEccZ3JFfzcVYAA54u27p1oAEsguZUKN5S5Ryn1rWG9sb2rtcW21B6fsQUcfoJ1IcxpHmZh1xdvVu9Q/dxjeXiVJuPv4yR/rvN6H4OnzFVZcZ+jGXBrmM91+SAflkTHraD1b2RXdmuar/dLckax4WpyXP2Ka/Ha9i4Rmlhzufz+RsAAAD//wMAolJWbPUEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUS2/bOBD+KwLPpkJKFCUK2IPhtFgjTWMk7u5ibxQ5jInIpEFRToMi/31BWa6dooe9CaN5fY/hD+TkHlCLlofDpyO4OKAFinYKFaTgmHBM6JaWLWMtEXnVkJKX9F+0QPYO3lCLaqYUVQJwCRww06TCXWkU1hoEJVSTomKpp3weUPsDSZtrOFoFuR/+gjBY71CL/rZO+9c0W9pc9X7UefA9rN0QpVNpma/r1R3+stxsHzanrAGGVJxbjVrUqJJBA4C5EQYzWlVYNKXBWnSi4IUw2phTmXURgpN9PuiXy3zto4Oo2iIvypzgQqD3BdIyyrRyJwfYvh3SFhNFtym+mMLTZ/sDHSGgtlicyTy1u1G9vdkP3Wh7fRNleIZogtzDqw8vcJQ9WqBD8AcI0cLEzcNTdlmJkpzkBS8IQQt0L9XOOsjWt9lDnwBL1RAGusQCjMZMUIkFK0usy0rWBTd1XSTAdxAc9Fdd760KfvAmZjPl2Yc5K++idaMfh2ztIjwHGU91n2U/AFqgzdj1dtg9gtRvW/84pn9u7BOW1RgCuJhtZNxlf8phh1okZKOoFFJIyZUyUBEwRBPJSa2Baw6aFbrWTDDNmSCKM9lRpmVFO5MiaIEexngY45n/71dLbIPd70FfNthOHG96GY0P+7UGF62xSZpzxmSovp9QZXPPVW/BxQ8koxYRCoUuG8CmoByzRkssq1JgzktugCpRF/REmLHP40+eiDGalaZqGt6JjhIoTN0wUpuOaGVKJXTVQV3UpG6gEYqQrmK06YQiGmjFdEIXvB5V/NUK5STQ4+jSdWbrBPvVOvydswsjm+CjV75Pgtkent6GCPvfqbby+4MfbEz4zSzttwHuvUt/5/ufOZuLlz5eYpDuIJtOT3ZUdU1dYN5phZksBW50TXHZ1YqKQiqA1P3WqxcIWTKYtG7S5Oyph6dsGdTORlBxDGnyPxOok5yfzydz4SP/+mm78gGWh8Nijv5xTCSdmp0N8OFd+Tak9J0f4hXkGdoT9Oa82JWdNBxPT5VNQa51A5pTXDBaYSYExY2oatzVTSc63kClLnbNHkHbACpO/bZhTNNmZn/ny2WI1kgVh3Q8X7ya7DT7c075dckr2X5Wn4ZfSTdbae5ESbLs0+nhnBz0v97N9/f39/8AAAD//wMAkjOOpigGAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUy27kNhD8FYHXiDLfEnVzbC8ycPzAziAJcmuRLZuxTA0oyt6F4X8PpLHXj0WAXAShSXZVV1f3E4lwj6Qlx/v92QPGPJGS5LCGBBOGMkMZ33HZKtVyXdVMas7s36Qk4Ry/k5bUyjnuLFKJBqnyTNNO9o56j5Yz7pnQaskJNxNpnwiEyuNDcFiN0x+YpjBG0pI/Q/Tj44INoXLDOPsqjQNu4pQhuoXM5ebknP5+fL27uj7cmnBaHlfBk5Y0TipsEKnpbU8V15raRvbU284KI2zv+/7wLMSMKcJQTf7uDd+POWJ2raiErBgVljyXxEOGhXIHE+6+7xcWq0SnS7xcw+tv+0QeMJFWlK9iHtIduSEc3U/dHAZ/tH5JSfZp3GPKAVc1rrbFGwnOKlYJIxgjJbkAdxsiFpvT4mpYSgTXMIVeUou9p8pyoFZJSb3UUAvT17VYSjzHFHF4l/UiuDROY5+LF5GLDzjb45OzCN2AC8YXGCYkJdlM2wzRwzBGPPuGbs6HXLs0L8cnY8whzuM8FZuY8SbBy/nr+5M5JYy5uIZ8W/wG0y1piYXGcbBgAYxzPWqGPfMMDKs9Gm/QK+Frr6zyRlnmjIKOKw+ad/0SISX5ddFwOzuH0/TGZjXJMKwcipc+nQwBY/4gI2kJ4yi8bJD2ghuqGg8UtLTUGGl65M7Wgr/C7CDdYCYt6Zuu65hznVCWOxAWpO+Z9Ka3QtQ1qF7qWgljNKvB+boWXHlZowPZS9YIUpLrNPrZ5c+tlmsDrtP4D7q8SEVaYpjWjYOuE0bxBmyDihvVGIu1VT0wJV0HXDfQdZyBZs42wgE3FlTndf9DpbN4EyKehmk/wPd3uE1lKvmLA2h4D+rT9S8J7vFxTHeXBxtXl2c7UpKvc1w2QrFZLPIYIv1mlom+mIccdrcJwaO/GD3+7CNcxqVYJ5R3EpxWkkqsHVXC1LRzhlENrPNSauSdJCU5Hd0dpmJxGIS4TNWPZFfb4ji525DR5Tkt/P5aeVxti+sBcj+m+w+b5F1hnwWohORs6YzHh8M2Wjka7xv0hlOhuKbKWk4bq2va1U1nO9OgdotiV3Pez7n4ij4kdHmt+MWLK+bJLbq7n8V4dcGLRTlbvLY9bLFV2v+1xJ5Lco8wzQnv123dPpFNjJhW5NP5MIqbeBGGIUzoxugn0hrd6IpJo18Y/uc9I+uKa2Gen5+f/wUAAP//AwBHzZ1MGwYAAA==

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUUW/cKBDHv4rFs3EAY2z8Fm1a3SrXy6pd9aq+YWZoUBy8Apw2ivLdT3i3yuXpdC8IjZnhP7/54xcSzCOSkVyfTh+eMOREapL9FhJMKMoUZfzI21HKkXeN0px1SnwnNfG3+ExG0ktrudVIW1RIJbCOTq2zFAA1ZxyY6GSpaX4kMr4Q4xvAJ2+xWdJXjMkvgYzkbx9g+VnuNr6x87JCE5cZ9yFlE2wR89d+d0v/vD4c7w7nUwlTSW48kJEMtpU4IFLltKOSdx3VQ+so6EkLJbQD585pPmSMwcxNgoe3+2HJAbMdRSPahlGhyWtNwGRTJE8m4fH5VFRsiG5KvN7C23Z8IU8YySjq3zDP5a7s7K8e07T6Ga7OazbpIW2rMzYv8ZnU5BSXE8bsceNznRI+TvPz0aSHj+czZZs+/EK7ZoTdsoZMRsJbRWqyAZpnk/0SqovG3ewx5O1jjj4kb/+rVktqcvelOswmuyU+vpvHlzPm/f+g/MnYex+w2t+QkTCOAtoBqRNcUTmAoaZrNVWqVQ651b3gpCa7JWQf1mVN1T5k/BG3lshIPpo54VnfdbT3PqPNayx9flPFV4e4wGpz9TZLzhrWtIyVpDWf1lx9RvARbcbSxDGul3pf36cIJbak3RojhlwdTL6v/jDpnoxEm8Fyo402RlnrsGPoGDCjWA+oQCFIAT1ILUFJzaySZuISTMcnVyKkJlisU53dKmFwTABFzg2Vreup6TpDlbAC5AB66AuTN47V3VzyjB2YRGipRgdUam6olm1Loe1ML5Tre1H4/0Zy8QNnpRjg0/ndbQoUwICgOBWSd1Rqzemgu55O/TDpSQ3Y2aL5FmPA+V+gPnkbl7S4XF0cUr1D93kN5d9RbW756QP9tc3oZrEPGKsyY+NDeSqXsb6+vr7+AwAA//8DAEkVCfaCBAAA

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTwY7cKBD9FYuz8QDG2Pg26tlVWpPstJJRstobpoo0igdaGE8SjfrfV7g7Sia33NCDV/XqveKFBPOEZCS3p9NfzxjyQmqS/QYJJhRlijL+yNtRypF3jdID01r9R2ri7/E7GUkvreVWI21RIZXAOjq1zlIA1JxxYKKTpab5vJDxhRjfAD57i01cPmJafAxkJJ98gPi19Da+sXNcoUlxxn1Ysgm2iPlnv7unb28Pjw+Hy6sFl0JuPJCRDLaVOCBS5bSjkncd1UPrKOhJCyW0A+cuNB8ypmDmZoEvP/tDzAGzHUUj2oZRocm5JmCyKZIns+Dj91NRsVl0V/B6g7fj+EKeMZFR1D/MvJS7sbO/sfHpyQS4cT745UhqckrxhCl73OzYJpxnk30M1bXJbvYYMqnJw4fqMJvsYnp65dGHy+j7P5j8nbFHH7Da35GRMI4C2gGpE1xROYChpms1VapVDrnVveCkJrsYsg9rXJdqHzJ+TptKMpK/zbzgRd9tskef0eY1Fen/qpI1fvN5F6EAjNTkkCKsNlc/7easYU3LyuXDmk9rrt4j+IQ2Y5npMa3X8r9RhBIbabemhCFXB5OP1RuzHMlItBksN9poY5S1DjuGjgEzivWAChSCFNCD1BKU1MwqaSYuwXR8cgUpuku61bZQ0LvJ9SCosb2iEruBGhgstZOVPWNge9e/srV6mAvP2IFJhJZqdECl5oZq2bYU2s70Qrm+FyWOH5ZcE+es+A34fPkamwIFMCAoToXkHZVaczrorqdTP0x6UgN2tmi+xxRw/sWod96muESXq+vCVK+se7+G8r2rbXm++kC/bZHdRfsFU1UiNz6Ubb6mfD6fz/8DAAD//wMAry4kfSUEAAA=

View File

@ -1,5 +0,0 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTS2/cNhD+KwLPosyXKFE3Y52iCzfxIlmkRW8UZxgTlqkFRTkJjP3vAbUbJO6tF4IY8Jv5HsNXEu0zkoHcnk7vXjDmhdQkh60kmNCUacr4kctBqYG3jTad1Fz8S2oS7vE7GUinnOPOIJWokSpgLR2ldxQADWccmGhV6Wm/LGR4JTY0gC/BYTMvnzEtYY5kIH+HCPPXMtuGxk3zCk2aJ9zHJdvoCpkP+909/ev2cHw4XF4tuBRwE4AMpHdSYY9ItTeeKt621PTSUzCjEVoYD95fYCFmTNFOzQJPv+bDnCNmN4hGyIZRYci5JmCzLZRHu+Dx+6mw2Cy6K/V6K2/X4ZW8YCKDqH+aeWl346Zw87yMa5jg5nJmuzwVlac0nzDlgJsnm8xpsjnMsbpO2k0BYyY1efhUHSab/Zye3xj16aJ//z/kH8v0d9/QrRlhN68xk4FwaUhN3lv3GCJW+zsyEMZRgOyResE1VT1YaltpqNZSe+TOdIKTmuzmmENc53Wp9jHjl7QJIAP5w04LXqjfJvcYMrq8pqLqH1124ZBmWF2ufvnPWcMayVgBrfm05uojQkjoMhZ9x7Re+/0HIrTYQLs1JYy5Otj8WP1pl0cyEGN7x62xxlrtnMeWoWfArGYdoAaNoAR0oIwCrQxzWtmRK7AtH32pkJpgibvaNsxbBcIqR1s7Mqp639PeW0cVWt9p7FCq/o2P1cNUcNb1TCFIatADVYZbapSUFGRrO6F914kSzU9LrulzVgwGfLn8lY2BBugRNKdC8ZYqYzjtTdvRsetHM+oeW1c432OKOP1m1Pvg0rzMPlfX5aneWPdxjeW/V9sifQ2RftsyupvdE6aqZGxDLOt9jfV8Pp9/AAAA//8DAOIvUl42BAAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRU0W7cKBT9FYtn4wDGGPwWTVrtKNvNqB11V/t2DdcNioNHgNNG0fz7Cs9U3Tyt9gWha87l3HMOfiMBnpEM5PZ0+vCCISdSk+y3kmBCUaYo40cuh9YMUjRdK3Vv9N+kJv4eX8lAemkttwZpiwqpdKyjYztZ6hwazrhjopOlJ3xLZHgj4BuHL95is6SvGJNfAhnInz645Xu5G3xj52V1TVxm3IeUIdhC5o/97p7+fns4PhwupxKmAm68IwMZx77vrNFUG9FTaRzQUXOgHbTOggCQTlxgPmSMAeYmuadf97slB8x2EI1oG0aFIeeaOMhQKI+Q8Ph6Kiw2ie5Kvd7K23Z4Iy8YySDqn2Je2t3Y2d88p3H1s7u5rBnSU9rWCWxe4iupySkuJ4zZ46bPbUr4PM6vR0hPHy9nyjZ9+IF2zeh2yxoyGQhvNanJJtA8Q/ZLqK4cd7PHkLePOfqQvP2vXi2pycOX6jBDnpb4/M6PLxeZ9/9D5U9gH33Aan9HBsI4CtdqpJPgikrtgELXGqpUqybk1vSCk5rslpB9WJc1VfuQ8VvcRiID+Qhzwgu/22gffUab11jm/EuVXB3i4labq19ectawpmWsgNZ8WnP1GZ2PaDOWIY5xvfb7+h4ilNhAuzVGDLk6QH6sfoP0SAZiQFsOBgyAsnbCjuHEHAPFeofKKXRSuN5JI52ShlklYeTSQcfHqVRITbBEp9rSyiane8UllaM2VI6dpnrkgvLeohiZEqKb3ulYPcwFB1Yzia6lBidHpeFAjWxb6toOeqGmvhcF91OSax44KwI7fLm8u42Bck6jU5wKyTsqjeFUm66nY69HMyqNnS2c7zEGnP8l1Cdv45KWKVfXhFTvpPu8hvLvqLa0fPeB/tg8ulvsE8aqeAw+lKdytfV8Pp//AQAA//8DAMx7OgqCBAAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS6Som2G3qOumXsSLtOhtSI5sIVpqQVGbBsb+94DaDRL31psw0DszfOadeSMRDkh6cns8/nLCmBdSkzxuIcGEpkxTxvdc9Yr1TDfMdMoo9g+pyfiIX0lPjPKee4tUokaqAmupk4OnIaDljAcmWlVywstC+jcCYxPwNHps5uUTpmWcI+nJX2MM85dSG8bGT/MamjRP+BCXDNGXZv58uHukf9zu9k+7y18LLkXcjIH0ZECuUbGOatFZqjrrqXVSUCfC0Co+BG/lRTbGjCnC1Czh84/6Yc4Rs+9FI2TDqLDkXJMAGUrLDhbcfz2WLjZE9yVeb+Hts38jJ0ykF/V3mJd0N34ab/J8nPCE0xHSgunGz4cDxEBqckzzEVMeccOyvXSaII9zrK7F7qYRYyY1eXqudhPkYU6Hd6yeLwge/geBEyZHeiJZK7UxqKSUYLUTDIPRXmvhPDAzOIHcI2utMpYbxqUVzMpWm84EYNaTmrxMs4Pp92XjZwVzXWBGaIEKrVBaW89Qdji0BlkArZ3uvA2yM0J1oZNgAbh1aLzVvhjkA/jXMWL1cE96wjiKIDukg+Caqi4AhVZaqrXUA3JvjeCkJndzzGNc53WpHmLGl7TxIz35FaYFL+Ruk38dM/q8pgL1b12K7dIcVp+rHw7grGGNZKyI1nxcc/URw5jQZyx492m95vuPRGixie7WlDDmagf5tfoNltdCBTrPwZanau8HbBkOLDDQzATUQWNQIpigrApaWea1AsdVgJa7oURITbAYrto8Lj3XzCmgILynCnlLXZmwGpzQw6ADBPmOY/U0FR34jikMklocAlWWA7VKShpkC0bowRgx/ITkaj7OCuCAp8u2bh3oEDoMmlOheEuVtZx2tjXUmc5Zpztsfen5EVPE6SdQH0af5mUecnX1bvUO3cc1lotTbT7+Mkb67zaj+9l/xlSVGcMYy4Jdx3quyQFhWRMetoPVv5Fd2a5qv90tWZbYipo8Z0h5PV7DWjZMG3U+n8/fAAAA//8DAIDgDUv1BAAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUS2/bOBD+KwLPpkJSFCUK2IPhtFgjTWMk7u5ibxQ5jInIokFRToMi/31BWa6dooe9CaN5fY/hD9SrPaAGLQ+HT0fo44AWKLopxAgTmAhM6JbyhpOGiLwUXFYF+RctkLuDN9SgimtNtQRcgADMDSlxW1iNjQFJCTWElTz1VM8Dan4g5XIDR6ch98NfEAbne9Sgv11v/GuarVyuOz+aPPgO1v0QVa/TMl/Xqzv8ZbnZPmxOWQMMqTh3BjXIAhXASY0FqyXmtdRYtgXDLTO25NQaLYtTmesjhF51+WBeLvONjz1E3bCcFTnBTKL3BTIqqrRyqwbYvh3SFhNFtym+mMLTZ/MDHSGghi3OZJ7a3ejO3eyHdnSduYkqPEO0Qe3h1YcXOKoOLdAh+AOE6GDi5uEpu6xESU5yJhghaIHuld65HrL1bfbQJcBK14SDKbAEazCXVGHJiwKbolQVE7aqmEULdAehh+6q673TwQ/exmymPPswZ+X76PrRj0O27iM8BxVPdZ9VNwBaoM3Ydm7YPYIyb1v/OKZ//dglLKsxBOhjtlFxl/2phh1qkFS1pkoqqZTQ2kJJwBJDlCCVAWEEGM5MZbjkRnBJtOCqpdyokrY2RdACPYzxMMYz/9+vltgGt9+DuWywnTjedCpaH/ZrA3101iVpzhmTobpuQpXNPVedgz5+IBk1iFBgpqgBW0YF5rVRWJWFxEIUwgLVsmL0RJh1z+NPnoi1hhe2rGvRypYSYLaqOalsS4y2hZambKFiFalqqKUmpC05rVupiQFacpPQBW9GHX+1QjEJ9Dj26TqzdYL96nr8XfALI5vgo9e+S4K5Dp7ehgj736m28vuDH1xM+O0s7bcB7n2f/s73P3M2Fy99vMQg3UE2nV5NDeOsUrjgLcNcaIpbVbS4ZIZLw42xJHW/9foFQpYMplw/aXL21MNTtgx65yLoOIY0+Z8J1EnOz+eTufCRf/20XfkAy8NhMUf/OCaSTs3OBvjwrnwbUvrOD/EK8gztCTp7XuzKTgaOp6fKpaAwpgYjKGaclphLSXEtywq3Vd3KVtRQ6otds0cwLoCOU79tGNO0mdnf+XIZorNKxyEdzxevJzvN/pxTfl3ySraf1afhV9LNVpo7UZIs+3R6OCcH/a938/39/f0/AAAA//8DANnE2XQoBgAA

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUT2/cNhDFv4rAsyjzn0hRN8Nu0YWbehEv2qA3ijOKiWipBUVtGhj73Qtqt0jcW27CQG/4+Js3fCPRHZH05P50+uWMMS+kJjlsJcGEpkxTxg9c9Yr1TDdc8NYa+zepSXjCb6QnRnnPvUUqUSNVwFo6yNFTALSccWCiVaWn+7yQ/o240ACeg8dmXv7EtIQ5kp78FSLMX8vZLjR+mldo0jzhLi7ZRV/M/LF7eKK/3+8Pz/vrXwsuRdwEID0ZkWtUrKNadJaqznpqBynoIGBsFR/BW3mVhZgxRTc1C3z5fj7MOWL2vWiEbBgVllxqAi67YnlwCx6+nYqLDdFjqddbefvs38gZE+lF/R/Ma7s7P4W7ZR0mPON0cmnBdOfn49FFIDU5pfmEKQfcsJwxDfMSciHqvXFdxzl0zqjWQCdbIQ1IhtAx04IF67TSo/BtO8LQWaNGBYIr0KjUYEhNNnDT5HKYY3Xz/jAFjJnU5Pml2k8uj3M6vkP/ciW6+wmgxTbpiWSt1MagklI6qwfBEIz2WovBO2bGQSD3yFqrjOWGcWkFs7LVpjPgmPWkJh+cfw0Rq90j6QnjKEB2SEfBNVUdOOpaaanWUo/IvTWCk5o8zDGHuM7rUu1ixs9puy/pya9uWvB60/vkX0NGn9dUIHzSJYz7NMPqc/U9AJw1rJGMFdGaT2uuPiKEhD5jwXFI663f/yRCi030sKaEMVd7l1+r39zySnpiXee5s846p70fsWU4MmBOMwOoQSMoAQaUVaCVZV4rN3AFruXDWCqkJljyVm0R93bkfkBBjfGGKguGDsYIKqUyetQOW/aeY/U8FZ3zHVMIklocgSrLHbVKSgqydUbo0Rgx/oDkFhbOCmDA83VZNwcaoEPQnArFW6qs5bSzbbHRDXbQHba+eH7CFHH6AdSH4NO8zGOublmr3qH7uMby4FRb7r6GSP/ZZvQ4+y+YqjJjF2LZr9tYLzU5olvWhMftverfyL4sV3XYni1ZdtiKmrxkl/J6upW1bJg26nK5XP4FAAD//wMA6XvJ6PQEAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/jNhCG/4rAs6mQFEWJuqVOFzWy2RibdFv0NiJHDhGZNCgq2yDIf19QdhBnFz30Joz4zsczL/lCPOyRdOTycPj9CX2ayIokt4QEE4oyRRm/57KTrGOq5IoL3dT/kBVx1/hMOtJIY7jRSCtUSKVlNe2rwVBrUXPGLRO1zDlhN5HuhYArLT45g2WYvmGcXPCkI385b8P3XBtcacYw2zKGETd+SuBNbubLZn1NP19u72+3x1MTTllcOks6MiBXKFlLlWg1la02VPeVoL2wQy35YI2ujjLnE0YPYznZx/f6NiSPyXSiFFXJqNDkdUUsJMgt9zDh/fMhd7Egusrx1RJePrsX8oSRdGL1BvOY7sKM7mI/9bMb7cUYdjvnd+vgB7ebI6RceEUOMRwwJocLnN+ch/j8Oex2OR/5BOOEZEXuMe6dh/H44woHmMd0F+a4kJns43+dIR2BOQWyIgvJcVzKFqdh1qNDn37RnhW+vSu2I6QhxP2HJd0d2W/+B/obMA/OY7G5Ih1hHIWtWqSD4IrK1gKFutJUqUoNyI1uBCcrsg4+OT+HeSo2PuHuRO1Df5fRPLiEJs0xz/S3ymbbxmBnk4r3BXNWsrJiLIvmdJhT8RWti2gS5iHu43zK95NEKLGIPrkRf8GznmNEn4otpIfiD5geSEc0tIaDBg2gjBmwZjgwy0CxxqKyCq0UtrFSS6ukZkZJ6Lm0UPN+yBGyIphNViy+1kPLhDKcQqsZla0aKAAA7YWUEhWzrW4+wC1ux6wD0zKJtqIaB0ul5kC1rCpqqxoaoYamEcMZp5MhOMvUz03454T25KUvR2u/zW7x6XiPlz6VtS1axamQvKZSa05bXTe0b9pe96rF2uTJ1sFP4Q3kN4x9mFzKj8iN824P489H3jdzjdHjeLadG2dimMKQipMtiw/7+jr7/IoVi0W/O0//XYxxFcwjxiIbC5w/W+br6+vrDwAAAP//AwCy3wbSDAUAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSTwW7cOAyGX8Xg2XIkWZYt34JJFzvIthm0g+5ib7JIN0IceSDLaYtg3r2QZ4q0e9uLIBAi+f8fqVcI9pmgh9vT6d0LhbRACclvIcmlZlwzLo5C9Yr3vK2kkcK06l8owd/Td+ihVc4JZ4jVpIkp5A0b6tExRDKCC+SyUbmm/bJA/wrWV0gv3lE1L58pLn4O0MPfPuD8Nfe2vnLTvGIV54n2YUk2uCzmw353z/66PRwfDpdXCy05ufIIPYwkNCneMS07w1RnHDNDLdkgcWyUGNGZ+pLmQ6IY7FQt+PTWH+cUKLleVrKuOJMGziWgTTZLHuxCx++nrGJDdJfj5Rberv0rvFCEXpY/YV7K3bjJ3zwvw+onvLmcyS5P2eUpzieKydPGZLM5TTb5ORTXTrvJU0hQwsOn4jDZNM7x+TdQny7+9//D/jF3f/eN3JoId/MaEvQgFIcS3lv36AMV+zvogQuSWHfERik0Ux1aZpvaMK1rPZJwppUCStjNIfmwzutS7EOiL3EzAD38YaeFLtJvo3v0iVxaY3b1j867cIgzri4Vb/wFr3hV86zkYU2nNRUfCX0klyj7O8b1Wu8/KVLLLWm3xkghFQebHos/7fIIPRjbOWGNNdZq50ZqOI0cudW8RdKoCZXEFpVRqJXhTis7CIW2EcOYI1AC5XEX24YNI0ntWs6c05KpztbMGNsy3QwNF5LrljLiN47Fw5TzrOu4IqyZoRGZMsIyo+qaYd3YVuqxbeX4C5Lr9AXPgJFeLn9lU6ARO0ItmFSiYcoYwTrTtGxou8EMuqPGZc33FANNv4B6712cl3lMxXV5it/QfVxD/u/FtkhffWDfthndze6JYpFnbH3I630d6/l8Pv8AAAD//wMAFaeXhjYEAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACoxUTW/jNhD9KwKvFRWSoihRt9TJokaaxFgbbdHbiBzFbGTKoKhkF0H+e0E52XwsCvQiCENy3ps3b+aJeDggacn58Xj5gD5OJCfRLSHBhKJMUcZ3XLaStawuhJZVzZu/SU7cFX4nLamlMdxopCUqpNKyinZlb6i1qDnjlolKppxwN5H2iYArLD44g8U4/YFhcqMnLfnTeTs+JmxwhRnG2RZhHHDtpwjeJDI369UV/f18s7vdnG5NOKXHhbOkJT1yhZI1VIlGU9loQ3VXCtoJ21eS99bo8vTM+YjBw1BM9v4N347RYzStKERZMCo0ec6JhQiJcgcT7r4fE4tFoosUz5fw8ts+kQcMpBX5q5indGdmcGeHqZvdYM+WL8nJMYxHDNHhosbtNnsjwVnBCqEEYyQn12D2zmO2vshuh1QimIZJtCXV2FsqNQeqZVlSW1ZQC9XXtehJTq4weBzeZb12JozT2MfsReTsA872fHXpoRswYXyBYUKSk/W0jeAtDKPHy29o5njKtQtzOl6NPjo/j/OUrX3EuwAv56/vV3MI6GO2gbjPfoNpT1qioTEcNGgAZUyPFcOeWQaK1RaVVWilsLWVWlolNTNKQselhYp3fYqQnPyaNNzOxuA0vbFZTDIMC4fspU+rwaGPH2QkLWEchS0bpL3gisrGAoWq1FSpUvXIja4Ff4XZQbjDmKzVdF3HjOmE1NyA0FDanpVW9VqIugbZl1UthVIVq8HYuhZc2rJGA2VfskaQnGzCaGcTP7e6XBqwCeM/aGKSirREsapqDHSdUJI3oBuUXMlGaay17IHJ0nTAqwa6jjOomNGNMMCVBtnZqv+h0qW/cx4v3HQc4Ps73KZQRfmLAWh4D/LT9S8BDvg4hvubk42Lm8sdycnX2aeNkK2TRR6dp99UmujreYhutw8IFu31aPFnH2Eal2yZUNmLynBlaSVsT2XVIQWjOOVGQtejlKVNSS9Gc48hSw4D59NU/Uh2u83Og9m7iCbOIfH7a+Fxu802A8R+DIcPm+RdYZ8FKETJWeqMxYfTNlo4KmsbtIpTIXlFpdacNrqqaVc3ne5Ug5VJit3O8TjH7CtaF9DEpeIXLy6Yqz2a+5/FeHXBi0U5S17bnrbYIu3/WmLPOTkgTHPAw7Kt2yey9h7Dgnwxn0Zx7a/dMLgJzejtRFrOpSoaLuQLw/+8J1hTCM2r5+fn538BAAD//wMAUvEfuhsGAAA=

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpRTwW7cIBD9FYuzcQBjbHyLNq26StusmqitesPMuIvqwArjNFG0/17h3ShNb72hB2/mzXvDM/HmHklPLg+Hdw/o00xKktwKCSYUZYoyfsdlL1nP2kpo2bVd84OUxF3jE+lJK63lViOtUSGVwBo61KOlAKg548BEI3NN83Mm/TMxrgJ8cBarMH/FOLvgSU++OQ/hd+5tXGWnsEAVw4RbPyfjbRbzebu5ph8vd3c3u9OrGedMrhyQnozIFUrWUSU6TWWnLdVDLeggYGwkH8Hq+kRzPmH0Zqpm+PXaH0LymGwvKlFXjApNjiUBk0yWPJgZ754OWcVq0VXGyxVej/0zecBIelG+mHkqd2End2HD/b3xcDE67+Y9KckhhgPG5HC1Y51wmkxywRfnJpvJoU+kJDe3xW4yaQzx/o1Ht6fRt/8x+Sdj985jsb0iPWEcBdQd0lFwRWUHhpqm1lSpWo3IrW4FJyXZBJ+cX8IyF1uf8GdcVZKevDfTjCd9l9HuXUKblpilf1c5a3x0aRMgA4yUZBcDLDYVr3ZzVrGqZvnyZkmHJRVfEFxEmzDPdBeXc/l/KEKJlbRZYkSfip1J++KDmfekJ9p0lhtttDHK2hEbhiMDZhRrARUoBCmgBaklKKmZVdIMXIJp+DBmJOvO6RbrQmmllZCtotygolINhnZ1N1A11MoOQ2e5Gt/YWtxMmWdsxyRCTTWOQKXmhmpZ1xTqxrRCjW0rMu/FknPinGW/AR9OX2NVoAA6BMWpkLyhUmtOO920dGi7QQ+qw8ZmzdcYPU5/GfXJ2RjmMKbivDDFG+u+LD5/72Jdnt/O08c1sqtgf2EscuTG+bzN55SPx+PxDwAAAP//AwCD0iScJQQAAA==

View File

@ -0,0 +1,5 @@
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS5Som7FO0IWbepEs0qK3ETmKCcvUgqScGMb+94LaDVKfil4IYsR3+M4zQ72RAM9IBnJ7On14wZATqUn2W0gwoSnTlPEjV4NiA+saYZiUXP9NauLv8ZUMpFPWcmuQStRIlWMtHeVkqXNoOOOOiVaVnPAtkeGNgG8cvniLzZK+Ykx+CWQgf/rglu/lbvCNnZfVNXGZcR9ShmCLmT/2u3v6++3h+HC4nEqYirjxjgxkQq5RsZ5q0RuqemOpGaWgo3BTq/jkrJEXmQ8ZY4C5Se7p1/1uyQGzHUQjZMOoMORcEwcZiuUREh5fT8XFhuiuxOstvG2HN/KCkQyi/gnzku7Gzv7mOY2rn93NZc2QntK2TmDzEl9JTU5xOWHMHjc+tynh8zi/HiE9fbycKdv04QfaNaPbLWvIZCBcdqQmG6B5huyXUF097maPIW8fc/QheftfuQqahy/VYYY8LfH5XT++XDDv/wflT2AffcBqf0cGwjgKJ3ukk+Caqt4BhVYaqrXUE3JrOsFJTXZLyD6sy5qqfcj4LW4lkYF8hDnhxd9ttI8+o81rLHX+pctcHeLiVpurX73krGGNZKyI1nxac/UZnY9oM5YijnG95vv6XiK02ES7NUYMuTpAfqx+g/RIBmKgtxwMGABt7YQtw4k5Bpp1DrXT6JRwnVNGOa0Ms1rByJWDlo9TiZCaYBmdaptWJowcjRIUmHNUKckpADcUOguyR1DSTe84Vg9z0YHtmUInqcHJUWU4UKOkpE620Ak9dZ0oup9IrvPAWQHs8OXy7jYH2rkeneZUKN5SZQynvWk7Onb9aEbdY2uL53uMAed/gfrkbVzSMuXqOiHVO3Sf11D+HdU2Ld99oD+2Ht0t9gljVXoMPpSncm3r+Xw+/wMAAP//AwANlERfggQAAA==

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using PlotLine.Services;
namespace PlotLine.Controllers;
public sealed class ArchivesController(IArchiveService archives) : Controller
{
public async Task<IActionResult> Index(int? projectId, string? entityType)
{
return View(await archives.GetArchivedItemsAsync(projectId, entityType));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Archive(string entityType, int entityId, int? projectId, string? archivedReason)
{
await archives.ArchiveAsync(entityType, entityId, archivedReason);
TempData["ArchiveMessage"] = $"{entityType} archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index), new { projectId, entityType });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Restore(string entityType, int entityId, int? projectId)
{
await archives.RestoreAsync(entityType, entityId);
TempData["ArchiveMessage"] = $"{entityType} restored.";
return RedirectToAction(nameof(Index), new { projectId, entityType });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteForever(string entityType, int entityId, int? projectId, string? confirmationText)
{
var result = await archives.DeleteForeverAsync(entityType, entityId, confirmationText ?? string.Empty);
TempData[result.Succeeded ? "ArchiveMessage" : "ArchiveError"] = result.Message;
return RedirectToAction(nameof(Index), new { projectId, entityType });
}
}

View File

@ -42,6 +42,7 @@ public sealed class BooksController(IBookService books) : Controller
public async Task<IActionResult> Archive(int id, int projectId)
{
await books.ArchiveAsync(id);
TempData["ArchiveMessage"] = "Book archived. You can restore it from Archived Items.";
return RedirectToAction("Details", "Projects", new { id = projectId });
}
}

View File

@ -42,6 +42,7 @@ public sealed class ChaptersController(IChapterService chapters) : Controller
public async Task<IActionResult> Archive(int id, int bookId)
{
await chapters.ArchiveAsync(id);
TempData["ArchiveMessage"] = "Chapter archived. You can restore it from Archived Items.";
return RedirectToAction("Details", "Books", new { id = bookId });
}
}

View File

@ -48,6 +48,16 @@ public sealed class CharactersController(ICharacterService characters) : Control
public async Task<IActionResult> Archive(int id, int projectId)
{
await characters.ArchiveCharacterAsync(id);
TempData["ArchiveMessage"] = "Character archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index), new { projectId });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ArchiveRelationship(int id, int characterId)
{
await characters.ArchiveRelationshipAsync(id);
TempData["ArchiveMessage"] = "Relationship archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Details), new { id = characterId });
}
}

View File

@ -48,6 +48,7 @@ public sealed class LocationsController(ILocationService locations) : Controller
public async Task<IActionResult> Archive(int id, int projectId)
{
await locations.ArchiveAsync(id);
TempData["ArchiveMessage"] = "Location archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index), new { projectId });
}
@ -76,6 +77,7 @@ public sealed class LocationsController(ILocationService locations) : Controller
public async Task<IActionResult> ArchiveRelationship(int id, int returnLocationId)
{
await locations.ArchiveRelationshipAsync(id);
TempData["ArchiveMessage"] = "Location relationship archived.";
return RedirectToAction(nameof(Details), new { id = returnLocationId });
}
}

View File

@ -42,6 +42,7 @@ public sealed class PlotLinesController(IPlotService plots) : Controller
public async Task<IActionResult> Archive(int id, int projectId)
{
await plots.ArchivePlotLineAsync(id);
TempData["ArchiveMessage"] = "Plot line archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index), new { projectId });
}
}

View File

@ -42,6 +42,7 @@ public sealed class PlotThreadsController(IPlotService plots) : Controller
public async Task<IActionResult> Archive(int id, int projectId)
{
await plots.ArchivePlotThreadAsync(id);
TempData["ArchiveMessage"] = "Plot thread archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index), new { projectId });
}
}

View File

@ -40,6 +40,7 @@ public sealed class ProjectsController(IProjectService projects) : Controller
public async Task<IActionResult> Archive(int id)
{
await projects.ArchiveAsync(id);
TempData["ArchiveMessage"] = "Project archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index));
}
}

View File

@ -83,6 +83,7 @@ public sealed class ScenariosController(IScenarioService scenarios) : Controller
public async Task<IActionResult> Archive(int id, int projectId)
{
await scenarios.ArchiveAsync(id);
TempData["ArchiveMessage"] = "Scenario archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index), new { projectId });
}
}

View File

@ -55,6 +55,7 @@ public sealed class ScenesController(
public async Task<IActionResult> Archive(int id, int chapterId)
{
await scenes.ArchiveAsync(id);
TempData["ArchiveMessage"] = "Scene archived. You can restore it from Archived Items.";
return RedirectToAction("Details", "Chapters", new { id = chapterId });
}
@ -273,6 +274,14 @@ public sealed class ScenesController(
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteCharacterKnowledge(int id, int sceneId, int? projectId, int? bookId)
{
await characters.DeleteKnowledgeAsync(id);
return RedirectForScene(sceneId, projectId, bookId);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddRelationshipEvent(RelationshipEventCreateViewModel model)
@ -281,6 +290,14 @@ public sealed class ScenesController(
return RedirectForScene(model.SceneID, model.ReturnProjectID, model.ReturnBookID);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteRelationshipEvent(int id, int sceneId, int? projectId, int? bookId)
{
await characters.DeleteRelationshipEventAsync(id);
return RedirectForScene(sceneId, projectId, bookId);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveWorkflow(SceneWorkflowEditViewModel model)

View File

@ -48,6 +48,7 @@ public sealed class StoryAssetsController(IAssetService assets) : Controller
public async Task<IActionResult> Archive(int id, int projectId)
{
await assets.ArchiveAssetAsync(id);
TempData["ArchiveMessage"] = "Asset archived. You can restore it from Archived Items.";
return RedirectToAction(nameof(Index), new { projectId });
}

View File

@ -118,6 +118,7 @@ public interface ICharacterRepository
Task<IReadOnlyList<CharacterKnowledgeItem>> ListKnowledgeBySceneAsync(int sceneId);
Task<IReadOnlyList<CharacterKnowledgeItem>> ListKnowledgeByCharacterAsync(int characterId);
Task<int> SaveKnowledgeAsync(CharacterKnowledgeItem knowledge);
Task DeleteKnowledgeAsync(int characterKnowledgeId);
Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByProjectAsync(int projectId);
Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByCharacterAsync(int characterId);
Task<int> SaveRelationshipAsync(CharacterRelationship relationship);
@ -125,6 +126,7 @@ public interface ICharacterRepository
Task<IReadOnlyList<RelationshipEvent>> ListRelationshipEventsBySceneAsync(int sceneId);
Task<IReadOnlyList<RelationshipEvent>> ListRelationshipEventsByCharacterAsync(int characterId);
Task<int> SaveRelationshipEventAsync(RelationshipEvent relationshipEvent);
Task DeleteRelationshipEventAsync(int relationshipEventId);
Task<(IReadOnlyList<Character> Characters, IReadOnlyList<SceneCharacter> Appearances)> GetTimelineAsync(int projectId, int? bookId);
}
@ -246,6 +248,15 @@ public interface IScenarioRepository
Task ArchiveAsync(int scenarioId);
}
public interface IArchiveRepository
{
Task<IReadOnlyList<ArchivedItem>> ListAsync(int? projectId, string? entityType);
Task<IReadOnlyList<Project>> ListProjectFiltersAsync();
Task ArchiveAsync(string entityType, int entityId, string? reason);
Task RestoreAsync(string entityType, int entityId);
Task<ArchiveDeleteResult> DeleteForeverAsync(string entityType, int entityId, string confirmationText);
}
public sealed class TimelineData
{
public Project? Project { get; init; }
@ -978,6 +989,55 @@ public sealed class ScenarioRepository(ISqlConnectionFactory connectionFactory)
}
}
public sealed class ArchiveRepository(ISqlConnectionFactory connectionFactory) : IArchiveRepository
{
public async Task<IReadOnlyList<ArchivedItem>> ListAsync(int? projectId, string? entityType)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<ArchivedItem>(
"dbo.Archive_List",
new { ProjectID = projectId, EntityType = entityType },
commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task<IReadOnlyList<Project>> ListProjectFiltersAsync()
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<Project>(
"dbo.Archive_ProjectFilterList",
commandType: CommandType.StoredProcedure);
return rows.ToList();
}
public async Task ArchiveAsync(string entityType, int entityId, string? reason)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.Archive_Entity",
new { EntityType = entityType, EntityID = entityId, ArchivedReason = reason },
commandType: CommandType.StoredProcedure);
}
public async Task RestoreAsync(string entityType, int entityId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"dbo.Archive_Restore",
new { EntityType = entityType, EntityID = entityId },
commandType: CommandType.StoredProcedure);
}
public async Task<ArchiveDeleteResult> DeleteForeverAsync(string entityType, int entityId, string confirmationText)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<ArchiveDeleteResult>(
"dbo.Archive_DeleteForever",
new { EntityType = entityType, EntityID = entityId, ConfirmationText = confirmationText },
commandType: CommandType.StoredProcedure);
}
}
public sealed class SceneDependencyRepository(ISqlConnectionFactory connectionFactory) : ISceneDependencyRepository
{
public async Task<SceneDependencyLookupData> GetLookupsAsync()
@ -1843,6 +1903,12 @@ public sealed class CharacterRepository(ISqlConnectionFactory connectionFactory)
commandType: CommandType.StoredProcedure);
}
public async Task DeleteKnowledgeAsync(int characterKnowledgeId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.CharacterKnowledge_Delete", new { CharacterKnowledgeID = characterKnowledgeId }, commandType: CommandType.StoredProcedure);
}
public async Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByProjectAsync(int projectId)
{
using var connection = connectionFactory.CreateConnection();
@ -1916,6 +1982,12 @@ public sealed class CharacterRepository(ISqlConnectionFactory connectionFactory)
commandType: CommandType.StoredProcedure);
}
public async Task DeleteRelationshipEventAsync(int relationshipEventId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync("dbo.RelationshipEvent_Delete", new { RelationshipEventID = relationshipEventId }, commandType: CommandType.StoredProcedure);
}
public async Task<(IReadOnlyList<Character> Characters, IReadOnlyList<SceneCharacter> Appearances)> GetTimelineAsync(int projectId, int? bookId)
{
using var connection = connectionFactory.CreateConnection();

View File

@ -1493,6 +1493,26 @@ public sealed class ScenarioComparisonRow
public bool HasMoved { get; set; }
}
public sealed class ArchivedItem
{
public string EntityType { get; set; } = string.Empty;
public int EntityID { get; set; }
public string DisplayName { get; set; } = string.Empty;
public int? ProjectID { get; set; }
public string? ProjectName { get; set; }
public int? BookID { get; set; }
public string? BookTitle { get; set; }
public DateTime? ArchivedDate { get; set; }
public string? ArchivedReason { get; set; }
public DateTime UpdatedDate { get; set; }
}
public sealed class ArchiveDeleteResult
{
public bool Succeeded { get; set; }
public string Message { get; set; } = string.Empty;
}
public sealed class AnalyticsBusyScene
{
public int SceneID { get; set; }

View File

@ -37,6 +37,7 @@ public class Program
builder.Services.AddScoped<IExportRepository, ExportRepository>();
builder.Services.AddScoped<IDynamicsRepository, DynamicsRepository>();
builder.Services.AddScoped<IScenarioRepository, ScenarioRepository>();
builder.Services.AddScoped<IArchiveRepository, ArchiveRepository>();
builder.Services.AddScoped<IProjectService, ProjectService>();
builder.Services.AddScoped<IBookService, BookService>();
builder.Services.AddScoped<IChapterService, ChapterService>();
@ -53,6 +54,7 @@ public class Program
builder.Services.AddScoped<IExportService, ExportService>();
builder.Services.AddScoped<IDynamicsService, DynamicsService>();
builder.Services.AddScoped<IScenarioService, ScenarioService>();
builder.Services.AddScoped<IArchiveService, ArchiveService>();
var app = builder.Build();

View File

@ -120,12 +120,15 @@ public interface ICharacterService
Task<CharacterDetailViewModel?> GetCharacterDetailAsync(int characterId);
Task<int> SaveCharacterAsync(CharacterEditViewModel model);
Task ArchiveCharacterAsync(int characterId);
Task ArchiveRelationshipAsync(int characterRelationshipId);
Task<int> AddSceneCharacterAsync(SceneCharacterCreateViewModel model);
Task<int> UpdateSceneCharacterAsync(SceneCharacterEditViewModel model);
Task DeleteSceneCharacterAsync(int sceneCharacterId);
Task<int> AddAttributeEventAsync(CharacterAttributeEventCreateViewModel model);
Task<int> AddKnowledgeAsync(CharacterKnowledgeCreateViewModel model);
Task DeleteKnowledgeAsync(int characterKnowledgeId);
Task<int> AddRelationshipEventAsync(RelationshipEventCreateViewModel model);
Task DeleteRelationshipEventAsync(int relationshipEventId);
string DisplayAge(Character character, DateTime? sceneStartDateTime = null);
}
@ -200,6 +203,14 @@ public interface IScenarioService
Task ArchiveAsync(int scenarioId);
}
public interface IArchiveService
{
Task<ArchivedItemsViewModel> GetArchivedItemsAsync(int? projectId, string? entityType);
Task ArchiveAsync(string entityType, int entityId, string? reason);
Task RestoreAsync(string entityType, int entityId);
Task<ArchiveDeleteResult> DeleteForeverAsync(string entityType, int entityId, string confirmationText);
}
public sealed class ProjectService(IProjectRepository projects, IBookRepository books) : IProjectService
{
public Task<IReadOnlyList<Project>> ListAsync() => projects.ListAsync();
@ -2402,6 +2413,50 @@ public sealed class ScenarioService(IProjectRepository projects, IBookRepository
public Task ArchiveAsync(int scenarioId) => scenarios.ArchiveAsync(scenarioId);
}
public sealed class ArchiveService(IArchiveRepository archives) : IArchiveService
{
private static readonly (string Value, string Label)[] EntityTypes =
[
("Project", "Projects"),
("Book", "Books"),
("Chapter", "Chapters"),
("Scene", "Scenes"),
("PlotLine", "Plot Lines"),
("PlotThread", "Plot Threads"),
("StoryAsset", "Story Assets"),
("Character", "Characters"),
("Location", "Locations"),
("Scenario", "Scenarios"),
("Relationship", "Relationships")
];
public async Task<ArchivedItemsViewModel> GetArchivedItemsAsync(int? projectId, string? entityType)
{
var normalisedType = EntityTypes.Any(x => x.Value == entityType) ? entityType : null;
var projects = await archives.ListProjectFiltersAsync();
return new ArchivedItemsViewModel
{
ProjectID = projectId,
EntityType = normalisedType,
ProjectOptions = projects.Select(project => new SelectListItem(
project.IsArchived ? $"{project.ProjectName} (Archived)" : project.ProjectName,
project.ProjectID.ToString(),
project.ProjectID == projectId)).ToList(),
EntityTypeOptions = EntityTypes.Select(type => new SelectListItem(type.Label, type.Value, type.Value == normalisedType)).ToList(),
Items = await archives.ListAsync(projectId, normalisedType)
};
}
public Task ArchiveAsync(string entityType, int entityId, string? reason) =>
archives.ArchiveAsync(entityType, entityId, reason);
public Task RestoreAsync(string entityType, int entityId) =>
archives.RestoreAsync(entityType, entityId);
public Task<ArchiveDeleteResult> DeleteForeverAsync(string entityType, int entityId, string confirmationText) =>
archives.DeleteForeverAsync(entityType, entityId, confirmationText);
}
public sealed class AssetService(IProjectRepository projects, IAssetRepository assets, ILocationRepository locations) : IAssetService
{
public async Task<StoryAssetListViewModel?> GetAssetsAsync(int projectId)
@ -2828,6 +2883,8 @@ public sealed class CharacterService(IProjectRepository projects, ICharacterRepo
public Task ArchiveCharacterAsync(int characterId) => characters.ArchiveCharacterAsync(characterId);
public Task ArchiveRelationshipAsync(int characterRelationshipId) => characters.ArchiveRelationshipAsync(characterRelationshipId);
public async Task<int> AddSceneCharacterAsync(SceneCharacterCreateViewModel model)
{
var characterId = model.CharacterID.GetValueOrDefault();
@ -2900,6 +2957,8 @@ public sealed class CharacterService(IProjectRepository projects, ICharacterRepo
Description = model.Description
});
public Task DeleteKnowledgeAsync(int characterKnowledgeId) => characters.DeleteKnowledgeAsync(characterKnowledgeId);
public async Task<int> AddRelationshipEventAsync(RelationshipEventCreateViewModel model)
{
var relationshipId = model.CharacterRelationshipID.GetValueOrDefault();
@ -2931,6 +2990,8 @@ public sealed class CharacterService(IProjectRepository projects, ICharacterRepo
});
}
public Task DeleteRelationshipEventAsync(int relationshipEventId) => characters.DeleteRelationshipEventAsync(relationshipEventId);
public string DisplayAge(Character character, DateTime? sceneStartDateTime = null)
{
if (character.BirthDate.HasValue && sceneStartDateTime.HasValue)

View File

@ -0,0 +1,444 @@
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
DECLARE @ArchiveTables TABLE (TableName sysname);
INSERT @ArchiveTables (TableName)
VALUES
(N'Projects'), (N'Books'), (N'Chapters'), (N'Scenes'), (N'PlotLines'), (N'PlotThreads'),
(N'StoryAssets'), (N'Characters'), (N'Locations'), (N'Scenarios'), (N'CharacterRelationships');
DECLARE @TableName sysname;
DECLARE @Sql nvarchar(max);
DECLARE archive_cursor CURSOR LOCAL FAST_FORWARD FOR SELECT TableName FROM @ArchiveTables;
OPEN archive_cursor;
FETCH NEXT FROM archive_cursor INTO @TableName;
WHILE @@FETCH_STATUS = 0
BEGIN
IF COL_LENGTH(N'dbo.' + @TableName, N'IsArchived') IS NULL
BEGIN
SET @Sql = N'ALTER TABLE dbo.' + QUOTENAME(@TableName) + N' ADD IsArchived bit NOT NULL CONSTRAINT ' + QUOTENAME(N'DF_' + @TableName + N'_IsArchived_Phase1T') + N' DEFAULT 0;';
EXEC sys.sp_executesql @Sql;
END
IF COL_LENGTH(N'dbo.' + @TableName, N'ArchivedDate') IS NULL
BEGIN
SET @Sql = N'ALTER TABLE dbo.' + QUOTENAME(@TableName) + N' ADD ArchivedDate datetime2 NULL;';
EXEC sys.sp_executesql @Sql;
END
IF COL_LENGTH(N'dbo.' + @TableName, N'ArchivedReason') IS NULL
BEGIN
SET @Sql = N'ALTER TABLE dbo.' + QUOTENAME(@TableName) + N' ADD ArchivedReason nvarchar(500) NULL;';
EXEC sys.sp_executesql @Sql;
END
FETCH NEXT FROM archive_cursor INTO @TableName;
END
CLOSE archive_cursor;
DEALLOCATE archive_cursor;
GO
CREATE OR ALTER PROCEDURE dbo.Archive_List
@ProjectID int = NULL,
@EntityType nvarchar(80) = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT EntityType, EntityID, DisplayName, ProjectID, ProjectName, BookID, BookTitle, ArchivedDate, ArchivedReason, UpdatedDate
FROM
(
SELECT N'Project' AS EntityType, p.ProjectID AS EntityID, p.ProjectName AS DisplayName, p.ProjectID, p.ProjectName,
CAST(NULL AS int) AS BookID, CAST(NULL AS nvarchar(200)) AS BookTitle, p.ArchivedDate, p.ArchivedReason, p.UpdatedDate
FROM dbo.Projects p
WHERE p.IsArchived = 1
UNION ALL
SELECT N'Book', b.BookID, b.BookTitle, b.ProjectID, p.ProjectName, b.BookID, b.BookTitle, b.ArchivedDate, b.ArchivedReason, b.UpdatedDate
FROM dbo.Books b INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID
WHERE b.IsArchived = 1
UNION ALL
SELECT N'Chapter', c.ChapterID, CONCAT(N'Ch ', c.ChapterNumber, N': ', c.ChapterTitle), b.ProjectID, p.ProjectName, b.BookID, b.BookTitle, c.ArchivedDate, c.ArchivedReason, c.UpdatedDate
FROM dbo.Chapters c INNER JOIN dbo.Books b ON b.BookID = c.BookID INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID
WHERE c.IsArchived = 1
UNION ALL
SELECT N'Scene', s.SceneID, CONCAT(N'Scene ', s.SceneNumber, N': ', s.SceneTitle), b.ProjectID, p.ProjectName, b.BookID, b.BookTitle, s.ArchivedDate, s.ArchivedReason, s.UpdatedDate
FROM dbo.Scenes s INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID INNER JOIN dbo.Projects p ON p.ProjectID = b.ProjectID
WHERE s.IsArchived = 1
UNION ALL
SELECT N'PlotLine', pl.PlotLineID, pl.PlotLineName, pl.ProjectID, p.ProjectName, pl.BookID, b.BookTitle, pl.ArchivedDate, pl.ArchivedReason, pl.UpdatedDate
FROM dbo.PlotLines pl INNER JOIN dbo.Projects p ON p.ProjectID = pl.ProjectID LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
WHERE pl.IsArchived = 1
UNION ALL
SELECT N'PlotThread', pt.PlotThreadID, pt.ThreadTitle, pl.ProjectID, p.ProjectName, pl.BookID, b.BookTitle, pt.ArchivedDate, pt.ArchivedReason, pt.UpdatedDate
FROM dbo.PlotThreads pt INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID INNER JOIN dbo.Projects p ON p.ProjectID = pl.ProjectID LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
WHERE pt.IsArchived = 1
UNION ALL
SELECT N'StoryAsset', sa.StoryAssetID, sa.AssetName, sa.ProjectID, p.ProjectName, CAST(NULL AS int), CAST(NULL AS nvarchar(200)), sa.ArchivedDate, sa.ArchivedReason, sa.UpdatedDate
FROM dbo.StoryAssets sa INNER JOIN dbo.Projects p ON p.ProjectID = sa.ProjectID
WHERE sa.IsArchived = 1
UNION ALL
SELECT N'Character', ch.CharacterID, ch.CharacterName, ch.ProjectID, p.ProjectName, CAST(NULL AS int), CAST(NULL AS nvarchar(200)), ch.ArchivedDate, ch.ArchivedReason, ch.UpdatedDate
FROM dbo.Characters ch INNER JOIN dbo.Projects p ON p.ProjectID = ch.ProjectID
WHERE ch.IsArchived = 1
UNION ALL
SELECT N'Location', l.LocationID, l.LocationName, l.ProjectID, p.ProjectName, CAST(NULL AS int), CAST(NULL AS nvarchar(200)), l.ArchivedDate, l.ArchivedReason, l.UpdatedDate
FROM dbo.Locations l INNER JOIN dbo.Projects p ON p.ProjectID = l.ProjectID
WHERE l.IsArchived = 1
UNION ALL
SELECT N'Scenario', sc.ScenarioID, sc.ScenarioName, sc.ProjectID, p.ProjectName, sc.BookID, b.BookTitle, sc.ArchivedDate, sc.ArchivedReason, sc.UpdatedDate
FROM dbo.Scenarios sc INNER JOIN dbo.Projects p ON p.ProjectID = sc.ProjectID LEFT JOIN dbo.Books b ON b.BookID = sc.BookID
WHERE sc.IsArchived = 1
UNION ALL
SELECT N'Relationship', cr.CharacterRelationshipID, CONCAT(ca.CharacterName, N' / ', cb.CharacterName), cr.ProjectID, p.ProjectName, CAST(NULL AS int), CAST(NULL AS nvarchar(200)), cr.ArchivedDate, cr.ArchivedReason, cr.UpdatedDate
FROM dbo.CharacterRelationships cr INNER JOIN dbo.Projects p ON p.ProjectID = cr.ProjectID INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
WHERE cr.IsArchived = 1
) archived
WHERE (@ProjectID IS NULL OR archived.ProjectID = @ProjectID)
AND (@EntityType IS NULL OR archived.EntityType = @EntityType)
ORDER BY ISNULL(ArchivedDate, UpdatedDate) DESC, EntityType, DisplayName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Archive_Entity
@EntityType nvarchar(80),
@EntityID int,
@ArchivedReason nvarchar(500) = NULL
AS
BEGIN
SET NOCOUNT ON;
IF @EntityType = N'Project' UPDATE dbo.Projects SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE ProjectID = @EntityID;
ELSE IF @EntityType = N'Book' UPDATE dbo.Books SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE BookID = @EntityID;
ELSE IF @EntityType = N'Chapter' UPDATE dbo.Chapters SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE ChapterID = @EntityID;
ELSE IF @EntityType = N'Scene' UPDATE dbo.Scenes SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE SceneID = @EntityID;
ELSE IF @EntityType = N'PlotLine' UPDATE dbo.PlotLines SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE PlotLineID = @EntityID;
ELSE IF @EntityType = N'PlotThread' UPDATE dbo.PlotThreads SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE PlotThreadID = @EntityID;
ELSE IF @EntityType = N'StoryAsset' UPDATE dbo.StoryAssets SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE StoryAssetID = @EntityID;
ELSE IF @EntityType = N'Character' UPDATE dbo.Characters SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE CharacterID = @EntityID;
ELSE IF @EntityType = N'Location' UPDATE dbo.Locations SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE LocationID = @EntityID;
ELSE IF @EntityType = N'Scenario' UPDATE dbo.Scenarios SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE ScenarioID = @EntityID;
ELSE IF @EntityType = N'Relationship' UPDATE dbo.CharacterRelationships SET IsArchived = 1, ArchivedDate = SYSUTCDATETIME(), ArchivedReason = @ArchivedReason, UpdatedDate = SYSUTCDATETIME() WHERE CharacterRelationshipID = @EntityID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Archive_Restore
@EntityType nvarchar(80),
@EntityID int
AS
BEGIN
SET NOCOUNT ON;
IF @EntityType = N'Project' UPDATE dbo.Projects SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE ProjectID = @EntityID;
ELSE IF @EntityType = N'Book' UPDATE dbo.Books SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE BookID = @EntityID;
ELSE IF @EntityType = N'Chapter' UPDATE dbo.Chapters SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE ChapterID = @EntityID;
ELSE IF @EntityType = N'Scene' UPDATE dbo.Scenes SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE SceneID = @EntityID;
ELSE IF @EntityType = N'PlotLine' UPDATE dbo.PlotLines SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE PlotLineID = @EntityID;
ELSE IF @EntityType = N'PlotThread' UPDATE dbo.PlotThreads SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE PlotThreadID = @EntityID;
ELSE IF @EntityType = N'StoryAsset' UPDATE dbo.StoryAssets SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE StoryAssetID = @EntityID;
ELSE IF @EntityType = N'Character' UPDATE dbo.Characters SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE CharacterID = @EntityID;
ELSE IF @EntityType = N'Location' UPDATE dbo.Locations SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE LocationID = @EntityID;
ELSE IF @EntityType = N'Scenario' UPDATE dbo.Scenarios SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE ScenarioID = @EntityID;
ELSE IF @EntityType = N'Relationship' UPDATE dbo.CharacterRelationships SET IsArchived = 0, ArchivedDate = NULL, ArchivedReason = NULL, UpdatedDate = SYSUTCDATETIME() WHERE CharacterRelationshipID = @EntityID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Archive_ProjectFilterList
AS
BEGIN
SET NOCOUNT ON;
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived
FROM dbo.Projects
ORDER BY IsArchived, ProjectName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Archive_DeleteForever
@EntityType nvarchar(80),
@EntityID int,
@ConfirmationText nvarchar(20)
AS
BEGIN
SET NOCOUNT ON;
IF UPPER(LTRIM(RTRIM(ISNULL(@ConfirmationText, N'')))) <> N'DELETE'
BEGIN
SELECT CAST(0 AS bit) AS Succeeded, N'Type DELETE to permanently delete an archived item.' AS Message;
RETURN;
END;
DECLARE @HasLinks bit = 0;
DECLARE @IsArchived bit = 0;
IF @EntityType = N'Project'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.Projects WHERE ProjectID = @EntityID;
IF EXISTS (
SELECT 1 FROM dbo.Books WHERE ProjectID = @EntityID
UNION ALL SELECT 1 FROM dbo.PlotLines WHERE ProjectID = @EntityID
UNION ALL SELECT 1 FROM dbo.StoryAssets WHERE ProjectID = @EntityID
UNION ALL SELECT 1 FROM dbo.Characters WHERE ProjectID = @EntityID
UNION ALL SELECT 1 FROM dbo.Locations WHERE ProjectID = @EntityID
UNION ALL SELECT 1 FROM dbo.Scenarios WHERE ProjectID = @EntityID
) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.Projects WHERE ProjectID = @EntityID;
END
ELSE IF @EntityType = N'Book'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.Books WHERE BookID = @EntityID;
IF EXISTS (
SELECT 1 FROM dbo.Chapters WHERE BookID = @EntityID
UNION ALL SELECT 1 FROM dbo.PlotLines WHERE BookID = @EntityID
UNION ALL SELECT 1 FROM dbo.Scenarios WHERE BookID = @EntityID
) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.Books WHERE BookID = @EntityID;
END
ELSE IF @EntityType = N'Chapter'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.Chapters WHERE ChapterID = @EntityID;
IF EXISTS (SELECT 1 FROM dbo.Scenes WHERE ChapterID = @EntityID) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.Chapters WHERE ChapterID = @EntityID;
END
ELSE IF @EntityType = N'Scene'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.Scenes WHERE SceneID = @EntityID;
IF EXISTS (
SELECT 1 FROM dbo.ScenePurposes WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.SceneMetricValues WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.ThreadEvents WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.AssetEvents WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.SceneCharacters WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.CharacterKnowledge WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.RelationshipEvents WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.SceneNotes WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.SceneChecklistItems WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.SceneAttachments WHERE SceneID = @EntityID
UNION ALL SELECT 1 FROM dbo.ScenarioSceneOrder WHERE SceneID = @EntityID
) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.Scenes WHERE SceneID = @EntityID;
END
ELSE IF @EntityType = N'PlotLine'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.PlotLines WHERE PlotLineID = @EntityID;
IF EXISTS (SELECT 1 FROM dbo.PlotThreads WHERE PlotLineID = @EntityID) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.PlotLines WHERE PlotLineID = @EntityID;
END
ELSE IF @EntityType = N'PlotThread'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.PlotThreads WHERE PlotThreadID = @EntityID;
IF EXISTS (
SELECT 1 FROM dbo.ThreadEvents WHERE PlotThreadID = @EntityID
UNION ALL SELECT 1 FROM dbo.CharacterKnowledge WHERE PlotThreadID = @EntityID
) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.PlotThreads WHERE PlotThreadID = @EntityID;
END
ELSE IF @EntityType = N'StoryAsset'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.StoryAssets WHERE StoryAssetID = @EntityID;
IF EXISTS (
SELECT 1 FROM dbo.AssetEvents WHERE StoryAssetID = @EntityID
UNION ALL SELECT 1 FROM dbo.AssetDependencies WHERE SourceAssetID = @EntityID OR TargetAssetID = @EntityID
UNION ALL SELECT 1 FROM dbo.AssetCustodyEvents WHERE StoryAssetID = @EntityID
UNION ALL SELECT 1 FROM dbo.CharacterKnowledge WHERE StoryAssetID = @EntityID
UNION ALL SELECT 1 FROM dbo.SceneAssetLocations WHERE StoryAssetID = @EntityID
) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.StoryAssets WHERE StoryAssetID = @EntityID;
END
ELSE IF @EntityType = N'Character'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.Characters WHERE CharacterID = @EntityID;
IF EXISTS (
SELECT 1 FROM dbo.SceneCharacters WHERE CharacterID = @EntityID
UNION ALL SELECT 1 FROM dbo.CharacterAttributeEvents WHERE CharacterID = @EntityID
UNION ALL SELECT 1 FROM dbo.CharacterKnowledge WHERE CharacterID = @EntityID
UNION ALL SELECT 1 FROM dbo.CharacterRelationships WHERE CharacterAID = @EntityID OR CharacterBID = @EntityID
) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.Characters WHERE CharacterID = @EntityID;
END
ELSE IF @EntityType = N'Location'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.Locations WHERE LocationID = @EntityID;
IF EXISTS (
SELECT 1 FROM dbo.Locations WHERE ParentLocationID = @EntityID
UNION ALL SELECT 1 FROM dbo.Scenes WHERE PrimaryLocationID = @EntityID
UNION ALL SELECT 1 FROM dbo.SceneCharacters WHERE LocationID = @EntityID OR EntryLocationID = @EntityID OR ExitLocationID = @EntityID
UNION ALL SELECT 1 FROM dbo.StoryAssets WHERE CurrentLocationID = @EntityID
UNION ALL SELECT 1 FROM dbo.SceneAssetLocations WHERE LocationID = @EntityID
UNION ALL SELECT 1 FROM dbo.LocationRelationships WHERE FromLocationID = @EntityID OR ToLocationID = @EntityID
) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.Locations WHERE LocationID = @EntityID;
END
ELSE IF @EntityType = N'Scenario'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.Scenarios WHERE ScenarioID = @EntityID;
IF EXISTS (
SELECT 1 FROM dbo.ScenarioSceneOrder WHERE ScenarioID = @EntityID
UNION ALL SELECT 1 FROM dbo.ScenarioWarnings WHERE ScenarioID = @EntityID
) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.Scenarios WHERE ScenarioID = @EntityID;
END
ELSE IF @EntityType = N'Relationship'
BEGIN
SELECT @IsArchived = IsArchived FROM dbo.CharacterRelationships WHERE CharacterRelationshipID = @EntityID;
IF EXISTS (SELECT 1 FROM dbo.RelationshipEvents WHERE CharacterRelationshipID = @EntityID) SET @HasLinks = 1;
IF @IsArchived = 1 AND @HasLinks = 0 DELETE FROM dbo.CharacterRelationships WHERE CharacterRelationshipID = @EntityID;
END
ELSE
BEGIN
SELECT CAST(0 AS bit) AS Succeeded, N'This archived item type is not supported for permanent delete.' AS Message;
RETURN;
END;
IF ISNULL(@IsArchived, 0) = 0
BEGIN
SELECT CAST(0 AS bit) AS Succeeded, N'Only archived items can be permanently deleted.' AS Message;
RETURN;
END;
IF @HasLinks = 1
BEGIN
SELECT CAST(0 AS bit) AS Succeeded, N'This item has linked story data and cannot yet be permanently deleted safely. Leave it archived instead.' AS Message;
RETURN;
END;
SELECT CAST(1 AS bit) AS Succeeded, N'Archived item permanently deleted.' AS Message;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Project_Archive @ProjectID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Project', @ProjectID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.Book_Archive @BookID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Book', @BookID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.Chapter_Archive @ChapterID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Chapter', @ChapterID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.Scene_Archive @SceneID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Scene', @SceneID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.PlotLine_Archive @PlotLineID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'PlotLine', @PlotLineID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.PlotThread_Archive @PlotThreadID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'PlotThread', @PlotThreadID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryAsset_Archive @StoryAssetID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'StoryAsset', @StoryAssetID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.Character_Archive @CharacterID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Character', @CharacterID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.Location_Archive @LocationID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Location', @LocationID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_Archive @ScenarioID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Scenario', @ScenarioID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.Relationship_Archive @CharacterRelationshipID int AS BEGIN SET NOCOUNT ON; EXEC dbo.Archive_Entity N'Relationship', @CharacterRelationshipID, NULL; END;
GO
CREATE OR ALTER PROCEDURE dbo.SceneCharacter_ListByScene
@SceneID int
AS
BEGIN
SET NOCOUNT ON;
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID,
CASE WHEN c.IsArchived = 1 THEN CONCAT(c.CharacterName, N' (archived)') ELSE c.CharacterName END AS CharacterName,
c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
sc.AppearanceNotes, sc.OutfitDescription, sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes,
sc.CreatedDate, sc.UpdatedDate
FROM dbo.SceneCharacters sc
INNER JOIN dbo.Characters 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
WHERE sc.SceneID = @SceneID
ORDER BY role.SortOrder, c.CharacterName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.ThreadEvent_ListByScene
@SceneID int
AS
BEGIN
SET NOCOUNT ON;
SELECT te.ThreadEventID, te.PlotThreadID,
CASE WHEN pt.IsArchived = 1 THEN CONCAT(pt.ThreadTitle, N' (archived)') ELSE pt.ThreadTitle END AS ThreadTitle,
pt.PlotLineID,
CASE WHEN pl.IsArchived = 1 THEN CONCAT(pl.PlotLineName, N' (archived)') ELSE pl.PlotLineName END AS PlotLineName,
pl.Colour, te.SceneID, te.EventTypeID, tet.TypeName AS EventTypeName, tet.MarkerText, te.EventTitle,
te.EventDescription, te.CreatedDate, te.UpdatedDate
FROM dbo.ThreadEvents te
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
WHERE te.SceneID = @SceneID
ORDER BY pl.SortOrder, pt.Importance DESC, te.CreatedDate;
END;
GO
CREATE OR ALTER PROCEDURE dbo.AssetEvent_ListByScene
@SceneID int
AS
BEGIN
SET NOCOUNT ON;
SELECT ae.AssetEventID, ae.StoryAssetID, sa.ProjectID,
CASE WHEN sa.IsArchived = 1 THEN CONCAT(sa.AssetName, N' (archived)') ELSE sa.AssetName END AS AssetName,
sa.AssetKindID, ak.KindName,
ae.SceneID, ae.AssetEventTypeID, aet.TypeName AS AssetEventTypeName, aet.MarkerText,
ae.FromStateID, fs.StateName AS FromStateName, ae.ToStateID, ts.StateName AS ToStateName,
ae.EventTitle, ae.EventDescription, ae.CreatedDate, ae.UpdatedDate
FROM dbo.AssetEvents ae
INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID
LEFT JOIN dbo.AssetStates fs ON fs.AssetStateID = ae.FromStateID
LEFT JOIN dbo.AssetStates ts ON ts.AssetStateID = ae.ToStateID
WHERE ae.SceneID = @SceneID
ORDER BY sa.AssetName, ae.AssetEventID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.RelationshipEvent_ListByScene
@SceneID int
AS
BEGIN
SET NOCOUNT ON;
SELECT re.RelationshipEventID, re.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID,
CASE WHEN ca.IsArchived = 1 THEN CONCAT(ca.CharacterName, N' (archived)') ELSE ca.CharacterName END AS CharacterAName,
cr.CharacterBID,
CASE WHEN cb.IsArchived = 1 THEN CONCAT(cb.CharacterName, N' (archived)') ELSE cb.CharacterName END AS CharacterBName,
CASE WHEN cr.IsArchived = 1 THEN CONCAT(rt.TypeName, N' (archived)') ELSE rt.TypeName END AS RelationshipTypeName,
re.SceneID, re.RelationshipStateID, rs.StateName AS RelationshipStateName, re.Intensity, re.IsKnownToOtherCharacter,
re.Description, re.CreatedDate, re.UpdatedDate
FROM dbo.RelationshipEvents re
INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
WHERE re.SceneID = @SceneID
ORDER BY ca.CharacterName, cb.CharacterName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.CharacterKnowledge_Delete
@CharacterKnowledgeID int
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM dbo.CharacterKnowledge WHERE CharacterKnowledgeID = @CharacterKnowledgeID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.RelationshipEvent_Delete
@RelationshipEventID int
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM dbo.RelationshipEvents WHERE RelationshipEventID = @RelationshipEventID;
END;
GO

View File

@ -351,6 +351,15 @@ public sealed class ScenarioCompareViewModel
public IReadOnlyList<ScenarioWarning> ScenarioWarnings { get; set; } = [];
}
public sealed class ArchivedItemsViewModel
{
public int? ProjectID { get; set; }
public string? EntityType { get; set; }
public IReadOnlyList<SelectListItem> ProjectOptions { get; set; } = [];
public IReadOnlyList<SelectListItem> EntityTypeOptions { get; set; } = [];
public IReadOnlyList<ArchivedItem> Items { get; set; } = [];
}
public sealed class TimelineViewModel
{
public Project Project { get; set; } = new();

View File

@ -0,0 +1,91 @@
@model ArchivedItemsViewModel
@{
ViewData["Title"] = "Archived Items";
}
<div class="page-heading">
<div>
<p class="eyebrow">Archive</p>
<h1>Archived Items</h1>
<p class="lead-text">Restore projects, scenes, characters, assets, locations, threads, scenarios, and other archived planning items.</p>
</div>
</div>
<section class="story-bible-section">
<form method="get" class="timeline-filter-grid archive-filter-form">
<div>
<label class="form-label">Project</label>
<select class="form-select form-select-sm" name="projectId" asp-items="Model.ProjectOptions">
<option value="">All Projects</option>
</select>
</div>
<div>
<label class="form-label">Entity type</label>
<select class="form-select form-select-sm" name="entityType" asp-items="Model.EntityTypeOptions">
<option value="">All archived items</option>
</select>
</div>
<div class="timeline-filter-actions">
<button class="btn btn-outline-primary btn-sm" type="submit">Filter</button>
<a class="btn btn-outline-secondary btn-sm" asp-action="Index">Clear</a>
</div>
</form>
</section>
<section class="story-bible-section">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Restore</p>
<h2>Archived Records</h2>
</div>
<span class="soft-count">@Model.Items.Count item@(Model.Items.Count == 1 ? "" : "s")</span>
</div>
@if (!Model.Items.Any())
{
<p class="muted">No archived items found for this filter.</p>
}
else
{
<div class="archive-item-list">
@foreach (var item in Model.Items)
{
<article class="archive-item-card">
<div>
<span class="archived-badge">@item.EntityType</span>
<h3>@item.DisplayName</h3>
<p class="muted">
@if (!string.IsNullOrWhiteSpace(item.ProjectName))
{
<span>@item.ProjectName</span>
}
@if (!string.IsNullOrWhiteSpace(item.BookTitle))
{
<span> / @item.BookTitle</span>
}
</p>
<p class="muted">Archived: @(item.ArchivedDate?.ToString("dd MMM yyyy HH:mm") ?? item.UpdatedDate.ToString("dd MMM yyyy HH:mm"))</p>
@if (!string.IsNullOrWhiteSpace(item.ArchivedReason))
{
<p>@item.ArchivedReason</p>
}
</div>
<div class="archive-actions">
<form asp-action="Restore" method="post" data-confirm-message="Restore this @item.EntityType.ToLowerInvariant()?&#10;&#10;This will return it to active lists and timelines where applicable.">
<input type="hidden" name="entityType" value="@item.EntityType" />
<input type="hidden" name="entityId" value="@item.EntityID" />
<input type="hidden" name="projectId" value="@Model.ProjectID" />
<button class="btn btn-outline-primary btn-sm restore-button" type="submit">Restore</button>
</form>
<form asp-action="DeleteForever" method="post" class="delete-forever-form" data-confirm-message="Permanently delete this @item.EntityType.ToLowerInvariant()?&#10;&#10;This cannot be undone. Any linked planning data may also be removed or become unavailable. Only continue if you are certain." data-require-delete-text="true">
<input type="hidden" name="entityType" value="@item.EntityType" />
<input type="hidden" name="entityId" value="@item.EntityID" />
<input type="hidden" name="projectId" value="@Model.ProjectID" />
<input type="hidden" name="confirmationText" value="" data-delete-confirm-target />
<button class="btn btn-outline-danger btn-sm" type="submit">Delete Forever</button>
</form>
</div>
</article>
}
</div>
}
</section>

View File

@ -39,6 +39,11 @@
<a class="btn btn-outline-primary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Timeline</a>
<a class="btn btn-outline-primary" asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID">Story health</a>
<a class="btn btn-primary" asp-controller="Chapters" asp-action="Create" asp-route-bookId="@Model.Book.BookID">Add chapter</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this book?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.Book.BookID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Book</button>
</form>
</div>
</div>
@ -71,6 +76,11 @@
<td>@chapter.Summary</td>
<td class="text-end">
<a class="btn btn-outline-secondary btn-sm" asp-controller="Chapters" asp-action="Edit" asp-route-id="@chapter.ChapterID">Edit</a>
<form asp-controller="Chapters" asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this chapter?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@chapter.ChapterID" />
<input type="hidden" name="bookId" value="@Model.Book.BookID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</td>
</tr>
}

View File

@ -44,6 +44,11 @@
<a class="btn btn-outline-primary" asp-controller="Writer" asp-action="Chapter" asp-route-id="@Model.Chapter.ChapterID">Chapter workflow</a>
<a class="btn btn-outline-primary" asp-controller="Exports" asp-action="ChapterBrief" asp-route-id="@Model.Chapter.ChapterID">Chapter brief</a>
<a class="btn btn-primary" asp-controller="Scenes" asp-action="Create" asp-route-chapterId="@Model.Chapter.ChapterID">Add scene</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this chapter?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.Chapter.ChapterID" />
<input type="hidden" name="bookId" value="@Model.Book.BookID" />
<button class="btn btn-outline-danger" type="submit">Archive Chapter</button>
</form>
</div>
</div>
@ -94,6 +99,11 @@
<td class="text-end">
<a class="btn btn-outline-secondary btn-sm" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.Book.BookID" asp-route-selectedSceneId="@scene.SceneID">Open in timeline</a>
<a class="btn btn-outline-secondary btn-sm" asp-controller="Scenes" asp-action="Edit" asp-route-id="@scene.SceneID">Edit</a>
<form asp-controller="Scenes" asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this scene?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@scene.SceneID" />
<input type="hidden" name="chapterId" value="@Model.Chapter.ChapterID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</td>
</tr>
}

View File

@ -38,6 +38,11 @@
<a class="btn btn-outline-primary" asp-controller="Exports" asp-action="Character" asp-route-id="@Model.Character.CharacterID">Export character</a>
<a class="btn btn-outline-secondary" asp-controller="Exports" asp-action="Character" asp-route-id="@Model.Character.CharacterID" asp-route-format="markdown">Markdown</a>
<a class="btn btn-outline-primary" asp-action="Edit" asp-route-id="@Model.Character.CharacterID">Edit character</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this character?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.Character.CharacterID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Character</button>
</form>
</div>
</div>
@ -65,6 +70,11 @@
<strong>@relationship.CharacterAName -> @relationship.CharacterBName</strong>
<span>@relationship.RelationshipTypeName</span>
<a asp-controller="Dynamics" asp-action="Relationship" asp-route-id="@relationship.CharacterRelationshipID">Evolution</a>
<form asp-action="ArchiveRelationship" method="post" class="archive-button-form" data-confirm-message="Archive this relationship?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@relationship.CharacterRelationshipID" />
<input type="hidden" name="characterId" value="@Model.Character.CharacterID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</div>
}
</div>

View File

@ -53,6 +53,11 @@
<span class="status-pill">Age @character.AgeAtSeriesStart</span>
}
<a class="btn btn-outline-secondary btn-sm ms-auto" asp-action="Edit" asp-route-id="@character.CharacterID">Edit</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this character?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@character.CharacterID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</div>
</article>
}

View File

@ -34,6 +34,11 @@
<a class="btn btn-outline-secondary" asp-action="Edit" asp-route-id="@Model.Location.LocationID">Edit</a>
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@firstScene?.SceneID" asp-route-FocusType="location" asp-route-FocusID="@Model.Location.LocationID">Back to timeline</a>
<a class="btn btn-outline-primary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">All locations</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this location?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.Location.LocationID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Location</button>
</form>
</div>
</div>
@ -93,7 +98,7 @@
}
<p class="muted">@relationship.Notes</p>
</div>
<form asp-action="ArchiveRelationship" method="post">
<form asp-action="ArchiveRelationship" method="post" data-confirm-message="Archive this location relationship?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@relationship.LocationRelationshipID" />
<input type="hidden" name="returnLocationId" value="@Model.Location.LocationID" />
<button class="btn btn-outline-secondary btn-sm" type="submit">Archive</button>

View File

@ -79,7 +79,7 @@ else
</div>
<div class="button-row compact-buttons">
<a class="btn btn-outline-secondary btn-sm" asp-action="Edit" asp-route-id="@location.LocationID">Edit</a>
<form asp-action="Archive" method="post">
<form asp-action="Archive" method="post" data-confirm-message="Archive this location?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@location.LocationID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>

View File

@ -8,7 +8,17 @@
<p class="eyebrow">@Model.Project?.ProjectName</p>
<h1>@ViewData["Title"]</h1>
</div>
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Back to plot lines</a>
<div class="button-row">
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Back to plot lines</a>
@if (Model.PlotLineID > 0)
{
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this plot line?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.PlotLineID" />
<input type="hidden" name="projectId" value="@Model.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Plot Line</button>
</form>
}
</div>
</div>
<form asp-action="Save" method="post" class="edit-panel">

View File

@ -57,6 +57,11 @@
<a class="btn btn-outline-secondary btn-sm" asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-FocusType="plotline" asp-route-FocusID="@plotLine.PlotLineID">Focus</a>
<a class="btn btn-outline-secondary btn-sm" asp-controller="PlotThreads" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID" asp-route-plotLineId="@plotLine.PlotLineID">Add thread</a>
<a class="btn btn-outline-secondary btn-sm" asp-action="Edit" asp-route-id="@plotLine.PlotLineID">Edit</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this plot line?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@plotLine.PlotLineID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</td>
</tr>
}

View File

@ -8,7 +8,17 @@
<p class="eyebrow">@Model.Project?.ProjectName</p>
<h1>@ViewData["Title"]</h1>
</div>
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Back to plot threads</a>
<div class="button-row">
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.ProjectID">Back to plot threads</a>
@if (Model.PlotThreadID > 0)
{
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this plot thread?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.PlotThreadID" />
<input type="hidden" name="projectId" value="@Model.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Plot Thread</button>
</form>
}
</div>
</div>
<form asp-action="Save" method="post" class="edit-panel">

View File

@ -59,6 +59,11 @@
<td class="text-end">
<a class="btn btn-outline-secondary btn-sm" asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-FocusType="plotthread" asp-route-FocusID="@thread.PlotThreadID">Focus</a>
<a class="btn btn-outline-secondary btn-sm" asp-action="Edit" asp-route-id="@thread.PlotThreadID">Edit</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this plot thread?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@thread.PlotThreadID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</td>
</tr>
}

View File

@ -21,6 +21,7 @@
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
<a asp-controller="Dynamics" asp-action="Story" asp-route-projectId="@Model.Project.ProjectID">Story dynamics</a>
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
<a asp-controller="Archives" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Archived Items</a>
<a asp-controller="Characters" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
@ -51,7 +52,12 @@
<a class="btn btn-outline-primary" asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
<a class="btn btn-outline-primary" asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
<a class="btn btn-outline-primary" asp-controller="Dynamics" asp-action="Story" asp-route-projectId="@Model.Project.ProjectID">Story dynamics</a>
<a class="btn btn-outline-secondary" asp-controller="Archives" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Archived Items</a>
<a class="btn btn-primary" asp-controller="Books" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID">Add book</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this project?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Project</button>
</form>
</div>
</div>
@ -82,6 +88,11 @@
<td>@book.Description</td>
<td class="text-end">
<a class="btn btn-outline-secondary btn-sm" asp-controller="Books" asp-action="Edit" asp-route-id="@book.BookID">Edit</a>
<form asp-controller="Books" asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this book?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@book.BookID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</td>
</tr>
}

View File

@ -30,6 +30,10 @@ else
<div class="button-row">
<a class="btn btn-outline-primary btn-sm" asp-action="Details" asp-route-id="@project.ProjectID">Open</a>
<a class="btn btn-outline-secondary btn-sm" asp-action="Edit" asp-route-id="@project.ProjectID">Edit</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this project?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@project.ProjectID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</div>
</article>
}

View File

@ -54,7 +54,7 @@
<div class="button-row mt-2">
<a class="btn btn-outline-primary btn-sm" asp-action="Timeline" asp-route-id="@scenario.ScenarioID">Open timeline</a>
<a class="btn btn-outline-secondary btn-sm" asp-action="Compare" asp-route-id="@scenario.ScenarioID">Compare</a>
<form asp-action="Archive" method="post">
<form asp-action="Archive" method="post" data-confirm-message="Archive this scenario?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@scenario.ScenarioID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>

View File

@ -19,7 +19,7 @@
<button class="btn btn-outline-primary" type="submit">Validate Scenario</button>
</form>
<a class="btn btn-outline-primary" asp-action="Compare" asp-route-id="@Model.Scenario.ScenarioID">Compare</a>
<form asp-action="Archive" method="post">
<form asp-action="Archive" method="post" data-confirm-message="Archive this scenario?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.Scenario.ScenarioID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Scenario</button>

View File

@ -47,6 +47,11 @@
<a class="btn btn-outline-primary" asp-controller="Exports" asp-action="SceneBrief" asp-route-id="@Model.SceneID">Scene brief</a>
<a class="btn btn-outline-secondary" asp-controller="Exports" asp-action="SceneBrief" asp-route-id="@Model.SceneID" asp-route-format="markdown">Markdown</a>
<a class="btn btn-outline-primary" asp-controller="Exports" asp-action="WritingSession" asp-route-sceneId="@Model.SceneID">Prepare Writing Session</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this scene?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.SceneID" />
<input type="hidden" name="chapterId" value="@Model.ChapterID" />
<button class="btn btn-outline-danger" type="submit">Archive Scene</button>
</form>
}
<a class="btn btn-outline-secondary" asp-controller="Chapters" asp-action="Details" asp-route-id="@Model.ChapterID">Back to chapter</a>
</div>

View File

@ -224,6 +224,15 @@
</div>
</form>
@if (Model.SceneID > 0)
{
<form asp-controller="Scenes" asp-action="Archive" method="post" class="inspector-archive-form archive-button-form mt-2" data-confirm-message="Archive this scene?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.SceneID" />
<input type="hidden" name="chapterId" value="@Model.ChapterID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive Scene</button>
</form>
}
@if (TempData["WriterWorkspaceError"] is string writerWorkspaceError)
{
<div class="alert alert-warning">@writerWorkspaceError</div>
@ -1170,6 +1179,13 @@
<span class="muted">@(item.AssetName ?? item.ThreadTitle)</span>
<span class="muted">@item.Description</span>
</div>
<form asp-controller="Scenes" asp-action="DeleteCharacterKnowledge" method="post">
<input type="hidden" name="id" value="@item.CharacterKnowledgeID" />
<input type="hidden" name="sceneId" value="@Model.SceneID" />
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
<button class="btn btn-outline-secondary btn-sm" type="submit">Remove</button>
</form>
</div>
}
</div>
@ -1210,6 +1226,13 @@
<span class="muted">@item.RelationshipStateName / intensity @item.Intensity</span>
<span class="muted">@item.Description</span>
</div>
<form asp-controller="Scenes" asp-action="DeleteRelationshipEvent" method="post">
<input type="hidden" name="id" value="@item.RelationshipEventID" />
<input type="hidden" name="sceneId" value="@Model.SceneID" />
<input type="hidden" name="projectId" value="@Model.ReturnProjectID" />
<input type="hidden" name="bookId" value="@Model.ReturnBookID" />
<button class="btn btn-outline-secondary btn-sm" type="submit">Remove</button>
</form>
</div>
}
</div>

View File

@ -36,10 +36,40 @@
</header>
<div class="container-fluid plotline-shell @ViewData["ShellClass"]">
<main role="main" class="pb-3">
@if (TempData["ArchiveMessage"] is string archiveMessage)
{
<div class="alert alert-info">@archiveMessage</div>
}
@if (TempData["ArchiveError"] is string archiveError)
{
<div class="alert alert-warning">@archiveError</div>
}
@RenderBody()
</main>
</div>
<div class="modal fade" id="plotlineConfirmModal" tabindex="-1" aria-labelledby="plotlineConfirmModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content plotline-confirm-modal">
<div class="modal-header">
<h2 class="modal-title fs-5" id="plotlineConfirmModalTitle">Confirm action</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cancel"></button>
</div>
<div class="modal-body">
<p id="plotlineConfirmModalMessage" class="mb-0"></p>
<div class="delete-confirm-field d-none mt-3" data-delete-confirm-field>
<label class="form-label" for="plotlineDeleteConfirmInput">Type DELETE to continue</label>
<input class="form-control" id="plotlineDeleteConfirmInput" autocomplete="off" data-delete-confirm-input />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" data-confirm-submit>Confirm</button>
</div>
</div>
</div>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>

View File

@ -36,6 +36,11 @@
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story assets</a>
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@firstSceneId" asp-route-FocusType="asset" asp-route-FocusID="@Model.Asset.StoryAssetID">Back to timeline</a>
<a class="btn btn-outline-primary" asp-action="Edit" asp-route-id="@Model.Asset.StoryAssetID">Edit asset</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this asset?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.Asset.StoryAssetID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Asset</button>
</form>
</div>
</div>

View File

@ -78,6 +78,11 @@
<span class="purpose-chip">Resolved</span>
}
<a class="btn btn-outline-secondary btn-sm ms-auto" asp-action="Edit" asp-route-id="@asset.StoryAssetID">Edit</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this asset?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@asset.StoryAssetID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive</button>
</form>
</div>
</article>
}

View File

@ -43,6 +43,7 @@
<a asp-controller="StoryAssets" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Assets</a>
<a asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
<a asp-controller="PlotThreads" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Threads</a>
<a asp-controller="Archives" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Archived Items</a>
</nav>
</section>
@ -75,6 +76,7 @@
<a class="btn btn-outline-secondary" asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.SelectedBookID">Warnings</a>
<a class="btn btn-outline-secondary" asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.SelectedBookID">Story health</a>
<a class="btn btn-outline-primary" asp-controller="Scenarios" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.SelectedBookID">Create Scenario</a>
<a class="btn btn-outline-secondary" asp-controller="Archives" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Archived Items</a>
</div>
</div>

View File

@ -121,6 +121,20 @@ a {
gap: 8px;
}
.button-row form,
.compact-buttons form,
.asset-card-footer form,
td.text-end form,
form[data-confirm-message] {
display: inline-flex;
align-items: center;
margin: 0;
}
td.text-end {
white-space: nowrap;
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
@ -2936,6 +2950,80 @@ body.dragging-location [data-drag-type="location"] {
border-color: rgba(163, 61, 61, 0.24);
}
.archive-filter-form {
align-items: end;
}
.archive-item-list {
display: grid;
gap: 10px;
}
.archive-item-card {
display: flex;
justify-content: space-between;
gap: 14px;
align-items: center;
border: 1px solid var(--plotline-line);
border-radius: 10px;
padding: 12px;
background: rgba(255, 255, 255, 0.82);
}
.archive-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
justify-items: end;
}
.delete-forever-form {
display: inline-flex;
margin: 0;
}
.archive-item-card h3 {
margin: 4px 0;
}
.archived-badge {
display: inline-flex;
border: 1px solid rgba(82, 75, 65, 0.16);
border-radius: 999px;
padding: 2px 8px;
color: var(--plotline-muted);
font-size: 0.72rem;
font-weight: 800;
text-transform: uppercase;
background: rgba(247, 245, 240, 0.86);
}
.restore-button {
white-space: nowrap;
}
.archive-button-form {
display: inline-flex;
align-items: center;
margin: 0;
}
.plotline-confirm-modal .modal-title {
color: var(--plotline-ink);
}
.plotline-confirm-modal .modal-body p {
white-space: pre-line;
}
.delete-confirm-field {
border: 1px solid rgba(163, 61, 61, 0.22);
border-radius: 8px;
padding: 12px;
background: rgba(255, 247, 244, 0.7);
}
@media print {
header,
.breadcrumb-trail,

View File

@ -1,3 +1,117 @@
(() => {
const modalElement = document.getElementById("plotlineConfirmModal");
const modal = modalElement && window.bootstrap ? new bootstrap.Modal(modalElement) : null;
const modalTitle = modalElement?.querySelector("#plotlineConfirmModalTitle");
const modalMessage = modalElement?.querySelector("#plotlineConfirmModalMessage");
const modalSubmit = modalElement?.querySelector("[data-confirm-submit]");
const deleteField = modalElement?.querySelector("[data-delete-confirm-field]");
const deleteInput = modalElement?.querySelector("[data-delete-confirm-input]");
let pendingForm = null;
let pendingSubmitter = null;
const splitMessage = (message) => {
const parts = message.split(/\n\s*\n/);
return {
title: parts.shift()?.trim() || "Confirm action",
body: parts.join("\n\n").trim()
};
};
const resetModal = () => {
pendingForm = null;
pendingSubmitter = null;
if (deleteInput) {
deleteInput.value = "";
}
deleteField?.classList.add("d-none");
modalSubmit?.removeAttribute("disabled");
};
modalElement?.addEventListener("hidden.bs.modal", resetModal);
deleteInput?.addEventListener("input", () => {
if (pendingForm?.dataset.requireDeleteText === "true" && modalSubmit) {
modalSubmit.disabled = deleteInput.value.trim() !== "DELETE";
}
});
modalSubmit?.addEventListener("click", () => {
if (!pendingForm) {
return;
}
if (pendingForm.dataset.requireDeleteText === "true") {
const target = pendingForm.querySelector("[data-delete-confirm-target], [name='confirmationText']");
if (target) {
target.value = deleteInput?.value.trim() || "";
}
}
pendingForm.dataset.confirmed = "true";
modal?.hide();
if (pendingForm.requestSubmit && pendingSubmitter) {
pendingForm.requestSubmit(pendingSubmitter);
} else if (pendingForm.requestSubmit) {
pendingForm.requestSubmit();
} else {
pendingForm.submit();
}
});
document.addEventListener("submit", (event) => {
const form = event.target;
if (!(form instanceof HTMLFormElement) || form.dataset.confirmed === "true") {
return;
}
const explicitMessage = form.dataset.confirmMessage;
const submitter = event.submitter instanceof HTMLElement ? event.submitter : form.querySelector("button[type='submit']");
const buttonText = submitter?.textContent || "";
const actionPath = form.getAttribute("action") || "";
const restoreAction = /restore/i.test(buttonText) || /\/restore\b/i.test(actionPath);
const deleteForeverAction = /delete forever/i.test(buttonText) || /\/deleteforever\b/i.test(actionPath);
const archiveAction = !restoreAction && !deleteForeverAction && (/archive/i.test(buttonText) || /\/archive\b/i.test(actionPath));
const removeAction = !deleteForeverAction && /(delete|remove)/i.test(buttonText);
if (!explicitMessage && !archiveAction && !restoreAction && !removeAction && !deleteForeverAction) {
return;
}
const message = explicitMessage
|| (archiveAction
? "Archive this item?\n\nThis will hide it from active lists and timelines, but the data will be kept and can be restored later."
: restoreAction
? "Restore this item?\n\nThis will return it to active lists and timelines where applicable."
: deleteForeverAction
? "Permanently delete this item?\n\nThis cannot be undone. Any linked planning data may also be removed or become unavailable. Only continue if you are certain."
: "Remove this item?\n\nThis removes the link or supporting record. Major story items are archived instead of permanently deleted.");
if (!modal || !modalElement || !modalTitle || !modalMessage || !modalSubmit) {
return;
}
event.preventDefault();
const content = splitMessage(message);
pendingForm = form;
pendingSubmitter = submitter;
modalTitle.textContent = form.dataset.confirmTitle || content.title;
modalMessage.textContent = form.dataset.confirmBody || content.body;
modalSubmit.textContent = form.dataset.confirmButtonText || buttonText.trim() || "Confirm";
modalSubmit.className = `btn ${form.dataset.confirmButtonClass || (restoreAction ? "btn-primary" : "btn-danger")}`;
const needsDeleteText = form.dataset.requireDeleteText === "true" || deleteForeverAction;
deleteField?.classList.toggle("d-none", !needsDeleteText);
if (needsDeleteText && modalSubmit) {
modalSubmit.disabled = true;
deleteInput?.focus();
}
modal.show();
if (needsDeleteText) {
window.setTimeout(() => deleteInput?.focus(), 200);
}
});
})();
(() => {
const roots = document.querySelectorAll(".scene-inspector-root");
if (!roots.length) {