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

UI #197

Merged
merged 4 commits into from
Feb 11, 2024
Merged

UI #197

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
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import { useSelector } from 'react-redux';
import axios from 'axios';
import { Button, Form, Icon, Modal } from 'semantic-ui-react';
import { visibilityOptions } from '../util/ProjectHelpers';
import useGlobalError from '../error/ErrorHooks';
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import axios from "axios";
import { Button, Form, Icon, Modal } from "semantic-ui-react";
import { visibilityOptions } from "../util/ProjectHelpers";
import useGlobalError from "../error/ErrorHooks";
import { useTypedSelector } from "../../state/hooks";

interface CreateProjectProps {
show: boolean;
onClose: () => void;
}

/**
* Modal tool to create a new Project.
*/
const CreateProject = ({ show, onClose }) => {

const CreateProject: React.FC<CreateProjectProps> = ({ show, onClose }) => {
// Global state and error handling
const { handleGlobalError } = useGlobalError();
const history = useHistory();
const org = useSelector((state) => state.org);
const org = useTypedSelector((state) => state.org);

// Form Data
const [projTitle, setProjTitle] = useState('');
const [projVis, setProjVis] = useState('private');
const [projTitle, setProjTitle] = useState("");
const [projVis, setProjVis] = useState("private");

// Form State
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -55,15 +58,17 @@ const CreateProject = ({ show, onClose }) => {
if (validateForm()) {
try {
setLoading(true);
const createRes = await axios.post('/project', {
const createRes = await axios.post("/project", {
title: projTitle,
visibility: projVis,
});
if (createRes.data.err) {
throw (new Error(createRes.data.errMsg));
throw new Error(createRes.data.errMsg);
}
if (createRes.data.projectID) {
history.push(`/projects/${createRes.data.projectID}?projectCreated=true`);
history.push(
`/projects/${createRes.data.projectID}?projectCreated=true`
);
} else {
history.push(`/projects?projectCreated=true`);
}
Expand All @@ -74,31 +79,14 @@ const CreateProject = ({ show, onClose }) => {
}
}

/**
* Updates the new Title in state.
*
* @param {React.ChangeEvent<HTMLInputElement>} e - Event that activated the handler.
*/
function handleProjectTitleChange(e) {
setProjTitle(e.target.value);
}

/**
* Updates the new Visibility setting in state.
*
* @param {React.ChangeEvent} _e - Event that activated the handler.
* @param {object} data - Date passed from the UI element.
* @param {string} data.value - The new visibility setting selection.
*/
function handleProjectVisibilityChange(_e, { value }) {
setProjVis(value);
}

return (
<Modal size="large" open={show} onClose={onClose}>
<Modal.Header>Create Project</Modal.Header>
<Modal.Content>
<p>This project will be created within <strong>{org.name}</strong>. You can add tags, collaborators, and more after creation.</p>
<p>
This project will be created within <strong>{org.name}</strong>. You
can add tags, collaborators, and more after creation.
</p>
<Form noValidate onSubmit={createProject}>
<Form.Input
fluid
Expand All @@ -107,15 +95,15 @@ const CreateProject = ({ show, onClose }) => {
required
type="text"
value={projTitle}
onChange={handleProjectTitleChange}
onChange={(e) => setProjTitle(e.target.value)}
error={titleErr}
/>
<Form.Select
fluid
label="Project Visibility"
placeholder="Visibility..."
options={visibilityOptions}
onChange={handleProjectVisibilityChange}
onChange={(e, { value }) => setProjVis(value as string)}
value={projVis}
/>
</Form>
Expand All @@ -131,19 +119,4 @@ const CreateProject = ({ show, onClose }) => {
);
};

CreateProject.propTypes = {
/**
* Opens or closes the modal.
*/
show: PropTypes.bool.isRequired,
/**
* Handler to activate when the modal is closed.
*/
onClose: PropTypes.func,
};

CreateProject.defaultProps = {
onClose: () => { },
};

export default CreateProject;
35 changes: 33 additions & 2 deletions client/src/components/projects/ProjectLinkButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Button, Header, Icon, Popup } from "semantic-ui-react";
import { normalizeURL } from "../util/HelperFunctions";
import { buildCommonsUrl, buildWorkbenchURL } from "../../utils/projectHelpers";
import { lazy, useState } from "react";
const CreateWorkbenchModal = lazy(() => import("./CreateWorkbenchModal"));

interface ProjectLinkButtonsProps {
libreLibrary?: string;
libreCoverID?: string;
projectLink?: string;
didCreateWorkbench?: boolean;
hasCommonsBook?: boolean;
projectID?: string;
projectTitle?: string;
}

const ProjectLinkButtons: React.FC<ProjectLinkButtonsProps> = ({
Expand All @@ -16,14 +20,28 @@ const ProjectLinkButtons: React.FC<ProjectLinkButtonsProps> = ({
projectLink,
didCreateWorkbench,
hasCommonsBook = false,
projectID,
projectTitle,
}) => {
const [showCreateWorkbenchModal, setShowCreateWorkbenchModal] =
useState(false);
const validWorkbench = didCreateWorkbench && libreCoverID && libreLibrary;

return (
<div>
<Header as="span" sub>
Important Links:{" "}
</Header>
<div className="flex flex-row mt-2">
{!projectLink && !didCreateWorkbench && (
<Button
color="blue"
onClick={() => setShowCreateWorkbenchModal(true)}
>
<Icon name="plus" />
Create Book
</Button>
)}
<Popup
content={
validWorkbench
Expand Down Expand Up @@ -74,15 +92,28 @@ const ProjectLinkButtons: React.FC<ProjectLinkButtonsProps> = ({
: () => {}
}
className={
hasCommonsBook && libreCoverID && libreLibrary ? "" : "!cursor-default opacity-45"
hasCommonsBook && libreCoverID && libreLibrary
? ""
: "!cursor-default opacity-45"
}
color={
hasCommonsBook && libreCoverID && libreLibrary ? "blue" : "grey"
}
color={hasCommonsBook && libreCoverID && libreLibrary ? "blue" : "grey"}
>
Commons Page
<Icon name="external alternate" className="!ml-2" />
</Button>
}
/>
{projectID && projectTitle && (
<CreateWorkbenchModal
show={showCreateWorkbenchModal}
projectID={projectID}
projectTitle={projectTitle}
onClose={() => setShowCreateWorkbenchModal(false)}
onSuccess={() => window.location.reload()}
/>
)}
</div>
</div>
);
Expand Down
19 changes: 0 additions & 19 deletions client/src/components/projects/ProjectPropertiesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -530,18 +530,6 @@ const ProjectPropertiesModal: React.FC<ProjectPropertiesModalProps> = ({
id="projectURL"
/>
</Form.Field>
<div>
{!getValues("projectURL") && (
<Button
color="blue"
onClick={() => setShowCreateWorkbenchModal(true)}
className="!mb-4"
>
<Icon name="plus" />
Create Book
</Button>
)}
</div>
</>
)}

Expand Down Expand Up @@ -978,13 +966,6 @@ const ProjectPropertiesModal: React.FC<ProjectPropertiesModalProps> = ({
},
]}
/>
<CreateWorkbenchModal
show={showCreateWorkbenchModal}
projectID={watch("projectID")}
projectTitle={watch("title")}
onClose={() => setShowCreateWorkbenchModal(false)}
onSuccess={() => window.location.reload()}
/>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => onClose()}>Cancel</Button>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/projects/ProjectView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1848,7 +1848,7 @@ const ProjectView = (props) => {
</Label.Group>
</div>
}
<ProjectLinkButtons libreCoverID={project.libreCoverID} libreLibrary={project.libreLibrary} projectLink={project.projectURL} didCreateWorkbench={project.didCreateWorkbench} hasCommonsBook={project.hasCommonsBook}/>
<ProjectLinkButtons projectID={project.projectID} libreCoverID={project.libreCoverID} libreLibrary={project.libreLibrary} projectLink={project.projectURL} projectTitle={project.title} didCreateWorkbench={project.didCreateWorkbench} hasCommonsBook={project.hasCommonsBook}/>
{(project.adaptCourseID && project.adaptCourseID !== '') && (
<div className="mt-1e">
<a
Expand Down
57 changes: 53 additions & 4 deletions client/src/screens/conductor/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Loader,
Card,
Popup,
SegmentProps,
} from "semantic-ui-react";
import { useEffect, useState, useCallback, lazy } from "react";
import { useTypedSelector } from "../../../state/hooks";
Expand All @@ -24,6 +25,7 @@ import useGlobalError from "../../../components/error/ErrorHooks";
import { pinProject } from "../../../utils/projectHelpers";
import Annnouncement from "../../../components/Home/Announcement";
import UserMenu from "../../../components/Home/UserMenu";
import { useMediaQuery } from "react-responsive";
const NewMemberModal = lazy(
() => import("../../../components/Home/NewMemberModal")
);
Expand All @@ -36,6 +38,9 @@ const ViewAnnouncementModal = lazy(
const NewAnnouncementModal = lazy(
() => import("../../../components/Home/NewAnnouncementModal")
);
const CreateProjectModal = lazy(
() => import("../../../components/projects/CreateProject")
);

const Home = () => {
const { handleGlobalError } = useGlobalError();
Expand Down Expand Up @@ -75,6 +80,15 @@ const Home = () => {
// Edit Pinned Projects Modal
const [showPinnedModal, setShowPinnedModal] = useState<boolean>(false);

// Create Project Modal
const [showCreateProjectModal, setShowCreateProjectModal] =
useState<boolean>(false);

const isTailwindXl = useMediaQuery(
{ minWidth: 1280 }, // Tailwind XL breakpoint
undefined
);

/**
* Check for query string values and update UI if necessary.
*/
Expand Down Expand Up @@ -228,6 +242,32 @@ const Home = () => {
setLoadedAllPinned(false);
}

const CreateProjectSegment = () => {
return (
<Segment padded className="!py-4">
<div className="flex flex-row justify-between items-center">
<p className="text-xl font-semibold">Create a Project</p>
<div className="right-flex">
<Popup
content="New Project"
trigger={
<Button
color="green"
onClick={() => setShowCreateProjectModal(true)}
icon
circular
>
<Icon name="add" />
</Button>
}
position="top center"
/>
</div>
</div>
</Segment>
);
};

return (
<Grid className="component-container" divided="vertically" stackable>
<div className="flex flex-col my-4 w-full">
Expand All @@ -246,9 +286,10 @@ const Home = () => {
</div>
)}
<div className="flex flex-col xl:flex-row">
<div className="flex flex-col mb-8 xl:w-1/6 xl:mr-12 xl:mb-0">
<div className="flex flex-col mb-4 xl:w-1/6 xl:mr-12 xl:mb-0">
<UserMenu />
</div>
{!isTailwindXl && <CreateProjectSegment />}
<div className="flex flex-col mb-8 xl:w-1/2 xl:mr-12 xl:mb-0">
<Segment
padded={pinnedProjects.length > 0}
Expand Down Expand Up @@ -331,11 +372,14 @@ const Home = () => {
</Segment>
</div>
<div className="flex flex-col mb-8 xl:w-1/3 xl:mb-0">
{isTailwindXl && (
<div className="!mb-2">
<CreateProjectSegment />
</div>
)}
<Segment padded>
<div className="dividing-header-custom">
<h3>
Announcements
</h3>
<h3>Announcements</h3>
{(user.isCampusAdmin === true || user.isSuperAdmin === true) && (
<div className="right-flex">
<Popup
Expand Down Expand Up @@ -410,6 +454,11 @@ const Home = () => {
onDataChange={() => getPinnedProjects()}
onClose={() => setShowPinnedModal(false)}
/>
{/* Create Project Modal */}
<CreateProjectModal
show={showCreateProjectModal}
onClose={() => setShowCreateProjectModal(false)}
/>
</Grid>
);
};
Expand Down
2 changes: 2 additions & 0 deletions server/models/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ const CollectionSchema = new Schema<CollectionInterface>(
// the organization's internal identifier string
type: String,
required: true,
index: true,
},
collID: {
// base62 8-digit identifier
type: String,
required: true,
index: true,
},
title: {
// the collection title/name
Expand Down
Loading
Loading