Skip to content
Open
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
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
2 changes: 1 addition & 1 deletion src/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "prettier"]
}
2 changes: 1 addition & 1 deletion src/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ yarn-error.log*

# typescript
*.tsbuildinfo
next-env.d.ts
next-env.d.ts
7 changes: 7 additions & 0 deletions src/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"tabWidth": 2,
"useTabs": false
}
3 changes: 2 additions & 1 deletion src/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# GraphQL Dashboard

## Development

For development run `yarn dev` and make sure that a backend is running at http://localhost:54321/graphql.
Example backends can be found under `/examples.`

## Production

We use next.js as a static site generater.
Run `yarn build` to generate the static assets.
The backend, which is responsible for authentication and authorization, will serve these static files.

42 changes: 21 additions & 21 deletions src/assets/caretDown.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
function CaretDown(props: any) {
return (
<svg
width="50px"
height="50px"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.47 9.4a.75.75 0 011.06 0l6.364 6.364a.25.25 0 00.354 0L18.612 9.4a.75.75 0 011.06 1.06l-6.364 6.364a1.75 1.75 0 01-2.475 0L4.47 10.46a.75.75 0 010-1.06z"
fill="#000"
/>
</svg>
)
}
export default CaretDown
function CaretDown(props: any) {
return (
<svg
width="50px"
height="50px"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.47 9.4a.75.75 0 011.06 0l6.364 6.364a.25.25 0 00.354 0L18.612 9.4a.75.75 0 011.06 1.06l-6.364 6.364a1.75 1.75 0 01-2.475 0L4.47 10.46a.75.75 0 010-1.06z"
fill="#000"
/>
</svg>
);
}

export default CaretDown;
14 changes: 7 additions & 7 deletions src/components/commandBubble.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.openClosedSwitch {
padding: 1.5rem 2ch;
opacity: 0.75;
padding: 1.5rem 2ch;
opacity: 0.75;
min-width: 10ch;
cursor: pointer;
transition: all 0.25s 0s ease-in-out;
Expand All @@ -27,7 +27,7 @@
}

.bubble:hover {
box-shadow: -8px 10px 12px 4px rgba(0, 0, 0, 0.18);
box-shadow: -8px 10px 12px 4px rgba(0, 0, 0, 0.18);
transform: scale(1.01);
}

Expand All @@ -45,7 +45,7 @@
opacity: 0.5;
}

