Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: replace doRemote... functions with useMutation #449

Merged
merged 1 commit into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion api/src/resolver_repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,6 @@ export default {
Query: {
repo,
getDashboardRepos,
getVisibility,
},
Mutation: {
createRepo,
Expand Down
6 changes: 0 additions & 6 deletions api/src/typedefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ export const typeDefs = gql`
codeiumAPIKey: String!
}

type Visibility {
collaborators: [User]
isPublic: Boolean
}

type Repo {
id: ID!
name: String
Expand Down Expand Up @@ -108,7 +103,6 @@ export const typeDefs = gql`
pod(id: ID!): Pod
getDashboardRepos: [Repo]
activeSessions: [String]
getVisibility(repoId: String!): Visibility
listAllRuntimes: [RuntimeInfo]
infoRuntime(sessionId: String!): RuntimeInfo
}
Expand Down
16 changes: 9 additions & 7 deletions ui/src/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ function useJump() {
const setFocusedEditor = useStore(store, (state) => state.setFocusedEditor);

const nodesMap = useStore(store, (state) => state.getNodesMap());
const pods = useStore(store, (state) => state.pods);

const reactflow = useReactFlow();

Expand Down Expand Up @@ -205,9 +204,12 @@ function useJump() {
}

// get the sibling nodes
const nodes = Array.from(nodesMap.values()).filter(
const siblings = Array.from(nodesMap.values()).filter(
(node) => node.parentNode === pod.parentNode
);
const children = Array.from(nodesMap.values()).filter(
(node) => node.parentNode === id
);

let to: null | Node = null;

Expand All @@ -220,7 +222,7 @@ function useJump() {
to = pod;
}
} else {
to = getBestNode(nodes, pod, "up");
to = getBestNode(siblings, pod, "up");
}
break;
case "ArrowDown":
Expand All @@ -231,7 +233,7 @@ function useJump() {
(pod.height || 1) ** 2 + (pod.width || 1) ** 2
);
let childDist = 0;
for (const child of pods[id].children) {
for (const child of children) {
childDist = Math.sqrt(
nodesMap.get(child.id)!.position.x ** 2 +
nodesMap.get(child.id)!.position.y ** 2
Expand All @@ -245,14 +247,14 @@ function useJump() {
to = pod;
}
} else {
to = getBestNode(nodes, pod, "down");
to = getBestNode(siblings, pod, "down");
}
break;
case "ArrowLeft":
to = getBestNode(nodes, pod, "left");
to = getBestNode(siblings, pod, "left");
break;
case "ArrowRight":
to = getBestNode(nodes, pod, "right");
to = getBestNode(siblings, pod, "right");
break;
case "Enter":
if (pod.type == "CODE") {
Expand Down
1 change: 0 additions & 1 deletion ui/src/components/MyMonaco.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,6 @@ export const MyMonaco = memo<MyMonacoProps>(function MyMonaco({
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const readOnly = useStore(store, (state) => state.role === "GUEST");
const showLineNumbers = useStore(store, (state) => state.showLineNumbers);
const initPodContent = useStore(store, (state) => state.initPodContent);
const clearResults = useStore(store, (s) => s.clearResults);
const wsRun = useStore(store, (state) => state.wsRun);
const setPodFocus = useStore(store, (state) => state.setPodFocus);
Expand Down
41 changes: 31 additions & 10 deletions ui/src/components/SettingDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useStore } from "zustand";
import { RepoContext } from "../lib/store";
import { Link, Stack } from "@mui/material";
import LaunchIcon from "@mui/icons-material/Launch";
import { useApolloClient } from "@apollo/client";
import { gql, useApolloClient, useMutation } from "@apollo/client";
import Button from "@mui/material/Button";
import DoneIcon from "@mui/icons-material/Done";
import CloseIcon from "@mui/icons-material/Close";
Expand All @@ -34,7 +34,6 @@ export function SettingDialog({ open = false }: SettingDiagProps) {
const user = useStore(store, (state) => state.user);
const isCustomToken = useStore(store, (state) => state.isCustomToken);
const setIsCustomToken = useStore(store, (state) => state.setIsCustomToken);
const updateAPIKey = useStore(store, (state) => state.updateAPIKey);
const client = useApolloClient();
const inputRef = useRef<HTMLInputElement>(null);
const [infoShow, setInfoShow] = useState(false);
Expand All @@ -53,6 +52,25 @@ export function SettingDialog({ open = false }: SettingDiagProps) {
setInfoShow(false);
};

const [updateCodeiumAPIKey, { data, error }] = useMutation(gql`
mutation updateCodeiumAPIKey($apiKey: String!) {
updateCodeiumAPIKey(apiKey: $apiKey)
}
`);

useEffect(() => {
if (data?.updateCodeiumAPIKey) {
setStatus("success");
setMessage(`${user.name}, welcome. Token updated`);
setInfoShow(true);
}
if (error) {
setStatus("error");
setMessage(error.message || "Unknown error");
setInfoShow(true);
}
}, [data, error]);

const updateToken = async () => {
const token = inputRef.current?.value.trim();
if (!token) {
Expand All @@ -70,13 +88,10 @@ export function SettingDialog({ open = false }: SettingDiagProps) {
: "Unknown error"
);
}
if (await updateAPIKey(client, api_key)) {
setStatus("success");
setMessage(`${name}, welcome. Token updated`);
setInfoShow(true);
} else {
throw new Error("Update failed");
}
updateCodeiumAPIKey({
variables: { apiKey: api_key },
refetchQueries: ["Me"],
});
} catch (e) {
setStatus("error");
setMessage((e as Error).message || "Unknown error");
Expand Down Expand Up @@ -131,7 +146,13 @@ export function SettingDialog({ open = false }: SettingDiagProps) {
{apiKey && (
<Button
endIcon={<CloseIcon />}
onClick={() => updateAPIKey(client, "")}
// FIXME use clearToken mutation instead.
onClick={() =>
updateCodeiumAPIKey({
variables: { apiKey: "" },
refetchQueries: ["Me"],
})
}
>
Clear Stored Token
</Button>
Expand Down
133 changes: 86 additions & 47 deletions ui/src/components/ShareProjDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Button from "@mui/material/Button";
import Alert from "@mui/material/Alert";
import { AlertColor } from "@mui/material/Alert";
import Snackbar from "@mui/material/Snackbar";
import { useState } from "react";
import { useEffect, useState } from "react";
import ListSubheader from "@mui/material/ListSubheader";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
Expand All @@ -26,7 +26,7 @@ import React, { useContext, useReducer } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { useStore } from "zustand";
import { RepoContext } from "../lib/store";
import { useApolloClient } from "@apollo/client";
import { gql, useApolloClient, useMutation } from "@apollo/client";

const initialState = { showInfo: false, status: "info", message: "wait..." };

Expand Down Expand Up @@ -102,15 +102,27 @@ function reducer(state, action) {
}
}

function CollaboratorList({ collaborators, dispatch, isOwner }) {
function CollaboratorList({ repoId, collaborators, dispatch, isOwner }) {
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const apolloClient = useApolloClient();
const deleteCollaborator = useStore(
store,
(state) => state.deleteCollaborator
const [deleteCollaborator, { data, loading, error }] = useMutation(
gql`
mutation deleteCollaborator($repoId: String!, $collaboratorId: String!) {
deleteCollaborator(repoId: $repoId, collaboratorId: $collaboratorId)
}
`
);

useEffect(() => {
if (error) {
dispatch({ type: "delete error", message: error.message });
}
if (data) {
dispatch({ type: "delete success", name: data.deleteCollaborator });
}
}, [error]);

if (!collaborators || collaborators?.length === 0) {
return (
<List dense={true}>
Expand All @@ -129,15 +141,6 @@ function CollaboratorList({ collaborators, dispatch, isOwner }) {
);
}

async function handleDeleteCollaborator(userId, name) {
const { success, error } = await deleteCollaborator(apolloClient, userId);
if (success) {
dispatch({ type: "delete success", name });
} else {
dispatch({ type: "delete error", message: error.message });
}
}

return (
<List
sx={{
Expand All @@ -155,10 +158,12 @@ function CollaboratorList({ collaborators, dispatch, isOwner }) {
edge="end"
aria-label="delete"
onClick={() =>
handleDeleteCollaborator(
collab.id,
collab.firstname + " " + collab.lastname
)
deleteCollaborator({
variables: {
repoId: repoId,
collaboratorId: collab.id,
},
})
}
>
<CloseIcon />
Expand All @@ -182,8 +187,46 @@ function CollaboratorList({ collaborators, dispatch, isOwner }) {
);
}

const aboutVisibility =
"A private project is only visible to you and collaborators, while a public project is visible to everyone. For both of them, only the owner can invite collaborators by their email addresses, and only collaborators can edit the project. The owner can change the visibility of a project at any time.";
const useUpdateVisibility = ({ dispatch }) => {
const [updateVisibility, { data, error }] = useMutation(gql`
mutation updateVisibility($repoId: String!, $isPublic: Boolean!) {
updateVisibility(repoId: $repoId, isPublic: $isPublic)
}
`);

useEffect(() => {
if (error) {
dispatch({ type: "change visibility error", message: error.message });
}
if (data) {
dispatch({ type: "change visibility success" });
}
}, [data, error]);
return updateVisibility;
};

const useAddCollaborator = ({ dispatch }) => {
const [addCollaborator, { data, error }] = useMutation(gql`
mutation addCollaborator($repoId: String!, $email: String!) {
addCollaborator(repoId: $repoId, email: $email)
}
`);

useEffect(() => {
if (error) {
dispatch({ type: "inivite error", message: error.message });
}
if (data) {
dispatch({ type: "inivite success", email: data.addCollaborator });
}
}, [data, error]);
return addCollaborator;
};

const aboutVisibility = `A private project is only visible to you and collaborators, while a public
project is visible to everyone. For both of them, only the owner can invite
collaborators by their email addresses, and only collaborators can edit the
project. The owner can change the visibility of a project at any time.`;

export function ShareProjDialog({
open = false,
Expand All @@ -197,34 +240,12 @@ export function ShareProjDialog({
const isPublic = useStore(store, (state) => state.isPublic);
const collaborators = useStore(store, (state) => state.collaborators);
const setShareOpen = useStore(store, (state) => state.setShareOpen);
const updateVisibility = useStore(store, (state) => state.updateVisibility);
const addCollaborator = useStore(store, (state) => state.addCollaborator);
const isOwner = useStore(store, (state) => state.role === "OWNER");
const title = useStore(store, (state) => state.repoName || "Untitled");
const url = `${window.location.protocol}//${window.location.host}/repo/${id}`;
const inputRef = React.useRef<HTMLInputElement>(null);

async function flipVisibility() {
if (await updateVisibility(apolloClient, !isPublic)) {
dispatch({ type: "change visibility success" });
} else {
dispatch({ type: "change visibility error", message: "Unknown error" });
}
}

async function onShare() {
const email = inputRef?.current?.value;
if (!email) {
dispatch({ type: "error", message: "Email cannot be empty" });
return;
}
const { success, error } = await addCollaborator(apolloClient, email);
if (success) {
dispatch({ type: "inivite success", email });
} else {
dispatch({ type: "inivite error", message: error.message });
}
}
const updateVisibility = useUpdateVisibility({ dispatch });
const addCollaborator = useAddCollaborator({ dispatch });

function onCloseAlert(event: React.SyntheticEvent | Event, reason?: string) {
if (reason === "clickaway") {
Expand Down Expand Up @@ -289,7 +310,14 @@ export function ShareProjDialog({
</IconButton>
</Tooltip>
{isOwner && (
<Button sx={{ float: "right" }} onClick={flipVisibility}>
<Button
sx={{ float: "right" }}
onClick={() => {
updateVisibility({
variables: { repoId: id, isPublic: !isPublic },
});
}}
>
Make it {isPublic ? "private" : "public"}
</Button>
)}
Expand All @@ -307,6 +335,7 @@ export function ShareProjDialog({
)}

<CollaboratorList
repoId={id}
collaborators={collaborators}
isOwner={isOwner}
dispatch={dispatch}
Expand Down Expand Up @@ -336,7 +365,17 @@ export function ShareProjDialog({
>
Cancel
</Button>
<Button onClick={onShare} disabled={!isOwner}>
<Button
onClick={() => {
const email = inputRef?.current?.value;
if (!email) {
dispatch({ type: "error", message: "Email cannot be empty" });
return;
}
addCollaborator({ variables: { repoId: id, email } });
}}
disabled={!isOwner}
>
Share
</Button>
</DialogActions>
Expand Down
Loading