Phase 2B
This commit is contained in:
parent
24dd787536
commit
92ab864471
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUS2/bOBD+KwLPpkJRFCUK2IPhtFgjTWMk7u5ibxQ5jInIpEFRToMi/31BWa6dooe9CaN5fY/hD+TkHlCLlofDpyO4OKAFinYKUUI5JhyTcluwtqJt1eSiFpyU7F+0QPYO3lCLaqZUoQTgEjhgpkmFu9IorDWIghSa0IqlnvJ5QO0PJG2u4WgV5H74C8JgvUMt+ts67V/TbGlz1ftR58H3sHZDlE6lZb6uV3f4y3KzfdicsgYYUnFuNWqRrlmheSOwMlRgxiTBHaEGC6U1F6STpjOnMusiBCf7fNAvl/naRwdRtTSnZU4wFeh9gbSMMq3cyQG2b4e0xUTRbYovpvD02f5ARwiopYszmad2N6q3N/uhG22vb6IMzxBNkHt49eEFjrJHC3QI/gAhWpi4eXjKLisVJCc55ZQQtED3Uu2sg2x9mz30CbBUDWGgSyzAaMxEIbFgZYl1WcmaclPXNAG+g+Cgv+p6b1XwgzcxmynPPsxZeRetG/04ZGsX4TnIeKr7LPsB0AJtxq63w+4RpH7b+scx/XNjn7CsxhDAxWwj4y77Uw471CIhG1VIIYWUXCkDFQFDNJGc1Bq45qAZ1bVmgmnOBFGcya5gWlZFZ1IELdDDGA9jPPP//WqJbbD7PejLBtuJ400vo/Fhv9bgojU2SXPOmAzV9xOqbO656i24+IFk1CJSANVlA9jQgmPWaIllVQrMeckNFErUtDgRZuzz+JMnYoxmpamahneiKwhQUzeM1KYjWplSCV11UNOa1A00QhHSVaxoOqGIhqJiOqELXo8q/mqFchLocXTpOrN1gv1qHf7O2YWRTfDRK98nwWwPT29DhP3vVFv5/cEPNib8Zpb22wD33qW/8/3PnM3FSx8vMUh3kE2nR6uOUMI4po0ymOma4g4kwaKsGK8bIAAKLdCtVy8QsmQwad2kydlTD0/ZMqidjaDiGNLkfyZQJzk/n0/mwkf+9dN25QMsD4fFHP3jmEg6NTsb4MO78m1I6Ts/xCvIM7Qn6M15sSs7aTieniqbglzrBjQvMGVFhZkQBW5EVeOubjrR8QYqdbFr9gjaBlBx6rcNY5o2M/s7Xy5DtEaqOKTj+eLVZKfZn3PKr0teyfaz+jT8SrrZSnOngiTLPp0ezslB/+vdfH9/f/8PAAD//wMABJt80ygGAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUwW7cNhCGX0WYsyiTEkWJuhnrBF24qRfJIi16ozijmLBMLUjKiWHsuxfUbpD6VPRCECPO8J9vfuoNvHkmGOD2dPrwQj5FKCG5LVTzWjGuGG+OQg5tM/C+kk0tRdv/DSW4e3qFATpprbCaWEOKmETesrGZLEMkLbhAXrcy1zTfIgxvYFyF9OIsVUv8SiG6xcMAfzqPy/d8t3GVnZcVq7DMtPcxGW+zmD/2u3v2++3h+HC4nIoUc3LlEAbATgpUvWZ2qjWT0nA28npi2iIqzUczjdMlzflEwZu5ivj0635ckqdkh7qqm4qzWsO5BDTJZMmjiXR8PWUVG6K7HC+38LYd3uCFAgx1+RPmpdyNnd3NcxxXN+PNZU0mPsVtnYxNS3iFEk5hOVFIjjY+tzHS8zi/Hk18+ng5k7fxww+yayLcLatPMIBoOihhAzTPJrnFF1eNu9mRT9vHFJyPzv5XrQZKePhSHGaTpiU8v5vHlwvm/f+g/MnYR+ep2N/BAFxQjU1PbKqFYrJHw0zbaKZUoyYSVne1gBJ2i0/Or8sai71P9C1sLcEAH80c6aLvNthHl8imNeQ+/1LZV4ew4GpT8WuWgle8ajjPSWs6ran4TOgC2US5iWNYr/W+vk+pVb0l7dYQyKfiYNJj8ZuJjzCANr0VRhttjLJ2opbTxJEbxTskhYpQ1tih1BKV1NwqaUYh0bRinHIESqBsnWJz69hMulNSsNaaiUkpiBmFxPpOj42Smqwc33EsHuacZ2zPJWHDNE3IpBaGadk0DJvWdLWauq7O/H8iufpB8AwY6eXy7jYFCrEnVILl18yk1oL1uu3Y2PWjHlVPrc2a7yl4mv8F6pOzYYnLlIqrQ4p36D6vPv87is0t351nP7YZ3S32iUKRZ2ycz0/lOtbz+Xz+BwAA//8DAK3VVNOCBAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTQW/cNhCF/4owZ1EmKYoSdTPWKbpwEy+SRVr0RnFGMWGZWlCUk8DY/x5Qu4GT3noRhAEf571vhq8Q7DNBD7en07sXCmmBEpLfSpJLzbhmvD4K1Td1z7tK1Uoa3v4LJfh7+g49tMo54QyxmjQxhbxhQz06hkhGcIFcNirfab8s0L+C9RXSi3dUzctnioufA/Twtw84f829ra/cNK9YxXmifViSDS6b+bDf3bO/bg/Hh8Pl1EJLFlceoQdslUDdGeZGaZhSlrOBy5EZh6gNH+w4jBeZD4lisFO14NNbf5xToOR6Wcm64kwaOJeANtlsebALHb+fsosN0V2ul1t5++1f4YUi9LL8CfNy3Y2b/M3zMqx+wpvLN9nlKac8xflEMXnamGwxp8kmP4fi2mk3eQoJSnj4VBwmm8Y5Pv8G6tMl//5/xD/m7u++kVsT4W5eQ4IehOJQwnvrHn2gYn8HPXBBEuuO2CiFZqpDy2xTG6Z1rUcSzrRSQAm7OSQf1nldin1I9CVuAaCHP+y00MX6bXSPPpFLa8yp/tF5Fw5xxtWl4o2/4BWvap6dPKzptKbiI6GP5BLlfMe4Xu/7j0RquYl2a4wUUnGw6bH40y6P0IOxnRPWWGOtdm6khtPIkVvNWySNmlBJbFEZhVoZ7rSyg1BoGzGMuQIlUB53sW2YwIGjVophbTRTQ9My46xkrtV80AYFbYjfOBYPU9ZZ13FFWDNDIzJlhGVG1TXDurGt1GPbyqz7ieQ6fcEzYKSXy1vZHGjEjlALJpVomDJGsM40LRvabjCD7qhx2fM9xUDTL6DeexfnZR5TcV2e4jd0H9eQ33uxLdJXH9i3bUZ3s3uiWOQZWx/yel/Hej6fzz8AAAD//wMAtGYnyDYEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTwY7cKBD9FYuz8QDG2Pg26tlVWpPstJJRstobpoo0igdaGE8SjfrfV7g7Sia33NCDV/XqveKFBPOEZCS3p9NfzxjyQmqS/QYJJhRlirL2kcuxa0c2NLLVnZTyP1ITf4/fyUh6aS23GmmLCqkE1tGpdZYCoOaMAxOdLDXN54WML8T4BvDZW2zi8hHT4mMgI/nkA8SvpbfxjZ3jCk2KM+7Dkk2wRcw/+909fXt7eHw4XF4tuBRy44GMBHrJQQ2aWic0ldIwOjHhqLYASrPJuMldaD5kTMHMzQJffvaHmANmO4pGtA2jQpNzTcBkUyRPZsHH76eiYrPoruD1Bm/H8YU8YyKjqH+YeSl3Y2d/Y+PTkwlw43zwy5HU5JTiCVP2uNmxTTjPJvsYqmuT3ewxZFKThw/VYTbZxfT0yqMPl9H3fzD5O2OPPmC1vyMjYRwFtANSJ7iicgBDTddqqlSrHHKre8FJTXYxZB/WuC7VPmT8nDaVZCR/m3nBi77bZI8+o81rKtL/VSVr/ObzLkIBGKnJIUVYba5+2s1Zw5qWlcuHNZ/WXL1H8AltxjLTY1qv5X+jCCU20m5NCUOuDiYfqzdmOZKRaDNYbrTRxihrHXYMHQNmFOsBFSgEKaAHqSUoqZlV0kxcgun45ApSdJd0q22hBqE4KieoGGRLpRWc6nZCOvUd4tA5KUT3ytbqYS48YwcmEVqq0QGVmhuqZdtSaDvTC+X6XpQ4flhyTZyz4jfg8+VrbAoUwICgOBWSd1Rqzemgu55O/TDpSQ3Y2aL5HlPA+Rej3nmb4hJdrq4LU72y7v0ayveutuX56gP9tkV2F+0XTFWJ3PhQtvma8vl8Pv8PAAD//wMAZYD86yUEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUXW/sJhD9KxavXRzAGBu/pZtcdZUmWd1dtVXfBhgnNA5eYZzcqyj/vcKb3HxcVeqLZQ0w58yZM/NEAtwj6cjp4XD+gCFNZEWSX0KCCUWZoqzac9nVVcfaUlZKK9X8TVbEX+B30pFGWsutRlqhQiodq6mpekudQ80Zd0zUMueEm4l0TwR86fDBWyzH6Q+Mkx8D6cifPrjxMWODL+0wzq6M44CbMCUINpO52qwv6O+n2/319nhrwik/Lr0jHXGN5E61mtpeaColMGqY6Km2zinNDPSmPz7zIWEMMJSTu3vDd2MKmGwnSlGVjApNnlfEQYJM2cCE+++HzGKR6CzHV0t4+e2eyANG0onVq5jHdCd28Cf3k5n94E6WL1mRQxwPGJPHRY3rXfFGgrOSlUIJxsiKXIK99QGLzVlxPeQSwbZMoquoxt5RqTlQLauKuqqGRqi+aUQu8QJjwOFd1ktv4ziNfSpeRC4+4OxO1+cBzIAZ4wsME5IV2Uy7BMHBMAY8/4Z2Tsdc+zjn4/UYkg/zOE/FJiS8ifBy/vp+PceIIRVbSLfFbzDdko5oaC0HDRpAWdtjzbBnjoFijUPlFDopXOOklk5JzaySYLh0UHPT5whZkV+zhrvZWpymNzaLSYZh4VC89Gk9eAzpg4ykI4yjcFWLtBdcUdk6oFBXmipVqR651Y3grzB7iDeYSEf61hjDrDVCam5BaKhczyqnei1E04Dsq7qRQqmaNWBd0wguXdWghaqvWCvIimzj6GabPre6WhqwjeM/aFOWinREsbpuLRgjlOQt6BYlV7JVGhste2CysgZ43YIxnEHNrG6FBa40SOPq/odK5+HGBzzz02GA7+9w21KV1S8WoOU9yE/Xv0S4x8cx3l0dbVxene/JinydQ94IxSZb5NEH+k3lib6ch+T3txHBobscHf7sI8zjUiwTKpVrNUpOeyF6KoWoKZjWUGF45ZwxTOvM5my0dxiL7DDwIU/Vj2TXu+I02luf0KY5Zn5/LTyud8V2gNSP8f7DJnlX2GcBSlFxljvj8OG4jRaOyrkWneJUSF5TqTWnra4baprWaKNarG3meD2nw5yKr+h8RJuWil+8uGCub9He/SzGqwteLMpZ9truuMUWaf/XEntekXuEaY54v2zr7olsQsC4IJ/Nx1HchEs/DH5COwY3kY4zplmpdatfKP73Ra5ZqVQjn5+fn/8FAAD//wMAY5ugbB0GAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRJF6c20HXaSOjeyiLfo2Ikc2a5laUJSTwPC/F9TauaJAXwRhSM45c+bMPJEID0h6cnY8Xj5izAupSQ5bSDChKdOUyYNgfct6ZZqOiVZI/TepSXiHn0lPOuUcdxapRI1UedbSQY6Oeo+WM+6ZaFXJCbcL6Z8IhMbjY3DYzMsfmJYwR9KTP0P088eCDaFx07z6Js0T7uKSIbpC5v3u/B39/ezmcH1zurXgUh43wZOeuNGC9+1InWGaKi4dNc4Jav2gZQuWeV9IQGhCzJgiTM3i77/i+zlHzK4XjZANo8KS55p4yFAoD7Dg4fOxsNgkuijxegtvv/0TecREelG/inlK98ZN4c3DMqxh8m+2L6nJMc1HTDngpsb1vvpKgrOGNUILxkhNrsDdhYjV7qK6nkqJ4AxT6CW1OHqqLAdqlZTUyxY6oceuEyOpyTtMEadvsl4Fl+ZlHnP1InL1Hc7+7PwywjBhwXgL04KkJrtlnyF6mOaIl5/QrfmU65DWcnw+xxziOq9LtYsZbxO8nL++P19TwpirG8h31W+w3JGeWDCOgwULoJ0bsWU4Ms9As86j9hq9Er7zyiqvlWVOKxi48tDyYSwRUpNfi4b71Tlclq9sNpNM08aheunT+RQw5u9kJD1hHIWXBukouKbKeKDQSku1lnpE7mwn+CvMAdItZtKT0QzDwJwbhLLcgbAg/cik16MVoutAjbLtlNC6ZR0433WCKy87dCBHyYwgNblJs19d/rHVcmvATZr/QZeLVKQnmrWtcTAMQituwBpUXCujLXZWjcCUdAPw1sAwcAYtc9YIB1xbUINvxy8qXcbbEPEiLMcJPn+DaxrdyF8cgOEjqB+uv03wgB/ndP/+ZOPm/eWB1OTDGstGqHbFIh9DpJ90GaardcrhcJcQPPqr2ePPPsIyLtU2odC6jolBUtWagapxRGqE41Sq0QzauRZ4eXExu3tMVXEYhFim6kuy6311ltxdyOjymgq/vzYe1/vqZoI8zunhu03yTWE/CtAIyVnpjMfH0zbaOGrvDXrNqVC8pcpaTo1tOzp0ZrCDNti6otj1mo9rrj6gDwld3ip+8eKGeX6H7v5nMV5d8GJRzorX9qcttkn7v5bYc00eEJY14cO2rfsnsosR04Z8sZ5GcRevwjSFBd0c/UJ627W8kZK/EPzPa6Y1jVTCPD8/P/8LAAD//wMA2D18ZxoGAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTwW7cOAyGX8Xg2XIkWZYt34JJFzvIthm0g+5ib7JIN0IceSDLaYtg3r2QZ4o2e9uLIBAi+f8fqVcI9pmgh9vT6d0LhbRACclvIcmlZlwzXh8l7xveq67SxnSqFv9CCf6evkMPrXJOOEOsJk1MIW/YUI+OIZIRXCCXjco17ZcF+lewvkJ68Y6qeflMcfFzgB7+9gHnr7m39ZWb5hWrOE+0D0uywWUxH/a7e/bX7eH4cLi8WmjJyZVH6MGNxiI2I3Md10yJ2rHOOckMDrpurOGIWYT1lQ+JYrBTteDTr/44p0DJ9bKSdcWZNHAuAW2yWfJgFzp+P2UVG6K7HC+38HbtX+GFIvSy/AnzUu7GTf7meRlWP+HN5Ux2ecouT3E+UUyeNiabzWmyyc+huHbaTZ5CghIePhWHyaZxjs9vQH26+N//D/vH3P3dN3JrItzNa0jQg1AcSnhv3aMPVOzvoAcuSGLdERul0Ex1aJltasO0rvVIwplWCihhN4fkwzqvS7EPib7EzQD08IedFrpIv43u0SdyaY3Z1T866zjEGVeXil/8Ba94VfOs5GFNpzUVHwl9JJco+zvG9VrvPylSyy1pt8ZIIRUHmx6LP+3yCD0Y2zlhjTXWaudGajiNHLnVvEXSqAmVxBaVUaiV4U4rOwiFthHDmCNQAuVxF9uGDW0nBFeOjdIZpnCUbGikYGpQTav1MNa2ecOxeJhynnUdV4Q1MzQiU0ZYZlRdM6wb20o9tq0cf0Nynb7gGTDSy+WvbAo0YkeoBZNKNEwZI1hnmpYNbTeYQXfUuKz5nmKg6TdQ772L8zKPqbguT/EG3cc15P9ebIv01Qf2bZvR3eyeKBZ5xtaHvN7XsZ7P5/MPAAAA//8DAK6M6YU2BAAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSU227cNhCGX0XgtSjzJB50Z9gt6rqpF/EiLXpHcUa2EC21oKhNA2PfPaB2g8S9650w0D+Hb/7hG4n+gKQjt8fjLyeMeSE1yeMWEkxoyjRlci9Y14qOy8ZI4ZTR/5CajI/4lXTEqBB4cEglaqQKWEt7OQQKgI4zDky0quT0Lwvp3ogfG8DTGLCZl0+YlnGOpCN/jRHmL6W2H5swzSs0aZ7wIS7Zx1Ca+fPh7pH+cbvbP+0ufy24FHEzAukICwi8B00HGThVjvXUOQsUFPPGuNahMBfZGDOm6Kdmgc8/6sOcI+bQiUbIhlHhyLkm4LMvLfd+wf3XY+liQ3Rf4vUW3j67N3LCRDpRf4d5SXcTpvEmz8cJTzgdfVow3YT5cPARSE2OaT5iyiNuWLZJp8nncY7VtdjdNGLMpCZPz9Vu8nmY0+Edq+cLgof/QeCEqScdkayV2hhUUkrvdC8YgtFBa9EHz8zQC+QBWeuUcdwwLp1gTrbaWAOeuUBq8jLNvZ9+XzZ+TrDeAjNCC1TohNLaBYbS4tAaZOC17rUNDqQ1Qlmw0jvvuevRBKdDMcgHH17HiNXDfZmGowBpkQ6Ca6oseOpb6ajWUg/IgzOCk5rczTGPcZ3XpXqIGV/Sxo905Fc/LXghd5vC65gx5DUVqH/rUmyXZlhDrn44gLOGNZKxIlrzcc3VR4QxYchY8O7Tes33H4nQYhPdrSlhzNXO59fqN7+8FireBu5dGVWHMGDLcGDAvGYGUINGUAIMKKdAK8eCVr7nCnzL+6FESE2wGK7aPD4w5wUoQyXDlipoLbVCIRUtSik1NyrYdxyrp6nofLBMIUjqcACqHPfUKSkpyNYboQdjxPATkqv5OCuAAU+Xa9060AAWQXMqFG+pco5T61pDe2N712uLbSg9P2KKOP0E6sMY0rzMQ66u3q3eofu4xvLiVJuPv4yR/rvt6H4OnzFVZcd+jOXArms91+SAflkTHrYHq3sju3Jd1X57t4RthDK6Js/Zp7wer2HpGqWZOZ/P528AAAD//wMAJUGrCvUEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS6Som2G36MJNvYgXbdAbxRnFRLTUgqI2DYz97wW1WyTuLTdhoJl555mX80aiOyLpyf3p9MsZY15ITXLYQoIJTZmmTB4E61vRc9mYjjHD9N+kJuEJv5GeGOU99xapRI1UAWvpIEdPAdByxoGJVpWa7vNC+jfiQgN4Dh6befkT0xLmSHryV4gwfy29XWj8NK/QpHnCXVyyi76I+WP38ER/v98fnvfXvxZcSnITgPSEeQQ+gKaj9JwqywZqbQcUFHPG2NaiMNe0EDOm6KZmgS/f+8OcI2bfi0bIhlFhyaUm4LIrkge34OHbqajYED2WeL2Ft8/+jZwxkV7U/8G8lrvzU7hb1mHCM04nlxZMd34+Hl0EUpNTmk+YcsANyxnTMC8hF6LeG9d1nEPnjGoNdLIV0oBkCB0zLViwTis9Ct+2IwydNWpUILgCjUoNZdAN3DS5HOZY3bQ/TAFjJjV5fqn2k8vjnI7v0L9cie5+AmiRTXoiWSu1MaiklM7qQTAEo73WYvCOmXEQyD2y1ipjuWFcWsGsbLXpDDhmPanJB+dfQ8Rq91i6cxQgO6Sj4JqqDhx1rbRUa6lH5N4awUlNHuaYQ1zndal2MePntM1LevKrmxa8Tnqf/GvI6POaCoRPuphxn2ZYfa6+G4CzhjWSsZK05tOaq48IIaHPWHAc0nqr978UocWW9LCmhDFXe5dfq9/c8kp6Yl3nubPOOqe9H7FlODJgTjMDqEEjKAEGlFWglWVeKzdwBa7lw1gipCZY/FZtFh8QmJNS0VEwQxVrgdrRSOql6vzoQIyGveNYPU8lz/mOKQRJLY5AleWOWiUlBdk6I/RojBh/QHIzC2cFMOD5+lg3BRqgQ9CcCsVbqqzltLOtoYPpBjvoDltfND9hijj9AOpD8Gle5jFXN69V79B9XGM5ONXmu68h0n+2HT3O/gumquzYhVje122tl5oc0S1rwuN2r/o3si+PqzpsZ0t0jVBG1+Qlu5TX0y0sbaM0M5fL5fIvAAAA//8DAKi9M8D0BAAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUXW/jKBSG/4rFtXEBY2x81013tFGn02ianV3tHYbjFNWBCHBnqyr/fYSTqumM9mLvrGPe8/GcF16RU3tAPbo+HH5/BpciKlGyS4gRJjARmNRbRvqG9bSuOlqTtqH/oBLZW3hBPWq51lRLwDUIwNyQBg/1qLExICmhhrCG55xqF1H/ipStDDxbDZWP3yBE6x3q0V/WGf8911a20pOfTRX8BGsXk3I6N/NlvbrFn6832/vN6VSEmMWVNahHRIOhgxF4rDXFXJIBS9kZbDhRbSsbCaw9yaxLEJyaqmie3usbnxwk3bOK1RXBTKJjiYxKKrc8qAjbl0PuYkF0k+PlEl4++1f0DAH1rHyDeUp3pSd7tY/DbCdzNfndzrrdyrvR7uagUi5cokPwBwjJwgLnN+tUePnsd7ucD31SUwRUoi2EvXVqOv24gVHNU3rwc1jIRPP0X2dQj9ScPCrRQnKalrLFeZjVZMGlX7QXhe8fis2k0ujD/sOSHk7s1/8D/Z3Sj9ZBsb7JGgrM1B3gkVGBeWcUVk0tsRC1GIFq2TKKSrTyLlk3+zkWa5dgd6b2ob/roB9tAp3mkGf6W2SzbYI3s07F+4IpqUhVE5JFczrMqfgKxgbQCfIQ2zCf8/0kYYItok92gl/wrOYQwKVio9Jj8YeKj6hHUnWaKqmkUkLrERoCIzFECdIaEEaA4cy0hktuBJdEC64Gyo1q6DDmCCoRZJMVi69H08qRK4EJAYY5yA4rLmoMAxsbomhXM/IBbnE/ZZ3SHeFgaixhNJhLqrDkdY1N3aiWibFt2XjB6WwISjL1SxP+GcGcvfTlZO232Q08n+7x0qcwpgMjKGacNphLSXEnmxYPbTfIQXTQ6DzZyrvo30B+gzD4aFN+RO6ss3s1/XzkfTO3EBxMF9u5szr46MdUnG1ZfNjX19nlV6xYLPrdOvzvYowbr58gFNlYyrqLZR6Px+MPAAAA//8DAHv/g4QMBQAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRU0W7cKBT9FYtn4wDG2PgtmrTaUbabUTvqrvYNc68bFAdGgNNG0fz7Cs+s2jyt9gWha87l3HMOfiPePCMZye3p9OEFfU6kJtltJcGEokxR1h4FGzsxctkMsheKq79JTdw9vpKR9NJabjXSFhVSCayjUztbCoCaMw5MdLL0NN8SGd+IcQ3gi7PYhPQVY3LBk5H86TyE7+Vu4xq7hBWaGBbc+5SNt4XMH/vdPf399nB8OFxOJUwF3DggI2EWgU+g6NxaTqVmE9V6AAqSmb7XnUbRX2DOZ4zeLE2Cp5/3Q8gesx1FI9qGUaHJuSZgsimUJ5Pw+HoqLDaJ7kq93srbdnwjLxjJKOp/xby0u7GLu3lO0+oWuLms2aSntK2zsTnEV1KTUwwnjNnhps9tSvg8La9Hk54+Xs6UbfrwA+2aEXZh9ZmMhLcdqckm0LKY7IKvrhx3i0Oft485Op+c/a9eLanJw5fqsJg8h/j8zo8vF5n3/0PlT8Y+Oo/V/q5gOApoB6Sz4IrKAQw1XaupUq2akVvdC05qsgs+O7+GNVV7n/Fb3EYiI/loloQXfrfRPrqMNq+xzPmXKrk6xACrzdVPLzlrWNMyVkBrPq25+ozgItqMZYhjXK/9vr6HCCU20G6NEX2uDiY/Vr+Z9EhGos1gudFGG6OsnbFjODNgRrEeUIFCkAJ6kFqCkppZJc3EJZiOT3OpkJpgiU61pVXOOEvVWipkK6nkglPDUFGrJ637iTOtxDsdq4el4IwdmERoqcYZqNTcUC3blkLbmV6oue/F/Isk1zxwVgQGfLm8u42BAhgQFKdC8o5KrTkddNfTqR8mPakBO1s432P0uPwi1CdnY0hhztU1IdU76T6vvvw7qi0t352nPzaP7oJ9wlgVj43z5alcbT2fz+d/AAAA//8DAHLELVyCBAAA
|
||||
@ -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+HT0fo44AWKLopxAgTmAhMii0jTckaynPGikKw8l+0QO4O3lCDKq411RJwAQIwN6TEbWE1NgYkJdQQVvLUUz0PqPmBlMsNHJ2G3A9/QRic71GD/na98a9ptnK57vxo8uA7WPdDVL1Oy3xdr+7wl+Vm+7A5ZQ0wpOLcGdQgosHQ1ghsC00xl6TFUtYGG05UVclSAqtOZa6PEHrV5YN5ucw3PvYQdcNyVuQEM4neF8ioqNLKrRpg+3ZIW0wU3ab4YgpPn80PdISAGrY4k3lqd6M7d7Mf2tF15iaq8AzRBrWHVx9e4Kg6tECH4A8QooOJm4en7LISJTnJmWCEoAW6V3rnesjWt9lDlwArXRMOpsASrMFcUoUlLwpsilJVTNiqYhYt0B2EHrqrrvdOBz94G7OZ8uzDnJXvo+tHPw7Zuo/wHFQ81X1W3QBogTZj27lh9wjKvG3945j+9WOXsKzGEKCP2UbFXfanGnaoQVLVmiqppFJCawslAUsMUYJUBoQRYDgzleGSG8El0YKrlnKjStraFEEL9DDGwxjP/H+/WmIb3H4P5rLBduJ406lofdivDfTRWZekOWdMhuq6CVU291x1Dvr4geTkKArMFDVgy6jAvDYKq7KQWIhCWKBaVoyeCLPuefzJE7HW8MKWdS1a2VICzFY1J5VtidG20NKULVSsIlUNtdSEtCWndSs1MUBLbhK64M2o469WKCaBHsc+XWe2TrBfXY+/C35hZBN89Np3STDXwdPbEGH/O9VWfn/wg4sJv52l/TbAve/T3/n+Z87m4qWPlxikO8im05M1qbRmLabGUMw1lbitoMVVVZd1KWxZ6ETUrdcvELJkMOX6SZOzpx6esmXQOxdBxzGkyf9MoE5yfj6fzIWP/Oun7coHWB4Oizn6xzGRdGp2NsCHd+XbkNJ3fohXkGdoT9DZ82JXdjJwPD1VLgWFMTUYQTHjtMRcSoprWVa4repWtqKGUl/smj2CcQF0nPptw5imzcz+zpfLEJ1VOg7peL54Pdlp9uec8uuSV7L9rD4Nv5JuttLciZKkxNPp4Zwc9L/ezff39/f/AAAA//8DAKqnhx8oBgAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTQW/cIBCF/4rF2TiAMTa+RZtWXaVtVk3UVr1hZtxFdWCFcZoo2v9e4d0qSW+9WWPe8OZ7wzPx5h5JTy4Ph3cP6NNMSpLcWhJMKMoUZfWdYH0jei6rrulq2ckfpCTuGp9IT1ppLbcaaY0KqQTW0KEeLQVAzRkHJhqZe5qfM+mfiXEV4IOzWIX5K8bZBU968s15CL/z3cZVdgoLVDFMuPVzMt5mM5+3m2v68XJ3d7M7nZpxzuLKAekJswh8AEXH2nIqNRuo1h1QkMy0rW40ivYkcz5h9GaqZvj1cj+E5DHZXlSirhgVmhxLAiaZbHkwM949HbKLFdFVrpdref3sn8kDRtKL8i/MU7sLO7kLG+7vjYeL0Xk370lJDjEcMCaHK451wmkyyQVfnC/ZTA59IiW5uS12k0ljiPdvGN2eRt/+x+SfjN07j8X2Kms4Cqg7pKPgisoODDVNralStRqRW90KTkqyCT45v4RlLrY+4c+4uiQ9eW+mGU/+LqPdu4Q2LTFb/65y1vjo0iZALjBSkl0MsNhUvODmrGJVzfLPmyUdllR8QXARbcI8011czu3/kQglVtFmiRF9KnYm7YsPZt6TnmjTWW600cYoa0dsGI4MmFGsBVSgEKSAFqSWoKRmVkkzcAmm4cOYK9l3TrdYF0qhaZtWCjrYQVNZjxnRwChvOrRdbaHVedIXrMXNlHXGdkwi1FTjCFRqbqiWdU2hbkwr1Ni2YnyF5Jw4Z5k34MPpaZwcAHQIilMheUOl1px2umnp0HaDHlSHjc2erzF6nF6B+uRsDHMYU3FemOINui+Lz8+7WJfnt/P0cY3sKthfGIscuXE+b/M55ePxePwDAAD//wMAoa2QhCUEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxU227jNhD9FYGvFRXeRIl6S50saqRJjLXRFn0bkaOYjUwZFJXsIsi/F5KTzWVRoC+CMCTnnDlzZp5IgAOShpwfj5cPGNJIcpL8EhJMaMo0ZXInWFOKhquiLquKCf43yYm/wu+kIZWylluDVKJGqhwraSs7S51Dwxl3TJRqzgl3I2meCPjC4YO3WAzjHxhHPwTSkD99cMPjjA2+sP0wuSIOPa7DmCDYmczNenVFfz/f7G43p1sjjvPjwjvSEGbR8dZp2knLqTKspcbUjjrFoKpMaVBUp2c+JIwB+mJ092/4bkgBk21EIWTBqDDkOScOEsyUWxhx9/04s1gkupjj+RJefpsn8oCRNCJ/FfOU7sz2/uwwtpPv3dnyJTk5xuGIMXlc1LjdZm8kOCtYIbRgjOTkGuzeB8zWF9ltP5cItmYKnaQGO0eV4UCNkpI6WUIldFdVoiM5ucIYsH+X9drbOIxDl7IXkbMPONvz1WWAtscZ4wv0I5KcrMdtguCgHwJefkM7pVOuXZzm49UQkg/TMI3ZOiS8i/By/vp+NcWIIWUbSPvsNxj3pCEGasvBgAHQ1nZYMuyYY6BZ5VA7jU4JVzlllNPKMKsVtFw5KHnbzRGSk19nDbeTtTiOb2wWk/T9wiF76dOq9xjSBxlnl3AUTtZIO8E1VbUDCqU0VGupO+TWVIK/wuwg3mEiDenqtm2Zta1QhlsQBqTrmHS6M0JUFahOlpUSWpesAuuqSnDlZIUWZCdZLUhONnFwk02fWy2XBmzi8A/aNEtFGqJZWdYW2lZoxWswNSquVa0NVkZ1wJS0LfCyhrblDEpmTS0scG1Ata7sfqh0Ge58wAs/Hnv4/g63LnQhf7EANe9Afbr+JcIBH4d4f3OycXFzuSM5+TqFeSNk69kijz7Qb3qe6OupT363jwgO3fXg8Gcf4Twu2WlCEZC5rqNStZyq1nbUlK2ilTOVlqhQLmwuBnuPMZsdBj7MU/Uj2e02O4927xPaNMWZ318Lj9tttukhdUM8fNgk7wr7LEAhJGdzZxw+nLbRwlE7V6PTnArFS6qM4bQ2ZUXbqm5Nq2ss7czxdkrHKWVf0fmINi0Vv3hxwVzt0d7/LMarC14sytnste1piy3S/q8l9pyTA8I4RTws27p5IusQMC7IF9NpFNfh2ve9H9EOwY2k4axkBTOleGH4n/c454ViSj0/Pz//CwAA//8DADl0x2IbBgAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTTW/cNhD9K8KcRZmkKErUzVin6MJNvEgWadEbxRnFhGVqQVFOAmP/e0DtBol764UgBnwz72P4CsE+E/Rwezq9e6GQFigh+a0kudSMa8bro+R9I3uhqk6ZlvPmXyjB39N36KFVzglniNWkiSnkDRvq0TFEMoIL5LJRuaf9skD/CtZXSC/eUTUvnykufg7Qw98+4Pw1z7a+ctO8YhXnifZhSTa4TObDfnfP/ro9HB8Ol1cLLRlceYQeuCMUA2o21k4wZfjAjOmQoeK2bU1jSLYXmA+JYrBTteDTr/k4p0DJ9bKSdcWZNHAuAW2ymfJgFzp+P2UWm0V3uV5u5e3av8ILRehl+dPMS7sbN/mb52VY/YQ3lzPZ5SmrPMX5RDF52jzZZE6TTX4OxXXSbvIUEpTw8Kk4TDaNc3x+Y9Sni/79/5B/zNPffSO3JsLdvIYEPYi6gxLeW/foAxX7u9xOkMS6IzZKoZnq0DLb1IZpXeuRhDOtFFDCbg7Jh3Vel2IfEn2JmwDo4Q87LXShfhvdo0/k0hqzqn903oVDnHF1qfjlv+AVr2rOM2hNpzUVHwl9JJco6zvG9drvPxCp5QbarTFSSMXBpsfiT7s8Qg/Gdk5YY4212rmRGk4jR241b5E0akIlsUVlFGpluNPKDkKhbcQw5gqUQDnuYtswzQV2TS2YJeyYItMwI7uRdQpHQW1jsHFvfCwepoyzruOKsGaGRmTKCMuMqmuGdWNbqce2leNvllzTFzwbjPRy+SsXBogdoRZMKtEwZYxgnWlaNrTdYAbdUeMy53uKgabfjHrvXZyXeUzFdXmKN9Z9XEP+78W2SF99YN+2jO5m90SxyBlbH/J6X2M9n8/nHwAAAP//AwCAbbUbNgQAAA==
|
||||
Binary file not shown.
@ -52,6 +52,15 @@ public sealed class CharactersController(ICharacterService characters) : Control
|
||||
return RedirectToAction(nameof(Index), new { projectId });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> SaveInitialRelationship(InitialRelationshipEditViewModel model)
|
||||
{
|
||||
await characters.SaveInitialRelationshipAsync(model);
|
||||
TempData["RelationshipMessage"] = model.CharacterRelationshipID == 0 ? "Initial relationship added." : "Initial relationship updated.";
|
||||
return RedirectToAction(nameof(Details), new { id = model.ReturnCharacterID ?? model.CharacterID });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ArchiveRelationship(int id, int characterId)
|
||||
|
||||
@ -143,6 +143,7 @@ public interface ICharacterRepository
|
||||
Task DeleteKnowledgeAsync(int characterKnowledgeId);
|
||||
Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByProjectAsync(int projectId);
|
||||
Task<IReadOnlyList<CharacterRelationship>> ListRelationshipsByCharacterAsync(int characterId);
|
||||
Task<IReadOnlyList<CharacterRelationship>> ListInitialRelationshipsByCharacterAsync(int characterId);
|
||||
Task<int> SaveRelationshipAsync(CharacterRelationship relationship);
|
||||
Task ArchiveRelationshipAsync(int characterRelationshipId);
|
||||
Task<IReadOnlyList<RelationshipEvent>> ListRelationshipEventsBySceneAsync(int sceneId);
|
||||
@ -2132,11 +2133,24 @@ public sealed class CharacterRepository(ISqlConnectionFactory connectionFactory)
|
||||
relationship.StartSceneID,
|
||||
relationship.EndSceneID,
|
||||
relationship.IsKnownToReader,
|
||||
relationship.Notes
|
||||
relationship.Notes,
|
||||
relationship.IsInitialRelationship,
|
||||
relationship.InitialBookID,
|
||||
relationship.ReaderInitiallyKnows,
|
||||
relationship.InitialRelationshipStateID,
|
||||
relationship.InitialIntensity,
|
||||
relationship.IsReciprocal
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<CharacterRelationship>> ListInitialRelationshipsByCharacterAsync(int characterId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<CharacterRelationship>("dbo.Relationship_ListInitialByCharacter", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
}
|
||||
|
||||
public async Task ArchiveRelationshipAsync(int characterRelationshipId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
|
||||
@ -651,6 +651,14 @@ public sealed class CharacterRelationship
|
||||
public int? EndSceneID { get; set; }
|
||||
public bool IsKnownToReader { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public bool IsInitialRelationship { get; set; }
|
||||
public int? InitialBookID { get; set; }
|
||||
public string? InitialBookTitle { get; set; }
|
||||
public bool ReaderInitiallyKnows { get; set; }
|
||||
public int? InitialRelationshipStateID { get; set; }
|
||||
public string? InitialRelationshipStateName { get; set; }
|
||||
public int? InitialIntensity { get; set; }
|
||||
public bool IsReciprocal { get; set; }
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public DateTime UpdatedDate { get; set; }
|
||||
public bool IsArchived { get; set; }
|
||||
@ -666,6 +674,7 @@ public sealed class RelationshipEvent
|
||||
public int CharacterBID { get; set; }
|
||||
public string CharacterBName { get; set; } = string.Empty;
|
||||
public string RelationshipTypeName { get; set; } = string.Empty;
|
||||
public bool IsReciprocal { get; set; }
|
||||
public int SceneID { get; set; }
|
||||
public decimal SceneNumber { get; set; }
|
||||
public string SceneTitle { get; set; } = string.Empty;
|
||||
@ -1121,7 +1130,11 @@ public sealed class StoryBibleRelationshipSummary
|
||||
public int CharacterBID { get; set; }
|
||||
public string CharacterBName { get; set; } = string.Empty;
|
||||
public string RelationshipTypeName { get; set; } = string.Empty;
|
||||
public string? InitialRelationshipName { get; set; }
|
||||
public string? InitialStateName { get; set; }
|
||||
public int? InitialIntensity { get; set; }
|
||||
public string? CurrentStateName { get; set; }
|
||||
public int? CurrentIntensity { get; set; }
|
||||
public int EventCount { get; set; }
|
||||
public string? FirstEventLabel { get; set; }
|
||||
public string? EventHistory { get; set; }
|
||||
|
||||
@ -124,6 +124,7 @@ public interface ICharacterService
|
||||
Task<int> SaveCharacterAsync(CharacterEditViewModel model);
|
||||
Task ArchiveCharacterAsync(int characterId);
|
||||
Task ArchiveRelationshipAsync(int characterRelationshipId);
|
||||
Task<int> SaveInitialRelationshipAsync(InitialRelationshipEditViewModel model);
|
||||
Task<int> AddSceneCharacterAsync(SceneCharacterCreateViewModel model);
|
||||
Task<int> UpdateSceneCharacterAsync(SceneCharacterEditViewModel model);
|
||||
Task DeleteSceneCharacterAsync(int sceneCharacterId);
|
||||
@ -2438,7 +2439,15 @@ public sealed class ExportService(IExportRepository exports) : IExportService
|
||||
{
|
||||
if (!rows.Any()) return;
|
||||
builder.AppendLine("## Relationships").AppendLine();
|
||||
foreach (var r in rows) builder.AppendLine($"- {r.CharacterAName} / {r.CharacterBName}: {r.RelationshipTypeName}; {r.CurrentStateName ?? "No state"}");
|
||||
foreach (var r in rows)
|
||||
{
|
||||
var initial = r.InitialRelationshipName ?? r.RelationshipTypeName;
|
||||
if (!string.IsNullOrWhiteSpace(r.InitialStateName)) initial += $" / {r.InitialStateName}";
|
||||
if (r.InitialIntensity.HasValue) initial += $" (intensity {r.InitialIntensity})";
|
||||
var current = r.CurrentStateName ?? "No current state";
|
||||
if (r.CurrentIntensity.HasValue) current += $" (intensity {r.CurrentIntensity})";
|
||||
builder.AppendLine($"- {r.CharacterAName} / {r.CharacterBName}: Initial: {initial}; Current: {current}; Events: {r.EventHistory ?? "No event history"}");
|
||||
}
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
@ -3112,7 +3121,7 @@ public sealed class LocationService(IProjectRepository projects, ILocationReposi
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CharacterService(IProjectRepository projects, ICharacterRepository characters) : ICharacterService
|
||||
public sealed class CharacterService(IProjectRepository projects, IBookRepository books, ICharacterRepository characters) : ICharacterService
|
||||
{
|
||||
public async Task<CharacterListViewModel?> GetCharactersAsync(int projectId)
|
||||
{
|
||||
@ -3170,6 +3179,17 @@ public sealed class CharacterService(IProjectRepository projects, ICharacterRepo
|
||||
return null;
|
||||
}
|
||||
|
||||
var projectCharacters = await characters.ListCharactersAsync(project.ProjectID);
|
||||
var relationshipLookups = await characters.GetLookupsAsync();
|
||||
var projectBooks = await books.ListByProjectAsync(project.ProjectID);
|
||||
|
||||
var relationshipContainers = await characters.ListRelationshipsByCharacterAsync(character.CharacterID);
|
||||
var initialRelationships = await characters.ListInitialRelationshipsByCharacterAsync(character.CharacterID);
|
||||
var relationshipEvents = await characters.ListRelationshipEventsByCharacterAsync(character.CharacterID);
|
||||
var eventsByRelationship = relationshipEvents
|
||||
.GroupBy(x => x.CharacterRelationshipID)
|
||||
.ToDictionary(x => x.Key, x => (IReadOnlyList<RelationshipEvent>)x.ToList());
|
||||
|
||||
return new CharacterDetailViewModel
|
||||
{
|
||||
Project = project,
|
||||
@ -3178,8 +3198,37 @@ public sealed class CharacterService(IProjectRepository projects, ICharacterRepo
|
||||
Appearances = await GetAppearancesByCharacterAsync(character.CharacterID),
|
||||
AttributeEvents = await characters.ListAttributeEventsByCharacterAsync(character.CharacterID),
|
||||
KnowledgeItems = await characters.ListKnowledgeByCharacterAsync(character.CharacterID),
|
||||
Relationships = await characters.ListRelationshipsByCharacterAsync(character.CharacterID),
|
||||
RelationshipEvents = await characters.ListRelationshipEventsByCharacterAsync(character.CharacterID)
|
||||
InitialRelationships = initialRelationships,
|
||||
CurrentRelationships = relationshipContainers
|
||||
.Select(relationship =>
|
||||
{
|
||||
eventsByRelationship.TryGetValue(relationship.CharacterRelationshipID, out var events);
|
||||
events ??= [];
|
||||
return new CurrentRelationshipViewModel
|
||||
{
|
||||
Relationship = relationship,
|
||||
LatestEvent = events.LastOrDefault(),
|
||||
Events = events
|
||||
};
|
||||
})
|
||||
.OrderBy(x => x.Relationship.CharacterAName)
|
||||
.ThenBy(x => x.Relationship.CharacterBName)
|
||||
.ThenBy(x => x.Relationship.RelationshipTypeName)
|
||||
.ToList(),
|
||||
RelationshipEvents = relationshipEvents,
|
||||
NewInitialRelationship = new InitialRelationshipEditViewModel
|
||||
{
|
||||
ProjectID = project.ProjectID,
|
||||
CharacterID = character.CharacterID,
|
||||
ReturnCharacterID = character.CharacterID,
|
||||
InitialIntensity = 5,
|
||||
ReaderInitiallyKnows = true,
|
||||
IsReciprocal = true
|
||||
},
|
||||
CharacterOptions = projectCharacters.Select(x => new SelectListItem(x.CharacterName, x.CharacterID.ToString())).ToList(),
|
||||
RelationshipTypeOptions = ChapterService.ToSelectList(relationshipLookups.RelationshipTypes, x => x.RelationshipTypeID, x => x.TypeName),
|
||||
RelationshipStateOptions = relationshipLookups.RelationshipStates.Select(x => new SelectListItem(x.StateName, x.RelationshipStateID.ToString())).ToList(),
|
||||
BookOptions = projectBooks.Select(x => new SelectListItem($"Book {x.BookNumber}: {x.BookTitle}", x.BookID.ToString())).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
@ -3203,6 +3252,37 @@ public sealed class CharacterService(IProjectRepository projects, ICharacterRepo
|
||||
|
||||
public Task ArchiveRelationshipAsync(int characterRelationshipId) => characters.ArchiveRelationshipAsync(characterRelationshipId);
|
||||
|
||||
public async Task<int> SaveInitialRelationshipAsync(InitialRelationshipEditViewModel model)
|
||||
{
|
||||
if (model.RelatedCharacterID == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Choose a related character.");
|
||||
}
|
||||
|
||||
if (model.RelationshipTypeID == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Choose a relationship type.");
|
||||
}
|
||||
|
||||
return await characters.SaveRelationshipAsync(new CharacterRelationship
|
||||
{
|
||||
CharacterRelationshipID = model.CharacterRelationshipID,
|
||||
ProjectID = model.ProjectID,
|
||||
CharacterAID = model.CharacterID,
|
||||
CharacterBID = model.RelatedCharacterID,
|
||||
RelationshipTypeID = model.RelationshipTypeID,
|
||||
IsPermanent = true,
|
||||
IsKnownToReader = model.ReaderInitiallyKnows,
|
||||
Notes = model.Notes,
|
||||
IsInitialRelationship = true,
|
||||
InitialBookID = model.InitialBookID,
|
||||
ReaderInitiallyKnows = model.ReaderInitiallyKnows,
|
||||
InitialRelationshipStateID = model.InitialRelationshipStateID,
|
||||
InitialIntensity = model.InitialIntensity,
|
||||
IsReciprocal = model.IsReciprocal
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<int> AddSceneCharacterAsync(SceneCharacterCreateViewModel model)
|
||||
{
|
||||
var characterId = model.CharacterID.GetValueOrDefault();
|
||||
|
||||
248
PlotLine/Sql/030_Phase2A_RelationshipFoundations.sql
Normal file
248
PlotLine/Sql/030_Phase2A_RelationshipFoundations.sql
Normal file
@ -0,0 +1,248 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'IsInitialRelationship') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD IsInitialRelationship bit NOT NULL CONSTRAINT DF_CharacterRelationships_IsInitialRelationship DEFAULT 0;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'InitialBookID') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD InitialBookID int NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'InitialBookID') IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_CharacterRelationships_InitialBook')
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD CONSTRAINT FK_CharacterRelationships_InitialBook FOREIGN KEY (InitialBookID) REFERENCES dbo.Books(BookID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'ReaderInitiallyKnows') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD ReaderInitiallyKnows bit NOT NULL CONSTRAINT DF_CharacterRelationships_ReaderInitiallyKnows DEFAULT 0;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'InitialRelationshipStateID') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD InitialRelationshipStateID int NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'InitialRelationshipStateID') IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_CharacterRelationships_InitialState')
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD CONSTRAINT FK_CharacterRelationships_InitialState FOREIGN KEY (InitialRelationshipStateID) REFERENCES dbo.RelationshipStates(RelationshipStateID);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'InitialIntensity') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD InitialIntensity int NULL;
|
||||
END;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'InitialIntensity') IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM sys.check_constraints WHERE name = N'CK_CharacterRelationships_InitialIntensity')
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD CONSTRAINT CK_CharacterRelationships_InitialIntensity CHECK (InitialIntensity IS NULL OR InitialIntensity BETWEEN 1 AND 10);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF COL_LENGTH(N'dbo.CharacterRelationships', N'IsReciprocal') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.CharacterRelationships
|
||||
ADD IsReciprocal bit NOT NULL CONSTRAINT DF_CharacterRelationships_IsReciprocal DEFAULT 0;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Relationship_ListInitialByCharacter
|
||||
@CharacterID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT cr.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, ca.CharacterName AS CharacterAName,
|
||||
cr.CharacterBID, cb.CharacterName AS CharacterBName, cr.RelationshipTypeID, rt.TypeName AS RelationshipTypeName,
|
||||
cr.IsPermanent, cr.StartSceneID, cr.EndSceneID, cr.IsKnownToReader, cr.Notes,
|
||||
cr.IsInitialRelationship, cr.InitialBookID, b.BookTitle AS InitialBookTitle, cr.ReaderInitiallyKnows,
|
||||
cr.InitialRelationshipStateID, rs.StateName AS InitialRelationshipStateName, cr.InitialIntensity, cr.IsReciprocal,
|
||||
cr.CreatedDate, cr.UpdatedDate, cr.IsArchived
|
||||
FROM dbo.CharacterRelationships cr
|
||||
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
|
||||
LEFT JOIN dbo.Books b ON b.BookID = cr.InitialBookID
|
||||
LEFT JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = cr.InitialRelationshipStateID
|
||||
WHERE cr.IsArchived = 0
|
||||
AND cr.IsInitialRelationship = 1
|
||||
AND @CharacterID IN (cr.CharacterAID, cr.CharacterBID)
|
||||
ORDER BY COALESCE(b.SortOrder, 0), ca.CharacterName, cb.CharacterName, rt.SortOrder;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Relationship_ListByCharacter
|
||||
@CharacterID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT cr.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, ca.CharacterName AS CharacterAName,
|
||||
cr.CharacterBID, cb.CharacterName AS CharacterBName, cr.RelationshipTypeID, rt.TypeName AS RelationshipTypeName,
|
||||
cr.IsPermanent, cr.StartSceneID, cr.EndSceneID, cr.IsKnownToReader, cr.Notes,
|
||||
cr.IsInitialRelationship, cr.InitialBookID, b.BookTitle AS InitialBookTitle, cr.ReaderInitiallyKnows,
|
||||
cr.InitialRelationshipStateID, rs.StateName AS InitialRelationshipStateName, cr.InitialIntensity, cr.IsReciprocal,
|
||||
cr.CreatedDate, cr.UpdatedDate, cr.IsArchived
|
||||
FROM dbo.CharacterRelationships cr
|
||||
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
|
||||
LEFT JOIN dbo.Books b ON b.BookID = cr.InitialBookID
|
||||
LEFT JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = cr.InitialRelationshipStateID
|
||||
WHERE cr.IsArchived = 0 AND @CharacterID IN (cr.CharacterAID, cr.CharacterBID)
|
||||
ORDER BY ca.CharacterName, cb.CharacterName, rt.SortOrder;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Relationship_ListByProject
|
||||
@ProjectID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT cr.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, ca.CharacterName AS CharacterAName,
|
||||
cr.CharacterBID, cb.CharacterName AS CharacterBName, cr.RelationshipTypeID, rt.TypeName AS RelationshipTypeName,
|
||||
cr.IsPermanent, cr.StartSceneID, cr.EndSceneID, cr.IsKnownToReader, cr.Notes,
|
||||
cr.IsInitialRelationship, cr.InitialBookID, b.BookTitle AS InitialBookTitle, cr.ReaderInitiallyKnows,
|
||||
cr.InitialRelationshipStateID, rs.StateName AS InitialRelationshipStateName, cr.InitialIntensity, cr.IsReciprocal,
|
||||
cr.CreatedDate, cr.UpdatedDate, cr.IsArchived
|
||||
FROM dbo.CharacterRelationships cr
|
||||
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
|
||||
LEFT JOIN dbo.Books b ON b.BookID = cr.InitialBookID
|
||||
LEFT JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = cr.InitialRelationshipStateID
|
||||
WHERE cr.ProjectID = @ProjectID AND cr.IsArchived = 0
|
||||
ORDER BY ca.CharacterName, cb.CharacterName, rt.SortOrder;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Relationship_Save
|
||||
@CharacterRelationshipID int = NULL,
|
||||
@ProjectID int,
|
||||
@CharacterAID int,
|
||||
@CharacterBID int,
|
||||
@RelationshipTypeID int,
|
||||
@IsPermanent bit,
|
||||
@StartSceneID int = NULL,
|
||||
@EndSceneID int = NULL,
|
||||
@IsKnownToReader bit,
|
||||
@Notes nvarchar(max) = NULL,
|
||||
@IsInitialRelationship bit = 0,
|
||||
@InitialBookID int = NULL,
|
||||
@ReaderInitiallyKnows bit = 0,
|
||||
@InitialRelationshipStateID int = NULL,
|
||||
@InitialIntensity int = NULL,
|
||||
@IsReciprocal bit = 0
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
IF @InitialIntensity IS NOT NULL AND (@InitialIntensity < 1 OR @InitialIntensity > 10)
|
||||
THROW 53000, 'Initial relationship intensity must be between 1 and 10.', 1;
|
||||
|
||||
IF @CharacterRelationshipID IS NULL OR @CharacterRelationshipID = 0
|
||||
BEGIN
|
||||
INSERT dbo.CharacterRelationships
|
||||
(ProjectID, CharacterAID, CharacterBID, RelationshipTypeID, IsPermanent, StartSceneID, EndSceneID, IsKnownToReader, Notes,
|
||||
IsInitialRelationship, InitialBookID, ReaderInitiallyKnows, InitialRelationshipStateID, InitialIntensity, IsReciprocal)
|
||||
VALUES
|
||||
(@ProjectID, @CharacterAID, @CharacterBID, @RelationshipTypeID, @IsPermanent, @StartSceneID, @EndSceneID, @IsKnownToReader, @Notes,
|
||||
@IsInitialRelationship, @InitialBookID, @ReaderInitiallyKnows, @InitialRelationshipStateID, @InitialIntensity, @IsReciprocal);
|
||||
|
||||
SET @CharacterRelationshipID = CAST(SCOPE_IDENTITY() AS int);
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
UPDATE dbo.CharacterRelationships
|
||||
SET CharacterAID = @CharacterAID,
|
||||
CharacterBID = @CharacterBID,
|
||||
RelationshipTypeID = @RelationshipTypeID,
|
||||
IsPermanent = @IsPermanent,
|
||||
StartSceneID = @StartSceneID,
|
||||
EndSceneID = @EndSceneID,
|
||||
IsKnownToReader = @IsKnownToReader,
|
||||
Notes = @Notes,
|
||||
IsInitialRelationship = @IsInitialRelationship,
|
||||
InitialBookID = @InitialBookID,
|
||||
ReaderInitiallyKnows = @ReaderInitiallyKnows,
|
||||
InitialRelationshipStateID = @InitialRelationshipStateID,
|
||||
InitialIntensity = @InitialIntensity,
|
||||
IsReciprocal = @IsReciprocal,
|
||||
UpdatedDate = SYSUTCDATETIME()
|
||||
WHERE CharacterRelationshipID = @CharacterRelationshipID
|
||||
AND ProjectID = @ProjectID;
|
||||
END
|
||||
|
||||
SELECT @CharacterRelationshipID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.RelationshipEvent_ListByCharacter
|
||||
@CharacterID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT re.RelationshipEventID, re.CharacterRelationshipID, cr.ProjectID, cr.CharacterAID, ca.CharacterName AS CharacterAName,
|
||||
cr.CharacterBID, cb.CharacterName AS CharacterBName, rt.TypeName AS RelationshipTypeName,
|
||||
cr.IsReciprocal, re.SceneID, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle,
|
||||
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
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID
|
||||
INNER JOIN dbo.Chapters ch ON ch.ChapterID = s.ChapterID
|
||||
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
|
||||
WHERE @CharacterID IN (cr.CharacterAID, cr.CharacterBID)
|
||||
ORDER BY b.SortOrder, ch.SortOrder, s.SortOrder, re.CreatedDate, re.RelationshipEventID;
|
||||
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, ca.CharacterName AS CharacterAName,
|
||||
cr.CharacterBID, cb.CharacterName AS CharacterBName, rt.TypeName AS RelationshipTypeName,
|
||||
cr.IsReciprocal, 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, re.CreatedDate, re.RelationshipEventID;
|
||||
END;
|
||||
GO
|
||||
69
PlotLine/Sql/031_Phase2B_RelationshipTimeline.sql
Normal file
69
PlotLine/Sql/031_Phase2B_RelationshipTimeline.sql
Normal file
@ -0,0 +1,69 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.StoryBible_RelationshipSummaries
|
||||
@ProjectID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT cr.CharacterRelationshipID,
|
||||
cr.CharacterAID,
|
||||
ca.CharacterName AS CharacterAName,
|
||||
cr.CharacterBID,
|
||||
cb.CharacterName AS CharacterBName,
|
||||
rt.TypeName AS RelationshipTypeName,
|
||||
CASE WHEN cr.IsInitialRelationship = 1 THEN rt.TypeName ELSE NULL END AS InitialRelationshipName,
|
||||
initialState.StateName AS InitialStateName,
|
||||
cr.InitialIntensity,
|
||||
COALESCE(currentEvent.StateName, initialState.StateName, rt.TypeName) AS CurrentStateName,
|
||||
COALESCE(currentEvent.Intensity, cr.InitialIntensity) AS CurrentIntensity,
|
||||
ISNULL(events.EventCount, 0) AS EventCount,
|
||||
firstEvent.SceneLabel AS FirstEventLabel,
|
||||
events.EventHistory
|
||||
FROM dbo.CharacterRelationships cr
|
||||
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
|
||||
LEFT JOIN dbo.RelationshipStates initialState ON initialState.RelationshipStateID = cr.InitialRelationshipStateID
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT TOP (1) rs.StateName, re.Intensity
|
||||
FROM dbo.RelationshipEvents re
|
||||
INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID AND s.IsArchived = 0
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
|
||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
|
||||
WHERE re.CharacterRelationshipID = cr.CharacterRelationshipID
|
||||
ORDER BY b.SortOrder DESC, c.SortOrder DESC, s.SortOrder DESC, re.CreatedDate DESC, re.RelationshipEventID DESC
|
||||
) currentEvent
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT COUNT(*) AS EventCount,
|
||||
STRING_AGG(
|
||||
CONVERT(nvarchar(max), CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', rs.StateName, ISNULL(N' (' + CONVERT(nvarchar(20), re.Intensity) + N')', N''), ISNULL(N' - ' + re.Description, N''))),
|
||||
N' -> '
|
||||
) WITHIN GROUP (ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, re.CreatedDate, re.RelationshipEventID) AS EventHistory
|
||||
FROM dbo.RelationshipEvents re
|
||||
INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID AND s.IsArchived = 0
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
|
||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
|
||||
WHERE re.CharacterRelationshipID = cr.CharacterRelationshipID
|
||||
) events
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT TOP (1) CONCAT(b.BookTitle, N' / Ch ', c.ChapterNumber, N' / Scene ', s.SceneNumber, N': ', s.SceneTitle) AS SceneLabel
|
||||
FROM dbo.RelationshipEvents re
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = re.SceneID AND s.IsArchived = 0
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID AND c.IsArchived = 0
|
||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID AND b.IsArchived = 0
|
||||
WHERE re.CharacterRelationshipID = cr.CharacterRelationshipID
|
||||
ORDER BY b.SortOrder, c.SortOrder, s.SortOrder, re.CreatedDate, re.RelationshipEventID
|
||||
) firstEvent
|
||||
WHERE cr.ProjectID = @ProjectID AND cr.IsArchived = 0
|
||||
ORDER BY ca.CharacterName, cb.CharacterName;
|
||||
END;
|
||||
GO
|
||||
@ -938,8 +938,42 @@ public sealed class CharacterDetailViewModel
|
||||
public IReadOnlyList<SceneCharacter> Appearances { get; set; } = [];
|
||||
public IReadOnlyList<CharacterAttributeEvent> AttributeEvents { get; set; } = [];
|
||||
public IReadOnlyList<CharacterKnowledgeItem> KnowledgeItems { get; set; } = [];
|
||||
public IReadOnlyList<CharacterRelationship> Relationships { get; set; } = [];
|
||||
public IReadOnlyList<CharacterRelationship> InitialRelationships { get; set; } = [];
|
||||
public IReadOnlyList<CurrentRelationshipViewModel> CurrentRelationships { get; set; } = [];
|
||||
public IReadOnlyList<RelationshipEvent> RelationshipEvents { get; set; } = [];
|
||||
public InitialRelationshipEditViewModel NewInitialRelationship { get; set; } = new();
|
||||
public IReadOnlyList<SelectListItem> CharacterOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> RelationshipTypeOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> RelationshipStateOptions { get; set; } = [];
|
||||
public IReadOnlyList<SelectListItem> BookOptions { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class CurrentRelationshipViewModel
|
||||
{
|
||||
public CharacterRelationship Relationship { get; set; } = new();
|
||||
public RelationshipEvent? LatestEvent { get; set; }
|
||||
public IReadOnlyList<RelationshipEvent> Events { get; set; } = [];
|
||||
public string CurrentStateName => LatestEvent?.RelationshipStateName
|
||||
?? Relationship.InitialRelationshipStateName
|
||||
?? Relationship.RelationshipTypeName;
|
||||
public int? CurrentIntensity => LatestEvent?.Intensity ?? Relationship.InitialIntensity;
|
||||
public string? CurrentNotes => LatestEvent?.Description ?? Relationship.Notes;
|
||||
}
|
||||
|
||||
public sealed class InitialRelationshipEditViewModel
|
||||
{
|
||||
public int CharacterRelationshipID { get; set; }
|
||||
public int ProjectID { get; set; }
|
||||
public int CharacterID { get; set; }
|
||||
public int? ReturnCharacterID { get; set; }
|
||||
public int RelatedCharacterID { get; set; }
|
||||
public int RelationshipTypeID { get; set; }
|
||||
public int? InitialBookID { get; set; }
|
||||
public int? InitialRelationshipStateID { get; set; }
|
||||
public int? InitialIntensity { get; set; }
|
||||
public bool IsReciprocal { get; set; }
|
||||
public bool ReaderInitiallyKnows { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SceneCharacterCreateViewModel
|
||||
|
||||
@ -53,6 +53,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (TempData["RelationshipMessage"] is string relationshipMessage)
|
||||
{
|
||||
<div class="alert alert-info">@relationshipMessage</div>
|
||||
}
|
||||
|
||||
<section class="asset-detail-grid">
|
||||
<article class="plain-card">
|
||||
<h2>Profile</h2>
|
||||
@ -64,32 +69,265 @@
|
||||
<p><strong>Story importance:</strong> @storyImportance</p>
|
||||
</article>
|
||||
<article class="plain-card">
|
||||
<h2>Relationships</h2>
|
||||
@if (!Model.Relationships.Any())
|
||||
<h2>Initial Relationships</h2>
|
||||
@if (!Model.InitialRelationships.Any())
|
||||
{
|
||||
<p class="muted">No relationships yet.</p>
|
||||
<p class="muted">No initial relationships yet.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="asset-mini-list">
|
||||
@foreach (var relationship in Model.Relationships)
|
||||
@foreach (var relationship in Model.InitialRelationships)
|
||||
{
|
||||
var initialSourceId = relationship.CharacterAID;
|
||||
var initialRelatedId = relationship.CharacterBID;
|
||||
<div class="asset-mini-item">
|
||||
<strong>@relationship.CharacterAName -> @relationship.CharacterBName</strong>
|
||||
<span>@relationship.RelationshipTypeName</span>
|
||||
<span>Reciprocal: @(relationship.IsReciprocal ? "Yes" : "No")</span>
|
||||
<span>Reader initially knows: @(relationship.ReaderInitiallyKnows ? "Yes" : "No")</span>
|
||||
@if (!string.IsNullOrWhiteSpace(relationship.InitialRelationshipStateName) || relationship.InitialIntensity.HasValue)
|
||||
{
|
||||
<span>@relationship.InitialRelationshipStateName@if (relationship.InitialIntensity.HasValue) { <text> / Intensity @relationship.InitialIntensity</text> }</span>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(relationship.InitialBookTitle))
|
||||
{
|
||||
<span>Initial book: @relationship.InitialBookTitle</span>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(relationship.Notes))
|
||||
{
|
||||
<span>@relationship.Notes</span>
|
||||
}
|
||||
<a asp-action="Details" asp-route-id="@(relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBID : relationship.CharacterAID)">Open related character</a>
|
||||
<a asp-controller="Dynamics" asp-action="Relationship" asp-route-id="@relationship.CharacterRelationshipID">Evolution</a>
|
||||
<details>
|
||||
<summary>Edit</summary>
|
||||
<form asp-action="SaveInitialRelationship" method="post" class="row g-2 mt-2">
|
||||
<input type="hidden" name="CharacterRelationshipID" value="@relationship.CharacterRelationshipID" />
|
||||
<input type="hidden" name="ProjectID" value="@Model.Project.ProjectID" />
|
||||
<input type="hidden" name="CharacterID" value="@initialSourceId" />
|
||||
<input type="hidden" name="ReturnCharacterID" value="@Model.Character.CharacterID" />
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Related character</label>
|
||||
<select class="form-select form-select-sm" name="RelatedCharacterID" required>
|
||||
@foreach (var option in Model.CharacterOptions.Where(x => x.Value != initialSourceId.ToString()))
|
||||
{
|
||||
<option value="@option.Value" selected="@(option.Value == initialRelatedId.ToString())">@option.Text</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Relationship type</label>
|
||||
<select class="form-select form-select-sm" name="RelationshipTypeID">
|
||||
@foreach (var option in Model.RelationshipTypeOptions)
|
||||
{
|
||||
<option value="@option.Value" selected="@(option.Value == relationship.RelationshipTypeID.ToString())">@option.Text</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Awareness</label>
|
||||
<select class="form-select form-select-sm" name="InitialRelationshipStateID">
|
||||
<option value="">No awareness state</option>
|
||||
@foreach (var option in Model.RelationshipStateOptions)
|
||||
{
|
||||
<option value="@option.Value" selected="@(option.Value == relationship.InitialRelationshipStateID?.ToString())">@option.Text</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Initial book</label>
|
||||
<select class="form-select form-select-sm" name="InitialBookID">
|
||||
<option value="">Project start</option>
|
||||
@foreach (var option in Model.BookOptions)
|
||||
{
|
||||
<option value="@option.Value" selected="@(option.Value == relationship.InitialBookID?.ToString())">@option.Text</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="form-label">Confidence / intensity <output>@(relationship.InitialIntensity ?? 5)</output></label>
|
||||
<input class="form-range" name="InitialIntensity" type="range" min="1" max="10" step="1" value="@(relationship.InitialIntensity ?? 5)" oninput="this.previousElementSibling.querySelector('output').value = this.value" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-check mt-4">
|
||||
<input type="hidden" name="IsReciprocal" value="false" />
|
||||
<input class="form-check-input" name="IsReciprocal" type="checkbox" value="true" checked="@relationship.IsReciprocal" />
|
||||
<span class="form-check-label">Reciprocal</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="hidden" name="ReaderInitiallyKnows" value="false" />
|
||||
<input class="form-check-input" name="ReaderInitiallyKnows" type="checkbox" value="true" checked="@relationship.ReaderInitiallyKnows" />
|
||||
<span class="form-check-label">Reader initially knows</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Notes</label>
|
||||
<textarea class="form-control form-control-sm" name="Notes" rows="2">@relationship.Notes</textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button class="btn btn-outline-primary btn-sm" type="submit">Save initial relationship</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
<form asp-action="ArchiveRelationship" method="post" class="archive-button-form" data-confirm-message="Archive this relationship? 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>
|
||||
<button class="btn btn-outline-danger btn-sm" type="submit">Archive / Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<details class="mt-3">
|
||||
<summary>Add initial relationship</summary>
|
||||
<form asp-action="SaveInitialRelationship" method="post" class="row g-2 mt-2">
|
||||
<input type="hidden" name="ProjectID" value="@Model.Project.ProjectID" />
|
||||
<input type="hidden" name="CharacterID" value="@Model.Character.CharacterID" />
|
||||
<input type="hidden" name="ReturnCharacterID" value="@Model.Character.CharacterID" />
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Related character</label>
|
||||
<select class="form-select form-select-sm" name="RelatedCharacterID" required>
|
||||
<option value="">Choose character</option>
|
||||
@foreach (var option in Model.CharacterOptions.Where(x => x.Value != Model.Character.CharacterID.ToString()))
|
||||
{
|
||||
<option value="@option.Value">@option.Text</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Relationship type</label>
|
||||
<select class="form-select form-select-sm" name="RelationshipTypeID" asp-items="Model.RelationshipTypeOptions"></select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Awareness</label>
|
||||
<select class="form-select form-select-sm" name="InitialRelationshipStateID" asp-items="Model.RelationshipStateOptions">
|
||||
<option value="">No awareness state</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Initial book</label>
|
||||
<select class="form-select form-select-sm" name="InitialBookID" asp-items="Model.BookOptions">
|
||||
<option value="">Project start</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="form-label">Confidence / intensity <output>5</output></label>
|
||||
<input class="form-range" name="InitialIntensity" type="range" min="1" max="10" step="1" value="5" oninput="this.previousElementSibling.querySelector('output').value = this.value" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-check mt-4">
|
||||
<input type="hidden" name="IsReciprocal" value="false" />
|
||||
<input class="form-check-input" name="IsReciprocal" type="checkbox" value="true" checked />
|
||||
<span class="form-check-label">Reciprocal</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="hidden" name="ReaderInitiallyKnows" value="false" />
|
||||
<input class="form-check-input" name="ReaderInitiallyKnows" type="checkbox" value="true" checked />
|
||||
<span class="form-check-label">Reader initially knows</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Notes</label>
|
||||
<textarea class="form-control form-control-sm" name="Notes" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button class="btn btn-outline-primary btn-sm" type="submit">Add initial relationship</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="list-section mt-3">
|
||||
<h2>Current Relationships</h2>
|
||||
@if (!Model.CurrentRelationships.Any())
|
||||
{
|
||||
<p class="muted">No current relationship state yet.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="asset-mini-list">
|
||||
@foreach (var current in Model.CurrentRelationships)
|
||||
{
|
||||
var relationship = current.Relationship;
|
||||
var relatedCharacterId = relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBID : relationship.CharacterAID;
|
||||
var relatedCharacterName = relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBName : relationship.CharacterAName;
|
||||
<div class="asset-mini-item">
|
||||
<strong>@relatedCharacterName</strong>
|
||||
<span>@relationship.RelationshipTypeName · Current: @current.CurrentStateName</span>
|
||||
<span>Reciprocal: @(relationship.IsReciprocal ? "Yes" : "No") · Confidence / intensity: @(current.CurrentIntensity?.ToString() ?? "Unknown")</span>
|
||||
@if (!string.IsNullOrWhiteSpace(current.CurrentNotes))
|
||||
{
|
||||
<span>@current.CurrentNotes</span>
|
||||
}
|
||||
<a asp-action="Details" asp-route-id="@relatedCharacterId">Open related character</a>
|
||||
<a asp-controller="Dynamics" asp-action="Relationship" asp-route-id="@relationship.CharacterRelationshipID">Evolution</a>
|
||||
<details>
|
||||
<summary>View History</summary>
|
||||
<div class="asset-mini-list mt-2">
|
||||
<div class="asset-mini-item">
|
||||
<strong>Initial: @relationship.RelationshipTypeName</strong>
|
||||
<span>@(relationship.InitialRelationshipStateName ?? "No awareness state")@if (relationship.InitialIntensity.HasValue) { <text> · Intensity @relationship.InitialIntensity</text> }</span>
|
||||
<span>Reader initially knows: @(relationship.ReaderInitiallyKnows ? "Yes" : "No")</span>
|
||||
@if (!string.IsNullOrWhiteSpace(relationship.Notes))
|
||||
{
|
||||
<span>@relationship.Notes</span>
|
||||
}
|
||||
</div>
|
||||
@foreach (var item in current.Events)
|
||||
{
|
||||
<div class="asset-mini-item">
|
||||
<strong>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</strong>
|
||||
<span>@item.RelationshipStateName · Intensity @(item.Intensity?.ToString() ?? "Unknown")</span>
|
||||
<span>Awareness: @(item.IsKnownToOtherCharacter ? "known to other character" : "not known to other character")</span>
|
||||
@if (!string.IsNullOrWhiteSpace(item.Description))
|
||||
{
|
||||
<span>@item.Description</span>
|
||||
}
|
||||
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open scene</a>
|
||||
</div>
|
||||
}
|
||||
<div class="asset-mini-item">
|
||||
<strong>Current: @current.CurrentStateName</strong>
|
||||
<span>Confidence / intensity: @(current.CurrentIntensity?.ToString() ?? "Unknown")</span>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="list-section mt-3">
|
||||
<h2>Relationship Events</h2>
|
||||
@if (!Model.RelationshipEvents.Any())
|
||||
{
|
||||
<p class="muted">No relationship events yet.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="asset-mini-list">
|
||||
@foreach (var item in Model.RelationshipEvents)
|
||||
{
|
||||
<div class="asset-mini-item">
|
||||
<strong>@item.CharacterAName -> @item.CharacterBName: @item.RelationshipStateName</strong>
|
||||
<span>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
|
||||
<span>@item.RelationshipTypeName · Awareness: @(item.IsKnownToOtherCharacter ? "known to other character" : "not known to other character")</span>
|
||||
<span>Reciprocal: @(item.IsReciprocal ? "Yes" : "No") · Confidence / intensity: @(item.Intensity?.ToString() ?? "Unknown")</span>
|
||||
@if (!string.IsNullOrWhiteSpace(item.Description))
|
||||
{
|
||||
<span>@item.Description</span>
|
||||
}
|
||||
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
|
||||
<a asp-action="Details" asp-route-id="@(item.CharacterAID == Model.Character.CharacterID ? item.CharacterBID : item.CharacterAID)">Open related character</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="list-section mt-3">
|
||||
<h2>Scene appearances</h2>
|
||||
@if (!Model.Appearances.Any())
|
||||
@ -156,24 +394,3 @@
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="list-section mt-3">
|
||||
<h2>Relationship events</h2>
|
||||
@if (!Model.RelationshipEvents.Any())
|
||||
{
|
||||
<p class="muted">No relationship events yet.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="asset-mini-list">
|
||||
@foreach (var item in Model.RelationshipEvents)
|
||||
{
|
||||
<div class="asset-mini-item">
|
||||
<strong>@item.CharacterAName -> @item.CharacterBName: @item.RelationshipStateName</strong>
|
||||
<span>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
|
||||
<span>Intensity @item.Intensity. @item.Description</span>
|
||||
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
@ -1274,16 +1274,29 @@
|
||||
<input type="hidden" name="ReturnProjectID" value="@Model.ReturnProjectID" />
|
||||
<input type="hidden" name="ReturnBookID" value="@Model.ReturnBookID" />
|
||||
<div class="row g-2">
|
||||
@if (Model.RelationshipOptions.Count == 1)
|
||||
{
|
||||
<div class="col-12">
|
||||
<input type="hidden" name="CharacterRelationshipID" value="@Model.RelationshipOptions[0].Value" />
|
||||
<span class="form-control form-control-sm">@Model.RelationshipOptions[0].Text</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="col-12">
|
||||
<select class="form-select form-select-sm" name="CharacterRelationshipID" asp-items="Model.RelationshipOptions">
|
||||
<option value="">Create or choose relationship</option>
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
<div class="col-6"><select class="form-select form-select-sm" name="CharacterAID" asp-items="Model.CharacterOptions"><option value="">Character A</option></select></div>
|
||||
<div class="col-6"><select class="form-select form-select-sm" name="CharacterBID" asp-items="Model.CharacterOptions"><option value="">Character B</option></select></div>
|
||||
<div class="col-12"><select class="form-select form-select-sm" name="RelationshipTypeID" asp-items="Model.RelationshipTypeOptions"><option value="">Relationship type</option></select></div>
|
||||
<div class="col-8"><select class="form-select form-select-sm" name="RelationshipStateID" asp-items="Model.RelationshipStateOptions"></select></div>
|
||||
<div class="col-4"><input class="form-control form-control-sm" name="Intensity" type="number" min="1" max="10" placeholder="1-10" /></div>
|
||||
<div class="col-4">
|
||||
<label class="form-label">Intensity <output>5</output></label>
|
||||
<input class="form-range" name="Intensity" type="range" min="1" max="10" step="1" value="5" oninput="this.previousElementSibling.querySelector('output').value = this.value" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-check">
|
||||
<input class="form-check-input" name="IsKnownToOtherCharacter" type="checkbox" value="true" />
|
||||
|
||||
@ -246,10 +246,11 @@
|
||||
{
|
||||
<li>
|
||||
<a asp-controller="Characters" asp-action="Details" asp-route-id="@relationship.CharacterBID">@relationship.CharacterBName</a>
|
||||
<span>@relationship.RelationshipTypeName · @(relationship.CurrentStateName ?? "No current state")</span>
|
||||
<span>Initial: @(relationship.InitialRelationshipName ?? relationship.RelationshipTypeName)@if (!string.IsNullOrWhiteSpace(relationship.InitialStateName)) { <text> / @relationship.InitialStateName</text> }@if (relationship.InitialIntensity.HasValue) { <text> · Intensity @relationship.InitialIntensity</text> }</span>
|
||||
<span>Current: @(relationship.CurrentStateName ?? "No current state")@if (relationship.CurrentIntensity.HasValue) { <text> · Intensity @relationship.CurrentIntensity</text> }</span>
|
||||
@if (!string.IsNullOrWhiteSpace(relationship.EventHistory))
|
||||
{
|
||||
<span>@relationship.EventHistory</span>
|
||||
<span>Relationship Event History: @relationship.EventHistory</span>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user