.title {
.title {
flex-grow: 0;
margin: 1.5rem;
transform: translate(0, 2px);
Expand Down Expand Up @@ -77,10 +77,10 @@
.bubbleContent {
max-height: 0px;
transition: max-height 0.5s 0s ease;
overflow: hidden;
overflow: hidden;
}

.open .bubbleContent {
height: fit-content;
height: fit-content;
max-height: 500px;
}
}
20 changes: 10 additions & 10 deletions src/components/commandBubble.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { GraphQLArgument } from "graphql";
import { Maybe } from "graphql/jsutils/Maybe";
import { useState } from "react";
import CaretDown from "../assets/caretDown";
import styles from "./commandBubble.module.css";
import InputBox from "./inputBox";
import { argumentsToInputType } from "./inputType";
import { GraphQLArgument } from 'graphql';
import { Maybe } from 'graphql/jsutils/Maybe';
import { useState } from 'react';
import CaretDown from '../assets/caretDown';
import styles from './commandBubble.module.css';
import InputBox from './inputBox';
import { argumentsToInputType } from './inputType';

type Props = {
title: string;
Expand All @@ -17,12 +17,12 @@ export default function CommandBubble({
title,
titleWidthInCh,
description,
input
input,
}: Props) {
let [isOpen, toggleOpen] = useState(false);
return (
<div className={`${styles.bubble} ${isOpen ? styles.open : ""}`}>
<div className={styles.header} onClick={() => toggleOpen(o => !o)}>
<div className={`${styles.bubble} ${isOpen ? styles.open : ''}`}>
<div className={styles.header} onClick={() => toggleOpen((o) => !o)}>
<div
className={styles.title}
style={{ minWidth: `${titleWidthInCh}ch` }}
Expand Down
12 changes: 7 additions & 5 deletions src/components/inputBox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ComplexInput, InputType } from "./inputType";
import styles from "./inputType.module.css";
import { ComplexInput, InputType } from './inputType';
import styles from './inputType.module.css';

type Props = {
input: InputType | null;
Expand All @@ -8,15 +8,16 @@ type Props = {

function calculateLabelLength(input: ComplexInput) {
return Math.max(
...input.fields.filter((f) => !("fields" in f)).map((f) => f.name.length)
...input.fields.filter((f) => !('fields' in f)).map((f) => f.name.length)
);
}

export default function InputBox({ input, labelLength }: Props) {
if (!input) {
return null;
}
if ("fields" in input) { // ComplexInput
if ('fields' in input) {
// ComplexInput
let labelLength = calculateLabelLength(input);
return (
<div className={styles.complexType}>
Expand All @@ -26,7 +27,8 @@ export default function InputBox({ input, labelLength }: Props) {
))}
</div>
);
} else { // ScalarInput
} else {
// ScalarInput
return (
<div className={styles.scalarType}>
<div
Expand Down
86 changes: 43 additions & 43 deletions src/components/inputType.module.css
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
.complexType {
margin: 16px;
padding: 8px;
border: 3px dashed hsl(240 100% 50% / 0.6);
border-radius: 0.5rem;
width: fit-content;
position: relative;
}

.complexTypeName {
background-color: hsl(0 0% 100% / 1);
width: fit-content;
font-size: 80%;
font-weight: 600;
color: hsl(240 100% 50% / 1);
padding: 0.25em 0.25em 0 0.25em;
border-radius: 2em;
position: absolute;
top: -14px;
}

.scalarType {
display: flex;
margin-top: 8px;
}

.labelContainer {
margin-right: 16px;
}

.input, .label {
display: block;
}

.input {
width: 25ch;
font-size: 80%;
padding-left: 8px;
padding-right: 8px;
margin: 16px;
padding: 8px;
border: 3px dashed hsl(240 100% 50% / 0.6);
border-radius: 0.5rem;
width: fit-content;
position: relative;
}

}

.complexType .complexType {
margin-bottom: 8px;
}
.complexTypeName {
background-color: hsl(0 0% 100% / 1);
width: fit-content;
font-size: 80%;
font-weight: 600;
color: hsl(240 100% 50% / 1);
padding: 0.25em 0.25em 0 0.25em;
border-radius: 2em;
position: absolute;
top: -14px;
}

.scalarType {
display: flex;
margin-top: 8px;
}

.labelContainer {
margin-right: 16px;
}

.input,
.label {
display: block;
}

.input {
width: 25ch;
font-size: 80%;
padding-left: 8px;
padding-right: 8px;
}

.complexType .complexType {
margin-bottom: 8px;
}
78 changes: 50 additions & 28 deletions src/components/inputType.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,64 @@
import { GraphQLArgument, GraphQLInputObjectType, GraphQLNonNull, GraphQLScalarType } from "graphql";
import {
GraphQLArgument,
GraphQLInputObjectType,
GraphQLList,
GraphQLNonNull,
GraphQLScalarType,
} from 'graphql';

interface ScalarInput {
name: string;
name: string;
}

interface ComplexInput {
name: string;
fields: InputType[]
name: string;
fields: InputType[];
}

interface ListInput {
name: string;
fields: InputType[];
}

type InputType = ScalarInput | ComplexInput;
type InputType = ScalarInput | ComplexInput | ListInput;

function argumentToInputType(arg: GraphQLArgument): InputType {
let type = arg.type;
function argumentToInputType(arg: GraphQLArgument): InputType {
let type = arg.type;
if (type instanceof GraphQLNonNull) {
type = type.ofType;
}

if (type instanceof GraphQLNonNull) {
type = type.ofType;
}

if (type instanceof GraphQLScalarType) {
return {name: arg.name}
}
if (type instanceof GraphQLInputObjectType) {
return {name: arg.name, fields: Object.values(type.getFields()).map(f => argumentToInputType(f))}
}
throw new Error("unexpected type");
if (type instanceof GraphQLScalarType) {
return { name: arg.name };
}
if (type instanceof GraphQLInputObjectType) {
return {
name: arg.name,
fields: Object.values(type.getFields()).map((f) =>
argumentToInputType(f)
),
};
}
if (type instanceof GraphQLList) {
return { name: arg.name };
}
throw new Error('unexpected type');
}

function argumentsToInputType(args: readonly GraphQLArgument[]): InputType | null {
if (args.length == 0) {
return null;
}
if (args.length > 1) {
return {name: "input", fields: args.map(a => argumentToInputType(a))}
}
return argumentToInputType(args[0]);
function argumentsToInputType(
args: readonly GraphQLArgument[]
): InputType | null {
if (args.length == 0) {
return null;
}
if (args.length > 1) {
return {
name: 'input',
fields: args.map((a) => argumentToInputType(a)),
};
}
return argumentToInputType(args[0]);
}

export type {InputType, ComplexInput};
export {argumentsToInputType};
export type { InputType, ComplexInput };
export { argumentsToInputType };
Loading