Skip to content
This repository was archived by the owner on May 30, 2023. It is now read-only.

Commit 1fdf933

Browse files
authored
Merge pull request #229 from daita-technologies/feat/get-upload-pip-daita-token
feat: get upload pip daita token
2 parents 7aabd7b + 84fe7a0 commit 1fdf933

4 files changed

Lines changed: 243 additions & 12 deletions

File tree

src/components/UploadFile/UploadGuideDialog.tsx

Lines changed: 142 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
1+
import { ContentCopy, Visibility, VisibilityOff } from "@mui/icons-material";
12
import {
23
Box,
34
Dialog,
45
DialogContent,
56
DialogContentText,
67
DialogTitle,
8+
FormControl,
9+
Grid,
10+
IconButton,
11+
InputAdornment,
12+
InputLabel,
13+
OutlinedInput,
14+
Popover,
715
Typography,
816
} from "@mui/material";
17+
import MyButton from "components/common/MyButton";
18+
import { useMemo, useState } from "react";
919
import { useSelector } from "react-redux";
20+
import { toast } from "react-toastify";
1021
import { selectorUserInfo } from "reduxes/auth/selector";
22+
import {
23+
selectorCurrentProjectId,
24+
selectorCurrentProjectName,
25+
} from "reduxes/project/selector";
26+
import { projectApi } from "services";
27+
import { copy } from "utils/clipboard";
28+
29+
const DEFAULT_UPLOAD_TOKEN_VALUE = "Get your upload token now!";
30+
31+
const copyPopoverTextId = "copy-popover-text";
1132

