@tecsinapse/cortex-react 1.15.0-beta.7 → 1.15.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,20 +5,19 @@ var reactDom = require('react-dom');
5
5
  var Upload = require('./Upload.js');
6
6
  var Button = require('../Button.js');
7
7
  var io = require('react-icons/io');
8
- var React = require('react');
9
8
  var clsx = require('clsx');
10
9
  var io5 = require('react-icons/io5');
11
10
  var cortexCore = require('@tecsinapse/cortex-core');
11
+ var useManager = require('../../hooks/useManager.js');
12
12
 
13
13
  const Manager = ({
14
14
  open,
15
15
  files,
16
16
  onDelete,
17
17
  uploadProgressText = "Upload(s) in progress",
18
- onClose,
19
- type = "file"
18
+ onClose
20
19
  }) => {
21
- const [min, setMin] = React.useState(false);
20
+ const { min, setMin, regularFiles, folderFiles } = useManager.useManager({ files });
22
21
  return reactDom.createPortal(
23
22
  /* @__PURE__ */ jsxRuntime.jsx(
24
23
  "div",
@@ -47,7 +46,7 @@ const Manager = ({
47
46
  }
48
47
  )
49
48
  ] }),
50
- /* @__PURE__ */ jsxRuntime.jsx(
49
+ /* @__PURE__ */ jsxRuntime.jsxs(
51
50
  "div",
52
51
  {
53
52
  className: clsx.clsx("w-full h-auto max-h-[300px] gap-mili", {
@@ -55,16 +54,19 @@ const Manager = ({
55
54
  "flex flex-col": !min,
56
55
  "pb-kilo overflow-scroll pr-deca": files.length > 3
57
56
  }),
58
- children: type === "file" ? files.map((file, index) => /* @__PURE__ */ jsxRuntime.jsx(
59
- Upload.File,
60
- {
61
- file,
62
- index,
63
- onDelete,
64
- showDelete: false
65
- },
66
- file.uid
67
- )) : /* @__PURE__ */ jsxRuntime.jsx(Upload.FolderList, { files })
57
+ children: [
58
+ regularFiles.length > 0 ? regularFiles.map((file, index) => /* @__PURE__ */ jsxRuntime.jsx(
59
+ Upload.File,
60
+ {
61
+ file,
62
+ index,
63
+ onDelete,
64
+ showDelete: false
65
+ },
66
+ file.uid
67
+ )) : null,
68
+ folderFiles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Upload.FolderList, { files: folderFiles }) : null
69
+ ]
68
70
  }
69
71
  )
70
72
  ] })
@@ -2,10 +2,32 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var cortexCore = require('@tecsinapse/cortex-core');
5
+ var React = require('react');
5
6
  var fa6 = require('react-icons/fa6');
6
7
  var md = require('react-icons/md');
7
8
  var ProgressBar = require('../ProgressBar/ProgressBar.js');
8
9
 
10
+ const recursiveCountFolderElements = (node) => {
11
+ let count = 0;
12
+ for (const key in node) {
13
+ count++;
14
+ count += recursiveCountFolderElements(node[key]);
15
+ }
16
+ return count;
17
+ };
18
+ const countFolderElements = (paths, root) => {
19
+ if (!paths.length) return 0;
20
+ const tree = {};
21
+ for (const path of paths) {
22
+ const parts = path.replace(root + "/", "").split("/").filter(Boolean);
23
+ let current = tree;
24
+ for (const part of parts) {
25
+ if (!current[part]) current[part] = {};
26
+ current = current[part];
27
+ }
28
+ }
29
+ return recursiveCountFolderElements(tree);
30
+ };
9
31
  const File = ({
10
32
  file,
11
33
  index,
@@ -63,7 +85,22 @@ const File = ({
63
85
  )
64
86
  ] }, index);
65
87
  };
