Phase 1R
This commit is contained in:
parent
624ec9a394
commit
4c8f97d048
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUTW/bOBD9KwLPokJR1AcF7MFwWqyRpjESd3exN4ocxkRk0qAop0GR/76gJNdO0cPehBFn5r03b+YHsuIAqEWr4/HTCWwYUIqCmUKU0AqTCpN8l9O2pC3hGWclY1X5L0qRuYM31KKaSZlLDriACjBTpMRdoSVWCnhOckVoyWJN8Tyg9gcSJlNwMhIyN/wFfjDOohb9baxyr7G3MJns3agy73rY2CEIKyOYr5v1Hf6y2u4etvOrAYaYnBmFWpQzUpayo1gB0ZhVHcNcCYIFr/JOS9Yw4HOasQG8FX02qJdLf+WChSBbmtEiI5hy9J4iJYKIkDsxwO7tGFFMEt3GeDqFp8/2BzqBRy1Nz2LO5W5kb24OQzeaXt0E4Z8haC8O8Or8C5xEj1J09O4IPhiYtHl4Si6QcpKRjFaUEJSieyH3xkKyuU0e+khYyIYwUAXmoBVmPBeYs6LAqihFTStd11SjFN2Bt9BfVb030rvB6ZAskicf+qydDcaObhySjQ3w7EWY8z6LfgCUou3Y9WbYP4JQbzv3OMZ/duwjl/XoPdiQbEXYJ3+KYY9aVFY1LRlhUpRFDlRqTokmpOEFVYyCrCsFNaWyY4zrQhel7ITs6kZ3oIquzlGKHsZwHMNZ/+9XIHbeHA6gLgh2k8bbXgTt/GGjwAajTRzN+cVkqL6fWCVLzXVvwIYPIqMWkRyoKhrAmuYVZo0SWJQFx1VVVBpyyWsa0a2d1eZ5/KkT0VqxQpdNU3W8ywlQXTeM1LojSupCclV2UNOa1A00XBLSlSxvOi6JgrxkKrLzTo0y/GqFYhrQ42jjdiabSPvVWPy9YhdFtt4FJ10fB2Z6eHobAhx+N7W1OxzdYELkr5fRfhvg3tn4d9n/RbMleeXCJQZxD5Jp9SqiteRC4kqJGrOKl7ihBceS5VCIuma1bFCKbp18AZ9Egwljp5mcPfXwlKy83JsAMow+dv5nIjWP8/N5ZS56ZF8/7dbOw+p4TJfoH6co0lzsbIAPd+XbEJ/v3RBZBD9e+egJen3GdeUmBaf5UpmJplINqCrHlOUlZpznuOFljbu66XhXNVDKeGFmtyaPoIwHGaZ6u7nbIuzvbLnywWghwxB354uTk5sWey5PfgV5NbWf2XPzq8ktTloq5SQ69mm+m5OB/tfZfH9/f/8PAAD//wMAbXToGicGAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUXW/bNhSG/4rAa1EhKYoSdZc5K2akbdzGa4fdUeSRQ0QhDZJKFwT+7wNlB7Fb7GJ3AnXe8/Geh3xFTj0B6tH1fv/7M7gUUYmSXY4YYQITgQndUtY3rCeyajlnom3/RiWyt/CCetRyramWgGsQgLkhDR7qUWNjQFJCDWENzznVLqL+FSlbGXi2Giofv0GI1jvUo+/WGf8j11a20pOfTRX8BGsXk3I6N/N5vbrFH68327vNMSpCzOLKGtQjyknT6IFhA2TEXAwcS6MIVlLQYdS84yCPMusSBKemKprH9/rGJwdJ96xidUUwk+hQIqOSyi0PKsL2ZZ+7WCy6yeflcrx89q/oGQLqWflm5jHdlZ7s1VMcZjuZq8nvdtbtVt6NdjcHlXLhEu2D30NIFhZzfrNOhZePfrfL+dAHNUVAJdpCeLJOTccfNzCqeUr3fg6LM9E8/lcM6pGak0clWpycpqVscRpmNVlw6RftWeG7+2IzqTT68HSxpPuj9+v/Yf0npR+sg2J9g3pEKDBTd4BHRgXmnVFYNbXEQtRiBKplyygq0cq7ZN3s51isXYLdybWL/q6DfrAJdJpDnukvkWHbBG9mnYr3BVNSkaomJIvmtJ9T8RWMDaAT5CG2YT7l+0nCBFtEH+wEv9izmkMAl4qNSg/FHyo+oB41omUNJ1yrpqbA9CgZGQnpZM0MZ6BbYaBlTA+cy7Ee60YPSg9tNw5g6qHNY0OGrFi4ZgM0wJjEhnLAHDTBnewMVhIYNYyQrjMX5hZ3U9Yp3REOpsYSRoO5pApLXtfY1I1qmRjblo1nPm2PQFCSy59D+GcEc2Lp8xHtt9kNPB/v8dKnMKYDIyhmnDaYS0lxJ5sWD203yEF00OgMwcq76N+M/AZh8NGm/Ih8mS1kEi8C3vdyC8HBdLabT1YHH/2YihOUxcW2vs4uv2HFAugP6/A/CxY3Xj9CKDJWyrqzVR4Oh8O/AAAA//8DAH+Nh0gKBQAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUXW/cKBT9KxbPxuHL2PgtmrTaUbabUTvqrvYNw6VBcWAEOG0UzX9f4ZlVm6fVviCEOfeee87BbyjoZ0ATuj2dPrxAKBm1qPjtiBEmMZGY0CNlU88mSjpJlGJC/o1a5O/hFU1oEMZQowBzkICFJT2euTPYWlCUUEtYL2pN/S2j6Q1p31l48Qa6mL9Cyj4GNKE/fbDxe+2tfWeWuNouxQX2IRcdTCXzx353j3+/PRwfDpdbGXIFd96iCVFB+t7MDFsgDgs5C6ysJlgrSWdnxChAXWA+FEhBL122Tz/721gCFDOxjvGOYKbQuUVWF10pzzrD8fVUWWwS3dXzdjvettMbeoGEJtb+K+al3I1Z/M1znle/2JvLWnR+ytvqtCkxvaIWnVI8QSoeNn1uc4bneXk96vz08XKnbvOHH2DWAnYX11DqxHxALdoEWhZdfAzNleNu8RDK9rEkH7I3/1WLoxY9fGkOiy4upud3fny5yLz/Hyp/0ubRB2j2d2hChAKzfATsGJVYjFZj3XOFpeTSATVqYBS1aBdD8WGNa272ocC3tI2EJvRRLxku/G6TefQFTFlTnfMvWXN1SNGupjQ/vaSkIx0npILWclpL8xmsT2AK1CGOab3W+/oewiTbQLs1JQilOejy2Pym8yOaUC8H1gsijO45BWacYsQRMirOrGBgBmlhYMzMQijHHe/NrM08jG4Gy+ehTgg1Os2WVuEoFc4qPA6DwcIYgzWAxkSMPYAgehTzOx2bh6XitBmJAMuxAmexUFRjJTjHlvd6YNINA3O/SHLNAyW1vYWXy7vbGEhrR7CSYiZoj4VSFI+qH/A8jLOa5Qi9qU7eQwqw/CLUJ29SzNGV5pqQ5p10n9dQ/x3NlpbvPuAfm0d30TxBaqrH2of6VK62ns/n8z8AAAD//wMAC2rMKYIEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeL3pzbQddpI6N7KIt+sbL0FYtUwuKchIY/vdCWjtXFOiLIAw5cw7PnJknlN0DoB6dHY+Xj5DrjFpUhy3ECFOYKEzogbJesp6STlEtuJF/oxYN7+Az6pEWIdBgAXNQgEUkEnueAo4RLCU0EibFWtPdzqh/Qm7oIjwOAbpp/gPKPEwZ9ejPIcfp44rthi6M0xK7Mo2wy3N1Oaxk3u/O3+Hfz24O1zenWzPMa3I3RNQjKoiUwTMcgSQslBfYRkews4r6FIQRYE9pQ65Qshu7Od5/xY9TzVBDzzrGO4KZRc8tiq66lbJ3Mxw+H1cWm0QXa7zdwttv/4QeoaCeta9insq9CePw5mH2yzDGN9sXtehYpiOUOsCmxvW++UqCko50TDFCUIuuXLgbMjS7i+Z6XJ/ogiECIscWUsTCUoet4BxHLp1mKmnNEmrROygZxm+qXg2hTPOUavMicvMdzv7s/DI7P8KK8daNM6AW7eZ9dTm6ccpw+QnCUk+1DmVZj8+nXIe8TMvc7HKF2+Jezl/zz5dSINfmxtW75jc336EeSaWZFEQEJzkFFpJlJBFiLGdRMAhaRdCMBS+ETTxxGbwLXpvkIXKvKWrRr6uG+yUEmOevbDaTjOPGoXnp0/k4QK7fyYh6RCiwyA3gxKjCwkSHneQWK8VVAhqsZl9gDq7cQkU9SsZ7T0LwTFgaHLOOx0R4VMkyprUTiUstmFKSaBei1oyKyDUExxMnhqEW3ZQpLqH+2Gq+NeCmTP9AqKtUqEeKSGmC854pQY2zBgRVwigL2orkiODBOyqN854SJ0mwhgVHlXXCR5nsK/3LfDtkuBjm4+g+f4NrOtXxX4JzhiYnfrj+trgH+DiV+/cnG3fvLw+oRR+WvG6EZrda5OOQ8Se1TvTVMtbhcFfARYhXU4SffQTruDSnCQ1KcM8DDjxRLEyS2EovsQYPkLxMTAXUoosp3ENpVoe5Ia9T9aXY9b45K+FuqBDqUlZ+f208rvfNzehqmsrDd5vkm4f9KEDHOCVrZyI8nrbRxlHFaCAqipmgEgtrKTZWauy18dYrAzKsil0v9bjU5gPEoUCo24tfvLhhnt9BuP9ZjFcXvFiUktVr+9MW26T9X0vsuUUP4OalwMO2rfsntMsZyoZ8sZxGcZevhnEcZghTjjPqjaGdoVq/EPyva5awTnOtn5+fn/8FAAD//wMAPZDLtBkGAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTS2/cIBD+KxbnxeFlML5Fm1ZdpW1WyaqtesMwdFEcvMI4D0X73yu8W6XprReERnwz32N4RdE8AOrQ5eHw4RFintAK5bCUGGESE4kJ3VHWNayjpJaUt4qSn2iFwjW8oA4pYS21GjAHCVg40uCee4udA00JdYQ1ovQ0vybUvSITagePwUI9Tt8gTWGMqEPfQ3TjU5ltQm2HcXZ1GgfYxCmbaAuZr5v1Nf58ud3dbE+vJpgKuA4OdYgK0jS2Z9gB8VjIXmDtDMFGS9p7K1oB+gQLMUOKZqgnd/823405QrYdqxmvCWYaHVfImWwK5d5MsHs5FBaLRVelvlrKy7V7RY+QUMdWf8w8tbuwQ7h4mPo5DO7idGYz3ReVhzQeIOUAiyeLzGEwOYyxOk9aDwFiRit0c1dtB5P9mB7eGXV30r/5D/m7Mv3DM9g5g1uPc8wnLFqhL8buQ4Rqc4U6RCgwx1vAnlGJResMNg3XWEouPVCrFaNohdZjzCHO4zxVm5jhV1oEoA59NMMEJ+qXye5DBpvnVFT9kGUXtml0s83Vm/+U1KTmpDC5mfNhztUtuJDAZij6dmk+9/sHwiRbQOs5JYi52pq8rz6ZaY861EjFGkGENQ2nwKzXjHhCWs2ZEwyskg4UY7YXQnvueWN7Y3vV+h4c71VRCCXu6rxhTgLXCje2sVgw6nAvPcctgAKtlPLg3vlY3QwFZ2xLBDiONXiHhaYGa8E5drwxikmvFPN/WXJOn5Iy3sHj6a8sDKRzLThJMRO0wUJrilvdKNyrtte9bKGxJeRrSBGGv4z6Emwap9Hn6rw81TvrbudY/nu1LNJTiPh5yehqtPeQqpKxCbGs9znW4/F4/A0AAP//AwATWzwkNgQAAA==
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rA81LhlyhRN8Nu0YWbepEYbdHLghoOvaxlaktRTgNj/3tA7RqOm1NvEjHvfDx8hy8k2ickPbk6Hn96xphnsiE5rEeCCU2Zpozfc9E3oue85lJpppq/yIaEW/xKetIqAA4GqUSNVDnW0EF6oM6h4Yw7JhpVctqHmfQvxIba4XMArKf5d0xzmCLpyR8huulLqW1DDeO0uDpNI27jnG2E0sxv2+tb+uvV7v5ud46acS7iOjjSE65Y08AgqEPmqdKDosZZRq3RfPCgOoXmLAsxY4p2rGf3+FbfTTlihl7UQtaMCkNOG+JstqXlwc54//VYulgR3ZTzzXq8fvYv5BkT6cXmFeY53QcYw4e0RLIhxzQdMeWAK4N1rHG0OUyxumS+HgPGfA79GyHv17lEB61xg0GlWnBosZEOmBOKSezcwD1XCiSaDphRvGHgWSes96LprO8GJBty97najTb7KT29A/35zG/7P/B9tHAIEavtDekJ4yic7JB6wTVVnbPUNtJQraX2yMG0gpMNuZ5iDnGZlrnaxowPaZ2a9ORnO86X/q4SHEJGyEsqKP7UxTCjXSIc9sc0+TDiPsx7h94uYyY98RftLk1ugVy9XSRnNaslYyXxko9Lrj6hCwkhYxn0Pi2Xmv+RCC1W0fWSEsZc7Ww+VL/Y+UB60uhWNIopsI3kKMAbwTxjnZHCKYHQaoetEDAoZbz0soHBwtB2fkAnh7ZQwOKbar1SJ7QTzgEdpBBUeW3pIJDTdjDgQDrpNX/Hurobi85CxxQ6SQ16R5XhlholJXWysa3Qvm2FLxY/Hvf5bKlXJxWjuu9oXRzHGf8Rc8J/FpzPsC6898tZ7vD5vLnrGNq5Dp3mVCjeUGUMp51pWjq03WAG3WEDxTK3mCKO39H+GCBN8+RzdbFi9Y7/pyWW16dabfklRPrvaoabCR4xVcVMNsSybBf/nDbkCe28JHxaH6/+hRwtPNqHMorHhBFwD9MS87qfs3t8/eNvq/ZDJDudTqdvAAAA//8DAGGM7QYbBQAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy27bMBD8FYFnU+FLlKhb4LSokaYxmqAteqPIZUxEIQ2KygOB/72g7CJJb70RC87s7MzuKwr6AVCPzvf7T48Q8oRWKPulxAiTmEhM6C1lfcN6RmqiJGuY/I1WyF/CC+pRK4yhRgHmIAELSxo8cGewtaAooZawRhROfTeh/hVpX1t49AbqOP2ANPkYUI9++mDjU+mtfW3GONs6xRE2Yco6mCLm22Z9ib+eb2+vt8dfE0wFXHuLekQFaRozMGyBOCzkILCymmCtJB2cEZ0AdYT5kCEFPdaTvX/rb2MOkE3PasZrgplChxWyOusiedAT3L7si4rFootSXy3l5dm/okdIqGerv2Ye6c7M6M9MfHjQwZ45H/y0Qyu0T3EPKXtY7FgmHEedfQzVqcl69BAyWqHrm2o76uxievjg0c1x9M1/TH6lzc4HqDYXqEeEArO8A+wYlVh0VmPdcIWl5NIBNaplFK3QOobswxznqdqEDHdpUYl69FmPExz1nSez8xlMnlOR/kuWrOHZ53W0pYAL0TZFO5tcvflNSU1qTkghmfN+ztV3sD6ByVCGuk3zif8fCJNsAa3nlCDkaqvzrvqipx3qUSNb1ggijG44BWacYsQR0inOrGBgWmmhZcwMQijHHW/MoM3Qdm4Ay4e2CIUSb7VslLKdNco4bAgoLGBgWFE9YDVwrQ20ClT3wdfqeiw4bToiwHKswFksFNVYCc6x5Y1umXRty9w7S06RU1LaW3g83saiQFrbgZUUM0EbLJSiuFNNi4e2G9QgO2hMSfYSUoDxnVFX3qQ4RZer08ZUH6z7Pody39WyPU8+4Ocls4to7iFVJXPtQ1nnU8yHw+HwBwAA//8DAHsvl/wmBAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzm6Juht2ihptmERtt0duIM4qFaKUFRW0aGP7vBbVbJO6tN2LAGb585p15ZTMciXXs9nT66UxzXlnN8riHlFCOC8eFfJaqs6rTbSO0ts6rv1jNxkf6xjrmTYwyBuKaHHGDwvJeD5EjUpBColDWlJrweWXdK4OxQTqPkZpl/Z3SOi4z69gf44zL1/I2jE2clg2btEz0MK8Z5ljE/PZw98h/vT08fzxcbq20luRmRNYxHaV2ZiAevO65Ca3kwUrJvXZKSuxtq8IlbZwzpRmmZsUv39/HJc+UY6capRvBVWBvNUPIUCT3sNLzt1NRsSO6L/F6D+/H7pWdKbFO1f/CvJS7idN4s279RGeaTpBWSjdxOR5hRlazU1pOlPJIO5YzpX5Zx1yIxuihbaXEFryxHlttlfaoBWErvMWAAZxxg4rWDti3wZvBoJIGHRnTe1azHdw0QR6Xubpqv5tGmjOr2cen6jBBHpZ0fIf+6UL04X8ALbLLbWG1856M1hqC65Ug9C46p/oIwg+9IhlJ2GB8kF5IHZQIxUitRxAhspp9gPgyzlQ93LOOCUkKdUt8UNJx0yJwsDpw57QbSMbglWQ1u1vmPM7bsq3Vw5zpc9r/yzr2M0wrXX56m+LLmCnmLRUIf7pixkNacIu5+m4AKRrRaCFK0pZPW64+EY6JYqaC4zlt13r/SVFO7Ul3W0o05+oA+aX6BdYX1rEAbZQQIAC4GAeyggaBApzwSA4doVHo0QSDzgQRnYFeGgQr+6FEWM2o+K3aLa4sWOvbyI0NkpvBAu/bVnFHIAZDxmqw7zhWH6eSB7EVhlDzQANyEyTwYLTmqC145Qbv1fADkqtZpCiAkc6XYd0VOMSW0EmujLTchCB5G6znvW/70LuWbCyaHynNNP0A6sMY07IuQ66uXqveofu0zWXhVLvvvo4z/3vv0f0Sv1CqSo9hnMt8Xdv6VrMjwbolOu77qntlhzJc1fO+tpRupFO2Zk8ZUt5O17B2jQhSvr29vf0DAAD//wMAY0OS2PQEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUy27jNhSGX0XgWlR4EyVqlzod1MhkYkzcadEdRR45RGTSoKhMg8DvPqDsIM4MuuhOOOJ/Lt/5yVfk9R5Qh64Ph9+fwacJlSi5JcQIk5hITOiWsq5mHW8rUrdEKf4PKpG7hRfUoUYYQ40CzEECFpbUuOeDwdaCooRawmqRc+rdhLpXpF1l4dkZqML0DeLkgkcd+st5G77n2tpVZgyzrWIYYe2npL3JzXxZr27x5+vN9n5zOjXBlMWVs6hD3FAuxQBYNbzHQrUUq5pS3HDJKLV93TJ1kjmfIHo9VpN9eq9vQ/KQTMcqxiuCmULHElmddG651xNsXw65iwXRTY6XS3j57F7RM0TUsfIN5indlRnd1X7qZzfaqzHsds7vVsEPbjdHnXLhEh1iOEBMDhY4vzmv48vnsNvlfOiTHidAJdpC3Duvx9OPGxj0PKaHMMeFzGSf/usM6pCeU0AlWkiO41K2OA+zGh349Iv2ovD9Q7EZdRpC3H9Y0sOJ/fp/oL/T5tF5KNY3qEOEArO8BTwwKrForca65gpLyeUA1KiGUVSiVfDJ+TnMU7H2CXZnah/6u47m0SUwaY55pr9lNtsmBjubVLwvmJKKVJyQLJrTYU7FV7AugkmQh9jG+ZzvJwmTbBF9ciP8gmc1xwg+FRudHos/9PSIOqR0a6hWWmktjRmgJjAQS7QkjQVpJVjBbGOFElYKRYwUuqfC6pr2Q46gEkE2WbH4uqeGGaE5HhijWNTGYNVTg2shDTEDk0S3H+AW92PWadMSAZZjBYPFQlGNleAcW17rhsmhadhwwelsCEoy9UsT/jmBPXvpy8nab7NbeD7d46VPaW0LVlLMBK2xUIriVtUN7pu2V71soTZ5slXwU3gD+Q1iHyaX8iNy57zb6/HnI++buYXoYbzYzp0zMUxhSMXZlsWHfX2dfX7FisWi353H/y7GuAnmCWKRjaWdv1jm8Xg8/gAAAP//AwD+kh3KDAUAAA==
|
||||
@ -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/e0DtBol7640Y8J0ZPvMO30iEA5KO3B6Pv5ww5oXUJI9bSDChKdOU8T0XXSs6aRrnrGil/YfUZHzEr6QjRnnPvUMqUSNVgbW0l4OnIaDjjAcmWlVywstCujcCYxPwNHps5uUTpmWcI+nIX2MM85dSG8bGT/MamjRP+BCXDNGXZv58uHukf9zu9k+7y60FlyJuxkA6Ij2XWg1InZE9Vc5y6lrOqZFacB761gp3kY0xY4owNUv4/KN+mHPE7DvRCNkwKhw51yRAhtJyDwvuvx5LFxui+xKvt/B27N7ICRPpRP0d5iXdjZ/GmzwfJzzhdIS0YLrx8+EAMZCaHNN8xJRH3LBsL50myOMcq2uxu2nEmElNnp6r3QR5mNPhHavnC4KH/0HghKkvt1krtTGopJTgdC8YBqO91qL3wMzQC+QeWeuUcdwwLp1gTrbaWBOAOU9q8jLNPUy/Lxs/J1hvAzNCC1TohNLaeYbS4tAaZAG07rX1LkhrhLLBSnAA3PVovNO+GOQD+NcxYvVwTzrCOIogLdJBcE2VDUChlY5qLfWA3DsjOKnJ3RzzGNd5XaqHmPElbfxIR36FacELudvkX8eMPq+pQP1bl2K7NIfV5+qHAzhrWCMZK6I1H9dcfcQwJvQZC959Wq/5/iMRWmyiuzUljLnaQX6tfoPltVAB6zm48lTt/YAtw4EFBpqZgDpoDEoEE5RTQSvHvFbQcxWg5f1QIqQmWAxXbR4XPUrmvKQwMEsVG4A6FEgHr5TTrLUMwzuO1dNUdOAtUxgkdTgEqhwH6pSUNMgWjNCDMWL4CcnVfJwVwAFPl23dOtAhWAyaU6F4S5VznFrXGtob27teW2x96fkRU8TpJ1AfRp/mZR5ydfVu9Q7dxzWWH6fafPxljPTfbUb3s/+MqSozhjGWBbuO9VyTA8KyJjxsH1b3RnZlu6r99m8J2XAt2po8Z0h5PV7DUjfMcX4+n8/fAAAA//8DAMyRdbf1BAAA
|
||||
@ -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+HT0fo44AWKLopxAgTmAhM6JaypmRNUeecE84Z/RctkLuDN9SgimtNtQRcgADMDSlxW1iNjQFJCTWElTz1VM8Dan4g5XIDR6ch98NfEAbne9Sgv11v/GuarVyuOz+aPPgO1v0QVa/TMl/Xqzv8ZbnZPmxOWQMMqTh3BjWo0LQQ3AKWVdFiLmuKZUkprgrBKDVtWTN5KnN9hNCrLh/My2W+8bGHqBuWsyInmEn0vkBGRZVWbtUA27dD2mKi6DbFF1N4+mx+oCME1LDFmcxTuxvduZv90I6uMzdRhWeINqg9vPrwAkfVoQU6BH+AEB1M3Dw8ZZeVKMlJzgQjBC3QvdI710O2vs0eugRY6ZpwMAWWYA3mkioseVFgU5SqYsJWFbNoge4g9NBddb13OvjB25jNlGcf5qx8H10/+nHI1n2E56Diqe6z6gZAC7QZ284Nu0dQ5m3rH8f0rx+7hGU1hgB9zDYq7rI/1bBDDZKq1lRJJZUSWlsoCVhiiBKkMiCMAMOZqQyX3AguiRZctZQbVdLWpghaoIcxHsZ45v/71RLb4PZ7MJcNthPHm05F68N+baCPzrokzTljMlTXTaiyueeqc9DHDySjBhEKzBQ1YMuowLw2CquykFiIQligWlaMngiz7nn8yROx1vDClnUtWtlSAsxWNSeVbYnRttDSlC1UrCJVDbXUhLQlp3UrNTFAS24SuuDNqOOvVigmgR7HPl1ntk6wX12Pvwt+YWQTfPTad0kw18HT2xBh/zvVVn5/8IOLCb+dpf02wL3v09/5/mfO5uKlj5cYpDvIptOrK0JqKzQuNK0wb0HguqootowxWVVlqSuGFujW6xcIWTKYcv2kydlTD0/ZMuidi6DjGNLkfyZQJzk/n0/mwkf+9dN25QMsD4fFHP3jmEg6NTsb4MO78m1I6Ts/xCvIM7Qn6Ox5sSs7GTieniqXgsKYGoygmHFaYi4lxbUsK9xWdStbUUOpL3bNHsG4ADpO/bZhTNNmZn/ny2WIziodh3Q8X7ye7DT7c075dckr2X5Wn4ZfSTdbae5ESbLs0+nhnBz0v97N9/f39/8AAAD//wMAsR8nGCgGAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy27bMBD8FYFnU+FLlKhb4LSokbYxEqMteqO4q5qIQhkUlQcC/3tB2UWa3nohiJVmd2Z2+EqCfUDSksvD4cMjhjSRFUl+KQkmNGWaMr7joq1Eq2SpjGK1Fj/JivhrfCEtqZVz3BmkEjVSBayinewdBUDDGQcmKpV72l8TaV+J9SXgo3dYjtM3jJMfA2nJdx9gfMqzrS/dMM5QxnHATZiSDS6T+bpZX9PPl9vdzfb014RTBpceSEuk41KrHqmpZUeVaTg1Fee0llpwDl3VCHOC+ZAwBjuUE9y/zYcxBUyuFaWQJaPCkOOKgE02U+7shLuXQ2axWHSV66ulvFzbV/KIkbRi9cfMU7sLN/iLh6mb/QAXpzPZ6T6rPMTxgDF5XDxZZA6DTX4MxXnSevAYElmRm7tiO9jUj/HhnVF3J/2b/5C/y9M/PKObE8J6nEMiLeEyf/pi3d4HLDZXpCWMowDZIO0F11Q1YKmtpKFaS90jd6YWnKzIegzJh3mcp2ITEv6KiwDSko92mPBE/TK6vU/o0hyzqh86Z2EbR5hdKt7856xkpWQsg+Z0mFNxi+AjuoRZ3y7O537/QIQWC2g9x4ghFVub9sUnO+1JS4xtHLfGGmu1cz1WDHsGzGpWA2rQCEpADcoo0Mowp5XtuAJb8a7PFbIimNddLAmrUWqplaTATUMVakttA45ygB6saWrVN+98LG6GjLOuYQpBUoM9UGW4pUZJSUFWtha6r2vR/2XJefucZYMBH09vZWGgARoEzalQvKLKGE4bU9W0q5vOdLrBymXO1xgDDn8Z9cW7OE5jn4pzeIp31t3OIb/3YgnSkw/0ednR1ejuMRZ5x9aHHO/zWo/H4/E3AAAA//8DAFkKilI2BAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy27cOBD8FYFnUeZLlKibMd5FBk7Wg8RIFnuj2K0MEZkcUJSTwJh/X1AzQeLcciOKrO7qquYLCfYJyUBuT6e/njHkhdQk+w0STGjKNGX8kYuhFYOSjTKaCan+IzXx9/idDKRTznFnkErUSBWwlo5ychQADWccmGhVqWk/L2R4IdY3gM/eYROXj5gWHwMZyCcfIH4tva1v3BxXaFKccR+WbIMrYv7Z7+7p29vD48Ph8mrBpZAbD2Qg0nGp1YTUdHKkyvScmpZz2kktOIex7YW50HzImIKdmwW+/OwPMQfMbhCNkA2jwpBzTcBmWySPdsHH76eiYrPoruD1Bm/H4YU8YyKDqH+YeSl342Z/4+LTkw1wM/nglyOpySnFE6bscbNjm3CebfYxVNcmu9ljyKQmDx+qw2zzFNPTK48+XEbf/8Hk76w7+oDV/o4MhHEUIHukk+Caqh4sta00VGupJ+TOdIKTmuxiyD6scV2qfcj4OW0qyUD+tvOCF323yR19RpfXVKT/q0vW+M3nXYQCMFKTQ4qwulz9tJuzhjWSlcuHNZ/WXL1H8AldxjLTY1qv5X+jCC020m5NCUOuDjYfqzd2OZKBGNs7bo011mrnJmwZTgyY1awD1KARlIAOlFGglWFOKztyBbbl41SQorukW20LpRlDC2qkUjJNFQOkFoWjrOUtMxa0A/HK1uphLjzreqYQJDU4AVWGW2qUlBRkazuhp64T0y+WXBPnrPgN+Hz5GhcFAD2C5lQo3lJlDKe9aTs6dv1oRt1j64rme0wB51+MeuddikuccnVdmOqVde/XUL53tS3PVx/oty2yu+i+YKpK5NaHss3XlM/n8/l/AAAA//8DAEtVZ1slBAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUy27cOBD8FaHPosyXKFE3Y5xgB96sB8kgG+RGka2YsEwNSMqJYfjfF9TMwvFpsReCaLGK1dVFvUAwjwgDXJ9OH54w5AQ1ZL+VOOWKUEUoOzI+tHyQopGaCSXkd6jB3+IzDNBJa5nVSAQqJNLRloxissQ51IwyR3krC6f5kWB4AeMbh0/eYrOkrxiTXwIM8LcPbvlZ7ja+sfOyuiYuM+5DyibYIuav/e6W/Hl9ON4dzqcSpgJuvIMBhGVCyQmJ7sRIpO4Z0S1jpBOKM+bGtuf6DPMhYwxmbpJ7eLvfLTlgtgNvuGgo4Rpea3AmmyJ5NAmPz6eiYrPoptTrrbxthxd4wggDr/8180x3ZWd/9ZjG1c/u6rxmkx7Stk7G5iU+Qw2nuJwwZo+bP9cp4eM4Px9Nevh4PlO26cMvtGtGt1vWkGEAJhTUsBk0zyb7JVQXjbvZY8jbxxx9SN7+F5eAGu6+VIfZ5GmJj+/m8eVs8/5/uPzJ2HsfsNrfwACUIXeiRzJxpojsnSGmFZooJdSEzOqOM6hht4Tsw7qsqdqHjD/i1hIM8NHMCc/6rqO99xltXmPp85squTrExa02V2+zZLShjaC0gNZ8WnP1GZ2PaDOWJo5xvfB9fQ/him+g3RojhlwdTL6v/jDpHgbQprfMaKONUdZO2FKcqKNG0c6hcgqd5K5zUkunpKZWSTMy6UzLxqlUoAYs0am2tFo2GdTKEjnKjkhLOemtMASF0pJJw0dWPHnzsbqbC87Ynkp0gmicHJGaGaKlEMSJ1nRcTV3Hp98sueSB0ULm8On87jYFyrkenWKES9YSqTUjvW47Mnb9qEfVY2uL5luMAeffjPrkbVzSMuXqkpDqnXWf11D+HdWWlp8+kF/bjG4W+4CxKjM2PpSnchnr6+vr6z8AAAD//wMAN4ao/4IEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUy27kNhD8FYHXiDJfokTdHNuLDBw/sDNIgtyaZMtmLFMDirJ3YfjfA2ns9WMRIBdBaJJd1dXV/UQi3CPpyPF+f/aAMU+kJDmsIcGEpkxTxndcdLXolKyU0aqR5m9SknCO30lHGuUcdwapRI1UeVZTK3tHvUfDGfdM1GrJCTcT6Z4IhMrjQ3BYjdMfmKYwRtKRP0P04+OCDaFywzj7Ko0DbuKUIbqFzOXm5Jz+fny9u7o+3JpwWh5XwZOOSMelVj1S00hLlWk5NTXntJFacO5t3QpzeBZixhRhqCZ/94bvxxwxu05UQlaMCkOeS+Ihw0LZwoS77/uFxSrR6RIv1/D62z2RB0ykE+WrmId0R24IR/eTncPgj9YvKck+jXtMOeCqxtW2eCPBWcUqoQVjpCQX4G5DxGJzWlwNS4ngWqbQS2qw91QZDtQoKamXNTRC900jelKSc0wRh3dZL4JL4zT2uXgRufiAsz0+OYtgB1wwvsAwISnJZtpmiB6GMeLZN3RzPuTapXk5PhljDnEe56nYxIw3CV7OX9+fzClhzMU15NviN5huSUcMtI6DAQOgneuxZtgzz0CzxqP2Gr0SvvHKKK+VYU4rsFx5qLntlwgpya+LhtvZOZymNzarSYZh5VC89OlkCBjzBxlJRxhH4WWLtBdcU9V6oFBLQ7WWukfuTCP4K8wO0g1m0pG+tdYy56xQhjsQBqTvmfS6N0I0Dahe1o0SWtesAeebRnDlZYMOZC9ZK0hJrtPoZ5c/t1quDbhO4z/o8iIV6Yhmdd06sFZoxVswLSquVasNNkb1wJR0FnjdgrWcQc2caYUDrg0o6+v+h0pn8SZEPA3TfoDv73DbSlfyFwfQ8h7Up+tfEtzj45juLg82ri7PdqQkX+e4bIRis1jkMUT6TS8TfTEPOexuE4JHfzF6/NlHuIxLsU6ol1w72ViKogeqGmDUtojUaqcatNIZsahxOro7TMXiMAhxmaofya62xXFytyGjy3Na+P218rjaFtcD5H5M9x82ybvCPgtQCcnZ0hmPD4dttHLU3rfoNadC8ZoqYzhtTd1Q27TWWN1i7RbFrua8n3PxFX1I6PJa8YsXV8yTW3R3P4vx6oIXi3K2eG172GKrtP9riT2X5B5hmhPer9u6eyKbGDGtyKfzYRQ38SIMQ5jQjdFPpKuVairGhHhh+N/3jKqYEvr5+fn5XwAAAP//AwCgOTEOGwYAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS/zQzbBb1HVTL+JFWvRGcUa2EC21oKhNA2P/e0DtBol7600Y6J0ZPvPOvJHoD0g6cns8/nLCmBdSkzxuIcGEpkxTxvdcdkJ3zDVWSN4y8Q+pyfiIX0lHjAqBB4dUokaqgLW0l0OgAOg448BEq0pO/7KQ7o34sQE8jQGbefmEaRnnSDry1xhh/lJq+7EJ07xCk+YJH+KSfQylmT8f7h7pH7e7/dPu8teCSxE3I5COKOTCKXSUSWeoEopRJ9BQVBDc4Kw2Hi6yMWZM0U/NAp9/1Ic5R8yhE42QDaPCkXNNwGdfWu79gvuvx9LFhui+xOstvH12b+SEiXSi/g7zku4mTONNno8TnnA6+rRgugnz4eBj6eWY5iOmPOKGZXvpNPk8zrG6FrubRoyZ1OTpudpNPg9zOrxj9XxB8PA/CJww9aQjkrVSG4NKSumd7gVDMDpoLfrgmRl6gTwga50yjhvGpRPMyVYba8AzF0hNXqa599Pvy8bPCdZbYEZogQqdUFq7wFBaHFqDDLzWvbbBgbRGKAtWeuc9dz2a4HQoBvngw+sYsXq4Jx1hHAVIi3QQXFNlwVPfSke1lnpAHpwRnNTkbo55jOu8LtVDzPiSNn6kI7/6acELudsUXseMIa+pQP1bl2K7NMMacvXDAZw1rJGMFdGaj2uuPiKMCUPGgnef1mu+/0iEFpvobk0JY652Pr9Wv/nltVDxNnDvylN1CAO2DAcGzGtmADVoBCXAgHIKtHIsaOV7rsC3vB9KhNQEi+Gqi8fBgrBqoEbalqpWC+p4MNT32DMwPPTSvONYPU1F54NlCkFShwNQ5binTklJQbbeCD0YI4afkFzNx1kBDHi6bOvWgQawCJpToXhLlXOcWtca2hvbu15bbEPp+RFTxOknUB/GkOZlHnJ19W71Dt3HNZaLU20+/jJG+u82o/s5fMZUlRn7MZYFu471XJMD+mVNeNgOVvdGdmW7qv12t4RutDW2Js/Zp7wer2FpGy21OZ/P528AAAD//wMAx6rPOvUEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUy27jNhSGX0XgWlR4EyVqlzod1MhkYkzcadEdRR45RGTSoKhMg8DvPqDsIM4MuuhOOOJ/Lt/5yVfk9R5Qh64Ph9+fwacJlSi5JcQIk5hITOiW8o7JjqiqbduGUfYPKpG7hRfUoUYYQ40CzEECFpbUuOeDwdaCooRawmqRc+rdhLpXpF1l4dkZqML0DeLkgkcd+st5G77n2tpVZgyzrWIYYe2npL3JzXxZr27x5+vN9n5zOjXBlMWVs6hDAihTAhQmXDVYMEGwYtBgENaoQbWy0fYkcz5B9HqsJvv0Xt+G5CGZjlWMVwQzhY4lsjrp3HKvJ9i+HHIXC6KbHC+X8PLZvaJniKhj5RvMU7orM7qr/dTPbrRXY9jtnN+tgh/cbo465cIlOsRwgJgcLHB+c17Hl89ht8v50Cc9ToBKtIW4d16Ppx83MOh5TA9hjguZyT791xnUIT2ngEq0kBzHpWxxHmY1OvDpF+1F4fuHYjPqNIS4/7CkhxP79f9Af6fNo/NQrG9QhwgFZnkLeGBUYtFajXXNFZaSywGoUQ2jqESr4JPzc5inYu0T7M7UPvR3Hc2jS2DSHPNMf8tstk0MdjapeF8wJRWpOCFZNKfDnIqvYF0EkyAPsY3zOd9PEibZIvrkRvgFz2qOEXwqNjo9Fn/o6RF1SOnWUK200loaM0BNYCCWaEkaC9JKsILZxgolrBSKGCl0T4XVNe2HHEElgmyyYvE114JzzgxWVgxY1I3B2vYNplwYRikTgvQf4Bb3Y9Zp0xIBlmMFg8VCUY2V4BxbXuuGyaFp2HDB6WwISjL1SxP+OYE9e+nLydpvs1t4Pt3jpU9pbQtWUswErbFQiuJW1Q3um7ZXvWyhNnmyVfBTeAP5DWIfJpfyI3LnvNvr8ecj75u5hehhvNjOnTMxTGFIxdmWxYd9fZ19fsWKxaLfncf/Lsa4CeYJYpGNpZ2/WObxeDz+AAAA//8DACYP7R0MBQAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUTW/bOBD9KwLPpkJSFCUK2IPhtFgjTWMk7u5ibxQ5jInIokFRToMi/31BWa6dooe9CaP5ePPeG/5AvdoDatDycPh0hD4OaIGim0KMMIGJwIRuadEw0VCSs4oVhIt/0QK5O3hDDaq41lRLwAUIwNyQEreF1dgYkJRQQ1jJU0/1PKDmB1IuN3B0GnI//AVhcL5HDfrb9ca/ptnK5brzo8mD72DdD1H1OoH5ul7d4S/LzfZhc8oaYEjFuTOoQRwokxwkJoWsMGecYMmgwsCNllbWolLmVOb6CKFXXT6Yl8t842MPUTcsZ0VOMJPofYGMiipBbtUA27dDQjFRdJviiyk8fTY/0BECatjiTOap3Y3u3M1+aEfXmZuowjNEG9QeXn14gaPq0AIdgj9AiA4mbh6esgskSnKSM8EIQQt0r/TO9ZCtb7OHLi2sdE04mAJLsAZzSRWWvCiwKUpVMWGrilm0QHcQeuiuut47HfzgbcxmyrMPc1a+j64f/Thk6z7Cc1DxVPdZdQOgBdqMbeeG3SMo87b1j2P6149d2mU1hgB9zDYq7rI/1bBDDZKq1lRJJZUSWlsoCVhiiBKkMiCMAMOZqQyX3AguiRZctZQbVdLWpghaoIcxHsZ45v/7FYhtcPs9mAuC7cTxplPR+rBfG+ijsy5Jc86YDNV101bZ3HPVOejjB5JRgwgFZooasGVUYF4bhVVZSCxEISxQLStGT4RZ9zz+5IlYa3hhy7oWrWwpAWarmpPKtsRoW2hpyhYqVpGqhlpqQtqS07qVmhigJU8m3QRvRh1/tUIxCfQ49uk6s3Va+9X1+LvgF0Y2wUevfZcEcx08vQ0R9r9TbeX3Bz+4mPa3s7TfBrj3ffo73//M2Vy89PESg3QH2XR6FZjC8gqwaVmBOSdwOj3DWjC1rkXNkoy3Xr9AyJLBlOsnTc6eenjKlkHvXAQdx5Am/zMtdZLz8/lkLnzkXz9tVz7A8nBYzNE/jomkU7OzAT68K9+GlL7zQ7xaeV7tCTp7BnZlJwPH01PlUlAYU4MRFDNOS8ylpLiWZYXbqm5lK2oo9cWu2SMYF0DHqd82jGnazOzvfLkM0Vml45CO54vXk51mf84pv4K8ku1n9Wn4lXSzleZOlCTLPp0ezslB/+vdfH9/f/8PAAD//wMAGp/mGygGAAA=
|
||||
@ -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+a0kudSMa8bFUdS91L1oKt3JRhv+L5Tg7+k79NAq54QzxGrSxBTyhg316BgiGcEFctmo3NN+WaB/BesrpBfvqJqXzxQXPwfo4W8fcP6aZ1tfuWlesYrzRPuwJBtcJvNhv7tnf90ejg+Hy6uFlgyuPEIPioQ0igzjtWmZkoozI6llpNCZ0XS6tXiB+ZAoBjtVCz79mo9zCpRcLytZV5xJA+cS0CabKQ92oeP3U2axWXSX6+VW3q79K7xQhF6WP828tLtxk795XobVT3hzOZNdnrLKU5xPFJOnzZNN5jTZ5OdQXCftJk8hQQkPn4rDZNM4x+c3Rn266N//D/nHPP3dN3JrItzNa0jQg6gNlPDeukcfqNjfQQ9ckMS6IzZKoZnq0DLb1IZpXeuRhDOtFFDCbg7Jh3Vel2IfEn2JmwDo4Q87LXShfhvdo0/k0hqzqn903oVDnHF1qfjlv+AVr2rOM2hNpzUVHwl9JJco6zvG9drvPxCp5QbarTFSSMXBpsfiT7s8Qg/Gdk5YY4212rmRGk4jR241b5E0akIlsUVlFGpluNPKDkKhbcQw5gqUQDnuYtuwUdVcSeKMj9IwNVrJhq6VbJCtEELrpnVvfSwepoyzruOKsGaGRmTKCMuMqmuGdWNbqce2leNvllzTFzwbjPRy+SsbA43YEWrBpBINU8YI1pmmZUPbDWbQHTUbg3uKgabfjHrvXZyXeUzFdXmKN9Z9XEP+78W2SF99YN+2jO5m90SxyBlbH/J6X2M9n8/nHwAAAP//AwDZhTs/NgQAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4owZ1HmlyhRN2OdoAs39SJZpEVvFDmKCcvUgqScGMb+94LaDVKfil4IYsR3+M4zQ71BMM8IA9yeTh9eMOQENWS/hTjlilBFKDsyMXA1sLZRPZW6a/+GGvw9vsIAnbSWWY1EoEIiHW3JKCZLnEPNKHOUt7LkNN8SDG9gfOPwxVtslvQVY/JLgAH+9MEt38vdxjd2XlbXxGXGfUjZBFvM/LHf3ZPfbw/Hh8PlVMJUxI13MIBExrVETajQHZFcUqI5dgSls3rSveqMu8h8yBiDmZvknn7d75YcMNuBN1w0lHAN5xqcyaZYHk3C4+upuNgQ3ZV4vYW37fAGLxhh4PVPmJd0N3b2N89pXP3sbi5rNukpbetkbF7iK9RwissJY/a48blNCZ/H+fVo0tPHy5myTR9+oF0zut2yhgwDMKGghg3QPJvsl1BdPe5mjyFvH3P0IXn7X7kE1PDwpTrMJk9LfH7Xjy8XzPv/QfmTsY8+YLW/gwEoQ+5Ej2TiTBHZO0NMKzRRSqgJmdUdZ1DDbgnZh3VZU7UPGb/FrSQY4KOZE1783Ub76DPavMZS51+qzNUhLm61ufrVS0Yb2ghKi2jNpzVXn9H5iDZjKeIY12u+r+8lXPFNtFtjxJCrg8mP1W8mPcIA2vSWGW20McraCVuKE3XUKNo5VE6hk9x1TmrplNTUKmlGJp1p2TiVCNSAZXSqbVrbzhReLUE6CiKtEkQLIwgy17GxxZGa6R3H6mEuOmN7KtEJonFyRGpmiJZCECda03E1dR0vup9IrvPAaAHs8OXy7jYHyrkenWKES9YSqTUjvW47Mnb9qEfVY2uL53uMAed/gfrkbVzSMuXqOiHVO3Sf11D+HdU2Ld99ID+2Ht0t9gljVXpsfChP5drW8/l8/gcAAP//AwAKlhXBggQAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTwY7cKBD9FYuz8QDG2Pg26tlVWpPstJJRstobpoo0igdaGE8SjfrfV7g7Sia33NCDV/XqveKFBPOEZCS3p9NfzxjyQmqS/QYJJhRlijL+yNtRqJF3jRpk1w/6P1ITf4/fyUh6aS23GmmLCqkE1tGpdZYCoOaMAxOdLDXN54WML8T4BvDZW2zi8hHT4mMgI/nkA8SvpbfxjZ3jCk2KM+7Dkk2wRcw/+909fXt7eHw4XF4tuBRy44GMRCIXWqKmrNU9lUIyqgX2FCVY7fSgegMXmg8ZUzBzs8CXn/0h5oDZjqIRbcOo0ORcEzDZFMmTWfDx+6mo2Cy6K3i9wdtxfCHPmMgo6h9mXsrd2Nnf2Pj0ZALcOB/8ciQ1OaV4wpQ9bnZsE86zyT6G6tpkN3sMmdTk4UN1mE12MT298ujDZfT9H0z+ztijD1jt78hIGEcB7YDUCa6oHMBQ07WaKtUqh9zqXnBSk10M2Yc1rku1Dxk/p00lGcnfZl7wou822aPPaPOaivR/Vckav/m8i1AARmpySBFWm6ufdnPWsKZl5fJhzac1V+8RfEKbscz0mNZr+d8oQomNtFtTwpCrg8nH6o1ZjmQk2gyWG220Mcpahx1Dx4AZxXpABQpBCuhBaglKamaVNBOXYDo+uYIU3SXdaluorp/aaeiQOhwclWZAOijXUauB9cw4kCBe2Vo9zIVn7MAkQks1OqBSc0O1bFsKbWd6oVzfC/eLJdfEOSt+Az5fvsamQAEMCIpTIXlHpdacDrrr6dQPk57UgJ0tmu8xBZx/Meqdtyku0eXqujDVK+ver6F872pbnq8+0G9bZHfRfsFUlciND2Wbrymfz+fz/wAAAP//AwBHtmfAJQQAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAs6jwS/zQzbBb1HBTL2KjLXobcUaxEK20oKhNA8P/vaB2i8S99SIIA87wnWdeziub4UisYzen009nmvPKapbHPaSEslxYLuSz1J2ynWkbb5TxPvzFajY+0DfWMWdilDEQ12SJGxQt7/UQOSIFKSQK1ZpSEz6vrHtlMDZI5zFSs6y/U1rHZWYd+2Occfla7oaxidOyYZOWie7nNcMci5jf7m8f+K83h+fHw+XUSmtJbkZkHQvBOYyt5KF8jO4VD70HrlRPZHsTpBWXtHHOlGaYmhW/fL8flzxTjp1qlG4EV4G91QwhQ5Hcw0rP305FxY7orsTrPbz/dq/sTIl1qv4X5qXchziNH9atn+hM0wnSSulDXI5HmJHV7JSWE6U80o7lTKlf1jEXojE68F5K9OBM69DrVmmHWhB64VoMGMAaO6jYtgP2PjgzGFTSoCVjesdqtoObJsjjMldX7bfTSHNmNXt8qg4T5GFJx3fony5E7/8H0CKbdUyLVlvnyGitIdheCUJno7WqjyDc0CuSkUQbjAvSCamDEkG31nmHIEJkNfsI8WWcqbq/Yx0TkhRqT3xQ0nLjETi0OnBrtR1IxuCUZDW7XeY8ztuyrdX9nOlz2vtlHfsZppUund6k+DJminlLBcKftpjxkBbcYq6+G0CKRjRalJ4et3zacvWJcEwUMxUcz2m71vtPirJqT7rdUqI5VwfIL9UvsL4UhuCjhAABwMY4UCtoECjACodk0RIahQ5NMGhNENEa6KVBaGU/lAirGRW/VbvFXYhRhmHgwaHiZgjEYegVp9a46ACs8fSOY/U4lTyIXhhCzQMNyE2QwIPRmqNuwSk7OKeGH5BczSJFAYx0vjzWXYFF9IRWcmVky00IkvvQOt4734feempj0fxAaabpB1Afx5iWdRlydfVa9Q7dp20uC6faffd1nPnf+4zulviFUlVmDONc3td1rG81OxKsW6Ljvq+6V3Yoj6t63teW8o1U1tbsKUPK2+ka1m3jvHNvb29v/wAAAP//AwB1G8B29AQAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUS2/cNhDHv4rAsyjz/dDNsFvUdVMv4kVa9MbHyBaipRYUtWlg7HcPqN0gcW+9EMSA8+Bv/jNvKLkDoB7dHo+/nCCVBbWojJuJEaYwUZjQPeU9U72QnSGCU2n+QS0aH+Er6pEWIdBgAXNQgEUkEns+BBwjWEpoJEyKGtO9LKh/Q27sIpzGAN28fIK8jHNCPfprTHH+UnO7sQvTvMYuzxM8pKW4FGoxfz7cPeI/bnf7p93l1QJLde7GiHpkrdYxSIptPQT3DFtvHGbMAygvLFXk4jamAjm5qVvi5x/541wSlNCzjvGOYGbRuUXRFVdL9m6B/ddjrWJDdF/t7Wberv0bOkFGPWu/w7yEuwnTeFPm4wQnmI4uL5Bvwnw4uBRRi455PkIuI2xYtp9OkyvjnJprsrtphFRQi56em93kyjDnwztWzxcED/+DwAmyRz3iRHKlNQjOubPKMwJRq6AU88ERPXgGNACRVmhLNaHcMmK5VNro6IgNqEUv0+zd9Puy8bOMeBOJZoqBAMuEUjYQ4AYGqYFEp5RXJtjIjWbCRMOddY5aDzpYFapAPrjwOiZoHu5RjwgFFrkBPDCqsDDRYSe5xUpxNQANVjOKWnQ3pzKmdV6X5iEVeMkbP9SjX920wIXcbQ6vY4FQ1lyh/q1qsl2e4xpK80MBlHSk46QyelrLcS3NR4hjhlCg4t3n9RrvPy5Msc3pbs0ZUml2rrw2v7nltVJxJlBn61dVCANIAgOJxCmiI6ioIAoWdRRWRCUsCUo4T0V0kvqhWlCLoAqu2TRuiDSKS4epBo+FdB4bIQR2SkrFSIyR2nccm6ep+rlgiIDIsYUhYmGpw1ZwjiOXTjM1aM2Gn5BcxUdJBRzhdJnWrQIVo4GoKGaCSiyspdhYqbHXxluvDMhQK3iEnGD6CdSHMeR5mYfSXLXbvEP3cU114zSbjr+MCf+79eh+Dp8hN7XHbkx1wK5tPbfoAG5ZMxy2hdW/oV2drma/7S1mOsqUatFzcbmsx6uZy04brc/n8/kbAAAA//8DAMMGw8r1BAAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUXW/jKBSG/4rFtXEBY2x81013tFGn02ianV3tHYbjFNWBCHBnqyr/fYSTqumM9mJvLOuY8/W8r3lFTu0B9ej6cPj9GVyKqETJLiFGmMBEYEK3tO6Z6HlTdS2lTUf/QSWyt/CCetRyramWgGsQgLkhDR7qUWNjQFJCDWENzzXVLqL+FSlbGXi2Giofv0GI1jvUo7+sM/577q1spSc/myr4CdYuJuV0HubLenWLP19vtveb06kIMSdX1qAeSdm2RjcUy/zg9cCwHDqFGRsAxMAlFeSUZl2C4NRURfP03t/45CDpnlWsrghmEh1LZFRSeeRBRdi+HPIUC6KbHC+X8PLav6JnCKhn5RvMU7krPdmrfRxmO5mrye921u1W3o12NweVcuMSHYI/QEgWFji/WafCy2e/2+V66JOaIqASbSHsrVPT6cMNjGqe0oOfw0Immqf/OoN6pObkUYkWktO0tC3Oy6wmCy79knvR+P6h2EwqjT7sP4j0cGK//h/o75R+tA6K9Q3qEaHATN0BHhkVmHdGYdXUEgtRixGoli2jqEQr75J1s59jsXYJdmdqH+a7DvrRJtBpDnmnv0U22yZ4M+tUvAtMSUWqmuRJ7ud0mFPxFYwNoBPkJbZhPtf7KYUJtiR9shP8gmc1hwAuFRuVHos/VHzMOFSnqZJKKiW0HqEhMBJDlCCtAWEEGM5Ma7jkRnBJtOBqoNyohg5jjqASQTZZsfgaajaKWkmsx85gLroGSzXUWFLWSC1ZI4bxA9zifsp5SneEg6mxhNFgLqnCktc1NnWjWibGtmXjBaezISjJ1C9N+GcEc/bSl5O133Y38Hz6j5c5hTEdGEEx47TBXEqKO9m0eGi7QQ6ig0bnzVbeRf8G8huEwUeb8iVyZ53dq+nnI+/K3EJwMF2oc2d18NGPqTjbsvig19fZ5VusWCz63Tr872KMG6+fIBTZWMq6CzGPx+PxBwAAAP//AwA/OrCDDAUAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUwW7bOBD9FYFnUyEpihIF7MFwWqyRpjESd3exN4ocxkRk0aAop0GRf19Qlmun6GEvgjDkzLx57w1/oF7tATVoeTh8OkIfB7RA0U0hRpjARGBCt7RomGi4yFnJK0H4v2iB3B28oQZVXGuqJeACBGBuSInbwmpsDEhKqCGs5Kmmeh5Q8wMplxs4Og25H/6CMDjfowb97XrjX1Nv5XLd+dHkwXew7oeoep3AfF2v7vCX5Wb7sDndGmBIybkzqEFSVpXRJcUyfXjRMizbWmHGWgDRckkFOaW5PkLoVZcP5uXS3/jYQ9QNy1mRE8wkel8go6JKkFs1wPbtkFBMFN2m+GIKT7/ND3SEgBq2OJN5KnejO3ezH9rRdeYmqvAM0Qa1h1cfXuCoOrRAh+APEKKDiZuHp+wCiZKc5EwwkoDfK71zPWTr2+yhSwMrXRMOpsASrMFcUoUlLwpsilJVTNiqYhYt0B2EHrqrqvdOBz94G7OZ8uxDn5Xvo+tHPw7Zuo/wHFQ85X1W3QBogTZj27lh9wjKvG3945jO+rFLs6zGEKCP2UbFXfanGnZJF1VrqqSSSgmtLZQELDFECVIZEEaA4cxUhktuBJdEC65ayo0qaWtTBC3QwxgPYzzz//0KxDa4/R7MBcF24njTqWh92K8N9NFZl6Q535gM1XXTVNlcc9U56OMHklGDCAVmihqwZVRgXhuFVVlILEQhLFAtK0ZPhFn3PP7kiVhreGHLuhatbCkBZquak8q2xGhbaGnKFipWkaqGWmpC2pLTupWaGKAlN2m64M2o469WKCaBHsc+bWe2TmO/uh5/F/zCyCb46LXvkmCug6e3IcL+d6qt/P7gBxfT/HaW9tsA975Pp/P+z5zNyUsfLzFIe5BNq2dKSgjhGhNVl5iXosWKVwWmINqqJEChSDLeev0CIUsGU66fNDl76uEpWwa9cxF0HEPq/M801EnOz+eVufCRf/20XfkAy8NhMUf/OCaSTsXOBvjwrnwb0vWdH+LVyPNoT9DZM7ArOxk4np4ql4LCmBqMoJhxWmIuJcW1LCvcVnUrW1FDqS92zR7BuAA6TvW2YUzdZmZ/58tliM4qHYe0PF+8nuw0+3O+8ivIK9l+Zp+aX0k3W2muREmy7NPp4Zwc9L/ezff39/f/AAAA//8DAHaYtSMoBgAA
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTQW/cNhCF/4rAsyiTFEWJuhnrFF24iRfJIi16G3FGMWGZWlCUk8DY/x5Qu4GT3nohhIGG8943j68swDOxnt2eTu9eKKSFlSz5raSEMlwYLuRR1r0yfSOrphG6M82/rGT+nr6znrXaOeks8ZoMcY2i4UM9Oo5IVgqJQjU63wlfFta/MvAV0ot3VM3LZ4qLnwPr2d8+4Pw1zwZfuWlesYrzRPuwJAgui/mw393zv24Px4fD5a+FltxceWQ9s7Zt0TWS23zoelDcDh1wpQYiM2grjbi0+ZAoBpiqBZ/e5uOcAiXXq0rVleDKsnPJEBJkyQMsdPx+yio2RHe5Xm7l7bN/ZS8UWa/KnzAv1924yd88L8PqJ7y5nAmWp+zyFOcTxeRpY7LZnCZIfg7FddJu8hQSK9nDp+IwQRrn+PwbqE8X//v/Yf+Yp7/7Rm5NhLt5DYn1TNaWlew9uEcfqNjfsZ4JSQrrjviopOG6Q+DQ1JYbU5uRpLOtkqxkuzkkH9Z5XYp9SPQlbgZYz/6AaaGL9NvoHn0il9aYXf1jchYOccbVpeKNvxSVqGqRRT6s6bSm4iOhj+QSZX/HuF7v+0+LMmpr2q0xUkjFAdJj8ScsjxkKdE6CBQtgnBupETQKFGBEi2TQEGqFLWqr0WgrnNEwSI3QyGHMFVYyyusutoQ16JpukC0ni8C1GiQHJztOVA/KEIxa5FC8cSweptwHrhOasOaWRuTaSuBW1zXHuoFWmbFt1fgLkuv2pciAkV4ub2VTYBA7QiO50rLh2lrJO9u0fGi7wQ6mo8ZlzfcUA02/gHrvXZyXeUzFNTzFb+g+riG/92IL0lcf+LdtR3eze6JY5B2DDzne17Wez+fzDwAAAP//AwBiE6abNgQAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRU0W7cKhD9FYtn4wDG2Pgt2rS6q9w2q3bVVn0bw7hBcfAKcNoo2n+vsLdK83R1X0Zo4Axnzhx4IR4ekfTk+nR694Q+RVKS5NaUYEJRpijjR173QvUNrxrZMaXb76Qk7hafSU9aaQw3GmmNCqm0rKFDPRpqLWrOuGWikbkm/IikfyHgKotPzmA1xy8Yops96clX5+38M98NrjLTvNgqzBPufUzgTSbzcb+7pf9eH453h+1UxJjBlbOkJ1q3rTUNpzoHWQ+C6qEDKsSAqAapuWIbzPmEwcNURfvwer+dk8dkelGJumJUaHIuiYUEmfIAEY/Pp8xilegm58s1vS77F/KEgfSi/CPmVu7KTO7qMQ6Lm+zVFhPEh7jGEUyawzMpySnMJwzJ4arPdYz4OEzPR4gP77czeRnf/UKzJLS7efGJ9ITXipRkFWiaILnZFxeOu8mhT+tmCs5HZ/6rVk1Kcve5OEyQxjk8vpnH503m/f9Q+QOYe+ex2N+QnjCOwtYd0lFwRWVngUJTa6pUrUbkRreCk5LsZp+cX+YlFnuf8EdYWyI9eQ9TxI3fdTD3LqFJS8h9flPZV4cw28Wk4nWWnFWsqllmcrek05KKT2hdQJMwN3EMy6Xel7cQocQK2i0hoE/FAdJ98Q/E+9w5dIaDBg2gjBmxYTgyy0Cx1qKyCq0UtrVSS6ukZkZJGLi00PBhzBlSEszWKTa3Am90yxQFZWoqrVYUYJQUAczQqrZudUa86ljcTRkHpmMSbU01jpZKzYFqWdfU1g20Qo1tK8a/JLn4gbMssMWn7d2tDJS1HVrFqZC8oVJrTjvdtHRou0EPqsPGZAa3GDxOfwn1wZkwx3lMxcUhxRvpPi0+/x3F6pafztNf64xuZvOAocgzBufzU7mM9Xw+n38DAAD//wMAXVqRGIIEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTT2/cIBDFv4rFeXEAY2x8izatukrTrJqorXrDzDiL4sAK4/xRtN+9wrtVkt56sayxH/Pm94ZX4s0Dko6c7/efHtGniaxIcktJMKEoU5TxW151QnU1L+tatLzRv8mKuEt8IR1ppLXcaqQVKqQSWE37arAUADVnHJioZT7T3E2keyXGlYCPzmIZph8YJxc86chP5yE85d7GlXYMM5QxjLjxUzLeZjPfNutL+vV8e3u9Pf414ZTFpQPSEa2bBmzNqc4PWfWC6r41VIgeUfVSc8WOMucTRm/GcoL7t/4QksdkO1GKqmRUaHJYETDJZMu9mfD2ZZ9dLIgucn21lJfX7pU8YiSdWP2FeTzuzI7uzIaHB+PhbHDeTTuyIvsY9hiTwwXHMuE4muSCL05N1qNDn8iKXN8U29GkIcSHD4xujqNv/mPyK2N3zmOxuSAdYRwFVC3SQXBFZQuGmrrSVKlKDcitbgQnK7IOPjk/h3kqNj7hXVxcko58NuOER3/n0e5cQpvmmK3/UjlrfHZpHSAXcu9tDDDbVLzh5qxkZcXyx+s57edUfEdwEW3CPNNtnE/H/yMRSiyi9Rwj+lRsTdoVX8y0yyBMa7nRRhujrB2wZjgwYEaxBlCBQpACGpBagpKaWSVNzyWYmvdDrmTfOd1iWSg0rGGqQdrKvNUWDO11P1DAQbRaDapts+INa3E9Zp2xLZMIFdU4AJWaG6plVVGoatMINTSNGN4hOSXOWeYN+Hi8GosDBdAiKE6F5DWVWnPa6rqhfdP2ulct1jY7uMTocXwH6srZGKYwpOK0MMUHdN9nn693sSzPk/P0eYnsIth7jEWO3Dift/mU8uFwOPwBAAD//wMAq0uqsyUEAAA=
|
||||
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
39
PlotLine/Controllers/DynamicsController.cs
Normal file
39
PlotLine/Controllers/DynamicsController.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlotLine.Services;
|
||||
|
||||
namespace PlotLine.Controllers;
|
||||
|
||||
public sealed class DynamicsController(IDynamicsService dynamics) : Controller
|
||||
{
|
||||
public async Task<IActionResult> Story(int projectId)
|
||||
{
|
||||
var model = await dynamics.GetStoryDynamicsAsync(projectId);
|
||||
return model is null ? NotFound() : View(model);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Character(int id)
|
||||
{
|
||||
var model = await dynamics.GetCharacterArcAsync(id);
|
||||
return model is null ? NotFound() : View(model);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Relationship(int id)
|
||||
{
|
||||
var model = await dynamics.GetRelationshipEvolutionAsync(id);
|
||||
return model is null ? NotFound() : View(model);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Knowledge(int id)
|
||||
{
|
||||
var model = await dynamics.GetKnowledgeTimelineAsync(id);
|
||||
return model is null ? NotFound() : View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RunChecks(int projectId, int? bookId, int? sceneId)
|
||||
{
|
||||
await dynamics.RunContinuityChecksAsync(projectId, bookId, sceneId);
|
||||
return RedirectToAction(nameof(Story), new { projectId });
|
||||
}
|
||||
}
|
||||
@ -224,6 +224,15 @@ public interface IExportRepository
|
||||
Task<ExportPacket> GetSearchResultsAsync(int projectId, string query);
|
||||
}
|
||||
|
||||
public interface IDynamicsRepository
|
||||
{
|
||||
Task<(Character? Character, IReadOnlyList<CharacterArcPoint> ArcPoints, IReadOnlyList<RelationshipEvent> RelationshipEvents, IReadOnlyList<CharacterKnowledgeItem> KnowledgeEvents, IReadOnlyList<DynamicsIssue> Issues)> GetCharacterArcAsync(int characterId);
|
||||
Task<(CharacterRelationship? Relationship, IReadOnlyList<RelationshipProgressionPoint> Events, IReadOnlyList<DynamicsIssue> Issues)> GetRelationshipEvolutionAsync(int characterRelationshipId);
|
||||
Task<(Character? Character, IReadOnlyList<CharacterKnowledgeItem> KnowledgeEvents, IReadOnlyList<DynamicsIssue> Issues)> GetKnowledgeTimelineAsync(int characterId);
|
||||
Task<(IReadOnlyList<CharacterPairingSummary> Pairings, IReadOnlyList<DynamicsIssue> Issues)> GetStoryDynamicsAsync(int projectId);
|
||||
Task RunContinuityChecksAsync(int projectId, int? bookId, int? sceneId);
|
||||
}
|
||||
|
||||
public sealed class TimelineData
|
||||
{
|
||||
public Project? Project { get; init; }
|
||||
@ -838,6 +847,54 @@ public sealed class ExportRepository(ISqlConnectionFactory connectionFactory) :
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DynamicsRepository(ISqlConnectionFactory connectionFactory) : IDynamicsRepository
|
||||
{
|
||||
public async Task<(Character? Character, IReadOnlyList<CharacterArcPoint> ArcPoints, IReadOnlyList<RelationshipEvent> RelationshipEvents, IReadOnlyList<CharacterKnowledgeItem> KnowledgeEvents, IReadOnlyList<DynamicsIssue> Issues)> GetCharacterArcAsync(int characterId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
using var result = await connection.QueryMultipleAsync("dbo.Dynamics_CharacterArc", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
|
||||
return (
|
||||
await result.ReadSingleOrDefaultAsync<Character>(),
|
||||
(await result.ReadAsync<CharacterArcPoint>()).ToList(),
|
||||
(await result.ReadAsync<RelationshipEvent>()).ToList(),
|
||||
(await result.ReadAsync<CharacterKnowledgeItem>()).ToList(),
|
||||
(await result.ReadAsync<DynamicsIssue>()).ToList());
|
||||
}
|
||||
|
||||
public async Task<(CharacterRelationship? Relationship, IReadOnlyList<RelationshipProgressionPoint> Events, IReadOnlyList<DynamicsIssue> Issues)> GetRelationshipEvolutionAsync(int characterRelationshipId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
using var result = await connection.QueryMultipleAsync("dbo.Dynamics_RelationshipEvolution", new { CharacterRelationshipID = characterRelationshipId }, commandType: CommandType.StoredProcedure);
|
||||
return (
|
||||
await result.ReadSingleOrDefaultAsync<CharacterRelationship>(),
|
||||
(await result.ReadAsync<RelationshipProgressionPoint>()).ToList(),
|
||||
(await result.ReadAsync<DynamicsIssue>()).ToList());
|
||||
}
|
||||
|
||||
public async Task<(Character? Character, IReadOnlyList<CharacterKnowledgeItem> KnowledgeEvents, IReadOnlyList<DynamicsIssue> Issues)> GetKnowledgeTimelineAsync(int characterId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
using var result = await connection.QueryMultipleAsync("dbo.Dynamics_KnowledgeTimeline", new { CharacterID = characterId }, commandType: CommandType.StoredProcedure);
|
||||
return (
|
||||
await result.ReadSingleOrDefaultAsync<Character>(),
|
||||
(await result.ReadAsync<CharacterKnowledgeItem>()).ToList(),
|
||||
(await result.ReadAsync<DynamicsIssue>()).ToList());
|
||||
}
|
||||
|
||||
public async Task<(IReadOnlyList<CharacterPairingSummary> Pairings, IReadOnlyList<DynamicsIssue> Issues)> GetStoryDynamicsAsync(int projectId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
using var result = await connection.QueryMultipleAsync("dbo.Dynamics_StoryDashboard", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
|
||||
return ((await result.ReadAsync<CharacterPairingSummary>()).ToList(), (await result.ReadAsync<DynamicsIssue>()).ToList());
|
||||
}
|
||||
|
||||
public async Task RunContinuityChecksAsync(int projectId, int? bookId, int? sceneId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync("dbo.Dynamics_RunContinuityChecks", new { ProjectID = projectId, BookID = bookId, SceneID = sceneId }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SceneDependencyRepository(ISqlConnectionFactory connectionFactory) : ISceneDependencyRepository
|
||||
{
|
||||
public async Task<SceneDependencyLookupData> GetLookupsAsync()
|
||||
|
||||
@ -1365,6 +1365,67 @@ public sealed class ExportPacket
|
||||
public IReadOnlyList<StoryBibleSearchResult> SearchResults { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed class CharacterArcPoint
|
||||
{
|
||||
public int SceneID { get; set; }
|
||||
public string BookTitle { get; set; } = string.Empty;
|
||||
public decimal ChapterNumber { get; set; }
|
||||
public decimal SceneNumber { get; set; }
|
||||
public string SceneTitle { get; set; } = string.Empty;
|
||||
public int GlobalSceneIndex { get; set; }
|
||||
public bool IsPov { get; set; }
|
||||
public int? EmotionalWeight { get; set; }
|
||||
public int? Tension { get; set; }
|
||||
public int? Romance { get; set; }
|
||||
public int? Action { get; set; }
|
||||
public int? Darkness { get; set; }
|
||||
public int? HopeLightness { get; set; }
|
||||
public int? OverallIntensity { get; set; }
|
||||
public string? RoleInSceneTypeName { get; set; }
|
||||
public string? PresenceTypeName { get; set; }
|
||||
public string? EmotionalState { get; set; }
|
||||
public string? AppearanceNotes { get; set; }
|
||||
public string? KeyNotes { get; set; }
|
||||
}
|
||||
|
||||
public sealed class RelationshipProgressionPoint
|
||||
{
|
||||
public int RelationshipEventID { get; set; }
|
||||
public int CharacterRelationshipID { get; set; }
|
||||
public int SceneID { get; set; }
|
||||
public string BookTitle { get; set; } = string.Empty;
|
||||
public decimal ChapterNumber { get; set; }
|
||||
public decimal SceneNumber { get; set; }
|
||||
public string SceneTitle { get; set; } = string.Empty;
|
||||
public int GlobalSceneIndex { get; set; }
|
||||
public string RelationshipStateName { get; set; } = string.Empty;
|
||||
public int? Intensity { get; set; }
|
||||
public bool IsKnownToOtherCharacter { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CharacterPairingSummary
|
||||
{
|
||||
public int CharacterAID { get; set; }
|
||||
public string CharacterAName { get; set; } = string.Empty;
|
||||
public int CharacterBID { get; set; }
|
||||
public string CharacterBName { get; set; } = string.Empty;
|
||||
public int SharedSceneCount { get; set; }
|
||||
public string? FirstSceneLabel { get; set; }
|
||||
public string? LastSceneLabel { get; set; }
|
||||
}
|
||||
|
||||
public sealed class DynamicsIssue
|
||||
{
|
||||
public string Category { get; set; } = string.Empty;
|
||||
public string SeverityName { get; set; } = "Info";
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string? Detail { get; set; }
|
||||
public int? SceneID { get; set; }
|
||||
public int? EntityID { get; set; }
|
||||
public string? EntityType { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AnalyticsBusyScene
|
||||
{
|
||||
public int SceneID { get; set; }
|
||||
|
||||
@ -35,6 +35,7 @@ public class Program
|
||||
builder.Services.AddScoped<IStoryBibleRepository, StoryBibleRepository>();
|
||||
builder.Services.AddScoped<IWriterWorkspaceRepository, WriterWorkspaceRepository>();
|
||||
builder.Services.AddScoped<IExportRepository, ExportRepository>();
|
||||
builder.Services.AddScoped<IDynamicsRepository, DynamicsRepository>();
|
||||
builder.Services.AddScoped<IProjectService, ProjectService>();
|
||||
builder.Services.AddScoped<IBookService, BookService>();
|
||||
builder.Services.AddScoped<IChapterService, ChapterService>();
|
||||
@ -49,6 +50,7 @@ public class Program
|
||||
builder.Services.AddScoped<IStoryBibleService, StoryBibleService>();
|
||||
builder.Services.AddScoped<IWriterWorkspaceService, WriterWorkspaceService>();
|
||||
builder.Services.AddScoped<IExportService, ExportService>();
|
||||
builder.Services.AddScoped<IDynamicsService, DynamicsService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
@ -178,6 +178,15 @@ public interface IExportService
|
||||
Task<(string FileName, string ContentType, string Content)> ExportSearchResultsAsync(int projectId, string query, string format);
|
||||
}
|
||||
|
||||
public interface IDynamicsService
|
||||
{
|
||||
Task<CharacterArcViewModel?> GetCharacterArcAsync(int characterId);
|
||||
Task<RelationshipEvolutionViewModel?> GetRelationshipEvolutionAsync(int characterRelationshipId);
|
||||
Task<KnowledgeTimelineViewModel?> GetKnowledgeTimelineAsync(int characterId);
|
||||
Task<StoryDynamicsViewModel?> GetStoryDynamicsAsync(int projectId);
|
||||
Task RunContinuityChecksAsync(int projectId, int? bookId, int? sceneId);
|
||||
}
|
||||
|
||||
public sealed class ProjectService(IProjectRepository projects, IBookRepository books) : IProjectService
|
||||
{
|
||||
public Task<IReadOnlyList<Project>> ListAsync() => projects.ListAsync();
|
||||
@ -1585,9 +1594,12 @@ public sealed class PlotService(IProjectRepository projects, IBookRepository boo
|
||||
public sealed class ContinuityValidationService(
|
||||
IProjectRepository projects,
|
||||
IBookRepository books,
|
||||
IChapterRepository chapters,
|
||||
ISceneRepository scenes,
|
||||
IAssetRepository assets,
|
||||
ICharacterRepository characters,
|
||||
IWarningRepository warnings) : IContinuityValidationService
|
||||
IWarningRepository warnings,
|
||||
IDynamicsRepository dynamics) : IContinuityValidationService
|
||||
{
|
||||
public async Task<WarningDashboardViewModel?> GetDashboardAsync(
|
||||
int projectId,
|
||||
@ -1632,6 +1644,7 @@ public sealed class ContinuityValidationService(
|
||||
public async Task<ContinuityValidationSummary> ValidateProjectAsync(int projectId)
|
||||
{
|
||||
var summary = await warnings.ValidateProjectAsync(projectId);
|
||||
await dynamics.RunContinuityChecksAsync(projectId, null, null);
|
||||
await warnings.FinaliseSceneDependencyWarningsAsync(projectId, null);
|
||||
return summary;
|
||||
}
|
||||
@ -1642,6 +1655,7 @@ public sealed class ContinuityValidationService(
|
||||
var book = await books.GetAsync(bookId);
|
||||
if (book is not null)
|
||||
{
|
||||
await dynamics.RunContinuityChecksAsync(book.ProjectID, bookId, null);
|
||||
await warnings.FinaliseSceneDependencyWarningsAsync(book.ProjectID, bookId);
|
||||
}
|
||||
return summary;
|
||||
@ -1650,22 +1664,40 @@ public sealed class ContinuityValidationService(
|
||||
public async Task<ContinuityValidationSummary> ValidateSceneAsync(int sceneId)
|
||||
{
|
||||
var summary = await warnings.ValidateSceneAsync(sceneId);
|
||||
var scene = await scenes.GetAsync(sceneId);
|
||||
if (scene is not null)
|
||||
{
|
||||
var chapter = await chapters.GetAsync(scene.ChapterID);
|
||||
if (chapter is not null)
|
||||
{
|
||||
var book = await books.GetAsync(chapter.BookID);
|
||||
if (book is not null)
|
||||
{
|
||||
await dynamics.RunContinuityChecksAsync(book.ProjectID, book.BookID, sceneId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
public Task<ContinuityValidationSummary> ValidateAfterSceneMoveAsync(int projectId, int? bookId) =>
|
||||
bookId.HasValue ? warnings.ValidateBookAsync(bookId.Value) : warnings.ValidateProjectAsync(projectId);
|
||||
public async Task<ContinuityValidationSummary> ValidateAfterSceneMoveAsync(int projectId, int? bookId)
|
||||
{
|
||||
var summary = bookId.HasValue ? await warnings.ValidateBookAsync(bookId.Value) : await warnings.ValidateProjectAsync(projectId);
|
||||
await dynamics.RunContinuityChecksAsync(projectId, bookId, null);
|
||||
await warnings.FinaliseSceneDependencyWarningsAsync(projectId, bookId);
|
||||
return summary;
|
||||
}
|
||||
|
||||
public async Task<ContinuityValidationSummary> ValidateAssetAsync(int assetId)
|
||||
{
|
||||
var asset = await assets.GetAssetAsync(assetId);
|
||||
return asset is null ? new ContinuityValidationSummary() : await warnings.ValidateProjectAsync(asset.ProjectID);
|
||||
return asset is null ? new ContinuityValidationSummary() : await ValidateProjectAsync(asset.ProjectID);
|
||||
}
|
||||
|
||||
public async Task<ContinuityValidationSummary> ValidateCharacterAsync(int characterId)
|
||||
{
|
||||
var character = await characters.GetCharacterAsync(characterId);
|
||||
return character is null ? new ContinuityValidationSummary() : await warnings.ValidateProjectAsync(character.ProjectID);
|
||||
return character is null ? new ContinuityValidationSummary() : await ValidateProjectAsync(character.ProjectID);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<ContinuityWarning>> ListSceneWarningsAsync(int sceneId) => warnings.ListBySceneAsync(sceneId);
|
||||
@ -2176,6 +2208,85 @@ public sealed class ExportService(IExportRepository exports) : IExportService
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DynamicsService(IProjectRepository projects, IDynamicsRepository dynamics) : IDynamicsService
|
||||
{
|
||||
public async Task<CharacterArcViewModel?> GetCharacterArcAsync(int characterId)
|
||||
{
|
||||
var data = await dynamics.GetCharacterArcAsync(characterId);
|
||||
if (data.Character is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var project = await projects.GetAsync(data.Character.ProjectID);
|
||||
return project is null ? null : new CharacterArcViewModel
|
||||
{
|
||||
Project = project,
|
||||
Character = data.Character,
|
||||
ArcPoints = data.ArcPoints,
|
||||
RelationshipEvents = data.RelationshipEvents,
|
||||
KnowledgeEvents = data.KnowledgeEvents,
|
||||
Issues = data.Issues
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<RelationshipEvolutionViewModel?> GetRelationshipEvolutionAsync(int characterRelationshipId)
|
||||
{
|
||||
var data = await dynamics.GetRelationshipEvolutionAsync(characterRelationshipId);
|
||||
if (data.Relationship is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var project = await projects.GetAsync(data.Relationship.ProjectID);
|
||||
return project is null ? null : new RelationshipEvolutionViewModel
|
||||
{
|
||||
Project = project,
|
||||
Relationship = data.Relationship,
|
||||
Events = data.Events,
|
||||
Issues = data.Issues
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<KnowledgeTimelineViewModel?> GetKnowledgeTimelineAsync(int characterId)
|
||||
{
|
||||
var data = await dynamics.GetKnowledgeTimelineAsync(characterId);
|
||||
if (data.Character is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var project = await projects.GetAsync(data.Character.ProjectID);
|
||||
return project is null ? null : new KnowledgeTimelineViewModel
|
||||
{
|
||||
Project = project,
|
||||
Character = data.Character,
|
||||
KnowledgeEvents = data.KnowledgeEvents,
|
||||
Issues = data.Issues
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<StoryDynamicsViewModel?> GetStoryDynamicsAsync(int projectId)
|
||||
{
|
||||
var project = await projects.GetAsync(projectId);
|
||||
if (project is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = await dynamics.GetStoryDynamicsAsync(projectId);
|
||||
return new StoryDynamicsViewModel
|
||||
{
|
||||
Project = project,
|
||||
Pairings = data.Pairings,
|
||||
Issues = data.Issues
|
||||
};
|
||||
}
|
||||
|
||||
public Task RunContinuityChecksAsync(int projectId, int? bookId, int? sceneId) =>
|
||||
dynamics.RunContinuityChecksAsync(projectId, bookId, sceneId);
|
||||
}
|
||||
|
||||
public sealed class AssetService(IProjectRepository projects, IAssetRepository assets, ILocationRepository locations) : IAssetService
|
||||
{
|
||||
public async Task<StoryAssetListViewModel?> GetAssetsAsync(int projectId)
|
||||
|
||||
322
PlotLine/Sql/018_Phase1R_Dynamics.sql
Normal file
322
PlotLine/Sql/018_Phase1R_Dynamics.sql
Normal file
@ -0,0 +1,322 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
MERGE dbo.WarningTypes AS target
|
||||
USING (VALUES
|
||||
(N'Knowledge Continuity', N'Character may act on information before a recorded knowledge event.', 220),
|
||||
(N'Abrupt Relationship Change', N'Relationship intensity changes sharply between nearby scenes.', 230),
|
||||
(N'Long Character Absence', N'Character disappears for a long stretch of narrative scenes.', 240),
|
||||
(N'Character Underused', N'Character appears lightly compared with their story connections.', 250),
|
||||
(N'Scene Character Overload', N'Scene contains a large number of character appearances.', 260)
|
||||
) AS source (TypeName, Description, SortOrder)
|
||||
ON target.TypeName = source.TypeName
|
||||
WHEN MATCHED THEN UPDATE SET Description = source.Description, SortOrder = source.SortOrder, IsActive = 1
|
||||
WHEN NOT MATCHED THEN INSERT (TypeName, Description, SortOrder, IsActive) VALUES (source.TypeName, source.Description, source.SortOrder, 1);
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Dynamics_CharacterArc
|
||||
@CharacterID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @ProjectID int = (SELECT ProjectID FROM dbo.Characters WHERE CharacterID = @CharacterID);
|
||||
|
||||
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||
Height, EyeColour, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||
FROM dbo.Characters
|
||||
WHERE CharacterID = @CharacterID;
|
||||
|
||||
SELECT sc.SceneID, nso.BookTitle, nso.ChapterNumber, nso.SceneNumber, nso.SceneTitle, nso.GlobalSceneIndex,
|
||||
CAST(CASE WHEN COALESCE(s.POVCharacterID, c.POVCharacterID) = @CharacterID THEN 1 ELSE 0 END AS bit) AS IsPov,
|
||||
MAX(CASE WHEN smt.MetricName = N'Emotional Weight' THEN smv.Value END) AS EmotionalWeight,
|
||||
MAX(CASE WHEN smt.MetricName = N'Tension' THEN smv.Value END) AS Tension,
|
||||
MAX(CASE WHEN smt.MetricName = N'Romance' THEN smv.Value END) AS Romance,
|
||||
MAX(CASE WHEN smt.MetricName = N'Action' THEN smv.Value END) AS Action,
|
||||
MAX(CASE WHEN smt.MetricName = N'Darkness' THEN smv.Value END) AS Darkness,
|
||||
MAX(CASE WHEN smt.MetricName = N'Hope / Lightness' THEN smv.Value END) AS HopeLightness,
|
||||
MAX(CASE WHEN smt.MetricName = N'Overall Intensity' THEN smv.Value END) AS OverallIntensity,
|
||||
role.TypeName AS RoleInSceneTypeName,
|
||||
presence.TypeName AS PresenceTypeName,
|
||||
sc.EmotionalState,
|
||||
sc.AppearanceNotes,
|
||||
notes.KeyNotes
|
||||
FROM dbo.SceneCharacters sc
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = sc.SceneID
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
|
||||
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
|
||||
LEFT JOIN dbo.SceneMetricValues smv ON smv.SceneID = sc.SceneID
|
||||
LEFT JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
|
||||
OUTER APPLY
|
||||
(
|
||||
SELECT STRING_AGG(CONVERT(nvarchar(max), COALESCE(sn.NoteTitle, sn.NoteText)), N' | ') AS KeyNotes
|
||||
FROM dbo.SceneNotes sn
|
||||
INNER JOIN dbo.SceneNoteTypes snt ON snt.SceneNoteTypeID = sn.SceneNoteTypeID
|
||||
WHERE sn.SceneID = sc.SceneID AND sn.IsResolved = 0
|
||||
AND snt.TypeName IN (N'Emotional Intent', N'Character Reminder', N'Revision Note', N'TODO')
|
||||
) notes
|
||||
WHERE sc.CharacterID = @CharacterID
|
||||
GROUP BY sc.SceneID, nso.BookTitle, nso.ChapterNumber, nso.SceneNumber, nso.SceneTitle, nso.GlobalSceneIndex,
|
||||
s.POVCharacterID, c.POVCharacterID, role.TypeName, presence.TypeName, sc.EmotionalState, sc.AppearanceNotes, notes.KeyNotes
|
||||
ORDER BY nso.GlobalSceneIndex;
|
||||
|
||||
EXEC dbo.RelationshipEvent_ListByCharacter @CharacterID;
|
||||
EXEC dbo.CharacterKnowledge_ListByCharacter @CharacterID;
|
||||
|
||||
SELECT N'Character Presence' AS Category, ws.SeverityName, cw.Message AS Title, cw.Details AS Detail, cw.SceneID, cw.EntityID, cw.EntityType
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||
WHERE cw.ProjectID = @ProjectID AND cw.IsDismissed = 0
|
||||
AND (
|
||||
(cw.EntityType = N'Character' AND cw.EntityID = @CharacterID)
|
||||
OR cw.SceneID IN (SELECT SceneID FROM dbo.SceneCharacters WHERE CharacterID = @CharacterID)
|
||||
)
|
||||
AND wt.TypeName IN (N'Knowledge Continuity', N'Long Character Absence', N'Character Underused', N'Scene Character Overload');
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Dynamics_RelationshipEvolution
|
||||
@CharacterRelationshipID 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.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
|
||||
WHERE cr.CharacterRelationshipID = @CharacterRelationshipID;
|
||||
|
||||
SELECT re.RelationshipEventID, re.CharacterRelationshipID, re.SceneID, nso.BookTitle, nso.ChapterNumber, nso.SceneNumber,
|
||||
nso.SceneTitle, nso.GlobalSceneIndex, rs.StateName AS RelationshipStateName, re.Intensity, re.IsKnownToOtherCharacter, re.Description
|
||||
FROM dbo.RelationshipEvents re
|
||||
INNER JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = re.SceneID
|
||||
WHERE re.CharacterRelationshipID = @CharacterRelationshipID
|
||||
ORDER BY nso.GlobalSceneIndex, re.RelationshipEventID;
|
||||
|
||||
SELECT N'Relationship Continuity' AS Category, ws.SeverityName, cw.Message AS Title, cw.Details AS Detail, cw.SceneID, cw.EntityID, cw.EntityType
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||
WHERE cw.IsDismissed = 0 AND cw.EntityType = N'Relationship' AND cw.EntityID = @CharacterRelationshipID
|
||||
AND wt.TypeName = N'Abrupt Relationship Change';
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Dynamics_KnowledgeTimeline
|
||||
@CharacterID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
DECLARE @ProjectID int = (SELECT ProjectID FROM dbo.Characters WHERE CharacterID = @CharacterID);
|
||||
|
||||
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
|
||||
Height, EyeColour, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
||||
FROM dbo.Characters
|
||||
WHERE CharacterID = @CharacterID;
|
||||
|
||||
EXEC dbo.CharacterKnowledge_ListByCharacter @CharacterID;
|
||||
|
||||
SELECT N'Knowledge Continuity' AS Category, ws.SeverityName, cw.Message AS Title, cw.Details AS Detail, cw.SceneID, cw.EntityID, cw.EntityType
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||
WHERE cw.ProjectID = @ProjectID AND cw.IsDismissed = 0
|
||||
AND cw.EntityType = N'Character' AND cw.EntityID = @CharacterID
|
||||
AND wt.TypeName = N'Knowledge Continuity';
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Dynamics_StoryDashboard
|
||||
@ProjectID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
;WITH Pairings AS
|
||||
(
|
||||
SELECT a.CharacterID AS CharacterAID, ca.CharacterName AS CharacterAName,
|
||||
b.CharacterID AS CharacterBID, cb.CharacterName AS CharacterBName,
|
||||
a.SceneID, nso.GlobalSceneIndex,
|
||||
CONCAT(nso.BookTitle, N' / Ch ', nso.ChapterNumber, N' / Scene ', nso.SceneNumber, N': ', nso.SceneTitle) AS SceneLabel
|
||||
FROM dbo.SceneCharacters a
|
||||
INNER JOIN dbo.SceneCharacters b ON b.SceneID = a.SceneID AND b.CharacterID > a.CharacterID
|
||||
INNER JOIN dbo.Characters ca ON ca.CharacterID = a.CharacterID
|
||||
INNER JOIN dbo.Characters cb ON cb.CharacterID = b.CharacterID
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = a.SceneID
|
||||
WHERE ca.ProjectID = @ProjectID AND cb.ProjectID = @ProjectID AND ca.IsArchived = 0 AND cb.IsArchived = 0
|
||||
),
|
||||
Ranked AS
|
||||
(
|
||||
SELECT CharacterAID, CharacterAName, CharacterBID, CharacterBName, COUNT(*) AS SharedSceneCount,
|
||||
MIN(GlobalSceneIndex) AS FirstIndex, MAX(GlobalSceneIndex) AS LastIndex
|
||||
FROM Pairings
|
||||
GROUP BY CharacterAID, CharacterAName, CharacterBID, CharacterBName
|
||||
)
|
||||
SELECT TOP (40) r.CharacterAID, r.CharacterAName, r.CharacterBID, r.CharacterBName, r.SharedSceneCount,
|
||||
firstScene.SceneLabel AS FirstSceneLabel,
|
||||
lastScene.SceneLabel AS LastSceneLabel
|
||||
FROM Ranked r
|
||||
OUTER APPLY (SELECT TOP (1) SceneLabel FROM Pairings p WHERE p.CharacterAID = r.CharacterAID AND p.CharacterBID = r.CharacterBID ORDER BY p.GlobalSceneIndex) firstScene
|
||||
OUTER APPLY (SELECT TOP (1) SceneLabel FROM Pairings p WHERE p.CharacterAID = r.CharacterAID AND p.CharacterBID = r.CharacterBID ORDER BY p.GlobalSceneIndex DESC) lastScene
|
||||
ORDER BY r.SharedSceneCount DESC, r.CharacterAName, r.CharacterBName;
|
||||
|
||||
SELECT Category, SeverityName, Title, Detail, SceneID, EntityID, EntityType
|
||||
FROM
|
||||
(
|
||||
SELECT N'Continuity' AS Category, ws.SeverityName, cw.Message AS Title, cw.Details AS Detail, cw.SceneID, cw.EntityID, cw.EntityType
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||
WHERE cw.ProjectID = @ProjectID AND cw.IsDismissed = 0
|
||||
AND wt.TypeName IN (N'Knowledge Continuity', N'Abrupt Relationship Change', N'Long Character Absence', N'Character Underused', N'Scene Character Overload')
|
||||
) issues
|
||||
ORDER BY SeverityName DESC, Title;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Dynamics_RunContinuityChecks
|
||||
@ProjectID int,
|
||||
@BookID int = NULL,
|
||||
@SceneID int = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
;WITH RelationshipSteps AS
|
||||
(
|
||||
SELECT cr.CharacterRelationshipID, cr.ProjectID, nso.BookID, nso.ChapterID, re.SceneID,
|
||||
ca.CharacterName AS CharacterAName, cb.CharacterName AS CharacterBName, re.Intensity,
|
||||
LAG(re.Intensity) OVER (PARTITION BY cr.CharacterRelationshipID ORDER BY nso.GlobalSceneIndex, re.RelationshipEventID) AS PreviousIntensity,
|
||||
LAG(nso.SceneTitle) OVER (PARTITION BY cr.CharacterRelationshipID ORDER BY nso.GlobalSceneIndex, re.RelationshipEventID) AS PreviousSceneTitle
|
||||
FROM dbo.RelationshipEvents re
|
||||
INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID AND cr.IsArchived = 0
|
||||
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID
|
||||
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = re.SceneID
|
||||
WHERE cr.ProjectID = @ProjectID
|
||||
)
|
||||
INSERT dbo.ContinuityWarnings (ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
|
||||
SELECT rs.ProjectID, rs.BookID, rs.ChapterID, rs.SceneID, N'Relationship', rs.CharacterRelationshipID,
|
||||
wt.WarningTypeID, ws.WarningSeverityID,
|
||||
N'Possible emotional transition issue. Relationship intensity changes sharply between scenes.',
|
||||
CONCAT(rs.CharacterAName, N' / ', rs.CharacterBName, N' changes from ', rs.PreviousIntensity, N' to ', rs.Intensity, N'. Previous scene: ', rs.PreviousSceneTitle, N'.')
|
||||
FROM RelationshipSteps rs
|
||||
CROSS JOIN dbo.WarningTypes wt
|
||||
CROSS JOIN dbo.WarningSeverities ws
|
||||
WHERE wt.TypeName = N'Abrupt Relationship Change' AND ws.SeverityName = N'Info'
|
||||
AND rs.Intensity IS NOT NULL AND rs.PreviousIntensity IS NOT NULL AND ABS(rs.Intensity - rs.PreviousIntensity) >= 5
|
||||
AND (@BookID IS NULL OR rs.BookID = @BookID) AND (@SceneID IS NULL OR rs.SceneID = @SceneID)
|
||||
AND NOT EXISTS (SELECT 1 FROM dbo.ContinuityWarnings existing WHERE existing.ProjectID = rs.ProjectID AND existing.SceneID = rs.SceneID AND existing.EntityType = N'Relationship' AND existing.EntityID = rs.CharacterRelationshipID AND existing.WarningTypeID = wt.WarningTypeID AND existing.IsDismissed = 0 AND existing.IsIntentional = 0);
|
||||
|
||||
;WITH Appearances AS
|
||||
(
|
||||
SELECT sc.CharacterID, ch.CharacterName, nso.ProjectID, nso.BookID, nso.ChapterID, sc.SceneID, nso.GlobalSceneIndex,
|
||||
LAG(nso.GlobalSceneIndex) OVER (PARTITION BY sc.CharacterID ORDER BY nso.GlobalSceneIndex) AS PreviousIndex
|
||||
FROM dbo.SceneCharacters sc
|
||||
INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
|
||||
WHERE ch.ProjectID = @ProjectID AND ch.IsArchived = 0
|
||||
)
|
||||
INSERT dbo.ContinuityWarnings (ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
|
||||
SELECT ProjectID, BookID, ChapterID, SceneID, N'Character', CharacterID, wt.WarningTypeID, ws.WarningSeverityID,
|
||||
CONCAT(N'Possible review: ', CharacterName, N' is absent for ', GlobalSceneIndex - PreviousIndex - 1, N' narrative scenes.'),
|
||||
N'Long absences are sometimes useful, but this may be worth checking after a major beat.'
|
||||
FROM Appearances
|
||||
CROSS JOIN dbo.WarningTypes wt
|
||||
CROSS JOIN dbo.WarningSeverities ws
|
||||
WHERE wt.TypeName = N'Long Character Absence' AND ws.SeverityName = N'Info'
|
||||
AND PreviousIndex IS NOT NULL AND GlobalSceneIndex - PreviousIndex - 1 >= 12
|
||||
AND (@BookID IS NULL OR BookID = @BookID) AND (@SceneID IS NULL OR SceneID = @SceneID)
|
||||
AND NOT EXISTS (SELECT 1 FROM dbo.ContinuityWarnings existing WHERE existing.ProjectID = Appearances.ProjectID AND existing.SceneID = Appearances.SceneID AND existing.EntityType = N'Character' AND existing.EntityID = Appearances.CharacterID AND existing.WarningTypeID = wt.WarningTypeID AND existing.IsDismissed = 0 AND existing.IsIntentional = 0);
|
||||
|
||||
;WITH CharacterUsage AS
|
||||
(
|
||||
SELECT ch.ProjectID, ch.CharacterID, ch.CharacterName,
|
||||
COUNT(DISTINCT sc.SceneID) AS AppearanceCount,
|
||||
COUNT(DISTINCT cr.CharacterRelationshipID) AS RelationshipCount
|
||||
FROM dbo.Characters ch
|
||||
LEFT JOIN dbo.SceneCharacters sc ON sc.CharacterID = ch.CharacterID
|
||||
LEFT JOIN dbo.CharacterRelationships cr ON cr.ProjectID = ch.ProjectID
|
||||
AND cr.IsArchived = 0
|
||||
AND ch.CharacterID IN (cr.CharacterAID, cr.CharacterBID)
|
||||
WHERE ch.ProjectID = @ProjectID AND ch.IsArchived = 0 AND @SceneID IS NULL
|
||||
GROUP BY ch.ProjectID, ch.CharacterID, ch.CharacterName
|
||||
HAVING COUNT(DISTINCT cr.CharacterRelationshipID) >= 2 AND COUNT(DISTINCT sc.SceneID) <= 1
|
||||
)
|
||||
INSERT dbo.ContinuityWarnings (ProjectID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
|
||||
SELECT ProjectID, N'Character', CharacterID, wt.WarningTypeID, ws.WarningSeverityID,
|
||||
CONCAT(N'Possible review: ', CharacterName, N' has several relationship links but very few scene appearances.'),
|
||||
N'This may be intentional, but the character could be underused relative to their story connections.'
|
||||
FROM CharacterUsage
|
||||
CROSS JOIN dbo.WarningTypes wt
|
||||
CROSS JOIN dbo.WarningSeverities ws
|
||||
WHERE wt.TypeName = N'Character Underused' AND ws.SeverityName = N'Info'
|
||||
AND NOT EXISTS (SELECT 1 FROM dbo.ContinuityWarnings existing WHERE existing.ProjectID = CharacterUsage.ProjectID AND existing.EntityType = N'Character' AND existing.EntityID = CharacterUsage.CharacterID AND existing.WarningTypeID = wt.WarningTypeID AND existing.IsDismissed = 0 AND existing.IsIntentional = 0);
|
||||
|
||||
;WITH SceneCounts AS
|
||||
(
|
||||
SELECT nso.ProjectID, nso.BookID, nso.ChapterID, nso.SceneID, nso.SceneTitle, COUNT(*) AS CharacterCount
|
||||
FROM dbo.SceneCharacters sc
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
|
||||
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR nso.SceneID = @SceneID)
|
||||
GROUP BY nso.ProjectID, nso.BookID, nso.ChapterID, nso.SceneID, nso.SceneTitle
|
||||
HAVING COUNT(*) >= 7
|
||||
)
|
||||
INSERT dbo.ContinuityWarnings (ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
|
||||
SELECT ProjectID, BookID, ChapterID, SceneID, N'Scene', SceneID, wt.WarningTypeID, ws.WarningSeverityID,
|
||||
CONCAT(N'Possible review: this scene has ', CharacterCount, N' character appearances.'),
|
||||
N'Busy scenes can work well, but may need extra clarity around focus and blocking.'
|
||||
FROM SceneCounts
|
||||
CROSS JOIN dbo.WarningTypes wt
|
||||
CROSS JOIN dbo.WarningSeverities ws
|
||||
WHERE wt.TypeName = N'Scene Character Overload' AND ws.SeverityName = N'Info'
|
||||
AND NOT EXISTS (SELECT 1 FROM dbo.ContinuityWarnings existing WHERE existing.ProjectID = SceneCounts.ProjectID AND existing.SceneID = SceneCounts.SceneID AND existing.EntityType = N'Scene' AND existing.EntityID = SceneCounts.SceneID AND existing.WarningTypeID = wt.WarningTypeID AND existing.IsDismissed = 0 AND existing.IsIntentional = 0);
|
||||
|
||||
;WITH Knowledge AS
|
||||
(
|
||||
SELECT ck.CharacterID, ch.CharacterName, ck.StoryAssetID, ck.PlotThreadID, ck.SceneID, nso.ProjectID, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex,
|
||||
MIN(nso.GlobalSceneIndex) OVER (PARTITION BY ck.CharacterID, ISNULL(ck.StoryAssetID, 0), ISNULL(ck.PlotThreadID, 0)) AS FirstKnownIndex
|
||||
FROM dbo.CharacterKnowledge ck
|
||||
INNER JOIN dbo.Characters ch ON ch.CharacterID = ck.CharacterID
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ck.SceneID
|
||||
WHERE ch.ProjectID = @ProjectID
|
||||
),
|
||||
Candidate AS
|
||||
(
|
||||
SELECT DISTINCT sc.CharacterID, ch.CharacterName, te.PlotThreadID, ae.StoryAssetID, nso.ProjectID, nso.BookID, nso.ChapterID, sc.SceneID, nso.GlobalSceneIndex
|
||||
FROM dbo.SceneCharacters sc
|
||||
INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
|
||||
LEFT JOIN dbo.ThreadEvents te ON te.SceneID = sc.SceneID
|
||||
LEFT JOIN dbo.AssetEvents ae ON ae.SceneID = sc.SceneID
|
||||
WHERE ch.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR sc.SceneID = @SceneID)
|
||||
AND (te.PlotThreadID IS NOT NULL OR ae.StoryAssetID IS NOT NULL)
|
||||
)
|
||||
INSERT dbo.ContinuityWarnings (ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details)
|
||||
SELECT c.ProjectID, c.BookID, c.ChapterID, c.SceneID, N'Character', c.CharacterID, wt.WarningTypeID, ws.WarningSeverityID,
|
||||
CONCAT(N'Possible review: ', c.CharacterName, N' appears in a scene involving information without an earlier knowledge event.'),
|
||||
N'This is advisory only. Add a knowledge event if the character is meant to know this, or mark the warning intentional.'
|
||||
FROM Candidate c
|
||||
CROSS JOIN dbo.WarningTypes wt
|
||||
CROSS JOIN dbo.WarningSeverities ws
|
||||
WHERE wt.TypeName = N'Knowledge Continuity' AND ws.SeverityName = N'Info'
|
||||
AND NOT EXISTS
|
||||
(
|
||||
SELECT 1 FROM Knowledge k
|
||||
WHERE k.CharacterID = c.CharacterID
|
||||
AND ((c.StoryAssetID IS NOT NULL AND k.StoryAssetID = c.StoryAssetID) OR (c.PlotThreadID IS NOT NULL AND k.PlotThreadID = c.PlotThreadID))
|
||||
AND k.FirstKnownIndex <= c.GlobalSceneIndex
|
||||
)
|
||||
AND NOT EXISTS (SELECT 1 FROM dbo.ContinuityWarnings existing WHERE existing.ProjectID = c.ProjectID AND existing.SceneID = c.SceneID AND existing.EntityType = N'Character' AND existing.EntityID = c.CharacterID AND existing.WarningTypeID = wt.WarningTypeID AND existing.IsDismissed = 0 AND existing.IsIntentional = 0);
|
||||
END;
|
||||
GO
|
||||
@ -284,6 +284,39 @@ public sealed class WriterDashboardViewModel
|
||||
public IReadOnlyList<StoryBibleAttentionItem> StoryHealthReminders { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class CharacterArcViewModel
|
||||
{
|
||||
public Project Project { get; set; } = new();
|
||||
public Character Character { get; set; } = new();
|
||||
public IReadOnlyList<CharacterArcPoint> ArcPoints { get; set; } = [];
|
||||
public IReadOnlyList<RelationshipEvent> RelationshipEvents { get; set; } = [];
|
||||
public IReadOnlyList<CharacterKnowledgeItem> KnowledgeEvents { get; set; } = [];
|
||||
public IReadOnlyList<DynamicsIssue> Issues { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class RelationshipEvolutionViewModel
|
||||
{
|
||||
public Project Project { get; set; } = new();
|
||||
public CharacterRelationship Relationship { get; set; } = new();
|
||||
public IReadOnlyList<RelationshipProgressionPoint> Events { get; set; } = [];
|
||||
public IReadOnlyList<DynamicsIssue> Issues { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class KnowledgeTimelineViewModel
|
||||
{
|
||||
public Project Project { get; set; } = new();
|
||||
public Character Character { get; set; } = new();
|
||||
public IReadOnlyList<CharacterKnowledgeItem> KnowledgeEvents { get; set; } = [];
|
||||
public IReadOnlyList<DynamicsIssue> Issues { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class StoryDynamicsViewModel
|
||||
{
|
||||
public Project Project { get; set; } = new();
|
||||
public IReadOnlyList<CharacterPairingSummary> Pairings { get; set; } = [];
|
||||
public IReadOnlyList<DynamicsIssue> Issues { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class TimelineViewModel
|
||||
{
|
||||
public Project Project { get; set; } = new();
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">Project</a>
|
||||
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID">Timeline</a>
|
||||
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-bookId="@Model.BookID">Warnings</a>
|
||||
<a asp-controller="Dynamics" asp-action="Story" asp-route-projectId="@Model.Project.ProjectID">Story dynamics</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>
|
||||
|
||||
@ -33,6 +33,8 @@
|
||||
<div class="button-row">
|
||||
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
|
||||
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@firstAppearance?.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Back to timeline</a>
|
||||
<a class="btn btn-outline-primary" asp-controller="Dynamics" asp-action="Character" asp-route-id="@Model.Character.CharacterID">Character arc</a>
|
||||
<a class="btn btn-outline-primary" asp-controller="Dynamics" asp-action="Knowledge" asp-route-id="@Model.Character.CharacterID">Knowledge timeline</a>
|
||||
<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>
|
||||
@ -62,6 +64,7 @@
|
||||
<div class="asset-mini-item">
|
||||
<strong>@relationship.CharacterAName -> @relationship.CharacterBName</strong>
|
||||
<span>@relationship.RelationshipTypeName</span>
|
||||
<a asp-controller="Dynamics" asp-action="Relationship" asp-route-id="@relationship.CharacterRelationshipID">Evolution</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
136
PlotLine/Views/Dynamics/Character.cshtml
Normal file
136
PlotLine/Views/Dynamics/Character.cshtml
Normal file
@ -0,0 +1,136 @@
|
||||
@model CharacterArcViewModel
|
||||
@{
|
||||
ViewData["Title"] = $"{Model.Character.CharacterName} Arc";
|
||||
var arcScenes = Model.ArcPoints
|
||||
.OrderBy(point => point.GlobalSceneIndex)
|
||||
.Select(point => new
|
||||
{
|
||||
sceneId = point.SceneID,
|
||||
axisLabel = $"Ch {point.ChapterNumber:g}.{point.SceneNumber:g}",
|
||||
chapterLabel = $"Chapter {point.ChapterNumber:g}",
|
||||
sceneNumber = point.SceneNumber.ToString("g"),
|
||||
sceneTitle = point.SceneTitle,
|
||||
bookTitle = point.BookTitle,
|
||||
characterAppears = true,
|
||||
characterPresence = point.IsPov ? "POV scene" : point.PresenceTypeName ?? "Appears",
|
||||
timelineUrl = Url.Action("Edit", "Scenes", new { id = point.SceneID })
|
||||
})
|
||||
.ToList();
|
||||
var relationshipIntensityPoints = Model.RelationshipEvents
|
||||
.Where(item => item.Intensity.HasValue)
|
||||
.GroupBy(item => item.SceneID)
|
||||
.Select(group => new { sceneId = group.Key, value = Convert.ToInt32(Math.Round(group.Average(item => item.Intensity!.Value))) });
|
||||
var arcGraphMetrics = new[]
|
||||
{
|
||||
new { metricName = "Overall Intensity", points = Model.ArcPoints.Where(point => point.OverallIntensity.HasValue).Select(point => new { sceneId = point.SceneID, value = point.OverallIntensity!.Value }) },
|
||||
new { metricName = "Emotional Weight", points = Model.ArcPoints.Where(point => point.EmotionalWeight.HasValue).Select(point => new { sceneId = point.SceneID, value = point.EmotionalWeight!.Value }) },
|
||||
new { metricName = "Tension", points = Model.ArcPoints.Where(point => point.Tension.HasValue).Select(point => new { sceneId = point.SceneID, value = point.Tension!.Value }) },
|
||||
new { metricName = "Romance", points = Model.ArcPoints.Where(point => point.Romance.HasValue).Select(point => new { sceneId = point.SceneID, value = point.Romance!.Value }) },
|
||||
new { metricName = "Action", points = Model.ArcPoints.Where(point => point.Action.HasValue).Select(point => new { sceneId = point.SceneID, value = point.Action!.Value }) },
|
||||
new { metricName = "Darkness", points = Model.ArcPoints.Where(point => point.Darkness.HasValue).Select(point => new { sceneId = point.SceneID, value = point.Darkness!.Value }) },
|
||||
new { metricName = "Hope / Lightness", points = Model.ArcPoints.Where(point => point.HopeLightness.HasValue).Select(point => new { sceneId = point.SceneID, value = point.HopeLightness!.Value }) },
|
||||
new { metricName = "Relationship Intensity", points = relationshipIntensityPoints }
|
||||
}
|
||||
.Where(metric => metric.points.Any())
|
||||
.ToList();
|
||||
var arcGraphPayload = new
|
||||
{
|
||||
defaultMetrics = new[] { "Overall Intensity", "Emotional Weight", "Tension" },
|
||||
scenes = arcScenes,
|
||||
metrics = arcGraphMetrics
|
||||
};
|
||||
}
|
||||
|
||||
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
||||
<a asp-controller="Characters" asp-action="Details" asp-route-id="@Model.Character.CharacterID">@Model.Character.CharacterName</a>
|
||||
<span>Character arc</span>
|
||||
</nav>
|
||||
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Character Arc</p>
|
||||
<h1>@Model.Character.CharacterName</h1>
|
||||
<p class="lead-text">Appearances, emotional shape, relationship changes, knowledge gains, and advisory notes.</p>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<a class="btn btn-outline-primary" asp-controller="Dynamics" asp-action="Knowledge" asp-route-id="@Model.Character.CharacterID">Knowledge timeline</a>
|
||||
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Show on timeline</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="story-bible-section">
|
||||
<div class="story-bible-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Arc Shape</p>
|
||||
<h2>Character Metric Shape</h2>
|
||||
</div>
|
||||
<span class="soft-count">@arcGraphMetrics.Count metric@(arcGraphMetrics.Count == 1 ? "" : "s")</span>
|
||||
</div>
|
||||
@if (!arcGraphMetrics.Any() || !arcScenes.Any())
|
||||
{
|
||||
<p class="muted">Add scene metrics to scenes where @Model.Character.CharacterName appears to see their emotional and story shape.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="metric-shape-chart character-arc-chart" data-metric-shape-chart>
|
||||
<p class="metric-shape-intro">Compare this character's scene metrics across appearances in narrative order. Click a point to open the scene.</p>
|
||||
<div class="metric-toggle-row" data-metric-toggles></div>
|
||||
<div class="metric-chart-frame">
|
||||
<canvas aria-label="Comparative character arc line graph"></canvas>
|
||||
<p class="muted d-none" data-chart-fallback>Character arc chart could not load. The scene markers are still listed below.</p>
|
||||
</div>
|
||||
<script type="application/json">@Html.Raw(System.Text.Json.JsonSerializer.Serialize(arcGraphPayload))</script>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="story-bible-section">
|
||||
<div class="story-bible-grid">
|
||||
<article class="story-bible-card">
|
||||
<h3>Appearance Frequency</h3>
|
||||
<p class="writer-kpi">@Model.ArcPoints.Count</p>
|
||||
<p class="muted">@Model.ArcPoints.Count(x => x.IsPov) POV scene@(Model.ArcPoints.Count(x => x.IsPov) == 1 ? "" : "s")</p>
|
||||
</article>
|
||||
<article class="story-bible-card">
|
||||
<h3>Knowledge Gains</h3>
|
||||
<p class="writer-kpi">@Model.KnowledgeEvents.Count</p>
|
||||
</article>
|
||||
<article class="story-bible-card">
|
||||
<h3>Relationship Beats</h3>
|
||||
<p class="writer-kpi">@Model.RelationshipEvents.Count</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<partial name="_DynamicsIssues" model="Model.Issues" />
|
||||
|
||||
<section class="story-bible-section">
|
||||
<h2>Scene Markers</h2>
|
||||
<div class="writer-queue-list">
|
||||
@foreach (var point in Model.ArcPoints)
|
||||
{
|
||||
<article class="writer-queue-card">
|
||||
<div class="writer-queue-title">
|
||||
<h3>@point.BookTitle / Ch @point.ChapterNumber / Scene @point.SceneNumber: @point.SceneTitle</h3>
|
||||
@if (point.IsPov) { <span class="status-pill">POV</span> }
|
||||
</div>
|
||||
<div class="story-bible-meta">
|
||||
<span>Emotional @DisplayValue(point.EmotionalWeight)</span>
|
||||
<span>Tension @DisplayValue(point.Tension)</span>
|
||||
<span>Romance @DisplayValue(point.Romance)</span>
|
||||
<span>Action @DisplayValue(point.Action)</span>
|
||||
<span>@(point.RoleInSceneTypeName ?? "No role")</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(point.EmotionalState) || !string.IsNullOrWhiteSpace(point.KeyNotes))
|
||||
{
|
||||
<p>@point.EmotionalState @point.KeyNotes</p>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@functions {
|
||||
private static string DisplayValue(int? value) => value?.ToString() ?? "-";
|
||||
}
|
||||
30
PlotLine/Views/Dynamics/Knowledge.cshtml
Normal file
30
PlotLine/Views/Dynamics/Knowledge.cshtml
Normal file
@ -0,0 +1,30 @@
|
||||
@model KnowledgeTimelineViewModel
|
||||
@{
|
||||
ViewData["Title"] = $"{Model.Character.CharacterName} Knowledge";
|
||||
}
|
||||
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Knowledge Timeline</p>
|
||||
<h1>@Model.Character.CharacterName</h1>
|
||||
<p class="lead-text">What this character knows, suspects, misunderstands, or learns across the story.</p>
|
||||
</div>
|
||||
<a class="btn btn-outline-primary" asp-controller="Dynamics" asp-action="Character" asp-route-id="@Model.Character.CharacterID">Character arc</a>
|
||||
</div>
|
||||
|
||||
<partial name="_DynamicsIssues" model="Model.Issues" />
|
||||
|
||||
<section class="story-bible-section">
|
||||
<div class="writer-queue-list">
|
||||
@foreach (var item in Model.KnowledgeEvents)
|
||||
{
|
||||
<article class="writer-queue-card">
|
||||
<div class="writer-queue-title">
|
||||
<h3>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</h3>
|
||||
<span class="status-pill">@item.KnowledgeStateName</span>
|
||||
</div>
|
||||
<p><strong>@(item.AssetName ?? item.ThreadTitle ?? "Knowledge")</strong> @item.Description</p>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
52
PlotLine/Views/Dynamics/Relationship.cshtml
Normal file
52
PlotLine/Views/Dynamics/Relationship.cshtml
Normal file
@ -0,0 +1,52 @@
|
||||
@model RelationshipEvolutionViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Relationship Evolution";
|
||||
var maxIndex = Math.Max(Model.Events.Any() ? Model.Events.Max(x => x.GlobalSceneIndex) : 1, 1);
|
||||
}
|
||||
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Relationship Evolution</p>
|
||||
<h1>@Model.Relationship.CharacterAName -> @Model.Relationship.CharacterBName</h1>
|
||||
<p class="lead-text">@Model.Relationship.RelationshipTypeName</p>
|
||||
</div>
|
||||
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-FocusType="relationship" asp-route-FocusID="@Model.Relationship.CharacterRelationshipID">Show on timeline</a>
|
||||
</div>
|
||||
|
||||
<section class="story-bible-section">
|
||||
<div class="dynamics-timeline relationship">
|
||||
@foreach (var item in Model.Events)
|
||||
{
|
||||
var left = (item.GlobalSceneIndex - 1) * 100m / maxIndex;
|
||||
<a class="dynamics-marker" style="left:@left%" title="@item.RelationshipStateName @item.Intensity" asp-controller="Scenes" asp-action="Edit" asp-route-id="@item.SceneID">
|
||||
<span style="height:@Math.Max(item.Intensity ?? 3, 1)0%"></span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<partial name="_DynamicsIssues" model="Model.Issues" />
|
||||
|
||||
<section class="story-bible-section">
|
||||
<h2>Relationship History</h2>
|
||||
<div class="writer-queue-list">
|
||||
@foreach (var item in Model.Events)
|
||||
{
|
||||
<article class="writer-queue-card">
|
||||
<div class="writer-queue-title">
|
||||
<h3>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</h3>
|
||||
<span class="status-pill">@item.RelationshipStateName</span>
|
||||
</div>
|
||||
<div class="story-bible-meta">
|
||||
<span>Intensity @DisplayValue(item.Intensity)</span>
|
||||
<span>@(item.IsKnownToOtherCharacter ? "Known to other character" : "Private/uncertain")</span>
|
||||
</div>
|
||||
<p>@item.Description</p>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@functions {
|
||||
private static string DisplayValue(int? value) => value?.ToString() ?? "-";
|
||||
}
|
||||
41
PlotLine/Views/Dynamics/Story.cshtml
Normal file
41
PlotLine/Views/Dynamics/Story.cshtml
Normal file
@ -0,0 +1,41 @@
|
||||
@model StoryDynamicsViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Story Dynamics";
|
||||
}
|
||||
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Story Dynamics</p>
|
||||
<h1>@Model.Project.ProjectName</h1>
|
||||
<p class="lead-text">Character pairings, neglected arcs, abrupt transitions, knowledge jumps, and advisory continuity signals.</p>
|
||||
</div>
|
||||
<form asp-action="RunChecks" method="post">
|
||||
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||
<button class="btn btn-primary" type="submit">Run dynamics checks</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<partial name="_DynamicsIssues" model="Model.Issues" />
|
||||
|
||||
<section class="story-bible-section">
|
||||
<div class="story-bible-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Pairing Heatmap</p>
|
||||
<h2>Character Pairings</h2>
|
||||
</div>
|
||||
<span class="soft-count">@Model.Pairings.Count pairings</span>
|
||||
</div>
|
||||
<div class="story-bible-grid story-bible-grid-compact">
|
||||
@foreach (var pairing in Model.Pairings)
|
||||
{
|
||||
<article class="story-bible-card">
|
||||
<div class="story-bible-card-title">
|
||||
<h3>@pairing.CharacterAName + @pairing.CharacterBName</h3>
|
||||
<span>@pairing.SharedSceneCount scenes</span>
|
||||
</div>
|
||||
<p class="muted">First: @(pairing.FirstSceneLabel ?? "Not placed")</p>
|
||||
<p class="muted">Last: @(pairing.LastSceneLabel ?? "Not placed")</p>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
35
PlotLine/Views/Dynamics/_DynamicsIssues.cshtml
Normal file
35
PlotLine/Views/Dynamics/_DynamicsIssues.cshtml
Normal file
@ -0,0 +1,35 @@
|
||||
@model IReadOnlyList<DynamicsIssue>
|
||||
|
||||
<section class="story-bible-section">
|
||||
<div class="story-bible-section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Advisory checks</p>
|
||||
<h2>Things to Review</h2>
|
||||
</div>
|
||||
<span class="soft-count">@Model.Count item@(Model.Count == 1 ? "" : "s")</span>
|
||||
</div>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<p class="muted">No dynamics issues found for this view.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="attention-list">
|
||||
@foreach (var issue in Model)
|
||||
{
|
||||
<article>
|
||||
<span class="badge text-bg-light">@issue.Category</span>
|
||||
<strong>@issue.Title</strong>
|
||||
@if (!string.IsNullOrWhiteSpace(issue.Detail))
|
||||
{
|
||||
<p>@issue.Detail</p>
|
||||
}
|
||||
@if (issue.SceneID.HasValue)
|
||||
{
|
||||
<a asp-controller="Scenes" asp-action="Edit" asp-route-id="@issue.SceneID">Open scene</a>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
@ -18,6 +18,7 @@
|
||||
<a asp-controller="Writer" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Writer Workspace</a>
|
||||
<a asp-controller="StoryBible" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story Bible</a>
|
||||
<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="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>
|
||||
@ -47,6 +48,7 @@
|
||||
<a class="btn btn-outline-primary" asp-controller="Locations" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Locations</a>
|
||||
<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-primary" asp-controller="Books" asp-action="Create" asp-route-projectId="@Model.Project.ProjectID">Add book</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2746,6 +2746,54 @@ body.dragging-location [data-drag-type="location"] {
|
||||
margin: -8px 0 18px;
|
||||
}
|
||||
|
||||
.dynamics-timeline {
|
||||
position: relative;
|
||||
min-height: 128px;
|
||||
border: 1px solid var(--plotline-line);
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, rgba(47, 111, 99, 0.07), rgba(255,255,255,0.84));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dynamics-timeline::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
top: 64px;
|
||||
height: 1px;
|
||||
background: rgba(36, 32, 26, 0.18);
|
||||
}
|
||||
|
||||
.dynamics-marker {
|
||||
position: absolute;
|
||||
bottom: 18px;
|
||||
width: 12px;
|
||||
height: 86px;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dynamics-marker span {
|
||||
display: block;
|
||||
width: 8px;
|
||||
min-height: 12px;
|
||||
max-height: 82px;
|
||||
border-radius: 999px;
|
||||
background: var(--plotline-accent);
|
||||
box-shadow: 0 8px 20px rgba(47, 111, 99, 0.22);
|
||||
}
|
||||
|
||||
.dynamics-marker.pov span {
|
||||
background: #8a4b2f;
|
||||
}
|
||||
|
||||
.dynamics-timeline.relationship .dynamics-marker span {
|
||||
background: #7c5fb0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
header,
|
||||
.breadcrumb-trail,
|
||||
|
||||
@ -124,7 +124,7 @@
|
||||
"#444b57"
|
||||
];
|
||||
|
||||
const defaultMetrics = new Set(["Overall Intensity", "Tension", "Emotional Weight", "Action"]);
|
||||
const globalDefaultMetrics = new Set(["Overall Intensity", "Tension", "Emotional Weight", "Action"]);
|
||||
|
||||
const chapterGuidePlugin = {
|
||||
id: "plotlineChapterGuides",
|
||||
@ -186,8 +186,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultMetrics = new Set(payload.defaultMetrics?.length ? payload.defaultMetrics : globalDefaultMetrics);
|
||||
const labels = payload.scenes.map((scene) => scene.axisLabel);
|
||||
const sceneIndexById = new Map(payload.scenes.map((scene, index) => [scene.sceneId, index]));
|
||||
const datasets = payload.metrics.map((metric, metricIndex) => {
|
||||
const pointMap = new Map(metric.points.map((point) => [point.sceneId, point.value]));
|
||||
const colour = colours[metricIndex % colours.length];
|
||||
@ -259,7 +259,18 @@
|
||||
},
|
||||
afterBody(items) {
|
||||
const scene = payload.scenes[items[0].dataIndex];
|
||||
return scene.bookTitle ? scene.bookTitle : "";
|
||||
const lines = [];
|
||||
if (scene.characterPresence) {
|
||||
lines.push(scene.characterPresence);
|
||||
} else if (scene.characterAppears !== undefined) {
|
||||
lines.push(scene.characterAppears ? "Character appears" : "Character does not appear");
|
||||
}
|
||||
|
||||
if (scene.bookTitle) {
|
||||
lines.push(scene.bookTitle);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user