1233
const UploadGuideDialog = function ({
1334
isOpen,
@@ -20,6 +41,61 @@ const UploadGuideDialog = function ({
2041
const generalStyle = { margin: 0 };
2142
const commentStyle = { ...generalStyle, color: "#858e93" };
2243
const colorStringStyle = { color: "#98cae8" };
44+
45+
const [isShowToken, setIsShowToken] = useState(false);
46+
const [uploadTokenValue, setUploadTokenValue] = useState(
47+
DEFAULT_UPLOAD_TOKEN_VALUE
48+
);
49+
50+
const [isGettingToken, setIsGettingToken] = useState(false);
51+
52+
const currentProjectId = useSelector(selectorCurrentProjectId);
53+
const currentProjectName = useSelector(selectorCurrentProjectName);
54+
55+
const [copiedPopoverAnchorEl, setCopiedPopoverAnchorEl] =
56+
useState<HTMLButtonElement | null>(null);
57+
const isShowCopiedPopover = useMemo(
58+
() => Boolean(copiedPopoverAnchorEl),
59+
[copiedPopoverAnchorEl]
60+
);
61+
const handleCloseCopiedPopover = () => {
62+
setCopiedPopoverAnchorEl(null);
63+
};
64+
65+
const handleClickShowToken = () => {
66+
setIsShowToken(!isShowToken);
67+
};
68+
69+
const handleClickCopy = async (
70+
event: React.MouseEvent<HTMLButtonElement>
71+
) => {
72+
const isCopied = await copy(uploadTokenValue);
73+
74+
if (isCopied) {
75+
setCopiedPopoverAnchorEl(event.target as HTMLButtonElement);
76+
setTimeout(handleCloseCopiedPopover, 3000);
77+
} else {
78+
toast.error("Unable to copy.");
79+
}
80+
};
81+
82+
const handleGetUploadToken = () => {
83+
setIsGettingToken(true);
84+
projectApi
85+
.getUploadToken({
86+
projectId: currentProjectId,
87+
projectName: currentProjectName,
88+
})
89+
.then((getUploadTokenResponse: any) => {
90+
setIsGettingToken(false);
91+
if (getUploadTokenResponse && getUploadTokenResponse.error === false) {
92+
setUploadTokenValue(getUploadTokenResponse.data.token);
93+
} else {
94+
toast.error("Unable to get upload token. Please try again later!");
95+
}
96+
});
97+
};
98+
2399
return (
24100
<Dialog fullWidth maxWidth="md" open={isOpen} onClose={onClose}>
25101
<DialogTitle sx={{ p: 4 }}>
@@ -53,23 +129,79 @@ const UploadGuideDialog = function ({
53129
# upload your dataset using the following command
54130
</p>
55131
<p style={commentStyle}>
56-
# (don&apos;t forget to set `path_to_your_dataset`)
132+
# (don&apos;t forget to set `path_to_your_dataset`) daita
133+
my-folder
57134
</p>
58135
<p style={{ ...generalStyle, whiteSpace: "nowrap" }}>
59-
daita token=
60-
<span style={colorStringStyle}>
61-
&apos;d478a...&apos;
62-
</span>{" "}
63-
dataset_id=
64-
<span style={colorStringStyle}>
65-
&apos;6225d1...&apos;
66-
</span>{" "}
67-
input_dir=&apos;path_to_your_dataset&apos;
136+
daita --dir{" "}
137+
<span style={colorStringStyle}>path_to_your_dataset</span>{" "}
138+
--daita_token{" "}
139+
<span style={colorStringStyle}>your_upload_token</span>
68140
</p>
69141
</Box>
70142
</Typography>
71143
</Typography>
72144
</DialogContentText>
145+
<Box mt={4}>
146+
<Grid container columnSpacing={1} alignItems="center">
147+
<Grid item xs={9}>
148+
<FormControl variant="outlined" fullWidth>
149+
<InputLabel>Upload Token</InputLabel>
150+
<OutlinedInput
151+
type={isShowToken ? "text" : "password"}
152+
value={uploadTokenValue}
153+
readOnly
154+
disabled={isGettingToken}
155+
fullWidth
156+
endAdornment={
157+
<InputAdornment position="end">
158+
<IconButton
159+
id="copyPopoverTextId"
160+
sx={{ mr: 1 }}
161+
onClick={handleClickCopy}
162+
edge="end"
163+
>
164+
<ContentCopy />
165+
</IconButton>
166+
<Popover
167+
id={copyPopoverTextId}
168+
open={isShowCopiedPopover}
169+
anchorEl={copiedPopoverAnchorEl}
170+
onClose={handleCloseCopiedPopover}
171+
anchorOrigin={{
172+
vertical: "bottom",
173+
horizontal: "left",
174+
}}
175+
>
176+
<Typography sx={{ p: 1 }} variant="subtitle2">
177+
Copied!
178+
</Typography>
179+
</Popover>
180+
<IconButton
181+
onClick={handleClickShowToken}
182+
onMouseDown={handleClickShowToken}
183+
edge="end"
184+
>
185+
{isShowToken ? <VisibilityOff /> : <Visibility />}
186+
</IconButton>
187+
</InputAdornment>
188+
}
189+
label="Upload Token"
190+
/>
191+
</FormControl>
192+
</Grid>
193+
<Grid item xs={3}>
194+
<MyButton
195+
fullWidth
196+
variant="contained"
197+
isLoading={isGettingToken}
198+
onClick={handleGetUploadToken}
199+
>
200+
Get Upload Token
201+
</MyButton>
202+
</Grid>
203+
</Grid>
204+
</Box>
73205
</DialogContent>
74206
</Dialog>
75207
);

src/components/UploadFile/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ const UploadFile = function (props: UploadFileProps) {
476476
<input {...getInputProps()} multiple />
477477
{renderDropzoneContent()}
478478
</Box>
479-
{/* <Typography mt={1} fontStyle="italic" variant="body2" fontSize={14}>
479+
<Typography mt={1} fontStyle="italic" variant="body2" fontSize={14}>
480480
* If you would like to upload your dataset programmatically,{" "}
481481
<Typography
482482
component="span"
@@ -487,7 +487,7 @@ const UploadFile = function (props: UploadFileProps) {
487487
click here{" "}
488488
</Typography>
489489
for more information.
490-
</Typography> */}
490+
</Typography>
491491
<UploadGuideDialog
492492
onClose={handleCloseUploadGuideDialog}
493493
isOpen={isOpenUploadGuideDialog}

src/services/projectApi.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import {
66
VIEW_ALBUM_PAGE_SIZE,
77
uploadZipApiUrl,
88
generateApiUrl,
9+
ID_TOKEN_NAME,
910
} from "constants/defaultValues";
11+
import { ApiResponse } from "constants/type";
1012
import { FetchImagesParams, ImageSourceType } from "reduxes/album/type";
1113
import { GenerateImagePayload } from "reduxes/generate/type";
1214
import { UpdateProjectInfoPayload } from "reduxes/project/type";
15+
import { getLocalStorage } from "utils/general";
1316

1417
export interface UploadUpdateListObjectInfo {
1518
// NOTE: <bucket>/<identity_id>/<project_id>/<data_name>
@@ -54,6 +57,12 @@ export interface DeleteImagesRequestBody {
5457
listObjectInfo: Array<DeleteObjectInfo>;
5558
}
5659

60+
export interface GetUploadTokenResponse extends ApiResponse {
61+
data: {
62+
token: string;
63+
};
64+
}
65+
5766
export const convertToUnderScoreValue = (
5867
listObjectInfoItem: UploadUpdateListObjectInfo
5968
) => ({
@@ -303,6 +312,24 @@ const projectApi = {
303312
},
304313
{ headers: getAuthHeader() }
305314
),
315+
getUploadToken: ({
316+
idToken,
317+
projectId,
318+
projectName,
319+
}: {
320+
idToken?: string;
321+
projectId: string;
322+
projectName: string;
323+
}) =>
324+
axios.post(
325+
`${uploadZipApiUrl}/generate/daita_upload_token`,
326+
{
327+
id_token: idToken || getLocalStorage(ID_TOKEN_NAME) || "",
328+
project_id: projectId,
329+
project_name: projectName,
330+
},
331+
{ headers: getAuthHeader() }
332+
),
306333
};
307334

308335
export default projectApi;

src/utils/clipboard.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
export const copyStringToClipboard = (text: string) => {
2+
if (navigator.userAgent.match(/ipad|ipod|iphone|android/i)) {
3+
// save current contentEditable/readOnly status
4+
const el = document.createElement("textarea");
5+
el.value = text;
6+
el.setAttribute("readonly", "");
7+
document.body.appendChild(el);
8+
9+
const editable = el.contentEditable;
10+
const { readOnly } = el;
11+
12+
// convert to editable with readonly to stop iOS keyboard opening
13+
el.contentEditable = "true";
14+
el.readOnly = true;
15+
16+
// create a selectable range
17+
const range = document.createRange();
18+
range.selectNodeContents(el);
19+
20+
// select the range
21+
const selection = window.getSelection();
22+
if (selection) {
23+
selection.removeAllRanges();
24+
selection.addRange(range);
25+
el.setSelectionRange(0, 999999);
26+
27+
// restore contentEditable/readOnly to original state
28+
el.contentEditable = editable;
29+
el.readOnly = readOnly;
30+
const isCopiedOnMobile = document.execCommand("copy");
31+
document.body.removeChild(el);
32+
33+
return isCopiedOnMobile;
34+
}
35+
36+
return false;
37+
}
38+
39+
// Create new element
40+
const element = document.createElement("textarea");
41+
// Set value (text to be copied)
42+
element.value = text;
43+
44+
// Set non-editable to avoid focus and move outside of view
45+
element.setAttribute("readonly", "false");
46+
element.setAttribute("contenteditable", "true");
47+
// element.style.display = 'none';
48+
document.body.appendChild(element);
49+
// Select text inside element
50+
element.select();
51+
// Copy text to clipboard
52+
const isCopiedOnDesktop = document.execCommand("copy");
53+
// Remove temporary element
54+
document.body.removeChild(element);
55+
56+
return isCopiedOnDesktop;
57+
};
58+
59+
type CopyFn = (text: string) => Promise<boolean>;
60+
61+
export const copy: CopyFn = async (text) => {
62+
if (!navigator?.clipboard) {
63+
return copyStringToClipboard(text);
64+
}
65+
66+
try {
67+
await navigator.clipboard.writeText(text);
68+
return true;
69+
} catch (error) {
70+
return false;
71+
}
72+
};

0 commit comments

Comments
 (0)