66
- const Folder = ({ name, size, intent, loading }) => {
88
+ const Folder = ({ name, subItems }) => {
89
+ const size = countFolderElements(
90
+ subItems.map((it) => it.path),
91
+ name
92
+ );
93
+ const loading = React.useMemo(
94
+ () => subItems.some((it) => it.status === "uploading"),
95
+ [subItems]
96
+ );
97
+ const intent = React.useMemo(() => {
98
+ if (loading) return "info";
99
+ if ((subItems ?? []).some((item) => item.status === "error") && (subItems ?? []).some((item) => item.status === "success")) {
100
+ return "warning";
101
+ }
102
+ return "success";
103
+ }, [subItems]);
67
104
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col w-full", children: [
68
105
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between border rounded-t-mili shadow p-mili", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-centi", children: [
69
106
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "border-2 text-kilo text-primary-medium w-tera h-tera flex items-center justify-center rounded-mili", children: /* @__PURE__ */ jsxRuntime.jsx(fa6.FaRegFolder, {}) }),
@@ -80,79 +117,17 @@ const Folder = ({ name, size, intent, loading }) => {
80
117
  ] });
81
118
  };
82
119
  const FolderList = ({ files }) => {
83
- const paths = /* @__PURE__ */ new Set();
84
- const buildTree = (files2) => {
85
- const root = {
86
- type: "root",
87
- children: /* @__PURE__ */ new Map()
88
- };
89
- for (const file of files2) {
90
- const parts = file.file.relativePath.slice(1).split("/");
91
- parts.map((item) => paths.add(item));
92
- let current = root;
93
- parts.forEach((part, index) => {
94
- const isLast = index === parts.length - 1;
95
- if (isLast) {
96
- current.children.set(part, {
97
- type: "file",
98
- file
99
- });
100
- } else {
101
- if (!current.children.has(part)) {
102
- current.children.set(part, {
103
- type: "folder",
104
- children: /* @__PURE__ */ new Map()
105
- });
106
- }
107
- current = current.children.get(part);
108
- }
109
- });
110
- }
111
- return root;
112
- };
113
- const tree = buildTree(files);
114
- const count = (node) => {
115
- let total = 0;
116
- const files2 = [];
117
- for (const child of node.children.values()) {
118
- if (child.type === "file") {
119
- total += 1;
120
- files2.push(child.file);
121
- } else if (child.type === "folder") {
122
- total += 1;
123
- const nested = count(child);
124
- total += nested.total;
125
- files2.push(...nested.files);
126
- }
127
- }
128
- return { total, files: files2 };
129
- };
130
- const children = [];
131
- for (const [name, node] of tree.children) {
132
- const size = count(node).total;
133
- const files2 = count(node).files;
134
- children.push({
135
- name,
136
- size,
137
- files: files2
120
+ const folders = React.useMemo(() => {
121
+ const segments = {};
122
+ files.forEach((file) => {
123
+ const path = file.file.relativePath.replace(/^\//, "");
124
+ const root = path.split("/")[0];
125
+ const current = Array.from(segments?.[root] ?? []);
126
+ segments[root] = [...current, { path, status: file.status }];
138
127
  });
139
- }
140
- const folders = children.map((folder) => {
141
- const intent = (folder.files ?? []).some((item) => item.status === "success") ? "success" : (files ?? []).some((item) => item.status === "error") ? "error" : "info";
142
- return { ...folder, intent };
143
- });
144
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: folders.map(({ intent, name, size, files: files2 }, index) => /* @__PURE__ */ jsxRuntime.jsx(
145
- Folder,
146
- {
147
- name,
148
- size,
149
- intent,
150
- loading: files2.some(
151
- (file) => file.status === "uploading"
152
- )
153
- },
154
- index
155
- )) });
128
+ return segments;
129
+ }, [files]);
130
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: Object.entries(folders).map(([name, children], index) => /* @__PURE__ */ jsxRuntime.jsx(Folder, { name, subItems: children }, index)) });
156
131
  };
157
132
 
158
133
  exports.File = File;
