diff --git a/.coverage-fix.tmp b/.coverage-fix.tmp new file mode 100644 index 0000000000..7df2305e3a --- /dev/null +++ b/.coverage-fix.tmp @@ -0,0 +1,3 @@ +./src/i18n/i18next.ts +./src/commons/workspace/sharedb-ace.ts +./src/react-app-env.ts diff --git a/lcov.info b/lcov.info new file mode 100644 index 0000000000..a7819e63bd --- /dev/null +++ b/lcov.info @@ -0,0 +1,1150 @@ +TN: +SF:src/react-app-env.ts +FNF:0 +FNH:0 +LF:0 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/commons/sagas/StoriesSaga.ts +FN:34,(anonymous_0) +FN:36,(anonymous_1) +FN:43,(anonymous_2) +FN:59,(anonymous_3) +FN:62,(anonymous_4) +FN:86,(anonymous_5) +FN:109,(anonymous_6) +FN:122,(anonymous_7) +FN:140,(anonymous_8) +FN:144,(anonymous_9) +FN:146,(anonymous_10) +FN:163,(anonymous_11) +FN:172,(anonymous_12) +FN:183,(anonymous_13) +FNF:14 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:0,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +FNDA:0,(anonymous_6) +FNDA:0,(anonymous_7) +FNDA:0,(anonymous_8) +FNDA:0,(anonymous_9) +FNDA:0,(anonymous_10) +FNDA:0,(anonymous_11) +FNDA:0,(anonymous_12) +FNDA:0,(anonymous_13) +DA:31,15 +DA:32,15 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:41,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:50,0 +DA:56,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:69,0 +DA:80,0 +DA:81,0 +DA:84,0 +DA:87,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:91,0 +DA:102,0 +DA:103,0 +DA:106,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:114,0 +DA:123,0 +DA:130,0 +DA:132,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:137,0 +DA:138,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:144,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:151,0 +DA:152,0 +DA:164,0 +DA:166,0 +DA:168,0 +DA:169,0 +DA:173,0 +DA:174,0 +DA:176,0 +DA:178,0 +DA:179,0 +DA:180,0 +DA:184,0 +DA:185,0 +DA:187,0 +DA:188,0 +DA:189,0 +DA:190,0 +LF:69 +LH:2 +BRDA:38,0,0,0 +BRDA:38,0,1,0 +BRDA:46,1,0,0 +BRDA:46,1,1,0 +BRDA:64,2,0,0 +BRDA:64,2,1,0 +BRDA:80,3,0,0 +BRDA:80,3,1,0 +BRDA:102,4,0,0 +BRDA:102,4,1,0 +BRDA:132,5,0,0 +BRDA:132,5,1,0 +BRDA:168,6,0,0 +BRDA:168,6,1,0 +BRDA:178,7,0,0 +BRDA:178,7,1,0 +BRDA:188,8,0,0 +BRDA:188,8,1,0 +BRF:18 +BRH:0 +end_of_record +TN: +SF:src/commons/workspace/sharedb-ace.ts +FNF:0 +FNH:0 +LF:0 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/features/stories/DragContext.ts +FN:10,(anonymous_0) +FNF:1 +FNH:0 +FNDA:0,(anonymous_0) +DA:8,15 +DA:10,15 +DA:11,0 +DA:13,0 +DA:14,0 +DA:17,0 +LF:6 +LH:2 +BRDA:13,0,0,0 +BRDA:13,0,1,0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/features/stories/StoriesTypes.ts +FNF:0 +FNH:0 +LF:0 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/features/stories/storiesComponents/BackendAccess.ts +FN:100,(anonymous_0) +FN:105,(anonymous_1) +FN:118,(anonymous_2) +FN:137,(anonymous_3) +FN:145,(anonymous_4) +FN:159,(anonymous_5) +FN:168,(anonymous_6) +FN:171,(anonymous_7) +FN:190,(anonymous_8) +FN:211,(anonymous_9) +FN:241,(anonymous_10) +FN:252,(anonymous_11) +FN:265,(anonymous_12) +FN:287,(anonymous_13) +FNF:14 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:0,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +FNDA:0,(anonymous_6) +FNDA:0,(anonymous_7) +FNDA:0,(anonymous_8) +FNDA:0,(anonymous_9) +FNDA:0,(anonymous_10) +FNDA:0,(anonymous_11) +FNDA:0,(anonymous_12) +FNDA:0,(anonymous_13) +DA:22,15 +DA:34,15 +DA:94,15 +DA:95,15 +DA:100,15 +DA:101,0 +DA:102,0 +DA:105,15 +DA:106,0 +DA:107,0 +DA:108,0 +DA:111,0 +DA:112,0 +DA:118,15 +DA:127,0 +DA:130,0 +DA:131,0 +DA:133,0 +DA:134,0 +DA:137,15 +DA:142,0 +DA:145,0 +DA:149,0 +DA:150,0 +DA:151,0 +DA:154,0 +DA:155,0 +DA:159,15 +DA:160,0 +DA:163,0 +DA:164,0 +DA:166,0 +DA:168,0 +DA:171,15 +DA:172,0 +DA:177,0 +DA:178,0 +DA:180,0 +DA:184,0 +DA:185,0 +DA:186,0 +DA:187,0 +DA:190,15 +DA:198,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:206,0 +DA:207,0 +DA:208,0 +DA:211,15 +DA:219,0 +DA:223,0 +DA:224,0 +DA:225,0 +DA:227,0 +DA:228,0 +DA:232,0 +DA:233,0 +DA:234,0 +DA:235,0 +DA:236,0 +DA:237,0 +DA:241,15 +DA:242,0 +DA:245,0 +DA:246,0 +DA:248,0 +DA:249,0 +DA:252,15 +DA:255,0 +DA:258,0 +DA:259,0 +DA:261,0 +DA:262,0 +DA:265,15 +DA:270,0 +DA:279,0 +DA:280,0 +DA:281,0 +DA:283,0 +DA:284,0 +DA:287,15 +DA:291,0 +DA:299,0 +DA:300,0 +DA:301,0 +DA:303,0 +DA:304,0 +LF:89 +LH:16 +BRDA:107,0,0,0 +BRDA:107,0,1,0 +BRDA:130,1,0,0 +BRDA:130,1,1,0 +BRDA:149,2,0,0 +BRDA:149,2,1,0 +BRDA:163,3,0,0 +BRDA:163,3,1,0 +BRDA:177,4,0,0 +BRDA:177,4,1,0 +BRDA:202,5,0,0 +BRDA:202,5,1,0 +BRDA:223,6,0,0 +BRDA:223,6,1,0 +BRDA:245,7,0,0 +BRDA:245,7,1,0 +BRDA:258,8,0,0 +BRDA:258,8,1,0 +BRDA:279,9,0,0 +BRDA:279,9,1,0 +BRDA:299,10,0,0 +BRDA:299,10,1,0 +BRF:22 +BRH:0 +end_of_record +TN: +SF:src/features/stories/storiesComponents/CreateStoryCell.tsx +FN:17,(anonymous_0) +FN:22,(anonymous_1) +FN:33,(anonymous_2) +FN:38,(anonymous_3) +FN:45,(anonymous_4) +FN:57,(anonymous_5) +FN:65,(anonymous_6) +FN:89,(anonymous_7) +FN:90,(anonymous_8) +FN:96,(anonymous_9) +FN:98,(anonymous_10) +FNF:11 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:0,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +FNDA:0,(anonymous_6) +FNDA:0,(anonymous_7) +FNDA:0,(anonymous_8) +FNDA:0,(anonymous_9) +FNDA:0,(anonymous_10) +DA:17,15 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:50,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:70,0 +DA:71,0 +DA:74,0 +DA:89,0 +DA:90,0 +DA:96,0 +DA:98,0 +LF:41 +LH:1 +BRDA:29,0,0,0 +BRDA:29,0,1,0 +BRDA:53,1,0,0 +BRDA:53,1,1,0 +BRDA:66,2,0,0 +BRDA:66,2,1,0 +BRDA:88,3,0,0 +BRDA:88,3,1,0 +BRDA:93,4,0,0 +BRDA:93,4,1,0 +BRF:10 +BRH:0 +end_of_record +TN: +SF:src/features/stories/storiesComponents/Draggable.tsx +FN:10,(anonymous_0) +FN:14,(anonymous_1) +FN:30,(anonymous_2) +FNF:3 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +DA:10,15 +DA:11,0 +DA:12,0 +DA:14,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:20,0 +DA:24,0 +DA:27,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:35,0 +DA:38,0 +LF:15 +LH:1 +BRDA:16,0,0,0 +BRDA:16,0,1,0 +BRDA:32,1,0,0 +BRDA:32,1,1,0 +BRF:4 +BRH:0 +end_of_record +TN: +SF:src/features/stories/storiesComponents/DropArea.tsx +FN:12,(anonymous_0) +FN:16,(anonymous_1) +FN:24,(anonymous_2) +FN:43,(anonymous_3) +FN:52,(anonymous_4) +FN:53,(anonymous_5) +FN:54,(anonymous_6) +FN:58,(anonymous_7) +FNF:8 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:0,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +FNDA:0,(anonymous_6) +FNDA:0,(anonymous_7) +DA:12,15 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:21,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:39,0 +DA:41,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:50,0 +DA:52,0 +DA:53,0 +DA:55,0 +DA:56,0 +DA:58,0 +LF:33 +LH:1 +BRDA:19,0,0,0 +BRDA:19,0,1,0 +BRDA:27,1,0,0 +BRDA:27,1,1,0 +BRDA:33,2,0,0 +BRDA:33,2,1,0 +BRDA:51,3,0,0 +BRDA:51,3,1,0 +BRF:8 +BRH:0 +end_of_record +TN: +SF:src/features/stories/storiesComponents/EditStoryCell.tsx +FN:20,(anonymous_0) +FN:22,EditStoryCell +FN:25,(anonymous_2) +FN:35,(anonymous_3) +FN:49,(anonymous_4) +FN:52,(anonymous_5) +FN:58,(anonymous_6) +FN:73,(anonymous_7) +FN:90,(anonymous_8) +FN:107,(anonymous_9) +FN:112,(anonymous_10) +FN:118,(anonymous_11) +FN:141,(anonymous_12) +FN:142,(anonymous_13) +FN:169,(anonymous_14) +FN:175,(anonymous_15) +FN:181,(anonymous_16) +FN:188,(anonymous_17) +FN:195,(anonymous_18) +FN:204,(anonymous_19) +FNF:20 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,EditStoryCell +FNDA:0,(anonymous_2) +FNDA:0,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +FNDA:0,(anonymous_6) +FNDA:0,(anonymous_7) +FNDA:0,(anonymous_8) +FNDA:0,(anonymous_9) +FNDA:0,(anonymous_10) +FNDA:0,(anonymous_11) +FNDA:0,(anonymous_12) +FNDA:0,(anonymous_13) +FNDA:0,(anonymous_14) +FNDA:0,(anonymous_15) +FNDA:0,(anonymous_16) +FNDA:0,(anonymous_17) +FNDA:0,(anonymous_18) +FNDA:0,(anonymous_19) +DA:20,15 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:44,0 +DA:46,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:73,0 +DA:74,0 +DA:84,0 +DA:85,0 +DA:87,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:100,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:122,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:128,0 +DA:129,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:136,0 +DA:139,0 +DA:141,0 +DA:142,0 +DA:170,0 +DA:176,0 +DA:182,0 +DA:183,0 +DA:189,0 +DA:190,0 +DA:196,0 +DA:197,0 +DA:199,0 +DA:205,0 +LF:89 +LH:1 +BRDA:36,0,0,0 +BRDA:36,0,1,0 +BRDA:44,1,0,0 +BRDA:44,1,1,0 +BRDA:64,2,0,0 +BRDA:64,2,1,0 +BRDA:84,3,0,0 +BRDA:84,3,1,0 +BRDA:95,4,0,0 +BRDA:95,4,1,0 +BRDA:97,5,0,0 +BRDA:97,5,1,0 +BRDA:113,6,0,0 +BRDA:113,6,1,0 +BRDA:119,7,0,0 +BRDA:119,7,1,0 +BRDA:121,8,0,0 +BRDA:121,8,1,0 +BRDA:121,9,0,0 +BRDA:121,9,1,0 +BRDA:124,10,0,0 +BRDA:124,10,1,0 +BRDA:144,11,0,0 +BRDA:144,11,1,0 +BRDA:147,12,0,0 +BRDA:147,12,1,0 +BRDA:155,13,0,0 +BRDA:155,13,1,0 +BRDA:208,14,0,0 +BRDA:208,14,1,0 +BRDA:226,15,0,0 +BRDA:226,15,1,0 +BRDA:232,16,0,0 +BRDA:232,16,1,0 +BRF:34 +BRH:0 +end_of_record +TN: +SF:src/features/stories/storiesComponents/SourceBlock.tsx +FN:43,parseMetadata +FN:53,(anonymous_1) +FN:58,(anonymous_2) +FN:59,(anonymous_3) +FN:73,(anonymous_4) +FN:76,(anonymous_5) +FN:82,(anonymous_6) +FN:88,(anonymous_7) +FN:92,(anonymous_8) +FN:97,(anonymous_9) +FN:108,(anonymous_10) +FN:113,(anonymous_11) +FN:165,(anonymous_12) +FN:176,(anonymous_13) +FN:222,(anonymous_14) +FN:240,(anonymous_15) +FN:244,(anonymous_16) +FN:249,(anonymous_17) +FN:257,(anonymous_18) +FN:264,(anonymous_19) +FN:293,(anonymous_20) +FN:301,(anonymous_21) +FN:335,(anonymous_22) +FN:338,(anonymous_23) +FN:345,(anonymous_24) +FN:347,(anonymous_25) +FN:375,(anonymous_26) +FNF:27 +FNH:0 +FNDA:0,parseMetadata +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:0,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +FNDA:0,(anonymous_6) +FNDA:0,(anonymous_7) +FNDA:0,(anonymous_8) +FNDA:0,(anonymous_9) +FNDA:0,(anonymous_10) +FNDA:0,(anonymous_11) +FNDA:0,(anonymous_12) +FNDA:0,(anonymous_13) +FNDA:0,(anonymous_14) +FNDA:0,(anonymous_15) +FNDA:0,(anonymous_16) +FNDA:0,(anonymous_17) +FNDA:0,(anonymous_18) +FNDA:0,(anonymous_19) +FNDA:0,(anonymous_20) +FNDA:0,(anonymous_21) +FNDA:0,(anonymous_22) +FNDA:0,(anonymous_23) +FNDA:0,(anonymous_24) +FNDA:0,(anonymous_25) +FNDA:0,(anonymous_26) +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:50,0 +DA:53,15 +DA:54,0 +DA:55,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:64,0 +DA:66,0 +DA:72,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:79,0 +DA:80,0 +DA:82,0 +DA:83,0 +DA:85,0 +DA:88,0 +DA:89,0 +DA:92,0 +DA:93,0 +DA:97,0 +DA:98,0 +DA:108,0 +DA:109,0 +DA:112,0 +DA:120,0 +DA:123,0 +DA:124,0 +DA:126,0 +DA:133,0 +DA:147,0 +DA:148,0 +DA:165,0 +DA:166,0 +DA:171,0 +DA:172,0 +DA:173,0 +DA:176,0 +DA:181,0 +DA:189,0 +DA:191,0 +DA:198,0 +DA:203,0 +DA:206,0 +DA:210,0 +DA:222,0 +DA:228,0 +DA:230,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:237,0 +DA:238,0 +DA:240,0 +DA:241,0 +DA:244,0 +DA:245,0 +DA:246,0 +DA:249,0 +DA:250,0 +DA:251,0 +DA:252,0 +DA:254,0 +DA:257,0 +DA:258,0 +DA:259,0 +DA:260,0 +DA:261,0 +DA:264,0 +DA:265,0 +DA:266,0 +DA:267,0 +DA:268,0 +DA:269,0 +DA:270,0 +DA:272,0 +DA:274,0 +DA:276,0 +DA:277,0 +DA:278,0 +DA:280,0 +DA:281,0 +DA:283,0 +DA:284,0 +DA:286,0 +DA:287,0 +DA:288,0 +DA:289,0 +DA:290,0 +DA:293,0 +DA:294,0 +DA:295,0 +DA:296,0 +DA:297,0 +DA:298,0 +DA:301,0 +DA:302,0 +DA:303,0 +DA:306,0 +DA:308,0 +DA:335,0 +DA:338,0 +DA:345,0 +DA:347,0 +DA:375,0 +LF:114 +LH:1 +BRDA:46,0,0,0 +BRDA:46,0,1,0 +BRDA:47,1,0,0 +BRDA:47,1,1,0 +BRDA:66,2,0,0 +BRDA:66,2,1,0 +BRDA:68,3,0,0 +BRDA:68,3,1,0 +BRDA:73,4,0,0 +BRDA:73,4,1,0 +BRDA:76,5,0,0 +BRDA:76,5,1,0 +BRDA:97,6,0,0 +BRDA:97,6,1,0 +BRDA:133,7,0,0 +BRDA:133,7,1,0 +BRDA:152,8,0,0 +BRDA:152,8,1,0 +BRDA:171,9,0,0 +BRDA:171,9,1,0 +BRDA:172,10,0,0 +BRDA:172,10,1,0 +BRDA:172,11,0,0 +BRDA:172,11,1,0 +BRDA:189,12,0,0 +BRDA:189,12,1,0 +BRDA:198,13,0,0 +BRDA:198,13,1,0 +BRDA:199,14,0,0 +BRDA:199,14,1,0 +BRDA:199,14,2,0 +BRDA:228,15,0,0 +BRDA:228,15,1,0 +BRDA:269,16,0,0 +BRDA:269,16,1,0 +BRDA:274,17,0,0 +BRDA:274,17,1,0 +BRDA:281,18,0,0 +BRDA:281,18,1,0 +BRDA:313,19,0,0 +BRDA:313,19,1,0 +BRDA:325,20,0,0 +BRDA:325,20,1,0 +BRF:43 +BRH:0 +end_of_record +TN: +SF:src/features/stories/storiesComponents/UserBlogContent.tsx +FN:30,handleEnvironment +FN:36,(anonymous_1) +FN:48,handleHeaders +FN:89,getYamlHeader +FN:105,getEnvironments +FN:114,constructHeader +FN:126,(anonymous_6) +FN:136,(anonymous_7) +FN:141,(anonymous_8) +FN:153,(anonymous_9) +FN:160,(anonymous_10) +FN:193,(anonymous_11) +FN:204,(anonymous_12) +FN:205,(anonymous_13) +FN:206,(anonymous_14) +FN:207,(anonymous_15) +FN:223,(anonymous_16) +FN:231,(anonymous_17) +FNF:18 +FNH:0 +FNDA:0,handleEnvironment +FNDA:0,(anonymous_1) +FNDA:0,handleHeaders +FNDA:0,getYamlHeader +FNDA:0,getEnvironments +FNDA:0,constructHeader +FNDA:0,(anonymous_6) +FNDA:0,(anonymous_7) +FNDA:0,(anonymous_8) +FNDA:0,(anonymous_9) +FNDA:0,(anonymous_10) +FNDA:0,(anonymous_11) +FNDA:0,(anonymous_12) +FNDA:0,(anonymous_13) +FNDA:0,(anonymous_14) +FNDA:0,(anonymous_15) +FNDA:0,(anonymous_16) +FNDA:0,(anonymous_17) +DA:24,15 +DA:26,15 +DA:27,15 +DA:28,15 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:40,0 +DA:44,0 +DA:49,0 +DA:50,0 +DA:57,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:68,0 +DA:69,0 +DA:72,0 +DA:76,0 +DA:78,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:99,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:111,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:126,15 +DA:130,0 +DA:132,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:144,0 +DA:145,0 +DA:148,0 +DA:150,0 +DA:153,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:157,0 +DA:160,0 +DA:161,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:165,0 +DA:166,0 +DA:167,0 +DA:168,0 +DA:170,0 +DA:175,0 +DA:178,0 +DA:194,0 +DA:195,0 +DA:196,0 +DA:198,0 +DA:204,0 +DA:205,0 +DA:206,0 +DA:207,0 +DA:219,0 +DA:223,0 +DA:232,0 +LF:87 +LH:5 +BRDA:35,0,0,0 +BRDA:35,0,1,0 +BRDA:40,1,0,0 +BRDA:40,1,1,0 +BRDA:49,2,0,0 +BRDA:49,2,1,0 +BRDA:62,3,0,0 +BRDA:62,3,1,0 +BRDA:62,3,2,0 +BRDA:76,4,0,0 +BRDA:76,4,1,0 +BRDA:91,5,0,0 +BRDA:91,5,1,0 +BRDA:95,6,0,0 +BRDA:95,6,1,0 +BRDA:148,7,0,0 +BRDA:148,7,1,0 +BRDA:163,8,0,0 +BRDA:163,8,1,0 +BRDA:166,9,0,0 +BRDA:166,9,1,0 +BRDA:195,10,0,0 +BRDA:195,10,1,0 +BRDA:219,11,0,0 +BRDA:219,11,1,0 +BRDA:221,12,0,0 +BRDA:221,12,1,0 +BRDA:222,13,0,0 +BRDA:222,13,1,0 +BRDA:237,14,0,0 +BRDA:237,14,1,0 +BRF:31 +BRH:0 +end_of_record +TN: +SF:src/features/stories/storiesComponents/ViewStoryCell.tsx +FN:10,ViewStoryCell +FN:15,(anonymous_1) +FNF:2 +FNH:0 +FNDA:0,ViewStoryCell +FNDA:0,(anonymous_1) +DA:12,0 +DA:13,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:22,0 +LF:6 +LH:0 +BRDA:16,0,0,0 +BRDA:16,0,1,0 +BRF:2 +BRH:0 +end_of_record +TN: +SF:src/i18n/i18next.ts +FNF:0 +FNH:0 +LF:0 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/pages/stories/Story.tsx +FN:21,(anonymous_0) +FN:25,(anonymous_1) +FN:28,(anonymous_2) +FN:52,(anonymous_3) +FN:62,(anonymous_4) +FN:99,(anonymous_5) +FN:102,(anonymous_6) +FNF:7 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:0,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) +FNDA:0,(anonymous_6) +DA:21,0 +DA:22,0 +DA:23,0 +DA:25,0 +DA:26,0 +DA:28,0 +DA:30,0 +DA:33,0 +DA:37,0 +DA:38,0 +DA:41,0 +DA:43,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:67,0 +DA:69,0 +DA:72,0 +DA:75,0 +DA:83,0 +DA:99,0 +DA:100,0 +DA:102,0 +DA:103,0 +LF:27 +LH:0 +BRDA:21,0,0,0 +BRDA:33,1,0,0 +BRDA:33,1,1,0 +BRDA:37,2,0,0 +BRDA:37,2,1,0 +BRDA:45,3,0,0 +BRDA:45,3,1,0 +BRDA:59,4,0,0 +BRDA:59,4,1,0 +BRDA:63,5,0,0 +BRDA:63,5,1,0 +BRDA:67,6,0,0 +BRDA:67,6,1,0 +BRF:13 +BRH:0 +end_of_record + + diff --git a/package.json b/package.json index 99c6b36a06..bb99c9610d 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,9 @@ "@blueprintjs/datetime2": "^2.3.3", "@blueprintjs/icons": "^5.9.0", "@blueprintjs/select": "^5.1.3", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@mantine/hooks": "^7.11.2", "@octokit/rest": "^20.0.0", "@reduxjs/toolkit": "^1.9.7", diff --git a/public/externalLibs/sound/soundToneMatrix.js b/public/externalLibs/sound/soundToneMatrix.js index 8638a90378..d0246757bf 100644 --- a/public/externalLibs/sound/soundToneMatrix.js +++ b/public/externalLibs/sound/soundToneMatrix.js @@ -36,7 +36,7 @@ var timeout_matrix; // for coloring the matrix accordingly while it's being played var timeout_color; -var timeout_objects = new Array(); +var timeout_objects = []; // vector_to_list returns a list that contains the elements of the argument vector // in the given order. @@ -54,7 +54,7 @@ function vector_to_list(vector) { function x_y_to_row_column(x, y) { var row = Math.floor((y - margin_length) / (square_side_length + distance_between_squares)); var column = Math.floor((x - margin_length) / (square_side_length + distance_between_squares)); - return Array(row, column); + return [row, column]; } // given the row number of a square, return the leftmost coordinate @@ -365,5 +365,5 @@ function clear_all_timeout() { clearTimeout(timeout_objects[i]); } - timeout_objects = new Array(); + timeout_objects = []; } diff --git a/src/commons/sagas/StoriesSaga.ts b/src/commons/sagas/StoriesSaga.ts index c071186724..a03a345b35 100644 --- a/src/commons/sagas/StoriesSaga.ts +++ b/src/commons/sagas/StoriesSaga.ts @@ -2,6 +2,9 @@ import { Context } from 'js-slang'; import { call, put, select } from 'redux-saga/effects'; import StoriesActions from 'src/features/stories/StoriesActions'; import { + defaultContent, + // updateHeader, + defaultHeader, deleteStory, deleteUserUserGroups, getAdminPanelStoriesUsers, @@ -21,7 +24,7 @@ import { combineSagaHandlers } from '../redux/utils'; import { resetSideContent } from '../sideContent/SideContentActions'; import { actions } from '../utils/ActionsHelper'; import { showSuccessMessage, showWarningMessage } from '../utils/notifications/NotificationsHelper'; -import { defaultStoryContent } from '../utils/StoriesHelper'; +// import { defaultHeader, defaultContent } from '../utils/StoriesHelper'; import { selectTokens } from './BackendSaga'; import { evalCodeSaga } from './WorkspaceSaga/helpers/evalCode'; @@ -47,7 +50,8 @@ const StoriesSaga = combineSagaHandlers(sagaActions, { } else { const defaultStory: StoryData = { title: '', - content: defaultStoryContent, + header: defaultHeader, + content: defaultContent, pinOrder: null }; yield put(actions.setCurrentStory(defaultStory)); @@ -68,6 +72,7 @@ const StoriesSaga = combineSagaHandlers(sagaActions, { tokens, userId, story.title, + story.header, story.content, story.pinOrder ); @@ -82,11 +87,14 @@ const StoriesSaga = combineSagaHandlers(sagaActions, { saveStory: function* (action) { const tokens: Tokens = yield selectTokens(); const { story, id } = action.payload; + console.log('In saveStory'); + console.log(story.header); const updatedStory: StoryView | null = yield call( updateStory, tokens, id, story.title, + story.header, story.content, story.pinOrder ); @@ -107,6 +115,11 @@ const StoriesSaga = combineSagaHandlers(sagaActions, { yield put(actions.getStoriesList()); }, + // updateHeader: function* (action) { + // const newHeader = action.payload; + // yield call(updateHeader, newHeader); + // }, + getStoriesUser: function* () { const tokens: Tokens = yield selectTokens(); const me: { diff --git a/src/commons/sideContent/SideContent.tsx b/src/commons/sideContent/SideContent.tsx index 75cffad579..226beffe30 100644 --- a/src/commons/sideContent/SideContent.tsx +++ b/src/commons/sideContent/SideContent.tsx @@ -70,7 +70,7 @@ const SideContent = ({ renderActiveTabPanelOnly, editorWidth, ...props }: SideCo {({ tabs: allTabs, alerts: tabAlerts, changeTabsCallback, selectedTab, height }) => (
- +
ReturnType; }; +let currentIndex: number; +let view: boolean; const handleCustomComponents: HandlerType = { code: (state, node) => { const rawLang = node.lang ?? ''; @@ -167,7 +169,9 @@ const handleCustomComponents: HandlerType = { // const lang = rawLang.substring(1, rawLang.length - 1); const props: SourceBlockProps = { content: node.value, - commands: node.meta ?? '' + commands: node.meta ?? '', + index: currentIndex, + isViewOnly: view }; // Disable typecheck as "source-block" is not a standard HTML tag const element = h('source-block', props) as any; @@ -175,7 +179,13 @@ const handleCustomComponents: HandlerType = { } }; -export const renderStoryMarkdown = (markdown: string): React.ReactNode => { +export const renderStoryMarkdown = ( + markdown: string, + index: number, + isViewOnly: boolean +): React.ReactNode => { + currentIndex = index; + view = isViewOnly; const mdast = fromMarkdown(markdown); const hast = toHast(mdast, { handlers: handleCustomComponents }) ?? h(); return ( diff --git a/src/commons/workspace/sharedb-ace.d.ts b/src/commons/workspace/sharedb-ace.ts similarity index 100% rename from src/commons/workspace/sharedb-ace.d.ts rename to src/commons/workspace/sharedb-ace.ts diff --git a/src/features/stories/DragContext.ts b/src/features/stories/DragContext.ts new file mode 100644 index 0000000000..6ea99a3da6 --- /dev/null +++ b/src/features/stories/DragContext.ts @@ -0,0 +1,18 @@ +import { createContext, useContext } from 'react'; + +type DragContextProps = { + index: number | null; + setIndex: (index: number) => void; +}; + +export const DragContext = createContext(null); + +export const useDragItem = () => { + const dragItem = useContext(DragContext); + + if (dragItem == null) { + throw Error('Drag Context cannot be null when in use'); + } + + return dragItem; +}; diff --git a/src/features/stories/StoriesActions.ts b/src/features/stories/StoriesActions.ts index ae90e48a81..3cf55e5753 100644 --- a/src/features/stories/StoriesActions.ts +++ b/src/features/stories/StoriesActions.ts @@ -28,6 +28,7 @@ const StoriesActions = createActions('stories', { createStory: (story: StoryParams) => story, saveStory: (story: StoryParams, id: number) => ({ story, id }), deleteStory: (id: number) => id, + updateHeader: (newHeader: string) => newHeader, // Auth-related actions getStoriesUser: () => ({}), diff --git a/src/features/stories/StoriesTypes.ts b/src/features/stories/StoriesTypes.ts index 3f524a6472..f4896c5d91 100644 --- a/src/features/stories/StoriesTypes.ts +++ b/src/features/stories/StoriesTypes.ts @@ -3,6 +3,14 @@ import { DebuggerContext } from 'src/commons/workspace/WorkspaceTypes'; import { InterpreterOutput, StoriesRole } from '../../commons/application/ApplicationTypes'; +export type StoryCell = { + // id: number; + index: number; + isCode: boolean; + env: string; + content: string; +}; + export type StoryMetadata = { authorId: number; authorName: string; @@ -10,7 +18,8 @@ export type StoryMetadata = { export type StoryData = { title: string; - content: string; + header: string; + content: StoryCell[]; pinOrder: number | null; }; diff --git a/src/features/stories/storiesComponents/BackendAccess.ts b/src/features/stories/storiesComponents/BackendAccess.ts index c494c13ed6..e590d072b4 100644 --- a/src/features/stories/storiesComponents/BackendAccess.ts +++ b/src/features/stories/storiesComponents/BackendAccess.ts @@ -6,12 +6,88 @@ import { showWarningMessage } from 'src/commons/utils/notifications/NotificationsHelper'; import { request } from 'src/commons/utils/RequestHelper'; +import { defaultStoryContent } from 'src/commons/utils/StoriesHelper'; import { RemoveLast } from 'src/commons/utils/TypeHelper'; import { store } from 'src/pages/createStore'; import { Tokens } from '../../../commons/application/types/SessionTypes'; import { NameUsernameRole } from '../../../pages/academy/adminPanel/subcomponents/AddStoriesUserPanel'; import { AdminPanelStoriesUser, StoryListView, StoryView } from '../StoriesTypes'; +import { StoryCell } from '../StoriesTypes'; + +// config: +// chapter: 4 +// variant: default + +export const defaultHeader: string = `--- +env: + iterFib: + chapter: 2 + variant: default + recuFib: + chapter: 4 + variant: default + rune: + chapter: 4 + variant: default`; + +export const defaultContent: StoryCell[] = [ + { + // id: 0, + index: 0, + isCode: true, + env: 'iterFib', + content: `function print(message) { + display(message); +} +draw_data(list(1, 2, 3, 4)); +display("hello world1"); +` + }, + { + // id: 1, + index: 1, + isCode: false, + env: '', + content: `# Hello world! +## hello world!! +hello world!!! +hello world!!! +\`\`\`\` +\`\`\`{source} +print("hello world") +\`\`\` +\`\`\`\` +` + }, + { + // id: 2, + index: 2, + isCode: true, + env: 'recuFib', + content: `print("source academy stories"); +` + }, + { + // id: 3, + index: 3, + isCode: true, + env: 'iterFib', + content: `print("hello world"); +` + }, + { + // id: 4, + index: 4, + isCode: true, + env: 'iterFib', + content: `print("why this cell?"); +` + } +]; + +let tempHeader = defaultHeader; +let tempContent = defaultContent; // Helpers @@ -83,7 +159,8 @@ export const getStories = async (tokens: Tokens): Promise ({ ...story, header: tempContent, content: tempContent })); }; export const getStory = async (tokens: Tokens, storyId: number): Promise => { @@ -95,7 +172,13 @@ export const getStory = async (tokens: Tokens, storyId: number): Promise => { const resp = await requestStoryBackend(`/groups/${getStoriesGroupId()}/stories`, 'POST', { - body: { authorId, title, content, pinOrder }, + body: { authorId, title, defaultStoryContent, pinOrder }, ...tokens }); if (!resp) { @@ -123,11 +207,12 @@ export const updateStory = async ( tokens: Tokens, id: number, title: string, - content: string, + header: string, + content: StoryCell[], pinOrder: number | null ): Promise => { const resp = await requestStoryBackend(`/groups/${getStoriesGroupId()}/stories/${id}`, 'PUT', { - body: { title, content, pinOrder }, + body: { title, defaultStoryContent, pinOrder }, ...tokens }); if (!resp) { @@ -136,7 +221,15 @@ export const updateStory = async ( } showSuccessMessage('Story saved'); const updatedStory = await resp.json(); - return updatedStory; + // return updatedStory; + + // change + console.log('in updateStory'); + tempContent = content; + tempHeader = header; + console.log(content, header); + const story = { ...updatedStory, content: content, header: header }; + return story; }; // Returns the deleted story, or null if errors occur diff --git a/src/features/stories/storiesComponents/CreateStoryCell.tsx b/src/features/stories/storiesComponents/CreateStoryCell.tsx new file mode 100644 index 0000000000..ace21ad358 --- /dev/null +++ b/src/features/stories/storiesComponents/CreateStoryCell.tsx @@ -0,0 +1,131 @@ +import { Menu, MenuItem } from '@blueprintjs/core'; +import { useState } from 'react'; +import AceEditor from 'react-ace'; +import { useDispatch } from 'react-redux'; +import { ControlButtonSaveButton } from 'src/commons/controlBar/ControlBarSaveButton'; +import { useTypedSelector } from 'src/commons/utils/Hooks'; +import { showWarningMessage } from 'src/commons/utils/notifications/NotificationsHelper'; + +import StoriesActions from '../StoriesActions'; +import { StoryCell } from '../StoriesTypes'; +import { getEnvironments } from './UserBlogContent'; + +type Props = { + index: number; +}; + +const NewStoryCell: React.FC = ({ index }) => { + const dispatch = useDispatch(); + const { currentStory: story, currentStoryId: storyId } = useTypedSelector(store => store.stories); + const envs = getEnvironments(story!.header); + const [isCode, setIsCode] = useState(false); + const [env, setEnv] = useState(envs[0]); + const [code, setCode] = useState(''); + const [isDirty, setIsDirty] = useState(false); + + if (!story) { + return
; + } + + const editorOnChange = (code: string) => { + setCode(code); + setIsDirty(code.trim() !== ''); + }; + + const reset = () => { + setCode(''); + setEnv(envs[0]); + setIsCode(false); + setIsDirty(false); + }; + + const saveNewStoryCell = () => { + const contents = story.content; + for (let i = index; i < contents.length; i++) { + contents[i].index += 1; + } + const newContent: StoryCell = { + index: index, + isCode: isCode, + env: isCode ? env : '', + content: code + }; + contents.push(newContent); + contents.sort((a, b) => a.index - b.index); + const newStory = { ...story, content: [...contents] }; + console.log('a new cell is saved'); + console.log(newStory); + dispatch(StoriesActions.setCurrentStory({ ...newStory })); + dispatch(StoriesActions.saveStory(newStory, storyId!)); + }; + + const saveButClicked = () => { + if (!isDirty) { + showWarningMessage('Cannot save empty story cell!'); + return; + } + saveNewStoryCell(); + reset(); + }; + + return ( +
+
+ + + + setIsCode(false)} text="Markdown" /> + setIsCode(true)} text="Source" /> + + + {isCode && ( +
+ + + {envs.map((env: string, index: number) => ( + { + setEnv(env); + }} + text={env} + /> + ))} + + +
+ )} +
+ +
+ ); +}; + +export default NewStoryCell; diff --git a/src/features/stories/storiesComponents/Draggable.tsx b/src/features/stories/storiesComponents/Draggable.tsx new file mode 100644 index 0000000000..2e75888bd8 --- /dev/null +++ b/src/features/stories/storiesComponents/Draggable.tsx @@ -0,0 +1,51 @@ +import React, { useRef } from 'react'; + +import { useDragItem } from '../DragContext'; + +interface DraggableProps { + children: React.ReactNode; + id: number; +} + +const Draggable: React.FC = ({ children, id }) => { + const elementRef = useRef(null); + const { setIndex } = useDragItem(); + + const handleDragStart = (e: React.DragEvent) => { + if (elementRef.current) { + elementRef.current.style.opacity = '0.7'; + // Critical: Set the drag image to be just this element + e.dataTransfer.setDragImage(elementRef.current, 0, 0); + setIndex(id); + } + + // Set the effect + e.dataTransfer.effectAllowed = 'move'; + + // Prevent text selection during drag + document.body.style.userSelect = 'none'; + }; + + const handleDragEnd = () => { + // Re-enable text selection + if (elementRef.current) { + elementRef.current.style.opacity = '1'; + } + document.body.style.userSelect = ''; + }; + + return ( +
+ {children} +
+ ); +}; + +export default Draggable; diff --git a/src/features/stories/storiesComponents/DropArea.tsx b/src/features/stories/storiesComponents/DropArea.tsx new file mode 100644 index 0000000000..c3dee9a817 --- /dev/null +++ b/src/features/stories/storiesComponents/DropArea.tsx @@ -0,0 +1,65 @@ +import { useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useTypedSelector } from 'src/commons/utils/Hooks'; + +import { useDragItem } from '../DragContext'; +import StoriesActions from '../StoriesActions'; + +interface DropAreaProps { + dropIndex: number; +} + +const DropArea: React.FC = ({ dropIndex }) => { + const [showDrop, setShowDrop] = useState(false); + const dragItem = useDragItem(); + const { currentStory: story, currentStoryId: storyId } = useTypedSelector(store => store.stories); + const dispatch = useDispatch(); + + if (!story) { + // will never reached here, as story has been checked in Story.tsx + return
; + } + + const onDrop = () => { + const contents = story!.content; + const dragIndex = dragItem!.index!; + if (dragIndex > dropIndex) { + console.log('front'); + for (let i = dropIndex + 1; i < dragIndex; i++) { + contents[i].index += 1; + } + contents[dragIndex].index = dropIndex + 1; + } else if (dragIndex < dropIndex) { + console.log('back'); + console.log(dragIndex, dropIndex); + for (let i = dragIndex + 1; i <= dropIndex; i++) { + contents[i].index -= 1; + } + contents[dragIndex].index = dropIndex; + } else { + return; + } + contents.sort((a, b) => a.index - b.index); + console.log(contents); + const newStory = { ...story, content: [...contents] }; + dispatch(StoriesActions.setCurrentStory(newStory)); + dispatch(StoriesActions.saveStory(newStory, storyId!)); + }; + + return ( +
setShowDrop(true)} + onDragLeave={() => setShowDrop(false)} + onDrop={() => { + onDrop(); + setShowDrop(false); + }} + onDragOver={e => e.preventDefault()} + > + Drop Here +
+ ); +}; + +export default DropArea; diff --git a/src/features/stories/storiesComponents/EditStoryCell.tsx b/src/features/stories/storiesComponents/EditStoryCell.tsx new file mode 100644 index 0000000000..eca00ab70c --- /dev/null +++ b/src/features/stories/storiesComponents/EditStoryCell.tsx @@ -0,0 +1,264 @@ +import { Button } from '@blueprintjs/core'; +import { createContext, useEffect, useState } from 'react'; +import AceEditor from 'react-ace'; +import { useDispatch } from 'react-redux'; +import { ControlButtonSaveButton } from 'src/commons/controlBar/ControlBarSaveButton'; +import { showSimpleConfirmDialog } from 'src/commons/utils/DialogHelper'; +import { useTypedSelector } from 'src/commons/utils/Hooks'; +import { renderStoryMarkdown } from 'src/commons/utils/StoriesHelper'; + +import StoriesActions from '../StoriesActions'; +import { StoryCell } from '../StoriesTypes'; +import NewStoryCell from './CreateStoryCell'; +import Draggable from './Draggable'; +import DropArea from './DropArea'; + +type Props = { + index: number; +}; + +export const SourceBlockContext = createContext<(isTyping: boolean) => void>(() => {}); + +function EditStoryCell(props: Props) { + const dispatch = useDispatch(); + const { currentStory: story, currentStoryId: storyId } = useTypedSelector(store => store.stories); + const [isCode, setIsCode] = useState(false); + const [env, setEnv] = useState(''); + const [storyContent, setStoryContent] = useState(''); + const [isEditMode, setEditMode] = useState(false); + const [isDirty, setIsDirty] = useState(false); + const [showButs, setShowButs] = useState(false); + const [showNewCellUp, setShowNewCellUp] = useState(false); + const [showNewCellDown, setShowNewCellDown] = useState(false); + + useEffect(() => { + if (!story) return; + setStoryContent(story.content[props.index].content); + setEnv(story.content[props.index].env); + setIsCode(story.content[props.index].isCode); + console.log(story.content[props.index], props.index, isCode); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [story]); + + if (!story) { + // will never reach here, as it has been checked in Story.tsx + return
; + } + + const editContent = (newContent: string) => { + console.log('content is editted'); + const contents = story.content; + contents.filter((story: StoryCell) => story.index == props.index)[0].content = newContent; + const newStory = { ...story, content: [...contents] }; + dispatch(StoriesActions.setCurrentStory(newStory)); + dispatch(StoriesActions.saveStory(newStory, storyId!)); + }; + + const saveButClicked = () => { + setEditMode(false); + setIsDirty(false); + setShowButs(false); + setShowNewCellUp(false); + setShowNewCellDown(false); + if (storyContent.trim().length > 0) { + const trimmedContent = storyContent.trim(); + setStoryContent(trimmedContent); + editContent(trimmedContent); + } else { + deleteWithoutConfirmation(); + } + }; + + const deleteWithConfirmation = async () => { + const confirm = await showSimpleConfirmDialog({ + contents: ( + <> +

Delete the story cell?

+

Note: This action is irreversible.

+ + ), + positiveIntent: 'danger', + positiveLabel: 'Delete' + }); + if (!confirm) { + return; + } + deleteWithoutConfirmation(); + }; + + const deleteWithoutConfirmation = () => { + console.log(`story cell ${props.index} is deleted`); + const contents = story.content; + const newContents = []; + for (let i = 0; i < contents.length; i++) { + if (props.index === i) { + continue; + } else if (props.index < i) { + contents[i].index--; + } + newContents.push(contents[i]); + } + const newStory = { ...story, content: newContents }; + dispatch(StoriesActions.setCurrentStory(newStory)); + dispatch(StoriesActions.saveStory(newStory, storyId!)); + }; + + const onEditorValueChange = (content: string) => { + setStoryContent(content); + setIsDirty(true); + }; + + const handleDoubleClick = () => { + if (!isCode) { + setEditMode(true); + } + }; + + const moveStoryCell = (moveUp: boolean) => { + const swapIndex = props.index + (moveUp ? -1 : 1); + // check if the user is moving the story cell out of the array bound + if (swapIndex < 0 || swapIndex >= story.content.length) { + return; + } + if (moveUp) { + story.content[swapIndex].index++; + story.content[props.index].index--; + } else { + story.content[swapIndex].index--; + story.content[props.index].index++; + } + const temp = story.content[props.index]; + story.content[props.index] = story.content[swapIndex]; + story.content[swapIndex] = temp; + const newStory = { ...story, content: [...story.content] }; + dispatch(StoriesActions.setCurrentStory(newStory)); + dispatch(StoriesActions.saveStory(newStory, storyId!)); + }; + + return ( +
setShowButs(true)} + onMouseLeave={() => setShowButs(false)} + > + {showNewCellUp && } + {isEditMode && ( +
+ +
+ )} +
+ {showButs && ( +
+ + + + + + +
+ )} + {isEditMode ? ( + + ) : ( + + {renderStoryMarkdown( + isCode ? '```{source} env:' + env + '\n' + storyContent : storyContent, + props.index, + false + )} + + )} +
+ {showNewCellDown && } + +
+ ); +} + +export default EditStoryCell; diff --git a/src/features/stories/storiesComponents/SourceBlock.tsx b/src/features/stories/storiesComponents/SourceBlock.tsx index 86ecd97114..b4d7af73d8 100644 --- a/src/features/stories/storiesComponents/SourceBlock.tsx +++ b/src/features/stories/storiesComponents/SourceBlock.tsx @@ -1,4 +1,4 @@ -import { Card, Classes } from '@blueprintjs/core'; +import { Card, Classes, Menu, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { Chapter, Variant } from 'js-slang/dist/types'; import React, { useEffect, useRef, useState } from 'react'; @@ -6,6 +6,7 @@ import AceEditor from 'react-ace'; import { useDispatch } from 'react-redux'; import { ResultOutput, styliseSublanguage } from 'src/commons/application/ApplicationTypes'; import { ControlBarRunButton } from 'src/commons/controlBar/ControlBarRunButton'; +import { ControlButtonSaveButton } from 'src/commons/controlBar/ControlBarSaveButton'; import ControlButton from 'src/commons/ControlButton'; import makeDataVisualizerTabFrom from 'src/commons/sideContent/content/SideContentDataVisualizer'; import makeHtmlDisplayTabFrom from 'src/commons/sideContent/content/SideContentHtmlDisplay'; @@ -21,11 +22,14 @@ import { makeSubstVisualizerTabFrom } from 'src/pages/playground/PlaygroundTabs' import { ExternalLibraryName } from '../../../commons/application/types/ExternalTypes'; import { Output } from '../../../commons/repl/Repl'; import { getModeString, selectMode } from '../../../commons/utils/AceHelper'; -import { DEFAULT_ENV } from './UserBlogContent'; +import { StoryCell } from '../StoriesTypes'; +import { DEFAULT_ENV, getEnvironments, handleHeaders } from './UserBlogContent'; export type SourceBlockProps = { content: string; commands: string; // env is in commands + index: number; + isViewOnly: boolean; }; /** @@ -50,8 +54,11 @@ const SourceBlock: React.FC = props => { const dispatch = useDispatch(); const [code, setCode] = useState(props.content); const [outputIndex, setOutputIndex] = useState(Infinity); - + const [isDirty, setIsDirty] = useState(false); + const { currentStory: story, currentStoryId: storyId } = useTypedSelector(store => store.stories); const envList = useTypedSelector(store => Object.keys(store.stories.envs)); + const { header: header } = story!; + const envs = getEnvironments(header); // setting env const commandsEnv = parseMetadata('env', props.commands); @@ -69,13 +76,39 @@ const SourceBlock: React.FC = props => { store => store.stories.envs[env]?.context.variant || Constants.defaultSourceVariant ); + const [currentEnv, setCurrentEnv] = useState(env); + const [currentChapter, setCurrentChapter] = useState(chapter); + + const getChapter = () => { + const envIndex = envs.indexOf(env); + // number indicating the chapter start from index 13 + return parseInt(header.split(`\n`)[envIndex * 3 + 3].substring(13)); + }; + useEffect(() => { setCode(props.content); }, [props.content]); + useEffect(() => { + setCurrentChapter(getChapter()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [story]); + const output = useTypedSelector(store => store.stories.envs[env]?.output || []); const { selectedTab, setSelectedTab } = useSideContent(`stories.${env}`); + // useEffect(() => { + // if (!selectedTab) { + // console.log("hello"); + // console.log(setSelectedTab); + // setSelectedTab(SideContentType.storiesRun); + // } + // }, []); + + useEffect(() => { + console.log('selected tab is changed: ', selectedTab); + }, [selectedTab]); + const onChangeTabs = React.useCallback( ( newTabId: SideContentType, @@ -87,11 +120,13 @@ const SourceBlock: React.FC = props => { dispatch( StoriesActions.toggleStoriesUsingSubst(newTabId === SideContentType.substVisualizer, env) ); + console.log(selectedTab); + console.log('selected tab: ', newTabId); setSelectedTab(newTabId); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [selectedTab] ); const envDisplayLabel = @@ -191,7 +226,9 @@ const SourceBlock: React.FC = props => { // is handled by the component setting. if (selectedTab) onChangeTabs(selectedTab, selectedTab, {} as any); + console.log('Running on ', selectedTab); dispatch(StoriesActions.evalStory(env, code)); + console.log(selectedTab); setOutputIndex(output.length); }; @@ -203,6 +240,68 @@ const SourceBlock: React.FC = props => { dispatch(StoriesActions.clearStoryEnv(env)); }; + const editorOnChange = (code: string) => { + setCode(code); + setIsDirty(true); + }; + + const deleteStoryCell = (contents: StoryCell[]) => { + console.log(`story cell ${props.index} is deleted`); + for (let i = props.index + 1; i < contents.length; i++) { + contents[i].index--; + } + contents.splice(props.index, 1); + }; + + const editHeader = (header: string[]) => { + console.log('In source block: chapter is editted'); + const index = envList.indexOf(currentEnv); + header[index * 3 + 3] = ` chapter: ${currentChapter}`; + return header; + }; + + const saveButClicked = () => { + setIsDirty(false); + const trimmedCode = code.trim(); + setCode(trimmedCode); + const contents = [...story!.content]; + if (trimmedCode.length === 0) { + deleteStoryCell(contents); + } else { + contents[props.index].content = trimmedCode; + } + if (currentEnv !== env) { + // set a new env + console.log('In source block: env is editted'); + console.log(currentEnv, env); + story!.content[props.index].env = currentEnv; + } + const newHeader = story!.header.split('\n'); + if (currentChapter !== chapter) { + // set a new chapter for the corresponding env, all source block with the same env will change tgt + console.log(currentChapter, chapter); + editHeader(newHeader); + } + execResetEnv(); + handleHeaders(newHeader.join('\n')); + const newStory = { ...story!, content: contents, header: newHeader.join('\n') }; + dispatch(StoriesActions.setCurrentStory(newStory)); + dispatch(StoriesActions.saveStory(newStory, storyId!)); + }; + + const changeEnv = (env: string) => { + setCurrentEnv(env); + const header = story!.header.split('\n'); + const index = envList.indexOf(env); + setCurrentChapter(+header[3 + index * 3].substring(13)); + setIsDirty(true); + }; + + const changeEnvChapter = (chapter: Chapter) => { + setCurrentChapter(chapter); + setIsDirty(true); + }; + selectMode(chapter, variant, ExternalLibraryName.NONE); return ( @@ -210,13 +309,52 @@ const SourceBlock: React.FC = props => {
- + {isDirty ? ( + + ) : ( + + )} - {envDisplayLabel} + {props.isViewOnly ? ( + envDisplayLabel + ) : ( +
+ + + {envList.map((env: string, index: number) => ( + changeEnv(env)} /> + ))} + + +

|

+ + + {[1, 2, 3, 4].map((chapter: Chapter, index: number) => ( + changeEnvChapter(chapter)} + text={styliseSublanguage(chapter, variant)} + /> + ))} + + +
+ )}
@@ -230,7 +368,9 @@ const SourceBlock: React.FC = props => { height="1px" width="100%" value={code} - onChange={code => setCode(code)} + onChange={editorOnChange} + // onFocus={() => setIsTyping(true)} + // onBlur={() => setIsTyping(false)} commands={[ { name: 'evaluate', diff --git a/src/features/stories/storiesComponents/UserBlogContent.tsx b/src/features/stories/storiesComponents/UserBlogContent.tsx index 2cabf33bc4..1c83284787 100644 --- a/src/features/stories/storiesComponents/UserBlogContent.tsx +++ b/src/features/stories/storiesComponents/UserBlogContent.tsx @@ -1,13 +1,25 @@ +import { Menu, MenuItem } from '@blueprintjs/core'; +import { TextInput } from '@tremor/react'; import { Chapter, Variant } from 'js-slang/dist/types'; import yaml from 'js-yaml'; import React, { useEffect, useState } from 'react'; import debounceRender from 'react-debounce-render'; +import { useDispatch } from 'react-redux'; +import { styliseSublanguage } from 'src/commons/application/ApplicationTypes'; +import ControlBar, { ControlBarProps } from 'src/commons/controlBar/ControlBar'; +import { ControlButtonSaveButton } from 'src/commons/controlBar/ControlBarSaveButton'; import Constants from 'src/commons/utils/Constants'; +import { useTypedSelector } from 'src/commons/utils/Hooks'; import { propsAreEqual } from 'src/commons/utils/MemoizeHelper'; -import { renderStoryMarkdown } from 'src/commons/utils/StoriesHelper'; +import { showWarningMessage } from 'src/commons/utils/notifications/NotificationsHelper'; import StoriesActions from 'src/features/stories/StoriesActions'; import { store } from '../../../pages/createStore'; +import { DragContext } from '../DragContext'; +import NewStoryCell from './CreateStoryCell'; +import DropArea from './DropArea'; +import EditStoryCell from './EditStoryCell'; +import ViewStoryCell from './ViewStoryCell'; export const DEFAULT_ENV = 'default'; @@ -33,7 +45,7 @@ function handleEnvironment(envConfig: Record): void { } } -function handleHeaders(headers: string): void { +export function handleHeaders(headers: string): void { if (headers === '') { store.dispatch( StoriesActions.addStoryEnv( @@ -61,7 +73,6 @@ function handleHeaders(headers: string): void { } } } catch (err) { - console.warn(err); if (err instanceof yaml.YAMLException) { // default headers store.dispatch( @@ -91,23 +102,154 @@ export function getYamlHeader(content: string): { header: string; content: strin }; } +export function getEnvironments(header: string): string[] { + const environments: string[] = []; + const temp = header.split('\n'); + for (let i = 2; i < temp.length - 1; i += 3) { + environments.push(temp[i].substring(2, temp[i].length - 1)); + } + return environments; +} + +export function constructHeader( + header: string, + env: string, + chapter: Chapter, + variant: Variant +): string { + const newHeader: string[] = header.split('\n'); + newHeader.push(` ${env}:`); + newHeader.push(` chapter: ${chapter}`); + newHeader.push(` variant: ${variant}`); + return newHeader.join('\n'); +} + type Props = { - fileContent: string; + isViewOnly: boolean; }; -const UserBlogContent: React.FC = ({ fileContent }) => { - const [content, setContent] = useState(''); +const UserBlogContent: React.FC = ({ isViewOnly }) => { + const [newEnv, setNewEnv] = useState(''); + // TODO: enable different variant + const variant: Variant = Variant.DEFAULT; + const [currentChapter, setEnvChapter] = useState(Chapter.SOURCE_1); + const [isDirty, setIsDirty] = useState(false); + const dispatch = useDispatch(); + const { currentStory: story, currentStoryId: storyId } = useTypedSelector(store => store.stories); + const { content: contents, header: header } = story!; + const [envs, setEnvs] = useState(getEnvironments(header)); + const [activeIndex, setActiveIndex] = useState(null); useEffect(() => { - const { header, content } = getYamlHeader(fileContent); - setContent(content); store.dispatch(StoriesActions.clearStoryEnv()); handleHeaders(header); - }, [fileContent]); + setEnvs(getEnvironments(header)); + console.log('header resets'); + }, [header]); + + if (!story) { + // will never reach here, as it has been check in Story.tsx + return
; + } + + const editHeader = (newHeader: string) => { + console.log('header is editted'); + const newStory = { ...story, header: newHeader }; + dispatch(StoriesActions.setCurrentStory(newStory)); + dispatch(StoriesActions.saveStory(newStory, storyId!)); + }; + + const saveButClicked = () => { + setNewEnv(''); + setIsDirty(false); + if (newEnv.trim() === '') { + showWarningMessage('environment name cannot be empty'); + return; + } else if (envs.includes(newEnv)) { + showWarningMessage(`${newEnv} already exists!`); + return; + } + const newHeader = header.concat(` + ${newEnv}: + chapter: ${currentChapter} + variant: default`); + editHeader(newHeader); + }; + + const controlBarProps: ControlBarProps = { + editorButtons: [ +
+ { + setNewEnv(e.target.value); + if (e.target.value.trim() !== '') { + setIsDirty(true); + } else { + setIsDirty(false); + } + }} + /> + + + setEnvChapter(1)} + text={styliseSublanguage(Chapter.SOURCE_1, variant)} + /> + setEnvChapter(2)} + text={styliseSublanguage(Chapter.SOURCE_2, variant)} + /> + setEnvChapter(3)} + text={styliseSublanguage(Chapter.SOURCE_3, variant)} + /> + setEnvChapter(4)} + text={styliseSublanguage(Chapter.SOURCE_4, variant)} + /> + + + +
+ ] + }; - return content ? ( + return contents.length > 0 ? (
-
{renderStoryMarkdown(content)}
+ {!isViewOnly && } + {isViewOnly ? ( + contents.map((story, key) => ) + ) : ( + +
+ +
+ {contents.map((_, key) => { + return ; + })} +
+ )} + {!isViewOnly && ( +
+ +
+ )}
) : (
diff --git a/src/features/stories/storiesComponents/ViewStoryCell.tsx b/src/features/stories/storiesComponents/ViewStoryCell.tsx new file mode 100644 index 0000000000..d13072bdcf --- /dev/null +++ b/src/features/stories/storiesComponents/ViewStoryCell.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react'; +import { renderStoryMarkdown } from 'src/commons/utils/StoriesHelper'; + +import { StoryCell } from '../StoriesTypes'; + +type Props = { + story: StoryCell; +}; + +function ViewStoryCell(props: Props) { + const { index, isCode, env, content } = props.story; + const [storyContent, setStoryContent] = useState(content); + + useEffect(() => { + if (isCode) { + setStoryContent('```{source} env:' + env + '\n' + content); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return
{renderStoryMarkdown(storyContent, index, true)}
; +} + +export default ViewStoryCell; diff --git a/src/i18n/i18next.d.ts b/src/i18n/i18next.ts similarity index 89% rename from src/i18n/i18next.d.ts rename to src/i18n/i18next.ts index dbf6283bcb..7f5741615e 100644 --- a/src/i18n/i18next.d.ts +++ b/src/i18n/i18next.ts @@ -1,8 +1,7 @@ import 'i18next'; // import all namespaces (for the default language, only) -import commons from 'locales/en/commons.json'; - +// import commons from 'locales/en/commons.json'; import { defaultLanguage, i18nLanguageCode } from './locales'; export type i18nDefaultLangKeys = (typeof defaultLanguage)[i18nLanguageCode.DEFAULT]; diff --git a/src/pages/stories/Stories.tsx b/src/pages/stories/Stories.tsx index db96feadb9..cbeae44b56 100644 --- a/src/pages/stories/Stories.tsx +++ b/src/pages/stories/Stories.tsx @@ -10,7 +10,6 @@ import GradingText from 'src/commons/grading/GradingText'; import { showSimpleConfirmDialog } from 'src/commons/utils/DialogHelper'; import { useTypedSelector } from 'src/commons/utils/Hooks'; import StoriesActions from 'src/features/stories/StoriesActions'; -import { getYamlHeader } from 'src/features/stories/storiesComponents/UserBlogContent'; import StoriesTable from './StoriesTable'; import StoryActions from './StoryActions'; @@ -141,14 +140,11 @@ const Stories: React.FC = () => { ({ ...story, content: getYamlHeader(story.content).content })) - .filter( - story => - // Always show pinned stories - story.isPinned || story.authorName.toLowerCase().includes(query.toLowerCase()) - )} + stories={storyList.filter( + story => + // Always show pinned stories + story.isPinned || story.authorName.toLowerCase().includes(query.toLowerCase()) + )} storyActions={story => { const isAuthor = storiesUserId === story.authorId; const hasWritePermissions = diff --git a/src/pages/stories/StoriesTable.tsx b/src/pages/stories/StoriesTable.tsx index d66d73379a..c0a45d304a 100644 --- a/src/pages/stories/StoriesTable.tsx +++ b/src/pages/stories/StoriesTable.tsx @@ -50,7 +50,7 @@ const StoriesTable: React.FC = ({ headers, stories, storyActions }) => { flex: 6, field: 'content', headerName: 'Content', - valueFormatter: ({ value }) => truncate(value), + valueFormatter: ({ value }) => truncate(value[0].content), cellStyle: { textAlign: 'left' } }, { diff --git a/src/pages/stories/Story.tsx b/src/pages/stories/Story.tsx index 676eb59cc1..23e72bdbfb 100644 --- a/src/pages/stories/Story.tsx +++ b/src/pages/stories/Story.tsx @@ -1,15 +1,15 @@ import 'js-slang/dist/editors/ace/theme/source'; -import { Classes, InputGroup } from '@blueprintjs/core'; +import { Classes } from '@blueprintjs/core'; +import { TextInput } from '@tremor/react'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; -import AceEditor, { IEditorProps } from 'react-ace'; import { useDispatch } from 'react-redux'; import { useParams } from 'react-router'; import ControlBar, { ControlBarProps } from 'src/commons/controlBar/ControlBar'; import { ControlButtonSaveButton } from 'src/commons/controlBar/ControlBarSaveButton'; import { useTypedSelector } from 'src/commons/utils/Hooks'; -import { scrollSync } from 'src/commons/utils/StoriesHelper'; +import { showWarningMessage } from 'src/commons/utils/notifications/NotificationsHelper'; import StoriesActions from 'src/features/stories/StoriesActions'; import UserBlogContent from '../../features/stories/storiesComponents/UserBlogContent'; @@ -24,6 +24,7 @@ const Story: React.FC = ({ isViewOnly = false }) => { const { currentStory: story, currentStoryId: storyId } = useTypedSelector(store => store.stories); const { id: idToSet } = useParams<{ id: string }>(); + useEffect(() => { // Clear screen on first load dispatch(StoriesActions.setCurrentStory(null)); @@ -37,27 +38,15 @@ const Story: React.FC = ({ isViewOnly = false }) => { return <>; } - const onEditorScroll = (e: IEditorProps) => { - const userblogContainer = document.getElementById('userblogContainer'); - if (userblogContainer) { - scrollSync(e, userblogContainer); - } - }; - - const onEditorValueChange = (val: string) => { - setIsDirty(true); - dispatch(StoriesActions.setCurrentStory({ ...story, content: val })); - }; - - const { title, content } = story; + const { title: title } = story; const controlBarProps: ControlBarProps = { editorButtons: [ isViewOnly ? ( <>{title} ) : ( - { @@ -71,6 +60,10 @@ const Story: React.FC = ({ isViewOnly = false }) => { { + if (story.title.trim() === '') { + showWarningMessage('story name cannot be empty'); + return; + } if (storyId) { // Update story dispatch(StoriesActions.saveStory(story, storyId)); @@ -79,6 +72,7 @@ const Story: React.FC = ({ isViewOnly = false }) => { dispatch(StoriesActions.createStory(story)); } // TODO: Set isDirty to false + setIsDirty(false); }} hasUnsavedChanges={isDirty} /> @@ -90,24 +84,8 @@ const Story: React.FC = ({ isViewOnly = false }) => {
- {!isViewOnly && ( - - )}
- +
diff --git a/src/react-app-env.d.ts b/src/react-app-env.ts similarity index 100% rename from src/react-app-env.d.ts rename to src/react-app-env.ts diff --git a/src/styles/_stories.scss b/src/styles/_stories.scss index deb55d57ad..3bdcfd5de8 100644 --- a/src/styles/_stories.scss +++ b/src/styles/_stories.scss @@ -51,6 +51,7 @@ padding: 16px 24px; font-size: 1rem; line-height: 1.5; + // cursor: grab; & > * { margin: 0; @@ -93,6 +94,34 @@ pre code { white-space: pre; } + + [draggable='true'] { + user-select: none; + -webkit-user-select: none; + cursor: grab; + position: relative; /* Ensure proper positioning */ + } + + [draggable='true']:active { + cursor: grabbing; + } + + .drop-area { + width: 100%; + height: 100px; + columns: #dcdcdc; + border: 1px dashed #dcdcdc; + border-radius: 10px; + padding: 15px; + opacity: 1; + transition: all 0.2s ease-in-out; + } + + .hide-drop { + opacity: 0; + height: 30px; + margin: 0px; + } } } } diff --git a/yarn.lock b/yarn.lock index 3cf4a3c557..da11d8deb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2054,6 +2054,55 @@ __metadata: languageName: node linkType: hard +"@dnd-kit/accessibility@npm:^3.1.1": + version: 3.1.1 + resolution: "@dnd-kit/accessibility@npm:3.1.1" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8 + languageName: node + linkType: hard + +"@dnd-kit/core@npm:^6.3.1": + version: 6.3.1 + resolution: "@dnd-kit/core@npm:6.3.1" + dependencies: + "@dnd-kit/accessibility": "npm:^3.1.1" + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/196db95d81096d9dc248983533eab91ba83591770fa5c894b1ac776f42af0d99522b3fd5bb3923411470e4733fcfa103e6ee17adc17b9b7eb54c7fbec5ff7c52 + languageName: node + linkType: hard + +"@dnd-kit/sortable@npm:^10.0.0": + version: 10.0.0 + resolution: "@dnd-kit/sortable@npm:10.0.0" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.3.0 + react: ">=16.8.0" + checksum: 10c0/37ee48bc6789fb512dc0e4c374a96d19abe5b2b76dc34856a5883aaa96c3297891b94cc77bbc409e074dcce70967ebcb9feb40cd9abadb8716fc280b4c7f99af + languageName: node + linkType: hard + +"@dnd-kit/utilities@npm:^3.2.2": + version: 3.2.2 + resolution: "@dnd-kit/utilities@npm:3.2.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/9aa90526f3e3fd567b5acc1b625a63177b9e8d00e7e50b2bd0e08fa2bf4dba7e19529777e001fdb8f89a7ce69f30b190c8364d390212634e0afdfa8c395e85a0 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.11.0": version: 11.11.0 resolution: "@emotion/babel-plugin@npm:11.11.0" @@ -9589,6 +9638,9 @@ __metadata: "@blueprintjs/select": "npm:^5.1.3" "@convergencelabs/ace-collab-ext": "npm:^0.6.0" "@craco/craco": "npm:^7.1.0" + "@dnd-kit/core": "npm:^6.3.1" + "@dnd-kit/sortable": "npm:^10.0.0" + "@dnd-kit/utilities": "npm:^3.2.2" "@mantine/hooks": "npm:^7.11.2" "@octokit/rest": "npm:^20.0.0" "@reduxjs/toolkit": "npm:^1.9.7"