diff --git a/src/content/app/tools/shared/components/tools-top-bar/ToolsTopBar.module.css b/src/content/app/tools/shared/components/tools-top-bar/ToolsTopBar.module.css
index 56bee2cac6..988b0e4acb 100644
--- a/src/content/app/tools/shared/components/tools-top-bar/ToolsTopBar.module.css
+++ b/src/content/app/tools/shared/components/tools-top-bar/ToolsTopBar.module.css
@@ -1,5 +1,7 @@
.topBar {
min-height: 74px;
+ display: flex;
+ flex-direction: column;
background-color: var(--color-light-grey);
padding: 24px 30px 20px;
box-shadow: 0 3px 5px var(--global-box-shadow-color);
diff --git a/src/content/app/tools/vep/VepPage.tsx b/src/content/app/tools/vep/VepPage.tsx
new file mode 100644
index 0000000000..c06d707ebe
--- /dev/null
+++ b/src/content/app/tools/vep/VepPage.tsx
@@ -0,0 +1,61 @@
+/**
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { lazy, useEffect } from 'react';
+
+import { useAppDispatch } from 'src/store';
+import useHasMounted from 'src/shared/hooks/useHasMounted';
+
+import { updatePageMeta } from 'src/shared/state/page-meta/pageMetaSlice';
+
+import type { ServerFetch } from 'src/routes/routesConfig';
+
+const VepPageContent = lazy(() => import('./VepPageContent'));
+
+// Copied these from the current Ensembl site; will probably need changing in the future.
+const pageTitle = 'Ensembl Variant Effect Predictor (VEP)';
+const pageDescription = `
+VEP determines the effect of your variants (SNPs, insertions, deletions, CNVs or structural variants)
+on genes, transcripts, and protein sequence, as well as regulatory regions.
+`;
+
+const VepPage = () => {
+ const hasMounted = useHasMounted();
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ dispatch(
+ updatePageMeta({
+ title: pageTitle,
+ description: pageDescription
+ })
+ );
+ }, []);
+
+ return hasMounted ? : null;
+};
+
+export default VepPage;
+
+// not really fetching anything; just setting page meta
+export const serverFetch: ServerFetch = async (params) => {
+ params.store.dispatch(
+ updatePageMeta({
+ title: pageTitle,
+ description: pageDescription
+ })
+ );
+};
diff --git a/src/content/app/tools/vep/VepPageContent.tsx b/src/content/app/tools/vep/VepPageContent.tsx
new file mode 100644
index 0000000000..7d8ba78f78
--- /dev/null
+++ b/src/content/app/tools/vep/VepPageContent.tsx
@@ -0,0 +1,57 @@
+/**
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Route, Routes } from 'react-router-dom';
+
+import VepAppBar from './components/vep-app-bar/VepAppBar';
+import VepTopBar from './components/vep-top-bar/VepTopBar';
+import VepForm from './views/vep-form/VepForm';
+import { NotFoundErrorScreen } from 'src/shared/components/error-screen';
+
+const VepPageContent = () => {
+ return (
+
+
+
+
+ );
+};
+
+const Main = () => {
+ return (
+
+
+
+
+ } />
+ List of unviewed submissions
}
+ />
+
+ List of viewed submissions} />
+ Results of a single VEP analysis}
+ />
+
+ } />
+
+
+ );
+};
+
+export default VepPageContent;
diff --git a/src/content/app/tools/vep/components/form-section/FormSection.module.css b/src/content/app/tools/vep/components/form-section/FormSection.module.css
new file mode 100644
index 0000000000..7149be777f
--- /dev/null
+++ b/src/content/app/tools/vep/components/form-section/FormSection.module.css
@@ -0,0 +1,9 @@
+.container {
+ border-width: 1px;
+ border-style: solid;
+ border-color: var(--form-section-border-color, var(--color-medium-light-grey));
+}
+
+.container + .container {
+ border-top: none;
+}
diff --git a/src/content/app/tools/vep/components/form-section/FormSection.tsx b/src/content/app/tools/vep/components/form-section/FormSection.tsx
new file mode 100644
index 0000000000..dadcc44166
--- /dev/null
+++ b/src/content/app/tools/vep/components/form-section/FormSection.tsx
@@ -0,0 +1,56 @@
+/**
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import classNames from 'classnames';
+
+import type { ReactNode } from 'react';
+
+import styles from './FormSection.module.css';
+
+/**
+ * This component has first appeared in VEP submission form;
+ * but it is almost a certainty that it will be used in other places as well.
+ *
+ * There already exist two similar components: Accordion and ExpandableSection;
+ * however, this new components doesn't seem to fit either of those:
+ * - Both the Accordion and the ExpandableSection components have a predefined
+ * toggle element (an upwards- or downwards-pointing chevron); and in case of
+ * the Accordion, the whole area of the closed section acts as a button.
+ * In contrast, this new component can have anything in the right-hand corner.
+ * - Both the Accordion and the ExpandableSection components have a clear
+ * distinction between the "closed" (collapsed) and the "opened" (expanded)
+ * states. In contrast, this new component can show none of its content,
+ * a little bit of its content, or its full content (see e.g. behaviour of
+ * a VEP options section, which may show none of the options, some of the options,
+ * or all options)
+ *
+ * Here is what this component needs to be able to do:
+ * - It has a border; yet, when it is immediately followed by another FormSection,
+ * their adjacent borders should visually collapse into one.
+ */
+
+type Props = {
+ children: ReactNode;
+ className?: string;
+};
+
+const FormSection = (props: Props) => {
+ const componentClasses = classNames(styles.container, props.className);
+
+ return
{props.children}
;
+};
+
+export default FormSection;
diff --git a/src/content/app/tools/vep/components/vep-app-bar/VepAppBar.tsx b/src/content/app/tools/vep/components/vep-app-bar/VepAppBar.tsx
new file mode 100644
index 0000000000..e746ca4451
--- /dev/null
+++ b/src/content/app/tools/vep/components/vep-app-bar/VepAppBar.tsx
@@ -0,0 +1,50 @@
+/**
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useAppSelector } from 'src/store';
+import { getEnabledCommittedSpecies } from 'src/content/app/species-selector/state/species-selector-general-slice/speciesSelectorGeneralSelectors';
+
+import AppBar, { AppName } from 'src/shared/components/app-bar/AppBar';
+import SpeciesManagerIndicator from 'src/shared/components/species-manager-indicator/SpeciesManagerIndicator';
+import { SelectedSpecies } from 'src/shared/components/selected-species';
+import SpeciesTabsSlider from 'src/shared/components/species-tabs-slider/SpeciesTabsSlider';
+import { AppName as AppNameText } from 'src/global/globalConfig';
+
+const VepAppBar = () => {
+ return (
+ }
+ topRight={}
+ topLeft={{AppNameText.TOOLS}}
+ />
+ );
+};
+
+const SpeciesTabs = () => {
+ const speciesList = useAppSelector(getEnabledCommittedSpecies);
+
+ const speciesTabs = speciesList.map((species) => (
+
+ ));
+
+ return {speciesTabs};
+};
+
+export default VepAppBar;
diff --git a/src/content/app/tools/vep/components/vep-top-bar/VepTopBar.module.css b/src/content/app/tools/vep/components/vep-top-bar/VepTopBar.module.css
new file mode 100644
index 0000000000..5def92bfd0
--- /dev/null
+++ b/src/content/app/tools/vep/components/vep-top-bar/VepTopBar.module.css
@@ -0,0 +1,64 @@
+.grid {
+ display: grid;
+ flex-grow: 1;
+ align-items: center;
+ grid-template-columns:
+ [vep-logo] max-content
+ [form-title] max-content
+ [transcript-set] auto
+ [submit-button] min-content
+ [vep-version] auto
+ [job-lists-navigation] auto;
+ column-gap: 32px;
+ white-space: nowrap;
+}
+
+.logo {
+ grid-column: vep-logo;
+ height: 16px;
+}
+
+.runAJob {
+ grid-column: form-title;
+ margin-left: 20px;
+ font-weight: var(--font-weight-bold);
+}
+
+.transcriptSet {
+ grid-column: transcript-set;
+ justify-self: end;
+}
+
+.transcriptSetSelector {
+ margin-left: 20px;
+ min-width: 300px;
+}
+
+.submit {
+ grid-column: submit-button;
+}
+
+.vepVersion {
+ grid-column: vep-version;
+ justify-self: end;
+ display: flex;
+ align-items: baseline;
+ white-space: pre;
+ color: var(--color-medium-dark-grey);
+}
+
+.vepVersion svg {
+ height: 11px;
+ margin-right: 0.6ch;
+ fill: var(--color-medium-dark-grey);
+}
+
+.vepVersion span {
+ color: var(--color-black);
+}
+
+.jobListsNavigation {
+ grid-column: job-lists-navigation;
+ display: flex;
+ column-gap: 12px;
+}
diff --git a/src/content/app/tools/vep/components/vep-top-bar/VepTopBar.tsx b/src/content/app/tools/vep/components/vep-top-bar/VepTopBar.tsx
new file mode 100644
index 0000000000..452de41b01
--- /dev/null
+++ b/src/content/app/tools/vep/components/vep-top-bar/VepTopBar.tsx
@@ -0,0 +1,60 @@
+/**
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import ToolsTopBar from 'src/content/app/tools/shared/components/tools-top-bar/ToolsTopBar';
+import { PrimaryButton } from 'src/shared/components/button/Button';
+import Logotype from 'static/img/brand/logotype.svg';
+import SimpleSelect from 'src/shared/components/simple-select/SimpleSelect';
+import ButtonLink from 'src/shared/components/button-link/ButtonLink';
+
+import logoUrl from 'static/img/tools/vep/ensembl-vep.svg?url';
+
+import styles from './VepTopBar.module.css';
+
+const VepTopBar = () => {
+ return (
+
+
+
+
Run a job
+
+ Transcript set
+
+
+ Run
+
+
+ Variant effect predictor
+ v111
+
+
+
+ Unviewed jobs
+
+
+ Jobs list
+
+
+
+
+ );
+};
+
+export default VepTopBar;
diff --git a/src/content/app/tools/vep/views/vep-form/VepForm.module.css b/src/content/app/tools/vep/views/vep-form/VepForm.module.css
new file mode 100644
index 0000000000..366f66ee26
--- /dev/null
+++ b/src/content/app/tools/vep/views/vep-form/VepForm.module.css
@@ -0,0 +1,73 @@
+.container {
+ margin-top: 22px;
+ margin-left: var(--double-standard-gutter);
+ margin-bottom: var(--global-padding-bottom);
+ max-width: 1160px;
+}
+
+/*
+* THE TOPMOST AREA, ABOVE OTHER FORM SECTIONS
+*/
+.topmostAreaGrid {
+ --padding-side: 42px;
+ display: grid;
+ grid-template-columns: [submission-name-field] 1fr [form-reset] max-content;
+ align-items: end;
+ padding-left: var(--padding-side);
+ padding-right: var(--padding-side);
+ margin-bottom: 20px;
+}
+
+.submissionName {
+ grid-column: submission-name-field;
+ justify-self: start;
+ display: flex;
+ align-items: center;
+ column-gap: 12px;
+ font-weight: var(--font-weight-light);
+ font-size: 11px;
+ white-space: nowrap;
+}
+
+.resetForm {
+ grid-column: form-reset;
+}
+
+
+.formSection {
+ min-height: 62px;
+}
+
+/*
+* TOP SECTIONS OF THE FORM
+*/
+
+/* Grid to use to display the regular content of the section, i.e. not the expanded content */
+.topFormSectionRegularGrid {
+ --column-gap: 20px;
+ --padding-side: 42px;
+ display: grid;
+ grid-template-columns:
+ [section-name] calc(152px - var(--column-gap))
+ [section-main-content] 1fr
+ [section-toggle] auto;
+ column-gap: var(--column-gap);
+ align-items: center;
+ height: 62px;
+}
+
+.topFormSectionName {
+ grid-column: section-name;
+ font-weight: var(--font-weight-light);
+ padding-left: var(--padding-side);
+}
+
+.topFormSectionMain {
+ grid-column: section-main-content;
+}
+
+.topFormSectionToggle {
+ grid-column: section-toggle;
+ padding-right: var(--padding-side);
+ display: flex;
+}
diff --git a/src/content/app/tools/vep/views/vep-form/VepForm.tsx b/src/content/app/tools/vep/views/vep-form/VepForm.tsx
new file mode 100644
index 0000000000..cc24cea26e
--- /dev/null
+++ b/src/content/app/tools/vep/views/vep-form/VepForm.tsx
@@ -0,0 +1,64 @@
+/**
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import FormSection from 'src/content/app/tools/vep/components/form-section/FormSection';
+import PlusButton from 'src/shared/components/plus-button/PlusButton';
+import TextButton from 'src/shared/components/text-button/TextButton';
+import ShadedInput from 'src/shared/components/input/ShadedInput';
+import { DeleteButtonWithLabel } from 'src/shared/components/delete-button/DeleteButton';
+
+import styles from './VepForm.module.css';
+
+const VepForm = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Species
+
+ Select a species / assembly
+
+
+
+
+
+
+
+
+
Variants
+
+ Add variants
+
+
+
+
+
+
+
+ );
+};
+
+export default VepForm;
diff --git a/src/header/launchbar/Launchbar.tsx b/src/header/launchbar/Launchbar.tsx
index f3f4fe09e0..8071d40e75 100644
--- a/src/header/launchbar/Launchbar.tsx
+++ b/src/header/launchbar/Launchbar.tsx
@@ -27,6 +27,7 @@ import LaunchbarButton from './LaunchbarButton';
import SpeciesSelectorLaunchbarButton from './SpeciesSelectorLaunchbarButton';
import EntityViewerLaunchbarButton from './EntityViewerLaunchbarButton';
import BlastLaunchbarButton from './BlastLaunchbarButton';
+import VepLaunchbarButton from './VepLaunchbarButton';
import Logotype from 'static/img/brand/logotype.svg';
@@ -63,6 +64,7 @@ const Launchbar = () => {
+
{
+ if (isProductionEnvironment()) {
+ return null;
+ }
+
+ // TODO: add the code to enable notifications after submissions have been enabled
+ return (
+
+ );
+};
+
+export default VepLaunchbarButton;
diff --git a/src/routes/routesConfig.tsx b/src/routes/routesConfig.tsx
index 843e0d175b..ba1c3ed3aa 100644
--- a/src/routes/routesConfig.tsx
+++ b/src/routes/routesConfig.tsx
@@ -34,6 +34,9 @@ import EntityViewerPage, {
import BlastPage, {
serverFetch as blastServerFetch
} from 'src/content/app/tools/blast/BlastPage';
+import VepPage, {
+ serverFetch as vepServerFetch
+} from 'src/content/app/tools/vep/VepPage';
import AboutPage, {
serverFetch as aboutPageServerFetch
} from 'src/content/app/about/AboutPage';
@@ -85,6 +88,11 @@ const routes: RouteConfig[] = [
element: ,
serverFetch: blastServerFetch
},
+ {
+ path: '/vep/*',
+ element: ,
+ serverFetch: vepServerFetch
+ },
{
path: '/about/*',
element: ,
diff --git a/src/shared/components/app-icon/AppIcon.module.css b/src/shared/components/app-icon/AppIcon.module.css
index c56d63f75d..888fb62700 100644
--- a/src/shared/components/app-icon/AppIcon.module.css
+++ b/src/shared/components/app-icon/AppIcon.module.css
@@ -49,6 +49,13 @@ get used when AppIcon is placed inside of the ImageButton component */
height: 75%;
}
+.vep svg {
+ --image-button-svg-width: 95%;
+ --image-button-svg-height: 95%;
+ width: 95%;
+ height: 95%;
+}
+
.help svg {
--image-button-svg-width: 70%;
--image-button-svg-height: 70%;
diff --git a/src/shared/components/app-icon/VepIcon.tsx b/src/shared/components/app-icon/VepIcon.tsx
new file mode 100644
index 0000000000..3a0d14c3cb
--- /dev/null
+++ b/src/shared/components/app-icon/VepIcon.tsx
@@ -0,0 +1,34 @@
+/**
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { memo } from 'react';
+import classNames from 'classnames';
+
+import SVGIcon from 'static/icons/icon_launchbar_vep.svg';
+
+import styles from './AppIcon.module.css';
+
+const BlastIcon = () => {
+ const elementClasses = classNames(styles.appIcon, styles.vep);
+
+ return (
+