@@ -13,7 +13,8 @@ const useFileUpload = ({
13
13
  allowMultiple = true,
14
14
  preventDuplicates = false,
15
15
  onDuplicate,
16
- hasManager = true
16
+ hasManager = true,
17
+ isFolder = false
17
18
  }) => {
18
19
  const [files, setFiles] = React.useState([]);
19
20
  const [duplicates, setDuplicates] = React.useState([]);
@@ -62,7 +63,8 @@ const useFileUpload = ({
62
63
  const newFiles = toProcess.map((file) => ({
63
64
  file,
64
65
  status: onAccept ? types.FileStatus.UPLOADING : types.FileStatus.SUCCESS,
65
- uid: uuid.v4()
66
+ uid: uuid.v4(),
67
+ isFolder
66
68
  }));
67
69
  try {
68
70
  setFiles((prevFiles) => [...prevFiles, ...newFiles]);
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+
5
+ const useManager = ({ files }) => {
6
+ const [min, setMin] = React.useState(false);
7
+ const folderFiles = React.useMemo(
8
+ () => files.filter((file) => file.isFolder),
9
+ [files]
10
+ );
11
+ const regularFiles = React.useMemo(
12
+ () => files.filter((file) => !file.isFolder),
13
+ [files]
14
+ );
15
+ return {
16
+ min,
17
+ setMin,
18
+ folderFiles,
19
+ regularFiles
20
+ };
21
+ };
22
+
23
+ exports.useManager = useManager;
@@ -3,20 +3,19 @@ import { createPortal } from 'react-dom';
3
3
  import { File, FolderList } from './Upload.js';
4
4
  import { Button } from '../Button.js';
5
5
  import { IoMdClose } from 'react-icons/io';
6
- import { useState } from 'react';
7
6
  import { clsx } from 'clsx';
8
7
  import { IoChevronUp, IoChevronDown } from 'react-icons/io5';
9
8
  import { manager } from '@tecsinapse/cortex-core';
9
+ import { useManager } from '../../hooks/useManager.js';
10
10
 
11
11
  const Manager = ({
12
12
  open,
13
13
  files,
14
14
  onDelete,
15
15
  uploadProgressText = "Upload(s) in progress",
16
- onClose,
17
- type = "file"
16
+ onClose
18
17
  }) => {
19
- const [min, setMin] = useState(false);
18
+ const { min, setMin, regularFiles, folderFiles } = useManager({ files });
20
19
  return createPortal(
21
20
  /* @__PURE__ */ jsx(
22
21
  "div",
@@ -45,7 +44,7 @@ const Manager = ({
45
44
  }
46
45
  )
47
46
  ] }),
48
- /* @__PURE__ */ jsx(
47
+ /* @__PURE__ */ jsxs(
49
48
  "div",
50
49
  {
51
50
  className: clsx("w-full h-auto max-h-[300px] gap-mili", {
@@ -53,16 +52,19 @@ const Manager = ({
53
52
  "flex flex-col": !min,
54
53
  "pb-kilo overflow-scroll pr-deca": files.length > 3
55
54
  }),
56
- children: type === "file" ? files.map((file, index) => /* @__PURE__ */ jsx(
57
- File,
58
- {
59
- file,
60
- index,
61
- onDelete,
62
- showDelete: false
63
- },
64
- file.uid
65
- )) : /* @__PURE__ */ jsx(FolderList, { files })
55
+ children: [
56
+ regularFiles.length > 0 ? regularFiles.map((file, index) => /* @__PURE__ */ jsx(
57
+ File,
58
+ {
59
+ file,
60
+ index,
61
+ onDelete,
62
+ showDelete: false
63
+ },
64
+ file.uid
65
+ )) : null,
66
+ folderFiles.length > 0 ? /* @__PURE__ */ jsx(FolderList, { files: folderFiles }) : null
67
+ ]
66
68
  }
67
69
  )
68
70
  ] })
@@ -1,9 +1,31 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { button } from '@tecsinapse/cortex-core';
3
+ import { useMemo } from 'react';
3
4
  import { FaRegFileLines, FaRegFolder } from 'react-icons/fa6';
4
5
  import { MdClose } from 'react-icons/md';
5
6
  import { ProgressBar } from '../ProgressBar/ProgressBar.js';
6
7
 
8
+ const recursiveCountFolderElements = (node) => {
9
+ let count = 0;
10
+ for (const key in node) {
11
+ count++;
12
+ count += recursiveCountFolderElements(node[key]);
13
+ }
14
+ return count;
15
+ };
16
+ const countFolderElements = (paths, root) => {
17
+ if (!paths.length) return 0;
18
+ const tree = {};
19
+ for (const path of paths) {
20
+ const parts = path.replace(root + "/", "").split("/").filter(Boolean);
21
+ let current = tree;
22
+ for (const part of parts) {
23
+ if (!current[part]) current[part] = {};
24
+ current = current[part];
25
+ }
26
+ }
27
+ return recursiveCountFolderElements(tree);
28
+ };
7
29
  const File = ({
8
30
  file,
9
31
  index,
@@ -61,7 +83,22 @@ const File = ({
61
83
  )
62
84
  ] }, index);
63
85
  };
64
- const Folder = ({ name, size, intent, loading }) => {
86
+ const Folder = ({ name, subItems }) => {
87
+ const size = countFolderElements(
88
+ subItems.map((it) => it.path),
89
+ name
90
+ );
91
+ const loading = useMemo(
92
+ () => subItems.some((it) => it.status === "uploading"),
93
+ [subItems]
94
+ );
95
+ const intent = useMemo(() => {
96
+ if (loading) return "info";
97
+ if ((subItems ?? []).some((item) => item.status === "error") && (subItems ?? []).some((item) => item.status === "success")) {
98
+ return "warning";
99
+ }
100
+ return "success";
101
+ }, [subItems]);
65
102
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-full", children: [
66
103
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between border rounded-t-mili shadow p-mili", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-centi", children: [
67
104
  /* @__PURE__ */ jsx("span", { className: "border-2 text-kilo text-primary-medium w-tera h-tera flex items-center justify-center rounded-mili", children: /* @__PURE__ */ jsx(FaRegFolder, {}) }),
@@ -78,79 +115,17 @@ const Folder = ({ name, size, intent, loading }) => {
78
115
  ] });
79
116
  };
80
117
  const FolderList = ({ files }) => {
81
- const paths = /* @__PURE__ */ new Set();
82
- const buildTree = (files2) => {
83
- const root = {
84
- type: "root",
85
- children: /* @__PURE__ */ new Map()
86
- };
87
- for (const file of files2) {
88
- const parts = file.file.relativePath.slice(1).split("/");
89
- parts.map((item) => paths.add(item));
90
- let current = root;
91
- parts.forEach((part, index) => {
92
- const isLast = index === parts.length - 1;
93
- if (isLast) {
94
- current.children.set(part, {
95
- type: "file",
96
- file
97
- });
98
- } else {
99
- if (!current.children.has(part)) {
100
- current.children.set(part, {
101
- type: "folder",
102
- children: /* @__PURE__ */ new Map()
103
- });
104
- }
105
- current = current.children.get(part);
106
- }
107
- });
108
- }
109
- return root;
110
- };
111
- const tree = buildTree(files);
112
- const count = (node) => {
113
- let total = 0;
114
- const files2 = [];
115
- for (const child of node.children.values()) {
116
- if (child.type === "file") {
117
- total += 1;
118
- files2.push(child.file);
119
- } else if (child.type === "folder") {
120
- total += 1;
121
- const nested = count(child);
122
- total += nested.total;
123
- files2.push(...nested.files);
124
- }
125
- }
126
- return { total, files: files2 };
127
- };
128
- const children = [];
129
- for (const [name, node] of tree.children) {
130
- const size = count(node).total;
131
- const files2 = count(node).files;
132
- children.push({
133
- name,
134
- size,
135
- files: files2
118
+ const folders = useMemo(() => {
119
+ const segments = {};
120
+ files.forEach((file) => {
121
+ const path = file.file.relativePath.replace(/^\//, "");
122
+ const root = path.split("/")[0];
123
+ const current = Array.from(segments?.[root] ?? []);
124
+ segments[root] = [...current, { path, status: file.status }];
136
125
  });
137
- }
138
- const folders = children.map((folder) => {
139
- const intent = (folder.files ?? []).some((item) => item.status === "success") ? "success" : (files ?? []).some((item) => item.status === "error") ? "error" : "info";
140
- return { ...folder, intent };
141
- });
142
- return /* @__PURE__ */ jsx(Fragment, { children: folders.map(({ intent, name, size, files: files2 }, index) => /* @__PURE__ */ jsx(
143
- Folder,
144
- {
145
- name,
146
- size,
147
- intent,
148
- loading: files2.some(
149
- (file) => file.status === "uploading"
150
- )
151
- },
152
- index
153
- )) });
126
+ return segments;
127
+ }, [files]);
128
+ return /* @__PURE__ */ jsx(Fragment, { children: Object.entries(folders).map(([name, children], index) => /* @__PURE__ */ jsx(Folder, { name, subItems: children }, index)) });
154
129
  };
155
130
 
156
131
  export { File, Folder, FolderList };
@@ -11,7 +11,8 @@ const useFileUpload = ({
11
11
  allowMultiple = true,
12
12
  preventDuplicates = false,
13
13
  onDuplicate,
14
- hasManager = true
14
+ hasManager = true,
15
+ isFolder = false
15
16
  }) => {
16
17
  const [files, setFiles] = useState([]);
17
18
  const [duplicates, setDuplicates] = useState([]);
@@ -60,7 +61,8 @@ const useFileUpload = ({
60
61
  const newFiles = toProcess.map((file) => ({
61
62
  file,
62
63
  status: onAccept ? FileStatus.UPLOADING : FileStatus.SUCCESS,
63
- uid: v4()
64
+ uid: v4(),
65
+ isFolder
64
66
  }));
65
67
  try {
66
68
  setFiles((prevFiles) => [...prevFiles, ...newFiles]);
@@ -0,0 +1,21 @@
1
+ import { useState, useMemo } from 'react';
2
+
3
+ const useManager = ({ files }) => {
4
+ const [min, setMin] = useState(false);
5
+ const folderFiles = useMemo(
6
+ () => files.filter((file) => file.isFolder),
7
+ [files]
8
+ );
9
+ const regularFiles = useMemo(
10
+ () => files.filter((file) => !file.isFolder),
11
+ [files]
12
+ );
13
+ return {
14
+ min,
15
+ setMin,
16
+ folderFiles,
17
+ regularFiles
18
+ };
19
+ };
20
+
21
+ export { useManager };
@@ -1,2 +1,2 @@
1
1
  import { ManagerProps } from './types';
2
- export declare const Manager: <T>({ open, files, onDelete, uploadProgressText, onClose, type, }: ManagerProps<T>) => any;
2
+ export declare const Manager: <T>({ open, files, onDelete, uploadProgressText, onClose, }: ManagerProps<T>) => any;
@@ -1,4 +1,4 @@
1
1
  import { FileProps, FolderListProps, FolderProps } from './types';
2
2
  export declare const File: <T>({ file, index, onDelete, showDelete, }: FileProps<T>) => import("react/jsx-runtime").JSX.Element;
3
- export declare const Folder: <T>({ name, size, intent, loading }: FolderProps) => import("react/jsx-runtime").JSX.Element;
3
+ export declare const Folder: ({ name, subItems }: FolderProps) => import("react/jsx-runtime").JSX.Element;
4
4
  export declare const FolderList: <T>({ files }: FolderListProps<T>) => import("react/jsx-runtime").JSX.Element;
@@ -5,5 +5,5 @@ export declare const Uploader: {
5
5
  Files: <T>({ files, onDelete, uploadProgressText, }: import("./types").FilesProps<T>) => import("react/jsx-runtime").JSX.Element;
6
6
  Modal: ({ open, onClose, children, title, }: import("./types").ModalProps) => any;
7
7
  Root: <T>({ open, onClose, files, onDelete, dropzoneProps, selectFileText, dropText, buttonText, uploadProgressText, titleModal, isManagerOpen, closeManager, }: import("./types").RootUploaderProps<T>) => import("react/jsx-runtime").JSX.Element;
8
- Manager: <T>({ open, files, onDelete, uploadProgressText, onClose, type, }: import("./types").ManagerProps<T>) => any;
8
+ Manager: <T>({ open, files, onDelete, uploadProgressText, onClose, }: import("./types").ManagerProps<T>) => any;
9
9
  };
@@ -1,4 +1,10 @@
1
1
  import { DropzoneInputProps, DropzoneRootProps, type FileError, type FileRejection, type FileWithPath } from 'react-dropzone';
2
+ declare global {
3
+ interface File {
4
+ relativePath: string;
5
+ path?: string;
6
+ }
7
+ }
2
8
  export interface FileItem {
3
9
  file: File;
4
10
  loading: 'loading' | 'success' | 'error';
@@ -11,9 +17,10 @@ export interface FileProps<T> {
11
17
  }
12
18
  export interface FolderProps {
13
19
  name: string;
14
- size: number;
15
- intent: 'default' | 'success' | 'warning' | 'info' | 'error';
16
- loading: boolean;
20
+ subItems: {
21
+ status: string;
22
+ path: string;
23
+ }[];
17
24
  }
18
25
  export interface FolderListProps<T> {
19
26
  files: FileUpload<T>[];
@@ -52,6 +59,7 @@ export type FileUpload<T> = {
52
59
  metadata?: T;
53
60
  uid: string;
54
61
  status: 'success' | 'error' | 'uploading';
62
+ isFolder?: boolean;
55
63
  };
56
64
  export interface RootUploaderProps<T> {
57
65
  open: boolean;
@@ -15,8 +15,9 @@ interface UseFileUploadOptions<T> {
15
15
  preventDuplicates?: boolean;
16
16
  onDuplicate?: (duplicates: File[]) => void;
17
17
  hasManager?: boolean;
18
+ isFolder?: boolean;
18
19
  }
19
- export declare const useFileUpload: <T>({ accept, onAccept, onFileRejected, maxSize, allowMultiple, preventDuplicates, onDuplicate, hasManager, }: UseFileUploadOptions<T>) => {
20
+ export declare const useFileUpload: <T>({ accept, onAccept, onFileRejected, maxSize, allowMultiple, preventDuplicates, onDuplicate, hasManager, isFolder, }: UseFileUploadOptions<T>) => {
20
21
  onOpen: () => void;
21
22
  onClose: () => void;
22
23
  onDelete: (index: number) => void;
@@ -0,0 +1,9 @@
1
+ import { FileUpload } from '../components';
2
+ export declare const useManager: <T>({ files }: {
3
+ files: FileUpload<T>[];
4
+ }) => {
5
+ min: boolean;
6
+ setMin: import("react").Dispatch<import("react").SetStateAction<boolean>>;
7
+ folderFiles: FileUpload<T>[];
8
+ regularFiles: FileUpload<T>[];
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tecsinapse/cortex-react",
3
- "version": "1.15.0-beta.7",
3
+ "version": "1.15.0-beta.9",
4
4
  "description": "React components based in @tecsinapse/cortex-core",
5
5
  "license": "MIT",
6
6
  "main": "dist/esm/index.js",
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "@floating-ui/react": "^0.26.18",
22
22
  "@internationalized/date": "3.7.0",
23
- "@tecsinapse/cortex-core": "1.2.0-beta.5",
23
+ "@tecsinapse/cortex-core": "1.2.0-beta.6",
24
24
  "clsx": "2.1.1",
25
25
  "currency.js": "2.0.4",
26
26
  "embla-carousel-autoplay": "^8.0.0",
@@ -48,5 +48,5 @@
48
48
  "react-icons": ">=5.2.0",
49
49
  "tailwind": ">=3.3.0"
50
50
  },
51
- "gitHead": "2082dfbefdac0dc3a0773f8876885fdae29a8609"
51
+ "gitHead": "7ddfc6577ee756d2a3c4906fc41cbc2e5c0418bb"
52
52
  }