Merge branch 'refactor/ui' into feat/storybook

This commit is contained in:
liyasthomas
2021-08-02 21:00:09 +05:30
175 changed files with 39560 additions and 5674 deletions

View File

@@ -103,8 +103,8 @@
---
- Choose theme: System, Light, Dark (default) and Black
- Choose accent color: Blue, Green (default), Teal, Indigo, Purple, Orange, Pink, Red, and Yellow
- Choose theme: System (default), Light, Dark and Black
- Choose accent color: Green (default), Teal, Blue, Indigo, Purple, Yellow, Orange, Red and Pink
- Toggle auto-scroll to response
<p>

View File

@@ -1 +1 @@
<svg width="2500" height="2432" viewBox="0 0 256 249" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><g fill="#161614"><path d="M127.505 0C57.095 0 0 57.085 0 127.505c0 56.336 36.534 104.13 87.196 120.99 6.372 1.18 8.712-2.766 8.712-6.134 0-3.04-.119-13.085-.173-23.739-35.473 7.713-42.958-15.044-42.958-15.044-5.8-14.738-14.157-18.656-14.157-18.656-11.568-7.914.872-7.752.872-7.752 12.804.9 19.546 13.14 19.546 13.14 11.372 19.493 29.828 13.857 37.104 10.6 1.144-8.242 4.449-13.866 8.095-17.05-28.32-3.225-58.092-14.158-58.092-63.014 0-13.92 4.981-25.295 13.138-34.224-1.324-3.212-5.688-16.18 1.235-33.743 0 0 10.707-3.427 35.073 13.07 10.17-2.826 21.078-4.242 31.914-4.29 10.836.048 21.752 1.464 31.942 4.29 24.337-16.497 35.029-13.07 35.029-13.07 6.94 17.563 2.574 30.531 1.25 33.743 8.175 8.929 13.122 20.303 13.122 34.224 0 48.972-29.828 59.756-58.22 62.912 4.573 3.957 8.648 11.717 8.648 23.612 0 17.06-.148 30.791-.148 34.991 0 3.393 2.295 7.369 8.759 6.117 50.634-16.879 87.122-64.656 87.122-120.973C255.009 57.085 197.922 0 127.505 0"/><path d="M47.755 181.634c-.28.633-1.278.823-2.185.389-.925-.416-1.445-1.28-1.145-1.916.275-.652 1.273-.834 2.196-.396.927.415 1.455 1.287 1.134 1.923M54.027 187.23c-.608.564-1.797.302-2.604-.589-.834-.889-.99-2.077-.373-2.65.627-.563 1.78-.3 2.616.59.834.899.996 2.08.36 2.65M58.33 194.39c-.782.543-2.06.034-2.849-1.1-.781-1.133-.781-2.493.017-3.038.792-.545 2.05-.055 2.85 1.07.78 1.153.78 2.513-.019 3.069M65.606 202.683c-.699.77-2.187.564-3.277-.488-1.114-1.028-1.425-2.487-.724-3.258.707-.772 2.204-.555 3.302.488 1.107 1.026 1.445 2.496.7 3.258M75.01 205.483c-.307.998-1.741 1.452-3.185 1.028-1.442-.437-2.386-1.607-2.095-2.616.3-1.005 1.74-1.478 3.195-1.024 1.44.435 2.386 1.596 2.086 2.612M85.714 206.67c.036 1.052-1.189 1.924-2.705 1.943-1.525.033-2.758-.818-2.774-1.852 0-1.062 1.197-1.926 2.721-1.951 1.516-.03 2.758.815 2.758 1.86M96.228 206.267c.182 1.026-.872 2.08-2.377 2.36-1.48.27-2.85-.363-3.039-1.38-.184-1.052.89-2.105 2.367-2.378 1.508-.262 2.857.355 3.049 1.398"/></g></svg>
<svg width="2500" height="2432" viewBox="0 0 256 249" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><g fill="currentColor"><path d="M127.505 0C57.095 0 0 57.085 0 127.505c0 56.336 36.534 104.13 87.196 120.99 6.372 1.18 8.712-2.766 8.712-6.134 0-3.04-.119-13.085-.173-23.739-35.473 7.713-42.958-15.044-42.958-15.044-5.8-14.738-14.157-18.656-14.157-18.656-11.568-7.914.872-7.752.872-7.752 12.804.9 19.546 13.14 19.546 13.14 11.372 19.493 29.828 13.857 37.104 10.6 1.144-8.242 4.449-13.866 8.095-17.05-28.32-3.225-58.092-14.158-58.092-63.014 0-13.92 4.981-25.295 13.138-34.224-1.324-3.212-5.688-16.18 1.235-33.743 0 0 10.707-3.427 35.073 13.07 10.17-2.826 21.078-4.242 31.914-4.29 10.836.048 21.752 1.464 31.942 4.29 24.337-16.497 35.029-13.07 35.029-13.07 6.94 17.563 2.574 30.531 1.25 33.743 8.175 8.929 13.122 20.303 13.122 34.224 0 48.972-29.828 59.756-58.22 62.912 4.573 3.957 8.648 11.717 8.648 23.612 0 17.06-.148 30.791-.148 34.991 0 3.393 2.295 7.369 8.759 6.117 50.634-16.879 87.122-64.656 87.122-120.973C255.009 57.085 197.922 0 127.505 0"/><path d="M47.755 181.634c-.28.633-1.278.823-2.185.389-.925-.416-1.445-1.28-1.145-1.916.275-.652 1.273-.834 2.196-.396.927.415 1.455 1.287 1.134 1.923M54.027 187.23c-.608.564-1.797.302-2.604-.589-.834-.889-.99-2.077-.373-2.65.627-.563 1.78-.3 2.616.59.834.899.996 2.08.36 2.65M58.33 194.39c-.782.543-2.06.034-2.849-1.1-.781-1.133-.781-2.493.017-3.038.792-.545 2.05-.055 2.85 1.07.78 1.153.78 2.513-.019 3.069M65.606 202.683c-.699.77-2.187.564-3.277-.488-1.114-1.028-1.425-2.487-.724-3.258.707-.772 2.204-.555 3.302.488 1.107 1.026 1.445 2.496.7 3.258M75.01 205.483c-.307.998-1.741 1.452-3.185 1.028-1.442-.437-2.386-1.607-2.095-2.616.3-1.005 1.74-1.478 3.195-1.024 1.44.435 2.386 1.596 2.086 2.612M85.714 206.67c.036 1.052-1.189 1.924-2.705 1.943-1.525.033-2.758-.818-2.774-1.852 0-1.062 1.197-1.926 2.721-1.951 1.516-.03 2.758.815 2.758 1.86M96.228 206.267c.182 1.026-.872 2.08-2.377 2.36-1.48.27-2.85-.363-3.039-1.38-.184-1.052.89-2.105 2.367-2.378 1.508-.262 2.857.355 3.049 1.398"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M118.2 208.3c19.9-60.3 76.5-103.6 143.7-103.6 36.1 0 68.6 12.8 94.3 33.7L430.5 64C385.2 24.4 327 0 261.8 0 160.9 0 74 57.6 32.3 141.9l85.9 66.4z" fill="#ea4335"/><path d="M348 384.3c-23.3 15-52.8 23-86.2 23-66.9 0-123.3-43-143.4-102.9l-86.2 65.4C73.9 454.3 160.8 512 261.8 512c62.6 0 122.4-22.2 167.1-64L348 384.3z" fill="#34a853"/><path d="M428.9 448c46.8-43.7 77.2-108.7 77.2-192 0-15.1-2.3-31.4-5.8-46.5H261.8v98.9h137.3c-6.8 33.3-25 59-51.1 75.9l80.9 63.7z" fill="#4a90e2"/><path d="M118.4 304.4c-5.1-15.2-7.9-31.4-7.9-48.4 0-16.7 2.7-32.7 7.6-47.7l-85.9-66.4C15.1 176.2 5.8 214.9 5.8 256c0 40.9 9.5 79.6 26.4 113.8l86.2-65.4z" fill="#fbbc05"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M118.2 208.3c19.9-60.3 76.5-103.6 143.7-103.6 36.1 0 68.6 12.8 94.3 33.7L430.5 64C385.2 24.4 327 0 261.8 0 160.9 0 74 57.6 32.3 141.9l85.9 66.4z" fill="currentColor"/><path d="M348 384.3c-23.3 15-52.8 23-86.2 23-66.9 0-123.3-43-143.4-102.9l-86.2 65.4C73.9 454.3 160.8 512 261.8 512c62.6 0 122.4-22.2 167.1-64L348 384.3z" fill="currentColor"/><path d="M428.9 448c46.8-43.7 77.2-108.7 77.2-192 0-15.1-2.3-31.4-5.8-46.5H261.8v98.9h137.3c-6.8 33.3-25 59-51.1 75.9l80.9 63.7z" fill="currentColor"/><path d="M118.4 304.4c-5.1-15.2-7.9-31.4-7.9-48.4 0-16.7 2.7-32.7 7.6-47.7l-85.9-66.4C15.1 176.2 5.8 214.9 5.8 256c0 40.9 9.5 79.6 26.4 113.8l86.2-65.4z" fill="currentColor"/></svg>

Before

Width:  |  Height:  |  Size: 726 B

After

Width:  |  Height:  |  Size: 746 B

1
assets/icons/graphql.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="64" width="64" viewBox="0 0 29.999 30" fill="currentColor"><path d="M4.08 22.864l-1.1-.636L15.248.98l1.1.636z"/><path d="M2.727 20.53h24.538v1.272H2.727z"/><path d="M15.486 28.332L3.213 21.246l.636-1.1 12.273 7.086zm10.662-18.47L13.874 2.777l.636-1.1 12.273 7.086z"/><path d="M3.852 9.858l-.636-1.1L15.5 1.67l.636 1.1z"/><path d="M25.922 22.864l-12.27-21.25 1.1-.636 12.27 21.25zM3.7 7.914h1.272v14.172H3.7zm21.328 0H26.3v14.172h-1.272z"/><path d="M15.27 27.793l-.555-.962 10.675-6.163.555.962z"/><path d="M27.985 22.5a2.68 2.68 0 01-3.654.981 2.68 2.68 0 01-.981-3.654 2.68 2.68 0 013.654-.981 2.665 2.665 0 01.98 3.654M6.642 10.174a2.68 2.68 0 01-3.654.981A2.68 2.68 0 012.007 7.5a2.68 2.68 0 013.654-.981 2.68 2.68 0 01.981 3.654M2.015 22.5a2.68 2.68 0 01.981-3.654 2.68 2.68 0 013.654.981 2.68 2.68 0 01-.981 3.654c-1.287.735-2.92.3-3.654-.98m21.343-12.326a2.68 2.68 0 01.981-3.654 2.68 2.68 0 013.654.981 2.68 2.68 0 01-.981 3.654 2.68 2.68 0 01-3.654-.981M15 30a2.674 2.674 0 112.674-2.673A2.68 2.68 0 0115 30m0-24.652a2.67 2.67 0 01-2.674-2.674 2.67 2.67 0 115.347 0A2.67 2.67 0 0115 5.347"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -15,17 +15,10 @@
}
::-webkit-scrollbar-thumb {
@apply bg-divider;
@apply bg-divider bg-clip-content;
@apply rounded-full;
@apply border-solid;
@apply border-4;
@apply border-transparent;
@apply bg-clip-content;
&:hover {
@apply bg-dividerDark;
@apply bg-clip-content;
}
@apply border-solid border-4 border-transparent;
@apply hover:(bg-dividerDark bg-clip-content);
}
::-webkit-scrollbar {
@@ -42,23 +35,18 @@
@apply text-primary;
}
::placeholder {
@apply text-secondaryLight;
@apply opacity-25;
}
html {
scroll-behavior: smooth;
}
body {
@apply bg-primary;
@apply text-secondary;
@apply !text-sm;
@apply text-secondary text-xs;
@apply font-medium;
@apply select-none;
@apply overflow-x-hidden;
overflow: overlay;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
@@ -73,7 +61,8 @@ body {
}
.fade-enter,
.fade-leave-to .page-enter,
.fade-leave-to,
.page-enter,
.page-leave-to,
.layout-enter,
.layout-leave-to {
@@ -81,14 +70,14 @@ body {
}
.material-icons {
font-size: 1.25rem !important;
font-size: 1rem !important;
}
.svg-icons {
@apply inline-flex;
@apply flex-shrink-0;
@apply h-5;
@apply w-5;
@apply h-4;
@apply w-4;
}
a {
@@ -96,27 +85,36 @@ a {
@apply text-current;
@apply no-underline;
@apply outline-none;
@apply focus-visible:ring;
@apply focus-visible:ring-inset;
@apply focus-visible:ring-accent;
@apply transition;
&.link {
@apply items-center;
@apply px-2 py-1;
@apply -mx-2 -my-1;
@apply text-accent;
@apply hover:text-accent;
@apply focus:text-accent;
@apply hover:text-accentDark;
@apply focus-visible:(ring ring-inset ring-accent);
}
}
.tippy-popper {
.tooltip-theme {
@apply bg-secondary;
@apply text-primaryLight;
@apply text-xs;
@apply bg-tooltip;
@apply text-primary text-xs;
@apply font-semibold;
@apply px-2;
@apply px-4;
@apply py-1 px-2;
@apply shadow;
kbd {
@apply first:ml-2;
@apply last:-mr-1;
@apply font-sans;
@apply bg-secondaryDark;
@apply text-primaryDark;
@apply rounded;
@apply px-1;
@apply ml-1;
}
}
.popover-theme {
@@ -124,59 +122,61 @@ a {
@apply text-secondary;
@apply p-2;
@apply shadow-lg;
@apply focus:outline-none;
}
&[x-placement^="top"] .tippy-tooltip .tippy-arrow {
border-top-color: var(--primary-color);
@apply border-t-primary;
}
&[x-placement^="bottom"] .tippy-tooltip .tippy-arrow {
border-bottom-color: var(--primary-color);
@apply border-b-primary;
}
&[x-placement^="left"] .tippy-tooltip .tippy-arrow {
border-left-color: var(--primary-color);
@apply border-l-primary;
}
&[x-placement^="right"] .tippy-tooltip .tippy-arrow {
border-right-color: var(--primary-color);
@apply border-r-primary;
}
}
.tippy-content > div {
@apply flex;
@apply flex-col;
@apply max-h-64;
@apply overflow-y-auto;
@apply flex flex-col;
@apply max-h-48;
@apply items-stretch;
@apply overflow-y-auto;
&::-webkit-scrollbar {
@apply hidden;
}
}
hr {
@apply border-b;
@apply border-dividerLight;
@apply border-b border-dividerLight;
}
.heading {
@apply font-bold;
@apply text-secondaryDark;
@apply text-lg;
@apply text-secondaryDark text-lg;
}
.textarea,
.input,
.select {
@apply flex;
@apply flex flex-1;
@apply w-full;
@apply px-4;
@apply py-2;
@apply px-4 py-2;
@apply bg-primary;
@apply truncate;
@apply rounded-lg;
@apply rounded;
@apply text-xs;
@apply font-semibold;
@apply border-2;
@apply border-divider;
@apply border border-divider;
@apply transition;
@apply focus:outline-none;
@apply focus:border-accent;
@apply focus:(outline-none border-accent);
@apply disabled:cursor-not-allowed;
}
.input[type="file"],
@@ -187,12 +187,11 @@ hr {
pre.ace_editor {
@apply font-mono;
@apply z-0;
@apply resize-none;
@apply z-0;
}
select {
@apply cursor-pointer;
.select {
@apply appearance-none;
&::-ms-expand {
@@ -203,20 +202,38 @@ select {
.select-wrapper {
@apply relative;
@apply w-full;
@apply cursor-pointer;
&::after {
@apply inline-block;
@apply absolute;
@apply inline-block;
@apply pointer-events-none;
@apply font-icon;
@apply text-secondaryLight;
@apply top-3;
@apply top-2.5;
@apply right-3;
content: "\e313";
}
}
.search-wrapper {
@apply relative;
@apply w-full;
&::after {
@apply absolute;
@apply inline-block;
@apply pointer-events-none;
@apply font-icon;
@apply text-secondaryLight;
@apply top-2;
@apply left-3;
content: "\e8b6";
}
}
input[type="checkbox"] {
@apply hidden;
@@ -226,9 +243,8 @@ input[type="checkbox"] {
@apply cursor-pointer;
&::before {
@apply border;
@apply border-secondary;
@apply rounded-lg;
@apply border border-secondary;
@apply rounded;
@apply inline-flex;
@apply items-center;
@apply justify-center;
@@ -249,19 +265,19 @@ input[type="checkbox"] {
}
.info-response {
@apply text-pink-400;
@apply text-pink-500;
}
.success-response {
@apply text-green-400;
@apply text-green-500;
}
.redir-response {
@apply text-yellow-400;
@apply text-yellow-500;
}
.cl-error-response {
@apply text-red-400;
@apply text-red-500;
}
.sv-error-response {
@@ -278,8 +294,6 @@ input[type="checkbox"] {
textarea {
@apply m-0;
@apply w-full;
line-height: 1;
}
.covers-response {
@@ -292,37 +306,41 @@ input[type="checkbox"] {
}
.toasted-container {
margin-bottom: 68px;
.toasted {
justify-content: space-between !important;
&.info {
background-color: var(--accent-color) !important;
color: var(--primary-color) !important;
font-weight: 700 !important;
}
&.bubble .action {
color: inherit !important;
}
&.toasted-primary {
@apply bg-tooltip;
@apply text-primary text-xs;
@apply justify-start;
@apply shadow;
@apply font-semibold;
.action {
margin-left: auto !important;
@apply ml-auto;
@apply transition;
@apply text-current;
@apply hover:(opacity-75 no-underline);
}
}
}
@media (max-width: 767px) {
main {
margin-bottom: env(safe-area-inset-bottom);
&.info {
@apply !bg-accent;
}
&.error {
@apply !bg-red-200;
@apply !text-red-800;
}
&.success {
@apply !bg-green-200;
@apply !text-green-800;
}
}
}
.splitpanes__splitter {
@apply relative;
@apply bg-primaryLight;
@apply transition;
}
.splitpanes--vertical > .splitpanes__splitter {
@@ -338,7 +356,7 @@ input[type="checkbox"] {
@apply inset-0;
@apply bg-dividerLight;
@apply opacity-0;
@apply z-1;
@apply z-30;
@apply transition;
content: "";
@@ -347,7 +365,7 @@ input[type="checkbox"] {
.splitpanes__splitter::after {
@apply absolute;
@apply inset-0;
@apply z-1;
@apply z-30;
@apply transition;
@apply flex;
@apply items-center;
@@ -379,3 +397,9 @@ input[type="checkbox"] {
bottom: -2px;
width: 100%;
}
@media (max-width: 767px) {
main {
margin-bottom: env(safe-area-inset-bottom);
}
}

View File

@@ -1,5 +1,5 @@
@mixin baseTheme {
--font-sans: "Montserrat", "sans-serif";
--font-sans: "Inter", "sans-serif";
--font-mono: "Roboto Mono", "monospace";
--font-icon: "Material Icons";
}
@@ -8,25 +8,25 @@
// Background color
--primary-color: theme("colors.true-gray.900");
// Light Background color
--primary-light-color: theme("colors.true-gray.800");
--primary-light-color: theme("colors.dark.600");
// Dark Background color
--primary-dark-color: theme("colors.dark.900");
// Text color
--secondary-color: theme("colors.true-gray.400");
// Light Text color
--secondary-light-color: theme("colors.true-gray.200");
--secondary-light-color: theme("colors.true-gray.500");
// Dark Text color
--secondary-dark-color: theme("colors.white");
--secondary-dark-color: theme("colors.true-gray.100");
// Border color
--divider-color: theme("colors.true-gray.700");
--divider-color: theme("colors.true-gray.800");
// Light Border color
--divider-light-color: theme("colors.true-gray.800");
--divider-light-color: theme("colors.dark.500");
// Dark Border color
--divider-dark-color: theme("colors.true-gray.600");
--divider-dark-color: theme("colors.dark.300");
// Error color
--error-color: theme("colors.true-gray.600");
--error-color: theme("colors.dark.800");
// Tooltip color
--tooltip-color: theme("colors.true-gray.800");
--tooltip-color: theme("colors.true-gray.100");
// Editor theme
--editor-theme: "merbivore_soft";
}
@@ -35,25 +35,25 @@
// Background color
--primary-color: theme("colors.white");
// Light Background color
--primary-light-color: theme("colors.true-gray.50");
--primary-light-color: theme("colors.blue-gray.50");
// Dark Background color
--primary-dark-color: theme("colors.true-gray.100");
--primary-dark-color: theme("colors.blue-gray.100");
// Text color
--secondary-color: theme("colors.true-gray.500");
--secondary-color: theme("colors.blue-gray.600");
// Light Text color
--secondary-light-color: theme("colors.true-gray.400");
--secondary-light-color: theme("colors.blue-gray.500");
// Dark Text color
--secondary-dark-color: theme("colors.true-gray.600");
--secondary-dark-color: theme("colors.blue-gray.700");
// Border color
--divider-color: theme("colors.true-gray.200");
--divider-color: theme("colors.blue-gray.200");
// Light Border color
--divider-light-color: theme("colors.true-gray.100");
--divider-light-color: theme("colors.blue-gray.100");
// Dark Border color
--divider-dark-color: theme("colors.true-gray.300");
--divider-dark-color: theme("colors.blue-gray.300");
// Error color
--error-color: theme("colors.true-gray.700");
--error-color: theme("colors.blue-gray.700");
// Tooltip color
--tooltip-color: theme("colors.true-gray.50");
--tooltip-color: theme("colors.blue-gray.800");
// Editor theme
--editor-theme: "textmate";
}
@@ -62,51 +62,36 @@
// Background color
--primary-color: theme("colors.dark.900");
// Light Background color
--primary-light-color: theme("colors.dark.700");
--primary-light-color: theme("colors.true-gray.900");
// Dark Background color
--primary-dark-color: theme("colors.dark.800");
// Text color
--secondary-color: theme("colors.true-gray.400");
// Light Text color
--secondary-light-color: theme("colors.true-gray.600");
--secondary-light-color: theme("colors.true-gray.500");
// Dark Text color
--secondary-dark-color: theme("colors.true-gray.100");
// Border color
--divider-color: theme("colors.dark.800");
--divider-color: theme("colors.true-gray.800");
// Light Border color
--divider-light-color: theme("colors.dark.700");
// Dark Border color
--divider-dark-color: theme("colors.dark.600");
--divider-dark-color: theme("colors.dark.300");
// Error color
--error-color: theme("colors.dark.800");
// Tooltip color
--tooltip-color: theme("colors.dark.500");
--tooltip-color: theme("colors.true-gray.100");
// Editor theme
--editor-theme: "vibrant_ink";
}
@mixin blueTheme {
// Accent color
--accent-color: theme("colors.blue.400");
// Light Accent color
--accent-light-color: theme("colors.blue.200");
// Dark Accent color
--accent-dark-color: theme("colors.blue.600");
// Gradient from
--gradient-from-color: theme("colors.blue.200");
// Gradient via
--gradient-via-color: theme("colors.blue.400");
// Gradient to
--gradient-to-color: theme("colors.blue.600");
--editor-theme: "twilight";
}
@mixin greenTheme {
// Accent color
--accent-color: rgb(97, 207, 123);
--accent-color: theme("colors.green.500");
// Light Accent color
--accent-light-color: rgba(73, 204, 104, 1);
--accent-light-color: theme("colors.green.400");
// Dark Accent color
--accent-dark-color: rgb(0, 71, 17);
--accent-dark-color: theme("colors.green.600");
// Gradient from
--gradient-from-color: theme("colors.green.200");
// Gradient via
@@ -117,12 +102,27 @@
@mixin tealTheme {
// Accent color
--accent-color: theme("colors.teal.400");
--accent-color: theme("colors.teal.500");
// Light Accent color
--accent-light-color: theme("colors.teal.200");
--accent-light-color: theme("colors.teal.400");
// Dark Accent color
--accent-dark-color: theme("colors.teal.600");
// Gradient from
--gradient-from-color: theme("colors.teal.200");
// Gradient via
--gradient-via-color: theme("colors.teal.400");
// Gradient to
--gradient-to-color: theme("colors.teal.600");
}
@mixin blueTheme {
// Accent color
--accent-color: theme("colors.blue.500");
// Light Accent color
--accent-light-color: theme("colors.blue.400");
// Dark Accent color
--accent-dark-color: theme("colors.blue.600");
// Gradient from
--gradient-from-color: theme("colors.blue.200");
// Gradient via
--gradient-via-color: theme("colors.blue.400");
@@ -132,92 +132,92 @@
@mixin indigoTheme {
// Accent color
--accent-color: theme("colors.indigo.400");
--accent-color: theme("colors.indigo.500");
// Light Accent color
--accent-light-color: theme("colors.indigo.200");
--accent-light-color: theme("colors.indigo.400");
// Dark Accent color
--accent-dark-color: theme("colors.indigo.600");
// Gradient from
--gradient-from-color: theme("colors.blue.200");
--gradient-from-color: theme("colors.indigo.200");
// Gradient via
--gradient-via-color: theme("colors.blue.400");
--gradient-via-color: theme("colors.indigo.400");
// Gradient to
--gradient-to-color: theme("colors.blue.600");
--gradient-to-color: theme("colors.indigo.600");
}
@mixin purpleTheme {
// Accent color
--accent-color: theme("colors.purple.400");
--accent-color: theme("colors.purple.500");
// Light Accent color
--accent-light-color: theme("colors.purple.200");
--accent-light-color: theme("colors.purple.400");
// Dark Accent color
--accent-dark-color: theme("colors.purple.600");
// Gradient from
--gradient-from-color: theme("colors.blue.200");
--gradient-from-color: theme("colors.purple.200");
// Gradient via
--gradient-via-color: theme("colors.blue.400");
--gradient-via-color: theme("colors.purple.400");
// Gradient to
--gradient-to-color: theme("colors.blue.600");
}
@mixin orangeTheme {
// Accent color
--accent-color: theme("colors.orange.400");
// Light Accent color
--accent-light-color: theme("colors.orange.200");
// Dark Accent color
--accent-dark-color: theme("colors.orange.600");
// Gradient from
--gradient-from-color: theme("colors.blue.200");
// Gradient via
--gradient-via-color: theme("colors.blue.400");
// Gradient to
--gradient-to-color: theme("colors.blue.600");
}
@mixin pinkTheme {
// Accent color
--accent-color: theme("colors.pink.400");
// Light Accent color
--accent-light-color: theme("colors.pink.200");
// Dark Accent color
--accent-dark-color: theme("colors.pink.600");
// Gradient from
--gradient-from-color: theme("colors.blue.200");
// Gradient via
--gradient-via-color: theme("colors.blue.400");
// Gradient to
--gradient-to-color: theme("colors.blue.600");
}
@mixin redTheme {
// Accent color
--accent-color: theme("colors.red.400");
// Light Accent color
--accent-light-color: theme("colors.red.200");
// Dark Accent color
--accent-dark-color: theme("colors.red.600");
// Gradient from
--gradient-from-color: theme("colors.blue.200");
// Gradient via
--gradient-via-color: theme("colors.blue.400");
// Gradient to
--gradient-to-color: theme("colors.blue.600");
--gradient-to-color: theme("colors.purple.600");
}
@mixin yellowTheme {
// Accent color
--accent-color: theme("colors.yellow.400");
--accent-color: theme("colors.yellow.500");
// Light Accent color
--accent-light-color: theme("colors.yellow.200");
--accent-light-color: theme("colors.yellow.400");
// Dark Accent color
--accent-dark-color: theme("colors.yellow.600");
// Gradient from
--gradient-from-color: theme("colors.blue.200");
--gradient-from-color: theme("colors.yellow.200");
// Gradient via
--gradient-via-color: theme("colors.blue.400");
--gradient-via-color: theme("colors.yellow.400");
// Gradient to
--gradient-to-color: theme("colors.blue.600");
--gradient-to-color: theme("colors.yellow.600");
}
@mixin orangeTheme {
// Accent color
--accent-color: theme("colors.orange.500");
// Light Accent color
--accent-light-color: theme("colors.orange.400");
// Dark Accent color
--accent-dark-color: theme("colors.orange.600");
// Gradient from
--gradient-from-color: theme("colors.orange.200");
// Gradient via
--gradient-via-color: theme("colors.orange.400");
// Gradient to
--gradient-to-color: theme("colors.orange.600");
}
@mixin redTheme {
// Accent color
--accent-color: theme("colors.red.500");
// Light Accent color
--accent-light-color: theme("colors.red.400");
// Dark Accent color
--accent-dark-color: theme("colors.red.600");
// Gradient from
--gradient-from-color: theme("colors.red.200");
// Gradient via
--gradient-via-color: theme("colors.red.400");
// Gradient to
--gradient-to-color: theme("colors.red.600");
}
@mixin pinkTheme {
// Accent color
--accent-color: theme("colors.pink.500");
// Light Accent color
--accent-light-color: theme("colors.pink.400");
// Dark Accent color
--accent-dark-color: theme("colors.pink.600");
// Gradient from
--gradient-from-color: theme("colors.pink.200");
// Gradient via
--gradient-via-color: theme("colors.pink.400");
// Gradient to
--gradient-to-color: theme("colors.pink.600");
}
:root {
@@ -241,27 +241,35 @@
:root[data-accent="blue"] {
@include blueTheme;
}
:root[data-accent="green"] {
@include greenTheme;
}
:root[data-accent="teal"] {
@include tealTheme;
}
:root[data-accent="indigo"] {
@include indigoTheme;
}
:root[data-accent="purple"] {
@include purpleTheme;
}
:root[data-accent="orange"] {
@include orangeTheme;
}
:root[data-accent="pink"] {
@include pinkTheme;
}
:root[data-accent="red"] {
@include redTheme;
}
:root[data-accent="yellow"] {
@include yellowTheme;
}

View File

@@ -1,22 +1,22 @@
<template>
<div class="flex bg-primary border-b justify-between border-dividerLight">
<div class="bg-dividerLight border-b border-divider flex justify-between">
<span class="flex">
<SmartLink
to="https://forms.gle/8yFiEynXB7h477Ns6"
blank
class="
relative
flex
py-2
px-4
transition
relative
items-center
justify-center
px-4
py-3
transition
group
"
>
<i class="material-icons mr-4">science</i>
<span class="text-secondaryDark text-xs">
<i class="mr-4 material-icons">science</i>
<span class="text-secondaryDark">
<span class="md:hidden"> Beta Layout </span>
<span class="hidden md:inline">
You're currently viewing an experimental beta layout
@@ -24,17 +24,16 @@
</span>
<span
class="
border-l border-divider
flex
font-semibold
text-accent
ml-4
pl-4
transition
items-center
justify-center
pl-4
ml-4
font-semibold
transition
border-l
group-hover:text-accentDark
border-divider
text-accent text-xs
"
>
<span class="md:hidden"> Give Feedback </span>
@@ -43,17 +42,13 @@
</SmartLink>
<SmartLink
to="https://hoppscotch.io"
class="flex items-center justify-center transition group"
class="flex transition items-center justify-center group"
>
<span class="text-secondaryDark text-xs">
<span class="text-secondaryDark">
Switch back to the Hoppscotch website
</span>
</SmartLink>
</span>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="close"
:title="$t('close')"
/>
<ButtonSecondary icon="close" />
</div>
</template>

View File

@@ -1,63 +0,0 @@
<template>
<SmartModal v-if="show" @close="hideModal">
<template #header>
<h3 class="heading">{{ $t("extensions") }}</h3>
<div>
<ButtonSecondary icon="close" @click.native="hideModal" />
</div>
</template>
<template #body>
<div class="flex flex-col px-2 space-y-2">
<SmartItem
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
blank
svg="firefox"
label="Firefox"
:info-icon="hasFirefoxExtInstalled ? 'check_circle' : ''"
/>
<SmartItem
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
blank
svg="chrome"
label="Chrome"
:info-icon="hasChromeExtInstalled ? 'check_circle' : ''"
/>
</div>
</template>
<template #footer>
<div class="px-2 text-secondaryLight text-xs">
{{ $t("extensions_info1") }}
</div>
</template>
</SmartModal>
</template>
<script>
import {
hasChromeExtensionInstalled,
hasFirefoxExtensionInstalled,
} from "~/helpers/strategies/ExtensionStrategy"
export default {
props: {
show: Boolean,
},
data() {
return {
hasChromeExtInstalled: hasChromeExtensionInstalled(),
hasFirefoxExtInstalled: hasFirefoxExtensionInstalled(),
}
},
watch: {
show() {
this.hasChromeExtInstalled = hasChromeExtensionInstalled()
this.hasFirefoxExtInstalled = hasFirefoxExtensionInstalled()
},
},
methods: {
hideModal() {
this.$emit("hide-modal")
},
},
}
</script>

110
components/app/Footer.vue Normal file
View File

@@ -0,0 +1,110 @@
<template>
<div>
<div class="flex justify-between">
<div>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="LEFT_SIDEBAR ? $t('hide.sidebar') : $t('show.sidebar')"
icon="menu_open"
:class="{ 'transform rotate-180': !LEFT_SIDEBAR }"
@click.native="toggleSetting('LEFT_SIDEBAR')"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="`${
ZEN_MODE ? $t('action.turn_off') : $t('action.turn_on')
} ${$t('layout.zen_mode')}`"
:icon="ZEN_MODE ? 'fullscreen_exit' : 'fullscreen'"
:class="{
'!text-accent focus:text-accent hover:text-accent': ZEN_MODE,
}"
@click.native="toggleSetting('ZEN_MODE')"
/>
</div>
<div>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="keyboard"
:title="$t('shortcuts')"
:shortcut="['?']"
@click.native="showShortcuts = true"
/>
<ButtonSecondary
v-if="navigatorShare"
v-tippy="{ theme: 'tooltip' }"
icon="share"
:title="$t('request.share')"
@click.native="nativeShare()"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="RIGHT_SIDEBAR ? $t('hide.sidebar') : $t('show.sidebar')"
icon="menu_open"
:class="['transform rotate-180', { 'rotate-0': !RIGHT_SIDEBAR }]"
@click.native="toggleSetting('RIGHT_SIDEBAR')"
/>
</div>
</div>
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import {
defaultSettings,
getSettingSubject,
applySetting,
toggleSetting,
} from "~/newstore/settings"
import type { KeysMatching } from "~/types/ts-utils"
type SettingsType = typeof defaultSettings
export default defineComponent({
data() {
return {
LEFT_SIDEBAR: null,
RIGHT_SIDEBAR: null,
ZEN_MODE: null,
showShortcuts: false,
navigatorShare: navigator.share,
}
},
subscriptions() {
return {
LEFT_SIDEBAR: getSettingSubject("LEFT_SIDEBAR"),
RIGHT_SIDEBAR: getSettingSubject("RIGHT_SIDEBAR"),
ZEN_MODE: getSettingSubject("ZEN_MODE"),
}
},
watch: {
ZEN_MODE(ZEN_MODE) {
this.applySetting("LEFT_SIDEBAR", !ZEN_MODE)
// this.applySetting("RIGHT_SIDEBAR", !ZEN_MODE)
},
},
methods: {
toggleSetting<K extends KeysMatching<SettingsType, boolean>>(key: K) {
toggleSetting(key)
},
applySetting<K extends keyof SettingsType>(key: K, value: SettingsType[K]) {
applySetting(key, value)
},
nativeShare() {
if (navigator.share) {
navigator
.share({
title: "Hoppscotch",
text: "Hoppscotch • Open source API development ecosystem - Helps you create requests faster, saving precious time on development.",
url: "https://hoppscotch.io",
})
.then(() => {})
.catch(console.error)
} else {
// fallback
}
},
},
})
</script>

View File

@@ -1,21 +1,30 @@
<template>
<header class="flex items-center justify-between p-2 flex-1">
<div class="inline-flex space-x-2 items-center font-bold flex-shrink-0">
<AppLogo class="h-6 mx-4" /> Hoppscotch
<header class="flex flex-1 py-2 px-4 items-center justify-between">
<div
class="
font-extrabold
space-x-2
flex-shrink-0
text-sm
inline-flex
items-center
"
>
<AppLogo />
</div>
<div class="inline-flex space-x-2 items-center flex-shrink-0">
<div class="space-x-2 flex-shrink-0 inline-flex items-center">
<AppGitHubStarButton class="mt-1 mr-2" />
<TabPrimary
id="installPWA"
v-tippy="{ theme: 'tooltip' }"
:title="$t('install_pwa')"
:title="$t('header.install_pwa')"
icon="offline_bolt"
@click.native="showInstallPrompt()"
/>
<span tabindex="-1">
<ButtonPrimary
v-if="currentUser === null"
label="Get Started"
label="Login"
@click.native="showLogin = true"
/>
<tippy
@@ -34,11 +43,11 @@
:url="currentUser.photoURL"
:alt="currentUser.displayName"
:title="
(currentUser.displayName ||
'<label><i>Name not found</i></label>') +
`${currentUser.displayName || 'Name not found'}` +
'<br>' +
(currentUser.email || '<label><i>Email not found</i></label>')
`<sub>${currentUser.email || 'Email not found'}</sub>`
"
:indicator="isOnLine ? 'bg-green-500' : 'bg-red-500'"
/>
<TabPrimary
v-else
@@ -47,12 +56,6 @@
icon="account_circle"
/>
</template>
<SmartItem
to="/profile"
icon="person"
:label="$t('profile')"
@click.native="$refs.user.tippy().hide()"
/>
<SmartItem
to="/settings"
icon="settings"
@@ -62,56 +65,8 @@
<FirebaseLogout @confirm-logout="$refs.user.tippy().hide()" />
</tippy>
</span>
<span tabindex="-1">
<tippy
ref="options"
interactive
tabindex="-1"
trigger="click"
theme="popover"
arrow
>
<template #trigger>
<TabPrimary
v-tippy="{ theme: 'tooltip' }"
:title="$t('more')"
icon="drag_indicator"
/>
</template>
<SmartItem
icon="extension"
:label="$t('extensions')"
@click.native="
showExtensions = true
$refs.options.tippy().hide()
"
/>
<SmartItem
icon="keyboard"
:label="$t('shortcuts')"
@click.native="
showShortcuts = true
$refs.options.tippy().hide()
"
/>
<SmartItem
v-if="navigatorShare"
icon="share"
:label="$t('share')"
@click.native="
nativeShare()
$refs.options.tippy().hide()
"
/>
</tippy>
</span>
</div>
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
<AppExtensions
:show="showExtensions"
@hide-modal="showExtensions = false"
/>
<AppShortcuts :show="showShortcuts" @hide-modal="showShortcuts = false" />
</header>
</template>
@@ -119,7 +74,6 @@
import intializePwa from "~/helpers/pwa"
import { currentUser$ } from "~/helpers/fb/auth"
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
// import { hasExtensionInstalled } from "~/helpers/strategies/ExtensionStrategy"
export default {
data() {
@@ -129,9 +83,7 @@ export default {
// prompt.
showInstallPrompt: null,
showLogin: false,
showExtensions: false,
showShortcuts: false,
navigatorShare: navigator.share,
isOnLine: navigator.onLine,
}
},
subscriptions() {
@@ -140,6 +92,13 @@ export default {
}
},
async mounted() {
window.addEventListener("online", () => {
this.isOnLine = true
})
window.addEventListener("offline", () => {
this.isOnLine = false
})
// Initializes the PWA code - checks if the app is installed,
// etc.
this.showInstallPrompt = await intializePwa()
@@ -151,7 +110,7 @@ export default {
theme: "toasted-primary",
action: [
{
text: this.$t("dismiss"),
text: this.$t("action.dismiss"),
onClick: (_, toastObject) => {
setLocalConfig("cookiesAllowed", "yes")
toastObject.goAway(0)
@@ -161,21 +120,5 @@ export default {
})
}
},
methods: {
nativeShare() {
if (navigator.share) {
navigator
.share({
title: "Hoppscotch",
text: "Hoppscotch • Open source API development ecosystem - Helps you create requests faster, saving precious time on development.",
url: "https://hoppscotch.io",
})
.then(() => {})
.catch(console.error)
} else {
// fallback
}
},
},
}
</script>

View File

@@ -1,19 +1,7 @@
<template>
<svg
class="logo fill-current"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 612 612"
>
<g xmlns="http://www.w3.org/2000/svg">
<path
d="M4.283 208.326c-28.015 71.685 84.358 182.589 250.992 247.71 166.634 65.121 324.428 59.801 352.442-11.884 17.432-44.606-19.496-104.396-90.027-158.893-.489 22.675-4.952 44.944-13.309 66.328-3.559 9.108-11.697 21.519-30.254 28.923-10.853 4.33-24.392 6.526-40.242 6.526-40.758 0-99.372-14.662-156.792-39.22-27.484-11.755-52.967-25.125-73.694-38.665-23.871-15.594-41.229-31.196-51.592-46.373-15.399-22.551-13.289-40.954-8.807-52.421 8.368-21.412 20.275-40.884 35.185-57.777-88.789-7.765-156.47 11.143-173.902 55.746zm542.506 203.868c7.682-5.648 19.708-2.339 26.861 7.39 7.153 9.729 6.724 22.194-.958 27.842s-19.709 2.339-26.861-7.39c-7.153-9.729-6.724-22.194.958-27.842zm-297.979-5.647c3.842-9.832 16.98-13.886 29.344-9.054 12.363 4.832 19.271 16.719 15.428 26.552-3.842 9.832-16.98 13.887-29.344 9.054-12.363-4.832-19.269-16.72-15.428-26.552zM51.312 246.776c-11.854 2.301-22.937-3.422-24.754-12.782-1.817-9.361 6.321-18.813 18.174-21.114 11.854-2.301 22.937 3.422 24.754 12.782 1.817 9.36-6.319 18.813-18.174 21.114z"
/>
<path
d="M433.885 363.563c24.904 0 42.999-6.106 48.633-20.52 9.669-24.741 13.162-50.371 11.229-75.19-4.919-63.176-45.008-121.096-107.987-145.708-20.393-7.97-41.378-11.745-62.027-11.744-43.118.003-84.718 16.481-116.132 45.623-18.252 16.932-33.069 38.136-42.738 62.878-21.567 55.188 173.67 144.661 269.022 144.661z"
/>
</g>
<svg class="logo" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<circle class="fill-current" r="8" cx="50%" cy="50%" />
<circle class="fill-primary" r="6" cx="50%" cy="50%" />
</svg>
</template>

View File

@@ -1,5 +1,5 @@
<template>
<section :id="label.toLowerCase()">
<section :id="label.toLowerCase()" class="flex flex-col flex-1">
<slot></slot>
</section>
</template>

View File

@@ -1,44 +1,76 @@
<template>
<SmartModal v-if="show" @close="hideModal">
<template #header>
<h3 class="heading">{{ $t("shortcuts") }}</h3>
<div>
<ButtonSecondary icon="close" @click.native="hideModal" />
</div>
</template>
<template #body>
<div class="px-2">
<AppSlideOver :show="show" @close="close()">
<template #content>
<div
v-for="(shortcut, index) in shortcuts"
:key="`shortcut-${index}`"
class="flex items-center"
>
<kbd
v-for="(key, keyIndex) in shortcut.keys"
:key="`shortcut-${index}-key-${keyIndex}`"
class="
py-2
px-4
m-1
text-xs
border border-divider
rounded-lg
font-bold
bg-primary
border-b border-dividerLight
flex
p-2
top-0
z-10
items-center
sticky
justify-between
"
>
{{ key }}
</kbd>
<span class="flex text-xs ml-4">
<h3 class="ml-4 heading">{{ $t("shortcuts") }}</h3>
<div>
<ButtonSecondary to="/settings" icon="tune" />
<ButtonSecondary icon="close" @click.native="close()" />
</div>
</div>
<!-- <div class="search-wrapper">
<input
v-model="filterText"
type="search"
class="bg-primaryLight border-b border-dividerLight flex font-semibold font-mono w-full py-2 pr-2 pl-8 focus:outline-none truncate"
:placeholder="$t('search')"
/>
</div> -->
<div
class="
divide-y divide-dividerLight
flex flex-col flex-1
overflow-auto
hide-scrollbar
"
>
<div
v-for="(map, mapIndex) in mappings"
:key="`map-${mapIndex}`"
class="space-y-4 py-4 px-6"
>
<h5 class="font-bold text-secondaryDark text-sm">
{{ map.section }}
</h5>
<div
v-for="(shortcut, shortcutIndex) in map.shortcuts"
:key="`map-${mapIndex}-shortcut-${shortcutIndex}`"
class="flex items-center"
>
<span class="flex flex-1 text-secondaryLight mr-4">
{{ shortcut.label }}
</span>
<span
v-for="(key, keyIndex) in shortcut.keys"
:key="`map-${mapIndex}-shortcut-${shortcutIndex}-key-${keyIndex}`"
class="shortcut-key"
>
{{ key }}
</span>
</div>
</div>
</div>
</template>
</SmartModal>
</AppSlideOver>
</template>
<script>
import { getPlatformSpecialKey } from "~/helpers/platformutils"
import {
getPlatformSpecialKey,
getPlatformAlternateKey,
} from "~/helpers/platformutils"
export default {
props: {
@@ -46,59 +78,85 @@ export default {
},
data() {
return {
filterText: "",
mappings: [
{
section: "General",
shortcuts: [
{
keys: [this.getSpecialKey(), "G"],
label: this.$t("send_request"),
keys: [getPlatformSpecialKey(), "G"],
label: this.$t("shortcut.send_request"),
},
{
keys: [this.getSpecialKey(), "S"],
label: this.$t("save_to_collections"),
keys: [getPlatformSpecialKey(), "S"],
label: this.$t("shortcut.save_to_collections"),
},
{
keys: [this.getSpecialKey(), "K"],
label: this.$t("copy_request_link"),
keys: [getPlatformSpecialKey(), "K"],
label: this.$t("shortcut.copy_request_link"),
},
{
keys: [this.getSpecialKey(), "I"],
label: this.$t("reset_request"),
keys: [getPlatformSpecialKey(), "I"],
label: this.$t("shortcut.reset_request"),
},
],
},
{
keys: ["Alt", "▲"],
label: this.$t("select_next_method"),
section: "Request",
shortcuts: [
{
keys: [getPlatformAlternateKey(), "↑"],
label: this.$t("shortcut.next_method"),
},
{
keys: ["Alt", ""],
label: this.$t("select_previous_method"),
keys: [getPlatformAlternateKey(), ""],
label: this.$t("shortcut.previous_method"),
},
{
keys: ["Alt", "G"],
label: this.$t("select_get_method"),
keys: [getPlatformAlternateKey(), "G"],
label: this.$t("shortcut.get_method"),
},
{
keys: ["Alt", "H"],
label: this.$t("select_head_method"),
keys: [getPlatformAlternateKey(), "H"],
label: this.$t("shortcut.head_method"),
},
{
keys: ["Alt", "P"],
label: this.$t("select_post_method"),
keys: [getPlatformAlternateKey(), "P"],
label: this.$t("shortcut.post_method"),
},
{
keys: ["Alt", "U"],
label: this.$t("select_put_method"),
keys: [getPlatformAlternateKey(), "U"],
label: this.$t("shortcut.put_method"),
},
{
keys: ["Alt", "X"],
label: this.$t("select_delete_method"),
keys: [getPlatformAlternateKey(), "X"],
label: this.$t("shortcut.delete_method"),
},
],
},
],
}
},
watch: {
$route() {
this.$emit("close")
},
},
methods: {
getSpecialKey: getPlatformSpecialKey,
hideModal() {
this.$emit("hide-modal")
close() {
this.$emit("close")
},
},
}
</script>
<style lang="scss" scoped>
.shortcut-key {
@apply bg-dividerLight;
@apply rounded;
@apply ml-2;
@apply py-1;
@apply px-2;
@apply inline-flex;
}
</style>

View File

@@ -7,7 +7,12 @@
:to="localePath(navigation.target)"
class="nav-link"
>
<i class="material-icons">{{ navigation.icon }}</i>
<i v-if="navigation.icon" class="material-icons">
{{ navigation.icon }}
</i>
<div v-if="navigation.svg" class="h-4 w-4">
<SmartIcon :name="navigation.svg" class="svg-icons" />
</div>
<span>{{ navigation.title }}</span>
</nuxt-link>
</nav>
@@ -19,12 +24,31 @@ export default {
data() {
return {
primaryNavigation: [
{ target: "index", icon: "home", title: "Home" },
{ target: "realtime", icon: "language", title: "Realtime" },
{ target: "graphql", icon: "code", title: "GraphQL" },
{ target: "doc", icon: "book", title: "Docs" },
{ target: "profile", icon: "person", title: "Profile" },
{ target: "settings", icon: "settings", title: "Settings" },
{
target: "index",
icon: "settings_ethernet",
title: this.$t("navigation.rest"),
},
{
target: "graphql",
svg: "graphql",
title: this.$t("navigation.graphql"),
},
{
target: "realtime",
icon: "language",
title: this.$t("navigation.realtime"),
},
{
target: "documentation",
icon: "book",
title: this.$t("navigation.doc"),
},
{
target: "settings",
icon: "settings",
title: this.$t("navigation.settings"),
},
],
}
},
@@ -34,22 +58,20 @@ export default {
<style scoped lang="scss">
.nav-link {
@apply p-4;
@apply flex-col;
@apply flex-1;
@apply hover:bg-primaryDark;
@apply hover:text-secondaryDark;
@apply flex flex-col flex-1;
@apply items-center;
@apply justify-center;
@apply transition;
@apply hover:bg-primaryDark;
@apply hover:text-secondaryDark;
.material-icons {
@apply transition-opacity;
@apply opacity-50;
.material-icons,
.svg-icons {
@apply opacity-75;
}
span {
@apply mt-2;
@apply text-xs;
@apply font-semibold;
}
@@ -57,7 +79,8 @@ export default {
@apply text-accent;
@apply hover:text-accent;
.material-icons {
.material-icons,
.svg-icons {
@apply opacity-100;
}
}

View File

@@ -0,0 +1,68 @@
<template>
<div>
<transition v-if="show" name="fade" appear>
<div class="inset-0 transition-opacity z-20 fixed" @keydown.esc="close()">
<div
class="bg-primaryDark opacity-75 inset-0 absolute"
tabindex="0"
@click="close()"
></div>
</div>
</transition>
<aside
class="
bg-primary
flex flex-col
h-full
max-w-full
shadow-xl
transform
transition
top-0
ease-in-out
right-0
w-96
z-30
duration-300
fixed
overflow-auto
"
:class="show ? 'translate-x-0' : 'translate-x-full'"
>
<slot name="content"></slot>
</aside>
</div>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
required: true,
default: false,
},
},
watch: {
show: {
immediate: true,
handler(show) {
if (process.client) {
if (show) document.body.style.setProperty("overflow", "hidden")
else document.body.style.removeProperty("overflow")
}
},
},
},
mounted() {
document.addEventListener("keydown", (e) => {
if (e.keyCode === 27 && this.show) this.close()
})
},
methods: {
close() {
this.$emit("close")
},
},
}
</script>

View File

@@ -4,28 +4,32 @@
:exact="exact"
:blank="blank"
class="
font-bold
py-2
transition
inline-flex
items-center
justify-center
py-2
font-semibold
transition
focus:outline-none
"
:class="[
color
? `text-${color}-800 bg-${color}-200 hover:text-${color}-900 hover:bg-${color}-300 focus:text-${color}-900 focus:bg-${color}-300`
: `text-white dark:text-accentDark bg-accent hover:bg-accentDark focus:bg-accentDark`,
: `text-primary bg-accent hover:bg-accentDark focus:bg-accentDark`,
label ? 'px-4' : 'px-2',
rounded ? 'rounded-full' : 'rounded-lg',
{ 'opacity-50 cursor-not-allowed': disabled },
rounded ? 'rounded-full' : 'rounded',
{ 'opacity-75 cursor-not-allowed': disabled },
{ 'pointer-events-none': loading },
{ 'px-4 py-4 text-lg': large },
{ 'px-6 py-4 text-lg': large },
{ 'shadow-lg hover:shadow-xl': shadow },
{
'text-white bg-gradient-to-tr from-gradientFrom via-gradientVia to-gradientTo':
gradient,
},
{
'border border-accent hover:border-accentDark focus:border-accentDark':
outline,
},
]"
:disabled="disabled"
:tabindex="loading ? '-1' : '0'"
@@ -37,27 +41,41 @@
>
<i
v-if="icon"
:class="label ? (reverse ? 'ml-2' : 'mr-2') : ''"
class="material-icons"
:class="[
'material-icons',
{ '!text-2xl': large },
label ? (reverse ? 'ml-2' : 'mr-2') : '',
]"
>
{{ icon }}
</i>
<SmartIcon
v-if="svg"
:name="svg"
:class="label ? (reverse ? 'ml-4' : 'mr-4') : ''"
class="svg-icons"
:class="[
'svg-icons',
{ '!h-6 !w-6': large },
label ? (reverse ? 'ml-2' : 'mr-2') : '',
]"
/>
{{ label }}
<span v-if="shortkey" class="px-1 ml-2 rounded bg-accentLight">{{
shortkey
}}</span>
<div v-if="shortcut.length && SHORTCUT_INDICATOR" class="ml-2">
<kbd
v-for="(key, index) in shortcut"
:key="`key-${index}`"
class="bg-accentLight rounded ml-1 px-1 inline-flex"
>
{{ key }}
</kbd>
</div>
</span>
<SmartSpinner v-else />
</SmartLink>
</template>
<script>
import { getSettingSubject } from "~/newstore/settings"
export default {
props: {
to: {
@@ -116,10 +134,24 @@ export default {
type: Boolean,
default: false,
},
shortkey: {
type: String,
default: "",
outline: {
type: Boolean,
default: false,
},
shortcut: {
type: Array,
default: () => [],
},
},
data() {
return {
SHORTCUT_INDICATOR: null,
}
},
subscriptions() {
return {
SHORTCUT_INDICATOR: getSettingSubject("SHORTCUT_INDICATOR"),
}
},
}
</script>

View File

@@ -4,22 +4,24 @@
:exact="exact"
:blank="blank"
class="
font-semibold
py-2
transition
inline-flex
items-center
justify-center
py-2
font-semibold
transition
focus:outline-none
hover:bg-primaryDark
"
:class="[
color
? `text-${color}-400 hover:text-${color}-600 focus:text-${color}-600`
? `text-${color}-500 hover:text-${color}-600 focus:text-${color}-600`
: 'text-secondary hover:text-secondaryDark focus:text-secondaryDark',
label ? 'px-3 rounded-lg' : 'px-2 rounded-full',
rounded ? 'rounded-full' : 'rounded-lg',
{ 'opacity-50 cursor-not-allowed': disabled },
label ? 'px-4' : 'px-2',
rounded ? 'rounded-full' : 'rounded',
{ 'opacity-75 cursor-not-allowed': disabled },
{ 'flex-row-reverse': reverse },
{ 'px-6 py-4 text-lg': large },
{
'border border-divider hover:border-dividerDark focus:border-dividerDark':
outline,
@@ -29,22 +31,46 @@
>
<i
v-if="icon"
:class="label ? (reverse ? 'ml-2' : 'mr-2') : ''"
class="material-icons"
:class="[
'material-icons',
{ '!text-2xl': large },
label ? (reverse ? 'ml-2' : 'mr-2') : '',
]"
>
{{ icon }}
</i>
<SmartIcon
v-if="svg"
:name="svg"
:class="label ? (reverse ? 'ml-2' : 'mr-2') : ''"
class="svg-icons"
:class="[
'svg-icons',
{ '!h-6 !w-6': large },
label ? (reverse ? 'ml-2' : 'mr-2') : '',
]"
/>
{{ label }}
<div v-if="shortcut.length && SHORTCUT_INDICATOR" class="ml-2">
<kbd
v-for="(key, index) in shortcut"
:key="`key-${index}`"
class="
bg-dividerLight
rounded
text-secondaryLight
ml-1
px-1
inline-flex
"
>
{{ key }}
</kbd>
</div>
</SmartLink>
</template>
<script>
import { getSettingSubject } from "~/newstore/settings"
export default {
props: {
to: {
@@ -87,10 +113,28 @@ export default {
type: Boolean,
default: false,
},
large: {
type: Boolean,
default: false,
},
outline: {
type: Boolean,
default: false,
},
shortcut: {
type: Array,
default: () => [],
},
},
data() {
return {
SHORTCUT_INDICATOR: null,
}
},
subscriptions() {
return {
SHORTCUT_INDICATOR: getSettingSubject("SHORTCUT_INDICATOR"),
}
},
}
</script>

View File

@@ -1,12 +1,12 @@
<template>
<SmartModal v-if="show" @close="hideModal">
<template #header>
<h3 class="heading">{{ $t("new_collection") }}</h3>
<h3 class="heading">{{ $t("collection.new") }}</h3>
<ButtonSecondary icon="close" @click.native="hideModal" />
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="selectLabelAdd" class="px-4 font-semibold pb-4 text-xs">
<div class="flex flex-col px-2">
<label for="selectLabelAdd" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input
@@ -14,7 +14,7 @@
v-model="name"
class="input"
type="text"
:placeholder="$t('my_new_collection')"
:placeholder="$t('collection.name')"
@keyup.enter="addNewCollection"
/>
</div>

View File

@@ -7,11 +7,8 @@
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label
for="selectLabelAddFolder"
class="px-4 font-semibold pb-4 text-xs"
>
<div class="flex flex-col px-2">
<label for="selectLabelAddFolder" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input
@@ -19,7 +16,7 @@
v-model="name"
class="input"
type="text"
:placeholder="$t('my_new_folder')"
:placeholder="$t('folder.new')"
@keyup.enter="addFolder"
/>
</div>

View File

@@ -18,15 +18,15 @@
type="text"
autofocus
class="
flex
w-full
px-4
text-xs
py-3
focus:outline-none
border-b border-dividerLight
font-medium
bg-primaryLight
border-b border-dividerLight
flex
font-medium
w-full
py-2
px-4
focus:outline-none
appearance-none
"
@change="updateSelectedTeam(myTeams[$event.target.value])"
>

View File

@@ -1,14 +1,14 @@
<template>
<SmartModal v-if="show" @close="hideModal">
<template #header>
<h3 class="heading">{{ $t("edit_collection") }}</h3>
<h3 class="heading">{{ $t("collection.edit") }}</h3>
<div>
<ButtonSecondary icon="close" @click.native="hideModal" />
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="selectLabelEdit" class="px-4 font-semibold pb-4 text-xs">{{
<div class="flex flex-col px-2">
<label for="selectLabelEdit" class="font-semibold px-4 pb-4">{{
$t("label")
}}</label>
<input

View File

@@ -1,18 +1,16 @@
<template>
<SmartModal v-if="show" @close="$emit('hide-modal')">
<template #header>
<h3 class="heading">{{ $t("edit_folder") }}</h3>
<h3 class="heading">{{ $t("folder.edit") }}</h3>
<div>
<ButtonSecondary icon="close" @click.native="hideModal" />
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label
for="selectLabelEditFolder"
class="px-4 font-semibold pb-4 text-xs"
>{{ $t("label") }}</label
>
<div class="flex flex-col px-2">
<label for="selectLabelEditFolder" class="font-semibold px-4 pb-4">{{
$t("label")
}}</label>
<input
id="selectLabelEditFolder"
v-model="name"

View File

@@ -7,10 +7,10 @@
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="selectLabelEditReq" class="px-4 font-semibold pb-4 text-xs">
{{ $t("label") }}</label
>
<div class="flex flex-col px-2">
<label for="selectLabelEditReq" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input
id="selectLabelEditReq"
v-model="requestUpdateData.name"

View File

@@ -33,7 +33,7 @@
</template>
<SmartItem
icon="assignment_returned"
:label="$t('import_from_gist')"
:label="$t('import.from_gist')"
@click.native="
readCollectionGist
$refs.options.tippy().hide()
@@ -43,9 +43,9 @@
v-tippy="{ theme: 'tooltip' }"
:title="
!currentUser
? $t('login_with_github_to') + $t('create_secret_gist')
? $t('export.require_github')
: currentUser.provider !== 'github.com'
? $t('login_with_github_to') + $t('create_secret_gist')
? $t('export.require_github')
: null
"
:disabled="
@@ -56,7 +56,7 @@
: false
"
icon="assignment_turned_in"
:label="$t('create_secret_gist')"
:label="$t('export.create_secret_gist')"
@click.native="
createCollectionGist
$refs.options.tippy().hide()
@@ -87,7 +87,7 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('preserve_current')"
icon="create_new_folder"
:label="$t('import_json')"
:label="$t('import.json')"
@click.native="openDialogChooseFileToImportFrom"
/>
<input
@@ -103,20 +103,20 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('preserve_current')"
icon="folder_shared"
:label="$t('import_from_my_collections')"
:label="$t('import.from_my_collections')"
@click.native="mode = 'import_from_my_collections'"
/>
<SmartItem
v-tippy="{ theme: 'tooltip' }"
:title="$t('download_file')"
icon="drive_file_move"
:label="$t('export_as_json')"
:label="$t('export.as_json')"
@click.native="exportJSON"
/>
</div>
<div
v-if="mode == 'import_from_my_collections'"
class="flex px-2 flex-col"
class="flex flex-col px-2"
>
<div class="select-wrapper">
<select
@@ -155,7 +155,7 @@
<ButtonPrimary
:disabled="mySelectedCollectionID == undefined"
icon="create_new_folder"
:label="$t('import')"
:label="$t('import.title')"
@click.native="importFromMyCollections"
/>
</span>
@@ -213,7 +213,7 @@ export default {
}
)
.then((res) => {
this.$toast.success(this.$t("gist_created"), {
this.$toast.success(this.$t("export.gist_created"), {
icon: "done",
})
window.open(res.html_url)
@@ -226,7 +226,7 @@ export default {
})
},
async readCollectionGist() {
const gist = prompt(this.$t("enter_gist_url"))
const gist = prompt(this.$t("import.gist_url"))
if (!gist) return
await this.$axios
.$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
@@ -390,22 +390,22 @@ export default {
},
exportJSON() {
this.getJSONCollection()
let text = this.collectionJson
text = text.replace(/\n/g, "\r\n")
const blob = new Blob([text], {
type: "text/json",
})
const anchor = document.createElement("a")
anchor.download = "hoppscotch-collection.json"
anchor.href = window.URL.createObjectURL(blob)
anchor.target = "_blank"
anchor.style.display = "none"
document.body.appendChild(anchor)
anchor.click()
document.body.removeChild(anchor)
const dataToWrite = this.collectionJson
const file = new Blob([dataToWrite], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO get uri from meta
a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}`
document.body.appendChild(a)
a.click()
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
},
fileImported() {
this.$toast.info(this.$t("file_imported"), {
@@ -413,7 +413,7 @@ export default {
})
},
failedImport() {
this.$toast.error(this.$t("import_failed"), {
this.$toast.error(this.$t("import.failed"), {
icon: "error",
})
},

View File

@@ -5,20 +5,17 @@
<ButtonSecondary icon="close" @click.native="hideModal" />
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="selectLabelSaveReq" class="px-4 font-semibold pb-4 text-xs">
{{ $t("token_req_name") }}</label
>
<div class="flex flex-col px-2">
<label for="selectLabelSaveReq" class="font-semibold px-4 pb-4">
{{ $t("request_name") }}
</label>
<input
id="selectLabelSaveReq"
v-model="requestData.name"
v-model="requestName"
class="input"
type="text"
@keyup.enter="saveRequestAs"
/>
<label class="px-4 pt-4 font-semibold pb-4 text-xs">
Select Location
</label>
<label class="font-semibold px-4 pt-4 pb-4"> Select Location </label>
<CollectionsGraphql
v-if="mode === 'graphql'"
:doc="false"
@@ -47,6 +44,7 @@
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import * as teamUtils from "~/helpers/teams/utils"
import {
saveRESTRequestAs,
@@ -54,20 +52,23 @@ import {
editGraphqlRequest,
saveGraphqlRequestAs,
} from "~/newstore/collections"
import { getRESTRequest, useRESTRequestName } from "~/newstore/RESTSession"
export default {
export default defineComponent({
props: {
// mode can be either "graphql" or "rest"
mode: { type: String, default: "rest" },
show: Boolean,
editingRequest: { type: Object, default: () => {} },
},
setup() {
return {
requestName: useRESTRequestName(),
}
},
data() {
return {
defaultRequestName: "Untitled Request",
path: "Path will appear here",
requestData: {
name: undefined,
name: this.requestName,
collectionIndex: undefined,
folderName: undefined,
requestIndex: undefined,
@@ -102,7 +103,7 @@ export default {
},
saveRequestAs() {
if (this.picked == null) {
this.$toast.error(this.$t("select_collection"), {
this.$toast.error(this.$t("collection.select"), {
icon: "error",
})
return
@@ -114,10 +115,7 @@ export default {
return
}
const requestUpdated = {
...this.$props.editingRequest,
name: this.$data.requestData.name,
}
const requestUpdated = getRESTRequest()
// Filter out all REST file inputs
if (this.mode === "rest" && requestUpdated.bodyParams) {
@@ -180,5 +178,5 @@ export default {
this.$emit("hide-modal")
},
},
}
})
</script>

View File

@@ -1,12 +1,12 @@
<template>
<SmartModal v-if="show" @close="hideModal">
<template #header>
<h3 class="heading">{{ $t("new_collection") }}</h3>
<h3 class="heading">{{ $t("collection.new") }}</h3>
<ButtonSecondary icon="close" @click.native="hideModal" />
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="selectLabelGqlAdd" class="px-4 font-semibold pb-4 text-xs">
<div class="flex flex-col px-2">
<label for="selectLabelGqlAdd" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input
@@ -14,7 +14,7 @@
v-model="name"
class="input"
type="text"
:placeholder="$t('my_new_collection')"
:placeholder="$t('collection.name')"
@keyup.enter="addNewCollection"
/>
</div>
@@ -44,7 +44,7 @@ export default Vue.extend({
methods: {
addNewCollection() {
if (!this.name) {
this.$toast.info(this.$t("invalid_collection_name").toString())
this.$toast.info(this.$t("collection.invalid_name").toString())
return
}

View File

@@ -7,11 +7,8 @@
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label
for="selectLabelGqlAddFolder"
class="px-4 font-semibold pb-4 text-xs"
>
<div class="flex flex-col px-2">
<label for="selectLabelGqlAddFolder" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input
@@ -19,7 +16,7 @@
v-model="name"
class="input"
type="text"
:placeholder="$t('my_new_folder')"
:placeholder="$t('folder.new')"
@keyup.enter="addFolder"
/>
</div>

View File

@@ -10,32 +10,24 @@
@dragend="dragging = false"
>
<span
class="
flex
justify-center
items-center
text-xs
w-10
truncate
cursor-pointer
"
class="cursor-pointer flex w-10 justify-center items-center truncate"
@click="toggleShowChildren()"
>
<i class="material-icons" :class="{ 'text-green-400': isSelected }">
<i class="material-icons" :class="{ 'text-green-500': isSelected }">
{{ getCollectionIcon }}
</i>
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="toggleShowChildren()"
>
@@ -44,7 +36,7 @@
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="create_new_folder"
:title="$t('new_folder')"
:title="$t('folder.new')"
class="group-hover:inline-flex hidden"
@click.native="
$emit('add-folder', {
@@ -69,7 +61,7 @@
</template>
<SmartItem
icon="create_new_folder"
:label="$t('new_folder')"
:label="$t('folder.new')"
@click.native="
$emit('add-folder', {
path: `${collectionIndex}`,
@@ -87,6 +79,7 @@
/>
<SmartItem
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -95,11 +88,11 @@
/>
</tippy>
</div>
<div v-show="showChildren || isFiltered">
<div v-if="showChildren || isFiltered">
<CollectionsGraphqlFolder
v-for="(folder, index) in collection.folders"
:key="`folder-${index}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:picked="picked"
:saving-mode="savingMode"
:folder="folder"
@@ -116,7 +109,7 @@
<CollectionsGraphqlRequest
v-for="(request, index) in collection.requests"
:key="`request-${index}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:picked="picked"
:saving-mode="savingMode"
:request="request"
@@ -134,25 +127,24 @@
collection.folders.length === 0 && collection.requests.length === 0
"
class="
flex
items-center
text-secondaryLight
flex-col
p-4
justify-center
ml-5
border-l border-dividerLight
flex flex-col
text-secondaryLight
ml-5
p-4
items-center
justify-center
"
>
<i class="material-icons opacity-50 pb-2">folder_open</i>
<span class="text-xs text-center">
{{ $t("collection_empty") }}
<i class="opacity-75 pb-2 material-icons">folder_open</i>
<span class="text-center">
{{ $t("empty.collection") }}
</span>
</div>
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_collection')"
:title="$t('confirm.remove_collection')"
@hide-modal="confirmRemove = false"
@resolve="removeCollection"
/>

View File

@@ -1,14 +1,14 @@
<template>
<SmartModal v-if="show" @close="hideModal">
<template #header>
<h3 class="heading">{{ $t("edit_collection") }}</h3>
<h3 class="heading">{{ $t("collection.edit") }}</h3>
<div>
<ButtonSecondary icon="close" @click.native="hideModal" />
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="selectLabelGqlEdit" class="px-4 font-semibold pb-4 text-xs">
<div class="flex flex-col px-2">
<label for="selectLabelGqlEdit" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input
@@ -48,7 +48,7 @@ export default Vue.extend({
methods: {
saveCollection() {
if (!this.name) {
this.$toast.info(this.$t("invalid_collection_name").toString())
this.$toast.info(this.$t("collection.invalid_name").toString())
return
}
const collectionUpdated = {

View File

@@ -1,17 +1,14 @@
<template>
<SmartModal v-if="show" @close="$emit('hide-modal')">
<template #header>
<h3 class="heading">{{ $t("edit_folder") }}</h3>
<h3 class="heading">{{ $t("folder.edit") }}</h3>
<div>
<ButtonSecondary icon="close" @click.native="hideModal" />
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label
for="selectLabelGqlEditFolder"
class="px-4 font-semibold pb-4 text-xs"
>
<div class="flex flex-col px-2">
<label for="selectLabelGqlEditFolder" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input

View File

@@ -7,11 +7,8 @@
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label
for="selectLabelGqlEditReq"
class="px-4 font-semibold pb-4 text-xs"
>
<div class="flex flex-col px-2">
<label for="selectLabelGqlEditReq" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input

View File

@@ -10,32 +10,24 @@
@dragend="dragging = false"
>
<span
class="
flex
justify-center
items-center
text-xs
w-10
truncate
cursor-pointer
"
class="cursor-pointer flex w-10 justify-center items-center truncate"
@click="toggleShowChildren()"
>
<i class="material-icons" :class="{ 'text-green-400': isSelected }">
<i class="material-icons" :class="{ 'text-green-500': isSelected }">
{{ getCollectionIcon }}
</i>
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="toggleShowChildren()"
>
@@ -46,7 +38,7 @@
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="create_new_folder"
:title="$t('new_folder')"
:title="$t('folder.new')"
class="group-hover:inline-flex hidden"
@click.native="$emit('add-folder', { folder, path: folderPath })"
/>
@@ -67,7 +59,7 @@
</template>
<SmartItem
icon="create_new_folder"
:label="$t('new_folder')"
:label="$t('folder.new')"
@click.native="
$emit('add-folder', { folder, path: folderPath })
$refs.options.tippy().hide()
@@ -83,6 +75,7 @@
/>
<SmartItem
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -91,11 +84,11 @@
/>
</tippy>
</div>
<div v-show="showChildren || isFiltered">
<div v-if="showChildren || isFiltered">
<CollectionsGraphqlFolder
v-for="(subFolder, subFolderIndex) in folder.folders"
:key="`subFolder-${subFolderIndex}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:picked="picked"
:saving-mode="savingMode"
:folder="subFolder"
@@ -112,7 +105,7 @@
<CollectionsGraphqlRequest
v-for="(request, index) in folder.requests"
:key="`request-${index}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:picked="picked"
:saving-mode="savingMode"
:request="request"
@@ -133,25 +126,24 @@
folder.requests.length === 0
"
class="
flex
items-center
text-secondaryLight
flex-col
p-4
justify-center
ml-5
border-l border-dividerLight
flex flex-col
text-secondaryLight
ml-5
p-4
items-center
justify-center
"
>
<i class="material-icons opacity-50 pb-2">folder_open</i>
<span class="text-xs text-center">
{{ $t("folder_empty") }}
<i class="opacity-75 pb-2 material-icons">folder_open</i>
<span class="text-center">
{{ $t("empty.folder") }}
</span>
</div>
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_folder')"
:title="$t('confirm.remove_folder')"
@hide-modal="confirmRemove = false"
@resolve="removeFolder"
/>

View File

@@ -20,7 +20,7 @@
</template>
<SmartItem
icon="assignment_returned"
:label="$t('import_from_gist')"
:label="$t('import.from_gist')"
@click.native="
readCollectionGist
$refs.options.tippy().hide()
@@ -30,9 +30,9 @@
v-tippy="{ theme: 'tooltip' }"
:title="
!currentUser
? $t('login_with_github_to') + $t('create_secret_gist')
? $t('export.require_github')
: currentUser.provider !== 'github.com'
? $t('login_with_github_to') + $t('create_secret_gist')
? $t('export.require_github')
: null
"
:disabled="
@@ -43,7 +43,7 @@
: false
"
icon="assignment_turned_in"
:label="$t('create_secret_gist')"
:label="$t('export.create_secret_gist')"
@click.native="
createCollectionGist
$refs.options.tippy().hide()
@@ -74,7 +74,7 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('preserve_current')"
icon="create_new_folder"
:label="$t('import_json')"
:label="$t('import.json')"
@click.native="openDialogChooseFileToImportFrom"
/>
<input
@@ -89,7 +89,7 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('download_file')"
icon="drive_file_move"
:label="$t('export_as_json')"
:label="$t('export.as_json')"
@click.native="exportJSON"
/>
</div>
@@ -140,7 +140,7 @@ export default {
}
)
.then((res) => {
this.$toast.success(this.$t("gist_created"), {
this.$toast.success(this.$t("export.gist_created"), {
icon: "done",
})
window.open(res.html_url)
@@ -153,7 +153,7 @@ export default {
})
},
async readCollectionGist() {
const gist = prompt(this.$t("enter_gist_url"))
const gist = prompt(this.$t("import.gist_url"))
if (!gist) return
await this.$axios
.$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
@@ -243,22 +243,22 @@ export default {
this.$refs.inputChooseFileToImportFrom.value = ""
},
exportJSON() {
let text = this.collectionJson
text = text.replace(/\n/g, "\r\n")
const blob = new Blob([text], {
type: "text/json",
})
const anchor = document.createElement("a")
anchor.download = "hoppscotch-collection.json"
anchor.href = window.URL.createObjectURL(blob)
anchor.target = "_blank"
anchor.style.display = "none"
document.body.appendChild(anchor)
anchor.click()
document.body.removeChild(anchor)
const dataToWrite = this.collectionJson
const file = new Blob([dataToWrite], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO get uri from meta
a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}`
document.body.appendChild(a)
a.click()
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
},
fileImported() {
this.$toast.info(this.$t("file_imported"), {
@@ -266,7 +266,7 @@ export default {
})
},
failedImport() {
this.$toast.error(this.$t("import_failed"), {
this.$toast.error(this.$t("import.failed"), {
icon: "error",
})
},

View File

@@ -10,33 +10,32 @@
>
<span
class="
font-mono font-bold
cursor-pointer
flex
font-mono font-bold
mx-2
w-12
justify-center
items-center
text-xs
w-12
mx-2
truncate
cursor-pointer
"
@click="!doc ? selectRequest() : {}"
>
<i class="material-icons" :class="{ 'text-green-400': isSelected }">
<i class="material-icons" :class="{ 'text-green-500': isSelected }">
{{ isSelected ? "check_circle" : "description" }}
</i>
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="!doc ? selectRequest() : {}"
>
@@ -79,6 +78,7 @@
/>
<SmartItem
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -89,7 +89,7 @@
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_request')"
:title="$t('confirm.remove_request')"
@hide-modal="confirmRemove = false"
@resolve="removeRequest"
/>

View File

@@ -1,29 +1,32 @@
<template>
<AppSection
label="collections"
:class="{ 'rounded-lg border-2 border-divider': savingMode }"
:class="{ 'rounded border border-divider': savingMode }"
>
<div
class="flex flex-col sticky top-10 z-10"
class="flex flex-col top-8 z-10 sticky"
:class="{ 'bg-primary': !savingMode }"
>
<div v-if="showCollActions" class="search-wrapper">
<input
v-if="showCollActions"
v-model="filterText"
type="search"
:placeholder="$t('search')"
class="
px-4
py-3
text-xs
border-b border-dividerLight
flex flex-1
font-medium
bg-primaryLight
border-b border-dividerLight
flex
font-semibold font-mono
w-full
py-2
pr-2
pl-9
focus:outline-none
truncate
"
/>
<div class="border-b flex justify-between flex-1 border-dividerLight">
</div>
<div class="border-b border-dividerLight flex flex-1 justify-between">
<ButtonSecondary
icon="add"
:label="$t('new')"
@@ -32,7 +35,7 @@
<ButtonSecondary
v-if="showCollActions"
v-tippy="{ theme: 'tooltip' }"
:title="$t('import_export')"
:title="$t('modal.import_export')"
icon="import_export"
@click.native="displayModalImportExport(true)"
/>
@@ -59,19 +62,24 @@
</div>
<div
v-if="collections.length === 0"
class="flex items-center text-secondaryLight flex-col p-4 justify-center"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="material-icons opacity-50 pb-2">create_new_folder</i>
<span class="text-xs text-center">
{{ $t("create_new_collection") }}
<i class="opacity-75 pb-2 material-icons">create_new_folder</i>
<span class="text-center pb-4">
{{ $t("empty.collections") }}
</span>
<ButtonSecondary
:label="$t('add.new')"
outline
@click.native="displayModalAdd(true)"
/>
</div>
<div
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
class="flex items-center text-secondaryLight flex-col p-4 justify-center"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="material-icons opacity-50 pb-2">manage_search</i>
<span class="text-xs text-center">
<i class="opacity-75 pb-2 material-icons">manage_search</i>
<span class="text-center">
{{ $t("nothing_found") }} "{{ filterText }}"
</span>
</div>

View File

@@ -1,28 +1,31 @@
<template>
<AppSection
label="collections"
:class="{ 'rounded-lg border-2 border-divider': saveRequest }"
:class="{ 'rounded border border-divider': saveRequest }"
>
<div
class="flex flex-col sticky z-10 top-0 bg-primary"
:class="{ '!top-10': !saveRequest && !doc }"
class="bg-primary rounded-t flex flex-col top-0 z-10 sticky"
:class="{ '!top-8': !saveRequest && !doc }"
>
<div v-if="!saveRequest" class="search-wrapper">
<input
v-if="!saveRequest"
v-model="filterText"
type="search"
:placeholder="$t('search')"
class="
px-4
py-3
text-xs
border-b border-dividerLight
flex flex-1
font-medium
bg-primaryLight
border-b border-dividerLight
flex
font-semibold font-mono
w-full
py-2
pr-2
pl-9
focus:outline-none
truncate
"
/>
</div>
<CollectionsChooseType
:collections-type="collectionsType"
:show="showTeamCollections"
@@ -30,7 +33,7 @@
@update-collection-type="updateCollectionType"
@update-selected-team="updateSelectedTeam"
/>
<div class="border-b flex justify-between flex-1 border-dividerLight">
<div class="border-b border-dividerLight flex flex-1 justify-between">
<ButtonSecondary
v-if="
collectionsType.type == 'team-collections' &&
@@ -40,7 +43,7 @@
v-tippy="{ theme: 'tooltip' }"
disabled
icon="add"
:title="$t('disable_new_collection')"
:title="$t('team.no_access')"
:label="$t('new')"
/>
<ButtonSecondary
@@ -57,7 +60,7 @@
collectionsType.selectedTeam == undefined
"
icon="import_export"
:title="$t('import_export')"
:title="$t('modal.import_export')"
@click.native="displayModalImportExport(true)"
/>
</div>
@@ -94,19 +97,37 @@
</div>
<div
v-if="collections.length === 0"
class="flex items-center text-secondaryLight flex-col p-4 justify-center"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="material-icons opacity-50 pb-2">create_new_folder</i>
<span class="text-xs text-center">
{{ $t("create_new_collection") }}
<i class="opacity-75 pb-2 material-icons">create_new_folder</i>
<span class="text-center pb-4">
{{ $t("empty.collections") }}
</span>
<ButtonSecondary
v-if="
collectionsType.type == 'team-collections' &&
(collectionsType.selectedTeam == undefined ||
collectionsType.selectedTeam.myRole == 'VIEWER')
"
v-tippy="{ theme: 'tooltip' }"
disabled
:title="$t('team.no_access')"
:label="$t('add.new')"
outline
/>
<ButtonSecondary
v-else
outline
:label="$t('add.new')"
@click.native="displayModalAdd(true)"
/>
</div>
<div
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
class="flex items-center text-secondaryLight flex-col p-4 justify-center"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="material-icons opacity-50 pb-2">manage_search</i>
<span class="text-xs text-center">
<i class="opacity-75 pb-2 material-icons">manage_search</i>
<span class="text-center">
{{ $t("nothing_found") }} "{{ filterText }}"
</span>
</div>
@@ -292,7 +313,7 @@ export default {
// Intented to be called by the CollectionAdd modal submit event
addNewRootCollection(name) {
if (!name) {
this.$toast.info(this.$t("invalid_collection_name"))
this.$toast.info(this.$t("collection.invalid_name"))
return
}
if (this.collectionsType.type === "my-collections") {
@@ -312,7 +333,7 @@ export default {
this.collectionsType.selectedTeam.id
)
.then(() => {
this.$toast.success(this.$t("collection_added"), {
this.$toast.success(this.$t("collection.created"), {
icon: "done",
})
})
@@ -328,7 +349,7 @@ export default {
// Intented to be called by CollectionEdit modal submit event
updateEditingCollection(newName) {
if (!newName) {
this.$toast.info(this.$t("invalid_collection_name"))
this.$toast.info(this.$t("collection.invalid_name"))
return
}
if (this.collectionsType.type === "my-collections") {
@@ -371,7 +392,7 @@ export default {
.renameCollection(this.$apollo, name, this.editingFolder.id)
.then(() => {
// Result
this.$toast.success(this.$t("folder_renamed"), {
this.$toast.success(this.$t("folder.renamed"), {
icon: "done",
})
})
@@ -486,7 +507,7 @@ export default {
})
.then(() => {
// Result
this.$toast.success(this.$t("folder_added"), {
this.$toast.success(this.$t("folder.created"), {
icon: "done",
})
this.$emit("update-team-collections")

View File

@@ -10,32 +10,24 @@
@dragend="dragging = false"
>
<span
class="
flex
justify-center
items-center
text-xs
w-10
truncate
cursor-pointer
"
class="cursor-pointer flex w-10 justify-center items-center truncate"
@click="toggleShowChildren()"
>
<i class="material-icons" :class="{ 'text-green-400': isSelected }">
<i class="material-icons" :class="{ 'text-green-500': isSelected }">
{{ getCollectionIcon }}
</i>
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="toggleShowChildren()"
>
@@ -44,8 +36,9 @@
<ButtonSecondary
v-if="doc && !selected"
v-tippy="{ theme: 'tooltip' }"
:title="$t('import')"
:title="$t('import.title')"
icon="check_box_outline_blank"
color="green"
@click.native="$emit('select-collection')"
/>
<ButtonSecondary
@@ -53,12 +46,14 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="check_box"
color="green"
@click.native="$emit('unselect-collection')"
/>
<ButtonSecondary
v-if="!doc"
v-tippy="{ theme: 'tooltip' }"
icon="create_new_folder"
:title="$t('new_folder')"
:title="$t('folder.new')"
class="group-hover:inline-flex hidden"
@click.native="
$emit('add-folder', {
@@ -84,7 +79,7 @@
</template>
<SmartItem
icon="create_new_folder"
:label="$t('new_folder')"
:label="$t('folder.new')"
@click.native="
$emit('add-folder', {
folder: collection,
@@ -103,6 +98,7 @@
/>
<SmartItem
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -111,11 +107,11 @@
/>
</tippy>
</div>
<div v-show="showChildren || isFiltered">
<div v-if="showChildren || isFiltered">
<CollectionsMyFolder
v-for="(folder, index) in collection.folders"
:key="`folder-${index}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:folder="folder"
:folder-index="index"
:folder-path="`${collectionIndex}/${index}`"
@@ -134,7 +130,7 @@
<CollectionsMyRequest
v-for="(request, index) in collection.requests"
:key="`request-${index}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:request="request"
:collection-index="collectionIndex"
:folder-index="-1"
@@ -156,25 +152,24 @@
(collection.requests == undefined || collection.requests.length === 0)
"
class="
flex
items-center
text-secondaryLight
flex-col
p-4
justify-center
ml-5
border-l border-dividerLight
flex flex-col
text-secondaryLight
ml-5
p-4
items-center
justify-center
"
>
<i class="material-icons opacity-50 pb-2">folder_open</i>
<span class="text-xs text-center">
{{ $t("collection_empty") }}
<i class="opacity-75 pb-2 material-icons">folder_open</i>
<span class="text-center">
{{ $t("empty.collection") }}
</span>
</div>
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_collection')"
:title="$t('confirm.remove_collection')"
@hide-modal="confirmRemove = false"
@resolve="removeCollection"
/>

View File

@@ -10,32 +10,24 @@
@dragend="dragging = false"
>
<span
class="
flex
justify-center
items-center
text-xs
w-10
truncate
cursor-pointer
"
class="cursor-pointer flex w-10 justify-center items-center truncate"
@click="toggleShowChildren()"
>
<i class="material-icons" :class="{ 'text-green-400': isSelected }">
<i class="material-icons" :class="{ 'text-green-500': isSelected }">
{{ getCollectionIcon }}
</i>
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="toggleShowChildren()"
>
@@ -46,7 +38,7 @@
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="create_new_folder"
:title="$t('new_folder')"
:title="$t('folder.new')"
class="group-hover:inline-flex hidden"
@click.native="$emit('add-folder', { folder, path: folderPath })"
/>
@@ -67,7 +59,7 @@
</template>
<SmartItem
icon="create_new_folder"
:label="$t('new_folder')"
:label="$t('folder.new')"
@click.native="
$emit('add-folder', { folder, path: folderPath })
$refs.options.tippy().hide()
@@ -88,6 +80,7 @@
/>
<SmartItem
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -96,11 +89,11 @@
/>
</tippy>
</div>
<div v-show="showChildren || isFiltered">
<div v-if="showChildren || isFiltered">
<CollectionsMyFolder
v-for="(subFolder, subFolderIndex) in folder.folders"
:key="`subFolder-${subFolderIndex}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:folder="subFolder"
:folder-index="subFolderIndex"
:collection-index="collectionIndex"
@@ -119,7 +112,7 @@
<CollectionsMyRequest
v-for="(request, index) in folder.requests"
:key="`request-${index}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:request="request"
:collection-index="collectionIndex"
:folder-index="folderIndex"
@@ -142,25 +135,24 @@
folder.requests.length === 0
"
class="
flex
items-center
text-secondaryLight
flex-col
p-4
justify-center
ml-5
border-l border-dividerLight
flex flex-col
text-secondaryLight
ml-5
p-4
items-center
justify-center
"
>
<i class="material-icons opacity-50 pb-2">folder_open</i>
<span class="text-xs text-center">
{{ $t("folder_empty") }}
<i class="opacity-75 pb-2 material-icons">folder_open</i>
<span class="text-center">
{{ $t("empty.folder") }}
</span>
</div>
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_folder')"
:title="$t('confirm.remove_folder')"
@hide-modal="confirmRemove = false"
@resolve="removeFolder"
/>

View File

@@ -10,15 +10,14 @@
>
<span
class="
font-mono font-bold
cursor-pointer
flex
font-mono font-bold
mx-2
w-12
justify-center
items-center
text-xs
w-12
mx-2
truncate
cursor-pointer
"
:class="getRequestLabelColor(request.method)"
@click="!doc ? selectRequest() : {}"
@@ -26,7 +25,7 @@
<i
v-if="isSelected"
class="material-icons"
:class="{ 'text-green-400': isSelected }"
:class="{ 'text-green-500': isSelected }"
>
check_circle
</i>
@@ -36,22 +35,22 @@
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="!doc ? selectRequest() : {}"
>
<span class="truncate"> {{ request.name }} </span>
</span>
<ButtonSecondary
v-if="!saveRequest"
v-if="!saveRequest && !doc"
v-tippy="{ theme: 'tooltip' }"
icon="replay"
:title="$t('restore')"
@@ -90,6 +89,7 @@
/>
<SmartItem
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -100,7 +100,7 @@
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_request')"
:title="$t('confirm.remove_request')"
@hide-modal="confirmRemove = false"
@resolve="removeRequest"
/>
@@ -108,6 +108,8 @@
</template>
<script>
import { translateToNewRequest } from "~/helpers/types/HoppRESTRequest"
import { setRESTRequest } from "~/newstore/RESTSession"
export default {
props: {
request: { type: Object, default: () => {} },
@@ -126,11 +128,11 @@ export default {
return {
dragging: false,
requestMethodLabels: {
get: "text-green-400",
post: "text-yellow-400",
put: "text-blue-400",
delete: "text-red-400",
default: "text-gray-400",
get: "text-green-500",
post: "text-yellow-500",
put: "text-blue-500",
delete: "text-red-500",
default: "text-gray-500",
},
confirmRemove: false,
}
@@ -157,8 +159,7 @@ export default {
requestIndex: this.requestIndex,
},
})
else
this.$store.commit("postwoman/selectRequest", { request: this.request })
else setRESTRequest(translateToNewRequest(this.request))
},
dragStart({ dataTransfer }) {
this.dragging = !this.dragging

View File

@@ -2,32 +2,24 @@
<div class="flex flex-col">
<div class="flex items-center group">
<span
class="
flex
justify-center
items-center
text-xs
w-10
truncate
cursor-pointer
"
class="cursor-pointer flex w-10 justify-center items-center truncate"
@click="toggleShowChildren()"
>
<i class="material-icons" :class="{ 'text-green-400': isSelected }">
<i class="material-icons" :class="{ 'text-green-500': isSelected }">
{{ getCollectionIcon }}
</i>
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="toggleShowChildren()"
>
@@ -36,8 +28,9 @@
<ButtonSecondary
v-if="doc && !selected"
v-tippy="{ theme: 'tooltip' }"
:title="$t('import')"
:title="$t('import.title')"
icon="check_box_outline_blank"
color="green"
@click.native="$emit('select-collection')"
/>
<ButtonSecondary
@@ -45,13 +38,14 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="check_box"
color="green"
@click.native="$emit('unselect-collection')"
/>
<ButtonSecondary
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
v-tippy="{ theme: 'tooltip' }"
icon="create_new_folder"
:title="$t('new_folder')"
:title="$t('folder.new')"
class="group-hover:inline-flex hidden"
@click.native="
$emit('add-folder', {
@@ -79,7 +73,7 @@
<SmartItem
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
icon="create_new_folder"
:label="$t('new_folder')"
:label="$t('folder.new')"
@click.native="
$emit('add-folder', {
folder: collection,
@@ -100,6 +94,7 @@
<SmartItem
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -108,11 +103,11 @@
/>
</tippy>
</div>
<div v-show="showChildren || isFiltered">
<div v-if="showChildren || isFiltered">
<CollectionsTeamsFolder
v-for="(folder, index) in collection.children"
:key="`folder-${folder}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:folder="folder"
:folder-index="index"
:folder-path="`${collectionIndex}/${index}`"
@@ -132,7 +127,7 @@
<CollectionsTeamsRequest
v-for="(request, index) in collection.requests"
:key="`request-${index}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:request="request.request"
:collection-index="collectionIndex"
:folder-index="-1"
@@ -153,25 +148,24 @@
(collection.requests == undefined || collection.requests.length === 0)
"
class="
flex
items-center
text-secondaryLight
flex-col
p-4
justify-center
ml-5
border-l border-dividerLight
flex flex-col
text-secondaryLight
ml-5
p-4
items-center
justify-center
"
>
<i class="material-icons opacity-50 pb-2">folder_open</i>
<span class="text-xs text-center">
{{ $t("collection_empty") }}
<i class="opacity-75 pb-2 material-icons">folder_open</i>
<span class="text-center">
{{ $t("empty.collection") }}
</span>
</div>
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_collection')"
:title="$t('confirm.remove_collection')"
@hide-modal="confirmRemove = false"
@resolve="removeCollection"
/>

View File

@@ -2,32 +2,24 @@
<div class="flex flex-col">
<div class="flex items-center group">
<span
class="
flex
justify-center
items-center
text-xs
w-10
truncate
cursor-pointer
"
class="cursor-pointer flex w-10 justify-center items-center truncate"
@click="toggleShowChildren()"
>
<i class="material-icons" :class="{ 'text-green-400': isSelected }">
<i class="material-icons" :class="{ 'text-green-500': isSelected }">
{{ getCollectionIcon }}
</i>
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="toggleShowChildren()"
>
@@ -39,7 +31,7 @@
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
v-tippy="{ theme: 'tooltip' }"
icon="create_new_folder"
:title="$t('new_folder')"
:title="$t('folder.new')"
class="group-hover:inline-flex hidden"
@click.native="$emit('add-folder', { folder, path: folderPath })"
/>
@@ -62,7 +54,7 @@
<SmartItem
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
icon="create_new_folder"
:label="$t('new_folder')"
:label="$t('folder.new')"
@click.native="
$emit('add-folder', { folder, path: folderPath })
$refs.options.tippy().hide()
@@ -85,6 +77,7 @@
<SmartItem
v-if="collectionsType.selectedTeam.myRole !== 'VIEWER'"
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -93,11 +86,11 @@
/>
</tippy>
</div>
<div v-show="showChildren || isFiltered">
<div v-if="showChildren || isFiltered">
<CollectionsTeamsFolder
v-for="(subFolder, subFolderIndex) in folder.children"
:key="`subFolder-${subFolderIndex}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:folder="subFolder"
:folder-index="subFolderIndex"
:collection-index="collectionIndex"
@@ -117,7 +110,7 @@
<CollectionsTeamsRequest
v-for="(request, index) in folder.requests"
:key="`request-${index}`"
class="ml-5 border-l border-dividerLight"
class="border-l border-dividerLight ml-5"
:request="request.request"
:collection-index="collectionIndex"
:folder-index="folderIndex"
@@ -137,25 +130,24 @@
(folder.requests == undefined || folder.requests.length === 0)
"
class="
flex
items-center
text-secondaryLight
flex-col
p-4
justify-center
ml-5
border-l border-dividerLight
flex flex-col
text-secondaryLight
ml-5
p-4
items-center
justify-center
"
>
<i class="material-icons opacity-50 pb-2">folder_open</i>
<span class="text-xs text-center">
{{ $t("folder_empty") }}
<i class="opacity-75 pb-2 material-icons">folder_open</i>
<span class="text-center">
{{ $t("empty.folder") }}
</span>
</div>
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_folder')"
:title="$t('confirm.remove_folder')"
@hide-modal="confirmRemove = false"
@resolve="removeFolder"
/>

View File

@@ -3,15 +3,14 @@
<div class="flex items-center group">
<span
class="
font-mono font-bold
cursor-pointer
flex
font-mono font-bold
mx-2
w-12
justify-center
items-center
text-xs
w-12
mx-2
truncate
cursor-pointer
"
:class="getRequestLabelColor(request.method)"
@click="!doc ? selectRequest() : {}"
@@ -19,7 +18,7 @@
<i
v-if="isSelected"
class="material-icons"
:class="{ 'text-green-400': isSelected }"
:class="{ 'text-green-500': isSelected }"
>
check_circle
</i>
@@ -29,22 +28,22 @@
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="!doc ? selectRequest() : {}"
>
<span class="truncate"> {{ request.name }} </span>
</span>
<ButtonSecondary
v-if="!saveRequest"
v-if="!saveRequest && !doc"
v-tippy="{ theme: 'tooltip' }"
icon="replay"
:title="$t('restore')"
@@ -83,6 +82,7 @@
/>
<SmartItem
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -93,7 +93,7 @@
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_request')"
:title="$t('confirm.remove_request')"
@hide-modal="confirmRemove = false"
@resolve="removeRequest"
/>
@@ -101,6 +101,8 @@
</template>
<script>
import { translateToNewRequest } from "~/helpers/types/HoppRESTRequest"
import { setRESTRequest } from "~/newstore/RESTSession"
export default {
props: {
request: { type: Object, default: () => {} },
@@ -117,11 +119,11 @@ export default {
data() {
return {
requestMethodLabels: {
get: "text-green-400",
post: "text-yellow-400",
put: "text-blue-400",
delete: "text-red-400",
default: "text-gray-400",
get: "text-green-500",
post: "text-yellow-500",
put: "text-blue-500",
delete: "text-red-500",
default: "text-gray-500",
},
confirmRemove: false,
}
@@ -144,8 +146,7 @@ export default {
requestID: this.requestIndex,
},
})
else
this.$store.commit("postwoman/selectRequest", { request: this.request })
else setRESTRequest(translateToNewRequest(this.request))
},
removeRequest() {
this.$emit("remove-request", {

View File

@@ -28,7 +28,7 @@ export default {
}
</script>
<style lang="scss" scoped>
<style scoped lang="scss">
.collection {
@apply flex flex-col flex-1;
@apply justify-center;

View File

@@ -24,13 +24,13 @@ export default {
}
</script>
<style lang="scss" scoped>
<style scoped lang="scss">
.folder {
@apply flex flex-col flex-1;
@apply justify-center;
@apply p-4;
@apply border-l border-divider;
@apply mt-4;
@apply border-l border-divider;
.material-icons {
@apply mr-4;

View File

@@ -120,7 +120,7 @@ export default {
@apply p-4;
@apply mt-4;
@apply border border-divider;
@apply rounded-lg;
@apply rounded;
h4 {
@apply mt-4;
@@ -137,7 +137,7 @@ export default {
@apply p-4;
@apply m-0;
@apply text-secondaryLight;
@apply border-b border-dashed border-divider;
@apply border-b border-divider;
&:last-child {
@apply border-b-0;

View File

@@ -7,8 +7,8 @@
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="selectLabelEnvAdd" class="px-4 font-semibold pb-4 text-xs">
<div class="flex flex-col px-2">
<label for="selectLabelEnvAdd" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input

View File

@@ -7,8 +7,8 @@
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="selectLabelEnvEdit" class="px-4 font-semibold pb-4 text-xs">
<div class="flex flex-col px-2">
<label for="selectLabelEnvEdit" class="font-semibold px-4 pb-4">
{{ $t("label") }}
</label>
<input
@@ -19,11 +19,8 @@
:placeholder="editingEnvironment.name"
@keyup.enter="saveEnvironment"
/>
<div class="flex justify-between items-center flex-1">
<label
for="variableList"
class="px-4 pt-4 font-semibold pb-4 text-xs"
>
<div class="flex flex-1 justify-between items-center">
<label for="variableList" class="font-semibold px-4 pt-4 pb-4">
{{ $t("env_variable_list") }}
</label>
<div>
@@ -36,49 +33,48 @@
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="add"
:title="$t('add_new')"
:title="$t('add.new')"
@click.native="addEnvironmentVariable"
/>
</div>
</div>
<div class="border-2 border-divider">
<div class="border-divider border rounded">
<div
v-for="(variable, index) in vars"
:key="`variable-${index}`"
class="
divide-x divide-dividerLight
border-b border-dividerLight
flex
border-b
divide-x
border-divider
divide-dashed divide-divider
"
:class="{ 'border-t': index == 0 }"
>
<input
v-model="variable.key"
class="
px-4
py-3
text-xs
flex flex-1
font-semibold
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none
"
:placeholder="$t('variable_count', { count: index + 1 })"
:placeholder="$t('count.variable', { count: index + 1 })"
:name="'param' + index"
/>
<input
v-model="variable.value"
class="
px-4
py-3
text-xs
flex flex-1
font-semibold
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none
"
:placeholder="$t('value_count', { count: index + 1 })"
:placeholder="$t('count.value', { count: index + 1 })"
:name="'value' + index"
/>
<div>
@@ -87,10 +83,31 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="delete"
color="red"
@click.native="removeEnvironmentVariable(index)"
/>
</div>
</div>
<div
v-if="vars.length === 0"
class="
flex flex-col
text-secondaryLight
p-4
items-center
justify-center
"
>
<i class="opacity-75 pb-2 material-icons">layers</i>
<span class="text-center pb-4">
{{ $t("empty.environments") }}
</span>
<ButtonSecondary
:label="$t('add.new')"
outline
@click.native="addEnvironmentVariable"
/>
</div>
</div>
</div>
</template>

View File

@@ -1,30 +1,22 @@
<template>
<div class="flex items-center group">
<span
class="
flex
justify-center
items-center
text-xs
w-10
truncate
cursor-pointer
"
class="cursor-pointer flex w-10 justify-center items-center truncate"
@click="$emit('edit-environment')"
>
<i class="material-icons">layers</i>
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
@click="$emit('edit-environment')"
>
@@ -32,6 +24,7 @@
{{ environment.name }}
</span>
</span>
<span>
<tippy
ref="options"
interactive
@@ -57,6 +50,7 @@
/>
<SmartItem
icon="delete"
color="red"
:label="$t('delete')"
@click.native="
confirmRemove = true
@@ -64,9 +58,10 @@
"
/>
</tippy>
</span>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_environment')"
:title="$t('confirm.remove_environment')"
@hide-modal="confirmRemove = false"
@resolve="removeEnvironment"
/>

View File

@@ -22,7 +22,7 @@
</template>
<SmartItem
icon="assignment_returned"
:label="$t('import_from_gist')"
:label="$t('import.from_gist')"
@click.native="
readEnvironmentGist
$refs.options.tippy().hide()
@@ -32,9 +32,9 @@
v-tippy="{ theme: 'tooltip' }"
:title="
!currentUser
? $t('login_with_github_to') + $t('create_secret_gist')
? $t('export.require_github')
: currentUser.provider !== 'github.com'
? $t('login_with_github_to') + $t('create_secret_gist')
? $t('export.require_github')
: null
"
:disabled="
@@ -45,7 +45,7 @@
: false
"
icon="assignment_turned_in"
:label="$t('create_secret_gist')"
:label="$t('export.create_secret_gist')"
@click.native="
createEnvironmentGist
$refs.options.tippy().hide()
@@ -76,7 +76,7 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('preserve_current')"
icon="create_new_folder"
:label="$t('import_json')"
:label="$t('import.json')"
@click.native="openDialogChooseFileToImportFrom"
/>
<input
@@ -91,7 +91,7 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('download_file')"
icon="drive_file_move"
:label="$t('export_as_json')"
:label="$t('export.as_json')"
@click.native="exportJSON"
/>
</div>
@@ -142,7 +142,7 @@ export default {
}
)
.then((res) => {
this.$toast.success(this.$t("gist_created"), {
this.$toast.success(this.$t("export.gist_created"), {
icon: "done",
})
window.open(res.html_url)
@@ -155,7 +155,7 @@ export default {
})
},
async readEnvironmentGist() {
const gist = prompt(this.$t("enter_gist_url"))
const gist = prompt(this.$t("import.gist_url"))
if (!gist) return
await this.$axios
.$get(`https://api.github.com/gists/${gist.split("/").pop()}`, {
@@ -223,22 +223,22 @@ export default {
this.importFromHoppscotch(environments)
},
exportJSON() {
let text = this.environmentJson
text = text.replace(/\n/g, "\r\n")
const blob = new Blob([text], {
type: "text/json",
})
const anchor = document.createElement("a")
anchor.download = "hoppscotch-environment.json"
anchor.href = window.URL.createObjectURL(blob)
anchor.target = "_blank"
anchor.style.display = "none"
document.body.appendChild(anchor)
anchor.click()
document.body.removeChild(anchor)
const dataToWrite = this.environmentJson
const file = new Blob([dataToWrite], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
// TODO get uri from meta
a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}`
document.body.appendChild(a)
a.click()
this.$toast.success(this.$t("download_started"), {
icon: "done",
})
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
},
fileImported() {
this.$toast.info(this.$t("file_imported"), {

View File

@@ -1,20 +1,20 @@
<template>
<AppSection label="environments">
<div class="flex flex-col sticky z-10 top-10 bg-primary">
<div class="bg-primary rounded-t flex flex-col top-8 z-10 sticky">
<div class="select-wrapper">
<select
v-model="selectedEnvironmentIndex"
:disabled="environments.length == 0"
class="
flex
w-full
px-4
text-xs
py-3
focus:outline-none
border-b border-dividerLight
font-medium
bg-primaryLight
border-b border-dividerLight
flex
font-semibold font-mono
w-full
py-2
px-4
focus:outline-none
appearance-none
"
>
<option :value="-1">No environment</option>
@@ -30,7 +30,7 @@
</option>
</select>
</div>
<div class="border-b flex justify-between flex-1 border-dividerLight">
<div class="border-b border-dividerLight flex flex-1 justify-between">
<ButtonSecondary
icon="add"
:label="$t('new')"
@@ -39,7 +39,7 @@
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="import_export"
:title="$t('import_export')"
:title="$t('modal.import_export')"
@click.native="displayModalImportExport(true)"
/>
</div>
@@ -60,12 +60,17 @@
/>
<div
v-if="environments.length === 0"
class="flex items-center text-secondaryLight flex-col p-4 justify-center"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="material-icons opacity-50 pb-2">library_add</i>
<span class="text-xs text-center">
{{ $t("create_new_environment") }}
<i class="opacity-75 pb-2 material-icons">library_add</i>
<span class="text-center pb-4">
{{ $t("empty.environments") }}
</span>
<ButtonSecondary
:label="$t('add.new')"
outline
@click.native="displayModalAdd(true)"
/>
</div>
<div class="flex flex-col">
<EnvironmentsEnvironment

View File

@@ -1,5 +1,5 @@
<template>
<SmartModal v-if="show" @close="hideModal">
<SmartModal v-if="show" dialog @close="hideModal">
<template #header>
<h3 class="heading">{{ $t("login_to_hoppscotch") }}</h3>
<div>
@@ -27,17 +27,17 @@
/>
</div>
<div v-if="mode === 'email'" class="flex flex-col space-y-2">
<div class="flex items-center">
<label for="email" class="flex items-center px-4">
<i class="material-icons opacity-75">mail</i>
<div class="flex relative items-center">
<label for="email" class="flex px-4 absolute items-center">
<i class="opacity-75 material-icons">mail</i>
</label>
<input
id="email"
v-model="form.email"
class="flex flex-1 rounded px-4 py-2 outline-none"
class="input !pl-12"
type="email"
name="email"
placeholder="enter your email"
:placeholder="$t('email')"
autocomplete="email"
required
spellcheck="false"
@@ -56,30 +56,34 @@
"
type="button"
tabindex="-1"
:label="$t('send_magic_link')"
:label="$t('auth.send_magic_link')"
@click.native="signInWithEmail"
/>
</div>
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
<div class="flex justify-center max-w-md items-center flex-col">
<i class="material-icons text-accent text-4xl"> verified </i>
<div class="flex flex-col max-w-md justify-center items-center">
<i class="text-accent material-icons !text-4xl">
mark_email_unread
</i>
<h3 class="font-bold my-2 text-center text-lg">
{{ $t("we_sent_magic_link") }}
{{ $t("auth.we_sent_magic_link") }}
</h3>
<p class="text-center">
{{ $t("we_sent_magic_link_description", { email: form.email }) }}
{{
$t("auth.we_sent_magic_link_description", { email: form.email })
}}
</p>
</div>
</div>
</template>
<template #footer>
<p v-if="mode === 'sign-in'" class="text-secondaryLight text-xs">
<p v-if="mode === 'sign-in'" class="text-secondaryLight">
By signing in, you are agreeing to our
<SmartAnchor class="link" to="/index" label="Terms of Service" />
and
<SmartAnchor class="link" to="/index" label="Privacy Policy" />.
</p>
<p v-if="mode === 'email'" class="text-secondaryLight text-xs">
<p v-if="mode === 'email'" class="text-secondaryLight">
<SmartAnchor
class="link"
label="← All sign in options"
@@ -88,14 +92,18 @@
</p>
<p
v-if="mode === 'email-sent'"
class="flex flex-1 justify-between text-secondaryLight text-xs"
class="flex flex-1 text-secondaryLight justify-between"
>
<SmartAnchor
class="link"
label="← Re-enter email"
@click.native="mode = 'email'"
/>
<SmartAnchor class="link" label="Dismiss" @click.native="hideModal" />
<SmartAnchor
class="link"
:label="$t('action.dismiss')"
@click.native="hideModal"
/>
</p>
</template>
</SmartModal>
@@ -149,7 +157,7 @@ export default {
const { additionalUserInfo } = await signInUserWithGoogle()
if (additionalUserInfo.isNewUser) {
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
this.$toast.info(`${this.$t("action.turn_on")} ${this.$t("sync")}`, {
icon: "sync",
duration: null,
closeOnSwipe: false,
@@ -195,7 +203,7 @@ export default {
return
}
this.$toast.info(`${this.$t("account_exists")}`, {
this.$toast.info(`${this.$t("auth.account_exists")}`, {
icon: "vpn_key",
duration: null,
closeOnSwipe: false,
@@ -225,7 +233,7 @@ export default {
setProviderInfo(credential.providerId, credential.accessToken)
if (additionalUserInfo.isNewUser) {
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
this.$toast.info(`${this.$t("action.turn_on")} ${this.$t("sync")}`, {
icon: "sync",
duration: null,
closeOnSwipe: false,
@@ -271,7 +279,7 @@ export default {
return
}
this.$toast.info(`${this.$t("account_exists")}`, {
this.$toast.info(`${this.$t("auth.account_exists")}`, {
icon: "vpn_key",
duration: null,
closeOnSwipe: false,

View File

@@ -10,7 +10,7 @@
/>
<SmartConfirmModal
:show="confirmLogout"
:title="$t('are_you_sure_logout')"
:title="$t('confirm.logout')"
@hide-modal="confirmLogout = false"
@resolve="logout"
/>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div
class="font-semibold text-xs field-title"
class="font-semibold field-title"
:class="{ 'field-highlighted': isHighlighted }"
>
{{ fieldName }}
@@ -24,31 +24,31 @@
</div>
<div
v-if="gqlField.description"
class="py-2 text-xs text-secondaryLight field-desc"
class="text-secondaryLight py-2 field-desc"
>
{{ gqlField.description }}
</div>
<div
v-if="gqlField.isDeprecated"
class="
inline-block
px-2
py-1
my-1
text-xs text-black
bg-yellow-200
rounded
font-semibold
bg-yellow-200
my-1
text-black
py-1
px-2
inline-block
field-deprecated
"
>
{{ $t("deprecated") }}
</div>
<div v-if="fieldArgs.length > 0">
<h5 class="my-2 text-xs">Arguments:</h5>
<div class="pl-4 border-l-2 border-divider">
<h5 class="my-2">Arguments:</h5>
<div class="border-divider border-l-2 pl-4">
<div v-for="(field, index) in fieldArgs" :key="`field-${index}`">
<span class="font-semibold text-xs">
<span class="font-semibold">
{{ field.name }}:
<GraphqlTypeLink
:gql-type="field.type"
@@ -57,7 +57,7 @@
</span>
<div
v-if="field.description"
class="py-2 text-xs text-secondaryLight field-desc"
class="text-secondaryLight py-2 field-desc"
>
{{ field.description }}
</div>

View File

@@ -1,7 +1,7 @@
<template>
<div :id="`type_${gqlType.name}`" class="p-4">
<div
class="font-semibold text-xs type-title"
class="font-semibold type-title"
:class="{ 'text-accent': isHighlighted }"
>
<span v-if="isInput" class="text-accent">input </span>
@@ -9,14 +9,11 @@
<span v-else-if="isEnum" class="text-accent">enum </span>
{{ gqlType.name }}
</div>
<div
v-if="gqlType.description"
class="py-2 text-xs text-secondaryLight type-desc"
>
<div v-if="gqlType.description" class="text-secondaryLight py-2 type-desc">
{{ gqlType.description }}
</div>
<div v-if="interfaces.length > 0">
<h5 class="my-2 text-xs">Interfaces:</h5>
<h5 class="my-2">Interfaces:</h5>
<div
v-for="(gqlInterface, index) in interfaces"
:key="`gqlInterface-${index}`"
@@ -24,37 +21,37 @@
<GraphqlTypeLink
:gql-type="gqlInterface"
:jump-type-callback="jumpTypeCallback"
class="pl-4 border-l-2 border-divider"
class="border-divider border-l-2 pl-4"
/>
</div>
</div>
<div v-if="children.length > 0" class="mb-2">
<h5 class="my-2 text-xs">Children:</h5>
<h5 class="my-2">Children:</h5>
<GraphqlTypeLink
v-for="(child, index) in children"
:key="`child-${index}`"
:gql-type="child"
:jump-type-callback="jumpTypeCallback"
class="pl-4 border-l-2 border-divider"
class="border-divider border-l-2 pl-4"
/>
</div>
<div v-if="gqlType.getFields">
<h5 class="my-2 text-xs">Fields:</h5>
<h5 class="my-2">Fields:</h5>
<GraphqlField
v-for="(field, index) in gqlType.getFields()"
:key="`field-${index}`"
class="pl-4 border-l-2 border-divider"
class="border-divider border-l-2 pl-4"
:gql-field="field"
:is-highlighted="isFieldHighlighted({ field })"
:jump-type-callback="jumpTypeCallback"
/>
</div>
<div v-if="isEnum">
<h5 class="my-2 text-xs">Values:</h5>
<h5 class="my-2">Values:</h5>
<div
v-for="(value, index) in gqlType.getValues()"
:key="`value-${index}`"
class="pl-4 border-l-2 border-divider"
class="border-divider border-l-2 pl-4"
v-text="value.name"
></div>
</div>

View File

@@ -1,7 +1,7 @@
<template>
<span
:class="isScalar ? 'text-secondaryLight' : 'cursor-pointer text-accent'"
class="font-mono text-xs"
class="font-mono"
@click="jumpToType"
>
{{ typeString }}

View File

@@ -3,16 +3,16 @@
<div class="flex items-center">
<span
class="
py-3
cursor-pointer
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
pl-4
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
font-semibold
group-hover:text-secondaryDark
"
data-testid="restore_history_entry"
@click="$emit('use-entry')"
@@ -24,22 +24,22 @@
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="delete"
color="red"
:title="$t('delete')"
class="group-hover:inline-flex hidden"
color="red"
data-testid="delete_history_entry"
@click.native="$emit('delete-entry')"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="expand ? $t('hide_more') : $t('show_more')"
:title="expand ? $t('hide.more') : $t('show.more')"
:icon="expand ? 'unfold_less' : 'unfold_more'"
class="group-hover:inline-flex hidden"
@click.native="expand = !expand"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="!entry.star ? $t('add_star') : $t('remove_star')"
:title="!entry.star ? $t('add.star') : $t('remove.star')"
:icon="entry.star ? 'star' : 'star_border'"
color="yellow"
:class="{ 'group-hover:inline-flex hidden': !entry.star }"
@@ -51,14 +51,7 @@
<span
v-for="(line, index) in query"
:key="`line-${index}`"
class="
text-xs
cursor-pointer
truncate
px-4
font-mono
text-secondaryLight
"
class="cursor-pointer font-mono text-secondaryLight px-4 truncate"
data-testid="restore_history_entry"
@click="$emit('use-entry')"
>

View File

@@ -2,28 +2,32 @@
<AppSection label="history">
<div
class="
flex
sticky
z-10
bg-primaryLight
top-10
border-b border-dividerLight
flex
top-8
z-10
sticky
"
>
<div class="search-wrapper">
<input
v-model="filterText"
type="search"
class="
px-4
py-3
text-xs
flex flex-1
font-medium
bg-primaryLight
flex
font-semibold font-mono
w-full
py-2
pr-2
pl-9
focus:outline-none
truncate
"
:placeholder="$t('search')"
/>
</div>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
data-testid="clear_history"
@@ -56,25 +60,25 @@
</div>
<div
v-if="!(filteredHistory.length !== 0 || history.length === 0)"
class="flex items-center text-secondaryLight flex-col p-4 justify-center"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="material-icons opacity-50 pb-2">manage_search</i>
<span class="text-xs text-center">
<i class="opacity-75 pb-2 material-icons">manage_search</i>
<span class="text-center">
{{ $t("nothing_found") }} "{{ filterText }}"
</span>
</div>
<div
v-if="history.length === 0"
class="flex items-center text-secondaryLight flex-col p-4 justify-center"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="material-icons opacity-50 pb-2">schedule</i>
<span class="text-xs text-center">
{{ $t("history_empty") }}
<i class="opacity-75 pb-2 material-icons">schedule</i>
<span class="text-center">
{{ $t("empty.history") }}
</span>
</div>
<SmartConfirmModal
:show="confirmRemove"
:title="$t('are_you_sure_remove_history')"
:title="$t('confirm.remove_history')"
@hide-modal="confirmRemove = false"
@resolve="clearHistory"
/>

View File

@@ -2,15 +2,14 @@
<div class="flex items-center group">
<span
class="
font-mono font-bold
cursor-pointer
flex
font-mono font-bold
mx-2
w-12
justify-center
items-center
text-xs
w-12
mx-2
truncate
cursor-pointer
"
:class="entryStatus.className"
data-testid="restore_history_entry"
@@ -21,15 +20,15 @@
</span>
<span
class="
py-3
cursor-pointer
pr-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
"
data-testid="restore_history_entry"
:title="duration"
@@ -42,15 +41,15 @@
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
icon="delete"
color="red"
:title="$t('delete')"
class="group-hover:inline-flex hidden"
color="red"
data-testid="delete_history_entry"
@click.native="$emit('delete-entry')"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="!entry.star ? $t('add_star') : $t('remove_star')"
:title="!entry.star ? $t('add.star') : $t('remove.star')"
:class="{ 'group-hover:inline-flex hidden': !entry.star }"
:icon="entry.star ? 'star' : 'star_border'"
color="yellow"

73
components/http/Body.vue Normal file
View File

@@ -0,0 +1,73 @@
<template>
<div>
<div class="flex flex-1 py-2 items-center justify-between">
<tippy
ref="contentTypeOptions"
interactive
tabindex="-1"
trigger="click"
theme="popover"
arrow
>
<template #trigger>
<div class="flex">
<span class="select-wrapper">
<input
id="contentType"
v-model="contentType"
class="
bg-primary
rounded
flex
font-semibold font-mono
w-full
py-2
px-4
transition
truncate
focus:outline-none
"
readonly
/>
</span>
</div>
</template>
<SmartItem
v-for="(contentTypeItem, index) in validContentTypes"
:key="`contentTypeItem-${index}`"
:label="contentTypeItem"
@click.native="
contentType = contentTypeItem
$refs.contentTypeOptions.tippy().hide()
"
/>
</tippy>
<SmartToggle :on="rawInput" class="px-4" @change="rawInput = !rawInput">
{{ $t("raw_input") }}
</SmartToggle>
</div>
<HttpBodyParameters v-if="!rawInput" :content-type="contentType" />
<HttpRawBody v-else :content-type="contentType" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { pluckRef } from "~/helpers/utils/composables"
import { useRESTRequestBody } from "~/newstore/RESTSession"
import { knownContentTypes } from "~/helpers/utils/contenttypes"
export default defineComponent({
setup() {
return {
contentType: pluckRef(useRESTRequestBody(), "contentType"),
rawInput: pluckRef(useRESTRequestBody(), "isRaw"),
}
},
data() {
return {
validContentTypes: Object.keys(knownContentTypes),
}
},
})
</script>

View File

@@ -2,20 +2,21 @@
<AppSection label="bodyParameters">
<div
class="
sticky
top-110px
z-10
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-24
z-10
sticky
items-center
justify-between
pl-4
border-b border-dividerLight
"
>
<label for="reqParamList" class="font-semibold text-xs">
<label for="reqParamList" class="font-semibold">
{{ $t("request_body") }}
</label>
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('clear')"
@@ -23,29 +24,24 @@
@click.native="clearContent('bodyParams', $event)"
/>
</div>
</div>
<div
v-for="(param, index) in bodyParams"
:key="`param-${index}`"
class="
flex
border-b border-dashed
divide-x
border-divider
divide-dashed divide-divider
"
class="divide-x divide-dividerLight border-b border-dividerLight flex"
:class="{ 'border-t': index == 0 }"
>
<input
class="
px-4
py-3
text-xs
flex flex-1
font-semibold
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none
"
:placeholder="$t('parameter_count', { count: index + 1 })"
:placeholder="$t('count.parameter', { count: index + 1 })"
:name="'param' + index"
:value="param.key"
autofocus
@@ -55,15 +51,15 @@
<input
v-if="!requestBodyParamIsFile(index)"
class="
px-4
py-3
text-xs
flex flex-1
font-semibold
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none
"
:placeholder="$t('value_count', { count: index + 1 })"
:placeholder="$t('count.value', { count: index + 1 })"
:name="'value' + index"
:value="param.value"
@change="
@@ -90,9 +86,9 @@
:title="
param.hasOwnProperty('active')
? param.active
? $t('turn_off')
: $t('turn_on')
: $t('turn_off')
? $t('action.turn_off')
: $t('action.turn_on')
: $t('action.turn_off')
"
:icon="
param.hasOwnProperty('active')
@@ -101,10 +97,11 @@
: 'check_box_outline_blank'
: 'check_box'
"
color="green"
@click.native="toggleActive(index, param)"
/>
</div>
<div v-if="contentType === 'multipart/form-data'">
<div>
<label for="attachment" class="p-0">
<ButtonSecondary
class="w-full"
@@ -126,6 +123,7 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="delete"
color="red"
@click.native="removeRequestBodyParam(index)"
/>
</div>

View File

@@ -5,8 +5,8 @@
<ButtonSecondary icon="close" @click.native="hideModal" />
</template>
<template #body>
<div class="px-2 flex flex-col">
<label for="requestType" class="px-4 font-semibold pb-4 text-xs">
<div class="flex flex-col px-2">
<label for="requestType" class="font-semibold px-4 pb-4">
{{ $t("choose_language") }}
</label>
<div class="flex flex-1">
@@ -22,20 +22,19 @@
<template #trigger>
<span
class="
flex
w-full
px-4
text-xs
py-3
rounded-lg
font-semibold
focus:outline-none
border-b border-dividerLight
bg-primaryLight
border border-dividerLight
rounded
cursor-pointer
flex
font-semibold
w-full
py-2
px-4
focus:outline-none
"
>
{{ codegens.find((x) => x.id === requestType).name }}
{{ codegens.find((x) => x.id === codegenType).name }}
</span>
</template>
<SmartItem
@@ -43,72 +42,109 @@
:key="`gen-${index}`"
:label="gen.name"
@click.native="
requestType = gen.id
codegenType = gen.id
$refs.options.tippy().hide()
"
/>
</tippy>
</span>
</div>
<div class="flex justify-between flex-1">
<label
for="generatedCode"
class="px-4 pt-4 font-semibold pb-4 text-xs"
>
<div class="flex flex-1 justify-between">
<label for="generatedCode" class="font-semibold px-4 pt-4 pb-4">
{{ $t("generated_code") }}
</label>
<ButtonSecondary
ref="copyRequestCode"
v-tippy="{ theme: 'tooltip' }"
:title="$t('copy_code')"
:icon="copyIcon"
@click.native="copyRequestCode"
/>
</div>
<SmartAceEditor
v-if="requestType"
v-if="codegenType"
ref="generatedCode"
:value="requestCode"
:lang="codegens.find((x) => x.id === requestType).language"
:lang="codegens.find((x) => x.id === codegenType).language"
:options="{
maxLines: '10',
minLines: '10',
fontSize: '14px',
maxLines: 16,
minLines: 8,
fontSize: '12px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
useWorker: false,
}"
styles="rounded-lg"
styles="rounded"
/>
</div>
</template>
<template #footer>
<ButtonPrimary
ref="copyRequestCode"
:label="$t('action.copy')"
:icon="copyIcon"
@click.native="copyRequestCode"
/>
<ButtonSecondary
:label="$t('action.dismiss')"
@click.native="hideModal"
/>
</template>
</SmartModal>
</template>
<script>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { codegens } from "~/helpers/codegen/codegen"
import { getRESTRequest } from "~/newstore/RESTSession"
import { getEffectiveRESTRequest } from "~/helpers/utils/EffectiveURL"
import { getCurrentEnvironment } from "~/newstore/environments"
import { copyToClipboard } from "~/helpers/utils/clipboard"
export default {
export default defineComponent({
props: {
show: Boolean,
requestCode: { type: String, default: null },
requestTypeProp: { type: String, default: "curl" },
},
data() {
return {
codegens,
copyIcon: "content_copy",
request: getRESTRequest(),
codegenType: "curl",
}
},
computed: {
requestType: {
get() {
return this.requestTypeProp
requestCode(): string {
const effectiveRequest = getEffectiveRESTRequest(
this.request,
getCurrentEnvironment()
)
const urlObj = new URL(effectiveRequest.effectiveFinalURL)
const baseURL = urlObj.origin
const path = urlObj.pathname
// TODO: Solidify
return codegens
.find((x) => x.id === this.codegenType)!
.generator({
auth: "None",
httpUser: null,
httpPassword: null,
method: effectiveRequest.method,
url: baseURL,
pathName: path,
queryString: urlObj.searchParams.toString(),
bearerToken: null,
headers: effectiveRequest.effectiveFinalHeaders,
rawInput: null,
rawParams: null,
rawRequestBody: "",
contentType: effectiveRequest.effectiveFinalHeaders.find(
(x) => x.key === "content-type"
),
})
},
set(val) {
this.$emit("set-request-type", val)
},
watch: {
show(goingToShow) {
if (goingToShow) {
this.request = getRESTRequest()
}
},
},
methods: {
@@ -119,15 +155,13 @@ export default {
this.$emit("handle-import")
},
copyRequestCode() {
this.$refs.generatedCode.editor.selectAll()
this.$refs.generatedCode.editor.focus()
document.execCommand("copy")
copyToClipboard(this.requestCode)
this.copyIcon = "done"
this.$toast.success(this.$t("copied_to_clipboard"), {
this.$toast.success(this.$t("copied_to_clipboard").toString(), {
icon: "done",
})
setTimeout(() => (this.copyIcon = "content_copy"), 1000)
},
},
}
})
</script>

View File

@@ -2,21 +2,21 @@
<AppSection label="headers">
<div
class="
sticky
top-110px
z-10
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-24
z-10
sticky
items-center
justify-between
pl-4
border-b border-dividerLight
"
>
<label for="headerList" class="font-semibold text-xs">
<label for="headerList" class="font-semibold">
{{ $t("header_list") }}
</label>
<div>
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('clear')"
@@ -25,7 +25,7 @@
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('add_new')"
:title="$t('add.new')"
icon="add"
@click.native="addHeader"
/>
@@ -34,21 +34,24 @@
<div
v-for="(header, index) in headers$"
:key="`header-${index}`"
class="
flex
border-b border-dashed
divide-x
border-divider
divide-dashed divide-divider
"
class="divide-x divide-dividerLight border-b border-dividerLight flex"
:class="{ 'border-t': index == 0 }"
>
<SmartAutoComplete
:placeholder="$t('header_count', { count: index + 1 })"
:placeholder="$t('count.header', { count: index + 1 })"
:source="commonHeaders"
:spellcheck="false"
:value="header.key"
autofocus
styles="
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-1
px-4
focus:outline-none
"
@input="
updateHeader(index, {
key: $event,
@@ -57,17 +60,39 @@
})
"
/>
<input
class="
px-4
py-3
text-xs
flex flex-1
font-semibold
<SmartEnvInput
v-if="EXPERIMENTAL_URL_BAR_ENABLED"
v-model="header.value"
:placeholder="$t('count.value', { count: index + 1 })"
styles="
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-1
px-4
focus:outline-none
"
:placeholder="$t('value_count', { count: index + 1 })"
@change="
updateHeader(index, {
key: header.key,
value: $event.target.value,
active: header.active,
})
"
/>
<input
v-else
class="
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none
"
:placeholder="$t('count.value', { count: index + 1 })"
:name="'value' + index"
:value="header.value"
@change="
@@ -84,9 +109,9 @@
:title="
header.hasOwnProperty('active')
? header.active
? $t('turn_off')
: $t('turn_on')
: $t('turn_off')
? $t('action.turn_off')
: $t('action.turn_on')
: $t('action.turn_off')
"
:icon="
header.hasOwnProperty('active')
@@ -95,6 +120,7 @@
: 'check_box_outline_blank'
: 'check_box'
"
color="green"
@click.native="
updateHeader(index, {
key: header.key,
@@ -109,10 +135,25 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="delete"
color="red"
@click.native="deleteHeader(index)"
/>
</div>
</div>
<div
v-if="headers$.length === 0"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="opacity-75 pb-2 material-icons">post_add</i>
<span class="text-center pb-4">
{{ $t("empty.headers") }}
</span>
<ButtonSecondary
outline
:label="$t('add.new')"
@click.native="addHeader"
/>
</div>
</AppSection>
</template>
@@ -126,30 +167,37 @@ import {
} from "~/newstore/RESTSession"
import { commonHeaders } from "~/helpers/headers"
import { getSettingSubject } from "~/newstore/settings"
export default {
data() {
return {
commonHeaders,
headers$: [],
}
},
subscriptions() {
return {
headers$: restHeaders$,
EXPERIMENTAL_URL_BAR_ENABLED: getSettingSubject(
"EXPERIMENTAL_URL_BAR_ENABLED"
),
}
},
// watch: {
// headers: {
// handler(newValue) {
// if (
// newValue[newValue.length - 1]?.key !== "" ||
// newValue[newValue.length - 1]?.value !== ""
// )
// this.addRequestHeader()
// },
// deep: true,
// },
// },
watch: {
headers$: {
handler(newValue) {
console.log("changed")
if (
(newValue[newValue.length - 1]?.key !== "" ||
newValue[newValue.length - 1]?.value !== "") &&
newValue.length
)
this.addHeader()
},
deep: true,
},
},
mounted() {
if (!this.headers$?.length) {
this.addHeader()

View File

@@ -1,41 +1,134 @@
<template>
<SmartModal v-if="show" @close="hideModal">
<template #header>
<h3 class="heading">{{ $t("import_curl") }}</h3>
<h3 class="heading">{{ $t("import.curl") }}</h3>
<div>
<ButtonSecondary icon="close" @click.native="hideModal" />
</div>
</template>
<template #body>
<div class="flex flex-col px-2">
<textarea
id="import-curl"
v-model="curl"
class="textarea"
autofocus
rows="8"
:placeholder="$t('enter_curl')"
></textarea>
</div>
</template>
<template #footer>
<span>
<ButtonPrimary :label="$t('import')" @click.native="handleImport" />
<ButtonPrimary
:label="$t('import.title')"
@click.native="handleImport"
/>
<ButtonSecondary :label="$t('cancel')" @click.native="hideModal" />
</span>
</template>
</SmartModal>
</template>
<script>
export default {
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import parseCurlCommand from "~/helpers/curlparser"
import {
HoppRESTHeader,
HoppRESTParam,
makeRESTRequest,
} from "~/helpers/types/HoppRESTRequest"
import { setRESTRequest } from "~/newstore/RESTSession"
export default defineComponent({
props: {
show: Boolean,
},
emits: ["hide-modal"],
data() {
return {
curl: "",
}
},
methods: {
hideModal() {
this.$emit("hide-modal")
},
handleImport() {
this.$emit("handle-import")
const text = this.curl
try {
const parsedCurl = parseCurlCommand(text)
const { origin, pathname } = new URL(
parsedCurl.url.replace(/"/g, "").replace(/'/g, "")
)
const endpoint = origin + pathname
const headers: HoppRESTHeader[] = []
const params: HoppRESTParam[] = []
if (parsedCurl.query) {
for (const key of Object.keys(parsedCurl.query)) {
const val = parsedCurl.query[key]!
if (Array.isArray(val)) {
val.forEach((value) => {
params.push({
key,
value,
active: true,
})
})
} else {
params.push({
key,
value: val!,
active: true,
})
}
}
}
if (parsedCurl.headers) {
for (const key of Object.keys(parsedCurl.headers)) {
headers.push({
key,
value: parsedCurl.headers[key],
active: true,
})
}
}
const method = parsedCurl.method.toUpperCase()
// let rawInput = false
// let rawParams: any | null = null
// if (parsedCurl.data) {
// rawInput = true
// rawParams = parsedCurl.data
// }
this.showCurlImportModal = false
setRESTRequest(
makeRESTRequest({
name: "Untitled request",
endpoint,
method,
params,
headers,
preRequestScript: "",
testScript: "",
body: {
contentType: "application/json",
body: "",
isRaw: false,
},
})
)
} catch (error) {
this.showCurlImportModal = false
this.$toast.error(this.$t("curl_invalid_format").toString(), {
icon: "error",
})
}
this.hideModal()
},
},
}
})
</script>

View File

@@ -2,21 +2,21 @@
<AppSection label="parameters">
<div
class="
sticky
top-110px
z-10
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-24
z-10
sticky
items-center
justify-between
pl-4
border-b border-dividerLight
"
>
<label for="paramList" class="font-semibold text-xs">
<label class="font-semibold">
{{ $t("parameter_list") }}
</label>
<div>
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('clear_all')"
@@ -25,7 +25,7 @@
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('add_new')"
:title="$t('add.new')"
icon="add"
@click.native="addParam"
/>
@@ -34,26 +34,42 @@
<div
v-for="(param, index) in params$"
:key="`param-${index}`"
class="
flex
border-b border-dashed
divide-x
border-divider
divide-dashed divide-divider
"
class="divide-x divide-dividerLight border-b border-dividerLight flex"
:class="{ 'border-t': index == 0 }"
>
<input
class="
px-4
py-3
text-xs
flex flex-1
font-semibold
<SmartEnvInput
v-if="EXPERIMENTAL_URL_BAR_ENABLED"
v-model="param.key"
:placeholder="$t('count.parameter', { count: index + 1 })"
styles="
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-1
px-4
focus:outline-none
"
:placeholder="$t('parameter_count', { count: index + 1 })"
@change="
updateParam(index, {
key: $event.target.value,
value: param.value,
active: param.active,
})
"
/>
<input
v-else
class="
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none
"
:placeholder="$t('count.parameter', { count: index + 1 })"
:name="'param' + index"
:value="param.key"
autofocus
@@ -65,17 +81,39 @@
})
"
/>
<input
class="
px-4
py-3
text-xs
flex flex-1
font-semibold
<SmartEnvInput
v-if="EXPERIMENTAL_URL_BAR_ENABLED"
v-model="param.value"
:placeholder="$t('count.value', { count: index + 1 })"
styles="
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-1
px-4
focus:outline-none
"
:placeholder="$t('value_count', { count: index + 1 })"
@change="
updateParam(index, {
key: param.key,
value: $event.target.value,
active: param.active,
})
"
/>
<input
v-else
class="
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none
"
:placeholder="$t('count.value', { count: index + 1 })"
:name="'value' + index"
:value="param.value"
@change="
@@ -92,9 +130,9 @@
:title="
param.hasOwnProperty('active')
? param.active
? $t('turn_off')
: $t('turn_on')
: $t('turn_off')
? $t('action.turn_off')
: $t('action.turn_on')
: $t('action.turn_off')
"
:icon="
param.hasOwnProperty('active')
@@ -103,6 +141,7 @@
: 'check_box_outline_blank'
: 'check_box'
"
color="green"
@click.native="
updateParam(index, {
key: param.key,
@@ -117,10 +156,25 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="delete"
color="red"
@click.native="deleteParam(index)"
/>
</div>
</div>
<div
v-if="params$.length === 0"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="opacity-75 pb-2 material-icons">post_add</i>
<span class="text-center pb-4">
{{ $t("empty.parameters") }}
</span>
<ButtonSecondary
:label="$t('add.new')"
outline
@click.native="addParam"
/>
</div>
</AppSection>
</template>
@@ -132,6 +186,7 @@ import {
deleteRESTParam,
deleteAllRESTParams,
} from "~/newstore/RESTSession"
import { getSettingSubject } from "~/newstore/settings"
export default {
data() {
@@ -142,20 +197,24 @@ export default {
subscriptions() {
return {
params$: restParams$,
EXPERIMENTAL_URL_BAR_ENABLED: getSettingSubject(
"EXPERIMENTAL_URL_BAR_ENABLED"
),
}
},
// watch: {
// params$: {
// handler(newValue) {
// if (
// newValue[newValue.length - 1]?.key !== "" ||
// newValue[newValue.length - 1]?.value !== ""
// )
// this.addParam()
// },
// deep: true,
// },
// },
watch: {
params$: {
handler(newValue) {
if (
(newValue[newValue.length - 1]?.key !== "" ||
newValue[newValue.length - 1]?.value !== "") &&
newValue.length
)
this.addParam()
},
deep: true,
},
},
mounted() {
if (!this.params$?.length) {
this.addParam()

View File

@@ -0,0 +1,53 @@
<template>
<AppSection label="preRequest">
<div
class="
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-24
z-10
sticky
items-center
justify-between
"
>
<label class="font-semibold">
{{ $t("javascript_code") }}
</label>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://github.com/hoppscotch/hoppscotch/wiki/Pre-Request-Scripts"
blank
:title="$t('wiki')"
icon="help_outline"
/>
</div>
<SmartJsEditor
v-model="preRequestScript"
:options="{
maxLines: Infinity,
minLines: 16,
fontSize: '12px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
complete-mode="pre"
/>
</AppSection>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { usePreRequestScript } from "~/newstore/RESTSession"
export default defineComponent({
setup() {
return {
preRequestScript: usePreRequestScript(),
}
},
})
</script>

View File

@@ -2,23 +2,23 @@
<div>
<div
class="
sticky
top-110px
z-10
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-24
z-10
sticky
items-center
justify-between
pl-4
border-b border-dividerLight
"
>
<label for="rawBody" class="font-semibold text-xs">
<label for="rawBody" class="font-semibold">
{{ $t("raw_request_body") }}
</label>
<div>
<div class="flex">
<ButtonSecondary
v-if="rawInput && contentType.endsWith('json')"
v-if="contentType.endsWith('json')"
ref="prettifyRequest"
v-tippy="{ theme: 'tooltip' }"
:title="$t('prettify_body')"
@@ -28,7 +28,7 @@
<label for="payload">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('import_json')"
:title="$t('import.json')"
icon="post_add"
@click.native="$refs.payload.click()"
/>
@@ -53,9 +53,9 @@
v-model="rawParamsBody"
:lang="rawInputEditorLang"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '14px',
maxLines: 16,
minLines: 8,
fontSize: '12px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
@@ -66,43 +66,39 @@
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import { getEditorLangForMimeType } from "~/helpers/editorutils"
import { pluckRef } from "~/helpers/utils/composables"
import { useRESTRequestBody } from "~/newstore/RESTSession"
export default {
export default defineComponent({
props: {
rawParams: { type: String, default: null },
contentType: { type: String, default: null },
rawInput: { type: Boolean, default: false },
contentType: {
type: String,
required: true,
},
data() {
},
setup() {
return {
rawParamsBody: pluckRef(useRESTRequestBody(), "body"),
prettifyIcon: "photo_filter",
}
},
computed: {
rawParamsBody: {
get() {
return this.rawParams
},
set(value) {
this.$emit("update-raw-body", value)
},
},
rawInputEditorLang() {
return getEditorLangForMimeType(this.contentType)
},
},
methods: {
clearContent(bodyParams, $event) {
this.$emit("clear-content", bodyParams, $event)
clearContent() {
this.rawParamsBody = ""
},
uploadPayload() {
this.$emit("update-raw-input", true)
const file = this.$refs.payload.files[0]
if (file !== undefined && file !== null) {
const reader = new FileReader()
reader.onload = ({ target }) => {
this.$emit("update-raw-body", target.result)
this.rawParamsBody = target.result
}
reader.readAsText(file)
this.$toast.info(this.$t("file_imported"), {
@@ -128,5 +124,5 @@ export default {
}
},
},
}
})
</script>

View File

@@ -1,9 +1,9 @@
<template>
<div class="sticky top-0 z-10 bg-primary flex p-4">
<div class="bg-primary flex p-4 top-0 z-10 sticky">
<div class="relative inline-flex">
<span class="select-wrapper">
<tippy
ref="options"
ref="methodOptions"
interactive
tabindex="-1"
trigger="click"
@@ -14,23 +14,23 @@
<input
id="method"
class="
flex
rounded-l-lg
bg-primaryLight
font-mono
w-32
px-4
py-2
truncate
text-secondaryDark
font-semibold
border border-divider
transition
focus:outline-none focus:border-accent
rounded-l
cursor-pointer
flex
font-semibold font-mono
h-8
text-secondaryDark
py-1
px-4
transition
w-28
truncate
focus:outline-none focus:border-accent
"
:value="newMethod$"
autofocus
:value="newMethod"
readonly
/>
</template>
<SmartItem
@@ -38,79 +38,64 @@
:key="`method-${index}`"
:label="method"
class="font-mono"
@click.native="
updateMethod(method)
$refs.options.tippy().hide()
"
@click.native="onSelectMethod(method)"
/>
</tippy>
</span>
</div>
<div class="flex-1 inline-flex">
<input
id="url"
v-model="newEndpoint$"
class="
w-full
font-mono font-semibold
truncate
text-secondaryDark
px-4
py-2
border border-divider
<SmartEnvInput
v-if="EXPERIMENTAL_URL_BAR_ENABLED"
v-model="newEndpoint"
:placeholder="$t('url')"
styles="
bg-primaryLight
border border-divider
flex
font-semibold font-mono
flex-1
text-secondaryDark
py-1
px-4
transition
truncate
focus:outline-none focus:border-accent
"
@enter="newSendRequest()"
/>
<input
v-else
id="url"
v-model="newEndpoint"
class="
bg-primaryLight
border border-divider
flex
font-semibold font-mono
flex-1
text-secondaryDark
py-1
px-4
transition
truncate
focus:outline-none focus:border-accent
"
name="url"
type="text"
spellcheck="false"
:placeholder="$t('url')"
autofocus
@keyup.enter="newSendRequest()"
/>
<!-- <SmartUrlField v-else v-model="uri" /> -->
</div>
<div class="flex">
<span
<ButtonPrimary
id="send"
class="
px-4
py-2
border border-accent
font-mono
flex
items-center
truncate
font-semibold
bg-accent
text-white
cursor-pointer
dark:text-accentDark
"
@click="newSendRequest"
>
{{ $t("send") }}
</span>
<!-- <span
v-else
id="cancel"
class="
px-4
py-2
border border-accent
font-mono
flex
items-center
truncate
font-semibold
bg-accent
text-white
cursor-pointer
"
@click="cancelRequest"
>
{{ $t("cancel") }}
</span> -->
class="rounded-none min-w-20"
:label="!loading ? $t('send') : $t('cancel')"
@click.native="!loading ? newSendRequest() : cancelRequest()"
/>
<span class="inline-flex">
<tippy
ref="sendOptions"
interactive
@@ -120,40 +105,22 @@
arrow
>
<template #trigger>
<span
class="
px-1
py-2
border border-accent
font-mono
flex
items-center
justify-center
truncate
font-semibold
bg-accent
text-white
rounded-r-lg
dark:text-accentDark
"
>
<i class="material-icons">keyboard_arrow_down</i>
</span>
<ButtonPrimary class="rounded-l-none" icon="keyboard_arrow_down" />
</template>
<SmartItem
:label="$t('import_curl')"
:label="$t('import.curl')"
icon="import_export"
@click.native="
showCurlImportModal = !showCurlImportModal
$refs.sendOptions.tippy().hide()
sendOptions.tippy().hide()
"
/>
<SmartItem
:label="$t('show_code')"
:label="$t('show.code')"
icon="code"
@click.native="
showCodegenModal = !showCodegenModal
$refs.sendOptions.tippy().hide()
sendOptions.tippy().hide()
"
/>
<SmartItem
@@ -161,30 +128,20 @@
:label="$t('clear_all')"
icon="clear_all"
@click.native="
clearContent('', $event)
$refs.sendOptions.tippy().hide()
clearContent()
sendOptions.tippy().hide()
"
/>
</tippy>
<span
class="
ml-4
px-4
py-2
border border-divider
font-mono
flex
items-center
justify-center
truncate
font-semibold
rounded-l-lg
cursor-pointer
"
@click="newSendRequest"
>
Save
</span>
<ButtonSecondary
class="rounded-r-none h-8 ml-2"
:label="$t('save')"
:shortcut="[getSpecialKey(), 'S']"
outline
@click.native="showSaveRequestModal = true"
/>
<span class="inline-flex">
<tippy
ref="saveOptions"
interactive
@@ -194,72 +151,76 @@
arrow
>
<template #trigger>
<span
class="
px-1
py-2
border border-divider
font-mono
flex
items-center
justify-center
truncate
font-semibold
rounded-r-lg
"
>
<i class="material-icons">keyboard_arrow_down</i>
</span>
<ButtonSecondary
icon="keyboard_arrow_down"
outline
class="rounded-l-none h-8"
/>
</template>
<SmartItem :description="$t('token_req_name')" />
<input
id="request-name"
v-model="name"
v-model="requestName"
:placeholder="$t('request_name')"
name="request-name"
type="text"
class="input text-sm"
class="mb-2 input"
/>
<SmartItem
ref="copyRequest"
:label="$t('copy_request_link')"
:icon="navigatorShare ? 'share' : 'content_copy'"
:label="$t('request.copy_link')"
:icon="hasNavigatorShare ? 'share' : 'content_copy'"
@click.native="
copyRequest()
$refs.saveOptions.tippy().hide()
saveOptions.tippy().hide()
"
/>
<SmartItem
ref="saveRequest"
:label="$t('save_to_collections')"
:label="$t('request.save_as')"
icon="create_new_folder"
@click.native="
saveRequest()
$refs.saveOptions.tippy().hide()
showSaveRequestModal = true
saveOptions.tippy().hide()
"
/>
</tippy>
</span>
</div>
<HttpImportCurl
:show="showCurlImportModal"
@hide-modal="showCurlImportModal = false"
/>
<HttpCodegenModal
:show="showCodegenModal"
@hide-modal="showCodegenModal = false"
/>
<CollectionsSaveRequest
mode="rest"
:show="showSaveRequestModal"
@hide-modal="showSaveRequestModal = false"
/>
</div>
</template>
<script>
<script lang="ts">
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api"
import {
updateRESTResponse,
restRequest$,
restEndpoint$,
setRESTEndpoint,
restMethod$,
updateRESTMethod,
resetRESTRequest,
useRESTRequestName,
} from "~/newstore/RESTSession"
import { createRESTNetworkRequestStream } from "~/helpers/network"
import { currentEnvironment$ } from "~/newstore/environments"
import { getEffectiveRESTRequestStream } from "~/helpers/utils/EffectiveURL"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { runRESTRequest$ } from "~/helpers/RequestRunner"
import { useStreamSubscriber, useStream } from "~/helpers/utils/composables"
import { defineActionHandler } from "~/helpers/actions"
import { copyToClipboard } from "~/helpers/utils/clipboard"
import { useSetting } from "~/newstore/settings"
export default {
data() {
return {
newMethod$: "",
methods: [
const methods = [
"GET",
"HEAD",
"POST",
@@ -270,47 +231,162 @@ export default {
"TRACE",
"PATCH",
"CUSTOM",
],
name: "",
newEndpoint$: "",
showCurlImportModal: false,
showCodegenModal: false,
navigatorShare: navigator.share,
effectiveStream$: null,
}
},
subscriptions() {
return {
newMethod$: restMethod$,
newEndpoint$: restEndpoint$,
effectiveStream$: getEffectiveRESTRequestStream(
restRequest$,
currentEnvironment$
),
}
},
watch: {
newEndpoint$(newVal) {
setRESTEndpoint(newVal)
},
},
mounted() {},
methods: {
updateMethod(method) {
updateRESTMethod(method)
},
newSendRequest() {
this.$subscribeTo(
createRESTNetworkRequestStream(
this.effectiveStream$,
currentEnvironment$
),
]
export default defineComponent({
setup() {
const {
$toast,
app: { i18n },
} = useContext()
const t = i18n.t.bind(i18n)
const { subscribeToStream } = useStreamSubscriber()
const newEndpoint = useStream(restEndpoint$, "", setRESTEndpoint)
const newMethod = useStream(restMethod$, "", updateRESTMethod)
const loading = ref(false)
const showCurlImportModal = ref(false)
const showCodegenModal = ref(false)
const showSaveRequestModal = ref(false)
const hasNavigatorShare = !!navigator.share
// Template refs
//
const methodOptions = ref<any | null>(null)
const saveOptions = ref<any | null>(null)
const sendOptions = ref<any | null>(null)
const newSendRequest = () => {
loading.value = true
subscribeToStream(
runRESTRequest$(),
(responseState) => {
console.log(responseState)
if (loading.value) {
// Check exists because, loading can be set to false
// when cancelled
updateRESTResponse(responseState)
}
},
() => {
loading.value = false
},
() => {
loading.value = false
}
)
}
const cancelRequest = () => {
loading.value = false
updateRESTResponse(null)
}
const updateMethod = (method: string) => {
updateRESTMethod(method)
}
const onSelectMethod = (method: string) => {
updateMethod(method)
// Vue-tippy has no typescript support yet
methodOptions.value.tippy().hide()
}
const clearContent = () => {
resetRESTRequest()
}
const copyRequest = () => {
if (navigator.share) {
const time = new Date().toLocaleTimeString()
const date = new Date().toLocaleDateString()
navigator
.share({
title: "Hoppscotch",
text: `Hoppscotch • Open source API development ecosystem at ${time} on ${date}`,
url: window.location.href,
})
.then(() => {})
.catch(() => {})
} else {
copyToClipboard(window.location.href)
$toast.info(t("copied_to_clipboard").toString(), {
icon: "done",
})
}
}
const cycleUpMethod = () => {
const currentIndex = methods.indexOf(newMethod.value)
if (currentIndex === -1) {
// Most probs we are in CUSTOM mode
// Cycle up from CUSTOM is PATCH
updateMethod("PATCH")
} else if (currentIndex === 0) {
updateMethod("CUSTOM")
} else {
updateMethod(methods[currentIndex - 1])
}
}
const cycleDownMethod = () => {
const currentIndex = methods.indexOf(newMethod.value)
if (currentIndex === -1) {
// Most probs we are in CUSTOM mode
// Cycle down from CUSTOM is GET
updateMethod("GET")
} else if (currentIndex === methods.length - 1) {
updateMethod("GET")
} else {
updateMethod(methods[currentIndex + 1])
}
}
defineActionHandler("request.send-cancel", () => {
if (!loading.value) newSendRequest()
else cancelRequest()
})
defineActionHandler("request.reset", clearContent)
defineActionHandler("request.copy-link", copyRequest)
defineActionHandler("request.method.next", cycleDownMethod)
defineActionHandler("request.method.prev", cycleUpMethod)
defineActionHandler(
"request.save",
() => (showSaveRequestModal.value = true)
)
defineActionHandler("request.method.get", () => updateMethod("GET"))
defineActionHandler("request.method.post", () => updateMethod("POST"))
defineActionHandler("request.method.put", () => updateMethod("PUT"))
defineActionHandler("request.method.delete", () => updateMethod("DELETE"))
defineActionHandler("request.method.head", () => updateMethod("HEAD"))
return {
newEndpoint,
newMethod,
methods,
loading,
newSendRequest,
requestName: useRESTRequestName(),
getSpecialKey: getPlatformSpecialKey,
showCurlImportModal,
showCodegenModal,
showSaveRequestModal,
hasNavigatorShare,
updateMethod,
clearContent,
copyRequest,
onSelectMethod,
EXPERIMENTAL_URL_BAR_ENABLED: useSetting("EXPERIMENTAL_URL_BAR_ENABLED"),
// Template refs
methodOptions,
sendOptions,
saveOptions,
}
},
},
}
})
</script>

View File

@@ -1,20 +1,46 @@
<template>
<div class="flex sticky top-0 z-10 bg-primary items-center p-4">
<div class="bg-primary flex p-4 top-0 z-10 sticky items-center">
<div
v-if="response == null"
class="
flex flex-1
items-center
flex flex-col flex-1
text-secondaryLight
flex-col
p-4
items-center
justify-center
"
>
<i class="material-icons opacity-50 pb-2">send</i>
<span class="text-xs text-center">
{{ $t("waiting_send_req") }}
<div class="flex space-x-2 pb-8">
<div class="flex flex-col space-y-4 items-end">
<span class="flex flex-1 items-center">
{{ $t("shortcut.send_request") }}
</span>
<span class="flex flex-1 items-center">
{{ $t("shortcut.reset_request") }}
</span>
<span class="flex flex-1 items-center">
{{ $t("shortcut.show_all") }}
</span>
</div>
<div class="flex flex-col space-y-4">
<div class="flex">
<span class="shortcut-key">{{ getSpecialKey() }}</span>
<span class="shortcut-key">G</span>
</div>
<div class="flex">
<span class="shortcut-key">{{ getSpecialKey() }}</span>
<span class="shortcut-key">I</span>
</div>
<div class="flex">
<span class="shortcut-key">?</span>
</div>
</div>
</div>
<ButtonSecondary
:label="$t('documentation')"
to="https://docs.hoppscotch.io"
blank
outline
/>
</div>
<div v-else>
<i v-if="response.type === 'loading'" class="animate-spin material-icons">
@@ -29,11 +55,11 @@
<span class="text-secondaryDark"> Status: </span>
{{ response.statusCode || $t("waiting_send_req") }}
</span>
<span v-if="response.meta.responseDuration" class="text-xs">
<span v-if="response.meta && response.meta.responseDuration">
<span class="text-secondaryDark"> Time: </span>
{{ `${response.meta.responseDuration} ms` }}
</span>
<span v-if="response.meta.responseSize" class="text-xs">
<span v-if="response.meta && response.meta.responseSize">
<span class="text-secondaryDark"> Size: </span>
{{ `${response.meta.responseSize} B` }}
</span>
@@ -44,6 +70,7 @@
<script>
import findStatusGroup from "~/helpers/findStatusGroup"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
export default {
props: {
@@ -57,5 +84,19 @@ export default {
return findStatusGroup(this.response.statusCode)
},
},
methods: {
getSpecialKey: getPlatformSpecialKey,
},
}
</script>
<style lang="scss" scoped>
.shortcut-key {
@apply bg-dividerLight;
@apply rounded;
@apply ml-2;
@apply py-1;
@apply px-2;
@apply inline-flex;
}
</style>

View File

@@ -0,0 +1,63 @@
<template>
<div class="divide-y divide-dividerLight">
<div v-if="results.tests">
<HttpTestResult
v-for="(result, index) in results.tests"
:key="`result-${index}`"
:results="result"
/>
</div>
<span
v-if="results.description"
class="
border-b border-dividerLight
flex
font-semibold
text-secondaryDark
py-2
px-4
items-center
"
>
{{ results.description }}
</span>
<div v-if="results.expectResults" class="divide-y divide-dividerLight">
<div
v-for="(result, index) in results.expectResults"
:key="`result-${index}`"
class="flex py-2 px-4 items-center"
>
<i
class="mr-4 material-icons"
:class="result.status === 'pass' ? 'text-green-500' : 'text-red-500'"
>
{{ result.status === "pass" ? "check" : "close" }}
</i>
<span v-if="result.message" class="font-semibold text-secondaryDark">
{{ result.message }}
</span>
<span class="text-secondaryLight">
{{
` \xA0 — \xA0test ${
result.status === "pass" ? $t("passed") : $t("failed")
}`
}}
</span>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "@nuxtjs/composition-api"
import { HoppTestResult } from "~/helpers/types/HoppTestResult"
export default defineComponent({
props: {
results: {
type: Object as PropType<HoppTestResult>,
default: null,
},
},
})
</script>

148
components/http/Tests.vue Normal file
View File

@@ -0,0 +1,148 @@
<template>
<SmartTabs styles="sticky top-24 z-20">
<SmartTab id="script" :label="$t('test.script')" :selected="true">
<div
class="
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-32
z-10
sticky
items-center
justify-between
"
>
<label class="font-semibold">
{{ $t("javascript_code") }}
</label>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://github.com/hoppscotch/hoppscotch/wiki/Post-Request-Tests"
blank
:title="$t('wiki')"
icon="help_outline"
/>
</div>
<SmartJsEditor
v-model="testScript"
:options="{
maxLines: Infinity,
minLines: 16,
fontSize: '12px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
complete-mode="test"
/>
</SmartTab>
<SmartTab
id="results"
:label="$t('test.results')"
:info="totalTests ? totalTests.toString() : ''"
>
<div
v-if="
testResults &&
(testResults.expectResults.length || testResults.tests.length)
"
>
<div
class="
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-32
z-10
sticky
items-center
justify-between
"
>
<label class="font-semibold"> Test Report </label>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('clear')"
icon="clear_all"
@click.native="clearContent()"
/>
</div>
<div class="flex p-2 items-center">
<SmartProgressRing
class="text-red-500"
:radius="16"
:stroke="4"
:progress="(failedTests / totalTests) * 100"
/>
<div class="ml-2">
<span v-if="failedTests" class="font-semibold text-red-500">
{{ failedTests }} failing,
</span>
<span v-if="passedTests" class="font-semibold text-green-500">
{{ passedTests }} successful,
</span>
<span class="font-semibold"> out of {{ totalTests }} tests. </span>
</div>
</div>
<HttpTestResult :results="testResults" />
</div>
<div
v-else
class="
flex flex-col
text-secondaryLight
p-4
items-center
justify-center
"
>
<i class="opacity-75 pb-2 material-icons">bug_report</i>
<span class="text-center">
{{ $t("empty.tests") }}
</span>
</div>
</SmartTab>
</SmartTabs>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import {
useTestScript,
restTestResults$,
setRESTTestResults,
} from "~/newstore/RESTSession"
import { useReadonlyStream } from "~/helpers/utils/composables"
export default defineComponent({
setup() {
return {
testScript: useTestScript(),
testResults: useReadonlyStream(restTestResults$, null),
}
},
computed: {
totalTests(): number | undefined {
return this.testResults?.expectResults.length
},
failedTests(): number | undefined {
return this.testResults?.expectResults.filter(
(result: { status: string }) => result.status === "fail"
).length
},
passedTests(): number | undefined {
return this.testResults?.expectResults.filter(
(result: { status: string }) => result.status === "pass"
).length
},
},
methods: {
clearContent() {
setRESTTestResults(null)
},
},
})
</script>

View File

@@ -49,13 +49,14 @@
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="delete"
color="red"
@click.native="removeOAuthToken(index)"
/>
</li>
</div>
</ul>
<p v-if="tokens.length === 0">
{{ $t("empty") }}
{{ $t("empty.protocols") }}
</p>
</template>
</SmartModal>

View File

@@ -1,22 +1,26 @@
<template>
<div class="flex p-4 bg-primaryLight rounded">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<div
class="
bg-primaryLight
rounded
flex
grid
p-4
gap-4
grid-cols-1
md:grid-cols-2
lg:grid-cols-3
"
>
<div
v-for="(cta, index) in ctas"
:key="`cta-${index}`"
class="inline-flex flex-col p-8"
class="flex-col p-8 inline-flex"
>
<i class="text-3xl material-icons text-accent">{{ cta.icon }}</i>
<i class="text-accent text-3xl material-icons">{{ cta.icon }}</i>
<div class="flex-grow">
<h2
class="
mt-4
mb-2
text-lg
font-semibold
transition
text-secondaryDark
"
class="font-semibold mt-4 text-lg text-secondaryDark mb-2 transition"
>
{{ cta.title }}
</h2>
@@ -32,7 +36,6 @@
</div>
</div>
</div>
</div>
</template>
<script>
@@ -42,32 +45,32 @@ export default {
ctas: [
{
icon: "layers",
title: "API Documentation",
title: "Feature",
description:
"Get up and running with Kooli in as little as 10 minutes.",
link: {
title: "API reference",
target: "https://docs.kooli.tech/api",
title: "Feature",
target: "https://docs.hoppscotch.io/api",
},
},
{
icon: "local_library",
title: "Guides",
title: "Feature",
description:
"Explore and start integrating Kooli's products and tools.",
link: {
title: "Guides and resources",
target: "https://docs.kooli.tech/guides",
title: "Feature",
target: "https://docs.hoppscotch.io/guides",
},
},
{
icon: "local_library",
title: "Guides",
title: "Feature",
description:
"Explore and start integrating Kooli's products and tools.",
link: {
title: "Guides and resources",
target: "https://docs.kooli.tech/guides",
title: "Feature",
target: "https://docs.hoppscotch.io/guides",
},
},
],

View File

@@ -1,26 +1,25 @@
<template>
<div class="flex flex-col p-4">
<div class="flex flex-col items-center">
<p class="my-4 font-semibold tracking-widest text-center text-accent">
<p class="font-semibold my-4 text-center text-accent tracking-widest">
FEATURES
</p>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
<div
v-for="(feature, index) in features"
:key="`feature-${index}`"
class="inline-flex flex-col p-8"
class="flex-col p-8 inline-flex"
>
<i class="text-4xl material-icons text-accent">{{ feature.icon }}</i>
<i class="text-accent text-4xl material-icons">{{ feature.icon }}</i>
<div class="flex-grow">
<h2
class="
mt-4
mb-2
text-lg
font-semibold
mt-4
text-lg text-secondaryDark
mb-2
transition
text-secondaryDark
"
>
{{ feature.title }}
@@ -47,44 +46,44 @@ export default {
features: [
{
icon: "offline_bolt",
title: "SaaS Executives",
title: "Feature",
description:
"Unblock your barriers to new markets to sell software globally, to companies of all sizes, at all different price points, and increase your revenue growth with Koolis SaaS Commerce Platform.",
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
link: { title: "Learn more", target: "/settings" },
},
{
icon: "stars",
title: "Product Managers",
title: "Feature",
description:
"Dont let your billing stack hold you back. Everything you need to grow your SaaS business. Subscription billing, payments, taxes and more, in one unified platform.",
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
link: { title: "Learn more", target: "/settings" },
},
{
icon: "supervised_user_circle",
title: "Creators",
title: "Feature",
description:
"Kooli is for artists and creators: writers, designers, software developers, musicians, educators, filmmakers, and anyone in-between. If you make stuff, you can sell that stuff.",
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
link: { title: "Learn more", target: "/settings" },
},
{
icon: "build_circle",
title: "Developers",
title: "Feature",
description:
"Focus on building, not billing. Save the blood, sweat and tears for your software development, not your billing stack, with Paddles subscription and commerce platform.",
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
link: { title: "Learn more", target: "/settings" },
},
{
icon: "monetization_on",
title: "Finance",
title: "Feature",
description:
"All your payments, subscriptions, taxes, invoices, SaaS metrics, and more in one place. Integrate your product, CRM, and accounting with only one tool, not dozens.",
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
link: { title: "Learn more", target: "/settings" },
},
{
icon: "group_work",
title: "Enterprise",
title: "Feature",
description:
"Accept payments in 135+ currencies to better serve your international customers with a single integration. No international business entity required. Fees exclude GST.",
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nam vel vero quia tenetur obcaecati. Distinctio nesciunt obcaecati deserunt.",
link: { title: "Learn more", target: "/settings" },
},
],

View File

@@ -1,28 +1,19 @@
<template>
<footer class="flex flex-col p-4">
<nav class="grid grid-cols-2 gap-4 md:grid-cols-4">
<footer class="flex flex-col p-6">
<nav class="grid gap-4 grid-cols-2 md:grid-cols-4">
<div class="flex flex-col space-y-2">
<span>
<AppLogo class="h-8" />
</span>
<span class="font-bold"> Hoppscotch </span>
<SmartChangeLanguage />
<ul class="space-y-2">
<h4 class="font-semibold my-2">Hoppscotch</h4>
<ul class="space-y-4">
<li>
<SmartAnchor label="Terms" to="/about/terms" class="footer-nav" />
<SmartChangeLanguage />
</li>
<li>
<SmartAnchor
label="Privacy"
to="/about/privacy"
class="footer-nav"
/>
<SmartColorModePicker />
</li>
</ul>
<SmartColorModePicker />
</div>
<div class="flex flex-col space-y-2">
<h4 class="my-2 font-semibold">Solutions</h4>
<h4 class="font-semibold my-2">Solutions</h4>
<ul class="space-y-2">
<li
v-for="(item, index) in navigation.solutions"
@@ -37,7 +28,7 @@
</ul>
</div>
<div class="flex flex-col space-y-2">
<h4 class="my-2 font-semibold">Platform</h4>
<h4 class="font-semibold my-2">Platform</h4>
<ul class="space-y-2">
<li
v-for="(item, index) in navigation.platform"
@@ -52,7 +43,7 @@
</ul>
</div>
<div class="flex flex-col space-y-2">
<h4 class="my-2 font-semibold">Company</h4>
<h4 class="font-semibold my-2">Company</h4>
<ul class="space-y-2">
<li
v-for="(item, index) in navigation.company"
@@ -77,72 +68,84 @@ export default {
navigation: {
solutions: [
{
name: "SaaS",
link: "/settings",
name: "RESTful",
link: "/",
},
{
name: "Products",
link: "/settings",
name: "WebSocket",
link: "/realtime",
},
{
name: "Creators",
link: "/settings",
name: "SSE",
link: "/realtime",
},
{
name: "Developers",
link: "/settings",
name: "Socket.IO",
link: "/realtime",
},
{
name: "Finance",
link: "/settings",
name: "MQTT",
link: "/realtime",
},
{
name: "Enterprise",
link: "/settings",
name: "GraphQL",
link: "/graphql",
},
],
platform: [
{
name: "Payments",
link: "/settings",
name: "API Designing",
link: "/",
},
{
name: "Subscriptions",
link: "/settings",
name: "API Development",
link: "/",
},
{
name: "API",
link: "https://docs.kooli.tech/api",
name: "API Testing",
link: "/",
},
{
name: "Guides",
link: "https://docs.kooli.tech/guides",
name: "API Deployment",
link: "/",
},
{
name: "API Documentation",
link: "/documentation",
},
{
name: "Integrations",
link: "/",
},
],
company: [
{
name: "About",
link: "/about",
link: "/",
},
{
name: "Jobs",
link: "/about/jobs",
},
{
name: "Integrations",
link: "/about/integrations",
name: "Careers",
link: "/careers",
},
{
name: "Support",
link: "",
link: "/",
},
{
name: "Contact",
link: "/about/contact",
link: "/",
},
{
name: "Blog",
link: "https://blog.kooli.tech",
link: "https://blog.hoppscotch.io",
},
{
name: "Community",
link: "/",
},
{
name: "Open Source",
link: "https://github.com/hoppscotch",
},
],
},
@@ -151,11 +154,11 @@ export default {
}
</script>
<style lang="scss" scoped>
<style scoped lang="scss">
.footer-nav {
&:hover,
&:focus {
@apply text-secondaryDark;
}
@apply px-2 py-1;
@apply -mx-2 -my-1;
@apply hover:text-secondaryDark;
@apply focus:text-secondaryDark;
}
</style>

View File

@@ -1,146 +0,0 @@
<template>
<div ref="globe"></div>
</template>
<script>
import ThreeGlobe from "three-globe"
import * as THREE from "three"
import TrackballControls from "three-trackballcontrols"
import geojson from "~/assets/geojson/ne_110m_admin_0_countries.geojson"
import texture from "~/assets/images/texture.png"
export default {
data() {
return {
globe: null,
renderer: null,
scene: null,
camera: null,
tbControls: null,
arcsData: [...Array(20).keys()].map(() => ({
startLat: (Math.random() - 0.5) * 180,
startLng: (Math.random() - 0.5) * 360,
endLat: (Math.random() - 0.5) * 180,
endLng: (Math.random() - 0.5) * 360,
color: [
["#00acee", "#aceeff", "#00ffac", "#aaef3e"][
Math.round(Math.random() * 3)
],
["#00acee", "#aceeff", "#00ffac", "#aaef3e"][
Math.round(Math.random() * 3)
],
],
})),
}
},
computed: {
labelsData() {
const labelsData = []
this.arcsData.forEach(
({ startLat, startLng, endLat, endLng, color }, linkIdx) =>
[
[startLat, startLng],
[endLat, endLng],
].forEach(([lat, lng], edgeIdx) =>
labelsData.push({
lat,
lng,
color: color[edgeIdx],
text: `${linkIdx} ${edgeIdx ? "tgt" : "src"}`,
})
)
)
return labelsData
},
},
mounted() {
this.init()
this.animate()
window.addEventListener(
"resize",
() => {
this.camera.aspect =
this.$refs.globe.clientWidth / this.$refs.globe.clientHeight
this.camera.updateProjectionMatrix()
this.renderer.setSize(
this.$refs.globe.clientWidth,
this.$refs.globe.clientHeight
)
},
false
)
},
methods: {
init() {
this.globe = new ThreeGlobe()
.globeImageUrl(texture)
.atmosphereColor("#aaaaaa")
// arcs layer
.arcsData(this.arcsData)
.arcColor("color")
.arcDashLength(1)
.arcDashGap(() => Math.random())
.arcStroke(0.6)
.arcDashInitialGap(() => Math.random() * 5)
.arcDashAnimateTime(2000)
// hex layer
.hexPolygonsData(geojson.features)
.hexPolygonResolution(3)
.hexPolygonMargin(0.5)
.hexPolygonColor(() => `#aaaaaa`)
// labels layer
.labelsData(this.labelsData)
.labelLat("lat")
.labelLng("lng")
.labelText("text")
.labelColor("color")
.labelSize(1.2)
.labelDotRadius(0.8)
// Setup renderer
this.renderer = new THREE.WebGLRenderer({
alpha: true,
})
this.renderer.setSize(
this.$refs.globe.clientWidth,
this.$refs.globe.clientHeight
)
this.$refs.globe.appendChild(this.renderer.domElement)
// Setup scene
this.scene = new THREE.Scene()
this.scene.background = null
this.scene.add(this.globe)
this.scene.add(new THREE.AmbientLight(0xffffff))
this.scene.add(new THREE.DirectionalLight(0xffffff, 0.6))
// Setup camera
this.camera = new THREE.PerspectiveCamera()
this.camera.aspect =
this.$refs.globe.clientWidth / this.$refs.globe.clientHeight
this.camera.updateProjectionMatrix()
this.camera.position.z = 300
// Add camera controls
this.tbControls = new TrackballControls(
this.camera,
this.renderer.domElement
)
this.tbControls.noZoom = true
this.tbControls.noPan = true
},
animate() {
this.renderer.render(this.scene, this.camera)
this.tbControls.update()
requestAnimationFrame(this.animate)
this.globe.rotation.y -= 0.0025
},
},
}
</script>

View File

@@ -1,51 +1,71 @@
<template>
<div class="flex p-4 relative">
<div class="relative my-16 z-10 max-w-3xl">
<div class="flex flex-col p-6 relative">
<div class="flex flex-col mt-16 items-center justify-center">
<h2
class="
font-extrabold
text-accent text-center
leading-none
tracking-tighter
font-semibold
text-accent text-4xl
md:text-5xl
lg:text-6xl
text-4xl
md:text-6xl
lg:text-8xl
"
>
Open Source
</h2>
<h3
class="
text-3xl
font-extrabold
my-4
font-mono
text-secondaryDark
text-center text-secondaryDark
leading-none
tracking-tighter
text-3xl
md:text-4xl
lg:text-4xl
font-semibold
lg:text-5xl
"
>
API Development Ecosystem
</h3>
<p class="text-lg my-4 text-secondaryLight max-w-4/5">
Millions of developers and companies build, ship, and maintain their
APIs on Hoppscotch the largest and most advanced development platform
in the world.
<p class="my-4 text-lg text-center max-w-2xl">
Thousands of developers and companies build, ship, and maintain their
APIs on Hoppscotch the transparent and most flexible API development
ecosystem in the world.
</p>
<div class="my-8 flex items-center">
<div class="flex space-x-4 my-8 justify-center items-center">
<ButtonPrimary
label="Dashboard"
icon="chevron_right"
class="my-4"
large
label="Get Started"
icon="arrow_forward"
rounded
reverse
large
@click.native="showLogin = true"
/>
<ButtonSecondary
to="https://github.com/hoppscotch/hoppscotch"
blank
outline
label="GitHub"
svg="github"
large
rounded
:shortcut="['30k Stars']"
/>
<ButtonSecondary label="Dashboard" icon="chevron_right" />
<ButtonPrimary label="GitHub" svg="github" large rounded />
<AppGitHubStarButton size="large" />
</div>
<!-- <LandingStats /> -->
</div>
<div class="lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2">
<LandingGlobe class="h-64 w-full sm:h-72 md:h-96 lg:h-full" />
</div>
<div class="flex flex-col items-center justify-center"></div>
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
</div>
</template>
<script>
export default {
data() {
return {
showLogin: false,
}
},
}
</script>

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex p-4 font-mono space-x-16">
<div class="flex font-mono space-x-16 p-6">
<div v-for="(stat, index) in stats" :key="`stat-${index}`">
<span class="text-xl font-bold">
<span class="font-bold text-xl">
{{ stat.count }}<span class="text-secondaryLight">+</span>
</span>
<br />
@@ -18,7 +18,7 @@ export default {
return {
stats: [
{ count: "350k", audience: "Developers" },
{ count: "10k", audience: "Organizations" },
{ count: "5k", audience: "Organizations" },
{ count: "1m", audience: "Requests" },
],
}

View File

@@ -1,21 +1,21 @@
<template>
<div class="flex flex-col p-4 mx-4 bg-primaryLight rounded">
<div class="bg-primaryLight rounded flex flex-col mx-6 p-4">
<div class="flex flex-col items-center">
<p class="my-4 font-semibold tracking-widest text-center">
<p class="font-semibold my-4 text-center tracking-widest">
EMPOWERING DEVELOPERS FROM
</p>
</div>
<div class="grid grid-cols-3 gap-4 md:grid-cols-4 lg:grid-cols-6">
<div class="grid gap-4 grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
<div
v-for="(user, index) in users"
:key="`user-${index}`"
class="inline-flex flex-col items-center justify-center px-4"
class="flex-col px-4 inline-flex items-center justify-center"
>
<img
:src="`/images/users/${user.image}`"
alt="Profile picture"
loading="lazy"
class="inline-flex flex-col object-contain object-center h-24 w-24"
class="flex-col object-contain object-center h-24 w-24 inline-flex"
/>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<div class="p-2 font-mono">
<div class="font-mono p-2">
<div
v-for="(header, index) in headers"
:key="`header-${index}`"
@@ -7,27 +7,25 @@
>
<span
class="
p-2
flex
min-w-0
text-xs
group-hover:text-secondaryDark
transition
font-semibold
min-w-0
p-2
transition
group-hover:text-secondaryDark
"
>
<span class="truncate">
<span class="rounded select-all truncate">
{{ header.key }}
</span>
</span>
<span
class="
font-mono font-bold
flex
font-mono font-bold
mx-2
justify-center
items-center
text-xs
mx-2
truncate
"
>
@@ -35,16 +33,16 @@
</span>
<span
class="
p-2
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
flex
font-semibold
flex-1
min-w-0
p-2
transition
group-hover:text-secondaryDark
"
>
<span class="truncate">
<span class="rounded select-all truncate">
{{ header.value }}
</span>
</span>

View File

@@ -1,5 +1,5 @@
<template>
<SmartTabs styles="sticky z-10 top-13">
<SmartTabs styles="sticky z-10 top-12">
<SmartTab
v-for="(lens, index) in validLenses"
:id="lens.lensName"
@@ -10,10 +10,10 @@
<component :is="lens.renderer" :response="response" />
</SmartTab>
<SmartTab
v-if="Object.keys(response.headers).length !== 0"
v-if="headerLength"
id="headers"
:label="$t('Headers')"
:info="Object.keys(response.headers).length.toString()"
:info="headerLength.toString()"
>
<LensesHeadersRenderer :headers="response.headers" />
</SmartTab>
@@ -32,7 +32,14 @@ export default {
response: { type: Object, default: () => {} },
},
computed: {
headerLength() {
if (!this.response || !this.response.headers) return 0
return Object.keys(this.response.headers).length
},
validLenses() {
if (!this.response) return []
return getSuitableLenses(this.response)
},
},

View File

@@ -2,24 +2,24 @@
<div>
<div
class="
flex flex-1
sticky
top-23
z-10
bg-primary
border-b border-dividerLight
flex flex-1
top-20
z-10
sticky
items-center
justify-between
border-b border-dividerLight
"
>
<label for="body" class="px-4 font-semibold text-xs">
<label for="body" class="font-semibold px-4">
{{ $t("response_body") }}
</label>
<div>
<ButtonSecondary
v-if="response.body"
v-tippy="{ theme: 'tooltip' }"
:title="previewEnabled ? $t('hide_preview') : $t('preview_html')"
:title="previewEnabled ? $t('hide.preview') : $t('preview_html')"
:icon="!previewEnabled ? 'visibility' : 'visibility_off'"
@click.native.prevent="togglePreview"
/>
@@ -35,7 +35,7 @@
v-if="response.body"
ref="copyResponse"
v-tippy="{ theme: 'tooltip' }"
:title="$t('copy_response')"
:title="$t('action.copy')"
:icon="copyIcon"
@click.native="copyResponse"
/>
@@ -47,8 +47,8 @@
:lang="'html'"
:options="{
maxLines: Infinity,
minLines: '16',
fontSize: '14px',
minLines: 16,
fontSize: '12px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
@@ -67,6 +67,7 @@
<script>
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
import { copyToClipboard } from "~/helpers/utils/clipboard"
export default {
mixins: [TextContentRendererMixin],
@@ -97,18 +98,12 @@ export default {
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
URL.revokeObjectURL(url)
this.downloadIcon = "save_alt"
}, 1000)
},
copyResponse() {
const aux = document.createElement("textarea")
const copy = this.responseBodyText
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
copyToClipboard(this.responseBodyText)
this.copyIcon = "done"
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",

View File

@@ -2,17 +2,17 @@
<div>
<div
class="
flex flex-1
sticky
top-23
z-10
bg-primary
border-b border-dividerLight
flex flex-1
top-20
z-10
sticky
items-center
justify-between
border-b border-dividerLight
"
>
<label for="body" class="px-4 font-semibold text-xs">
<label for="body" class="font-semibold px-4">
{{ $t("response_body") }}
</label>
<div>
@@ -98,7 +98,7 @@ export default {
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
URL.revokeObjectURL(url)
this.downloadIcon = "save_alt"
}, 1000)
},

View File

@@ -2,22 +2,22 @@
<div>
<div
class="
flex flex-1
sticky
top-23
z-10
bg-primary
border-b border-dividerLight
flex flex-1
top-20
z-10
sticky
items-center
justify-between
border-b border-dividerLight
"
>
<label for="body" class="px-4 font-semibold text-xs">
<label for="body" class="font-semibold px-4">
{{ $t("response_body") }}
</label>
<div>
<ButtonSecondary
v-if="response.body && canDownloadResponse"
v-if="response.body"
ref="downloadResponse"
v-tippy="{ theme: 'tooltip' }"
:title="$t('download_file')"
@@ -28,7 +28,7 @@
v-if="response.body"
ref="copyResponse"
v-tippy="{ theme: 'tooltip' }"
:title="$t('copy_response')"
:title="$t('action.copy')"
:icon="copyIcon"
@click.native="copyResponse"
/>
@@ -41,8 +41,8 @@
:provide-j-s-o-n-outline="true"
:options="{
maxLines: Infinity,
minLines: '16',
fontSize: '14px',
minLines: 16,
fontSize: '12px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
@@ -55,7 +55,7 @@
<script>
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
import { isJSONContentType } from "~/helpers/utils/contenttypes"
import { copyToClipboard } from "~/helpers/utils/clipboard"
export default {
mixins: [TextContentRendererMixin],
@@ -82,19 +82,11 @@ export default {
.split(";")[0]
.toLowerCase()
},
canDownloadResponse() {
return (
this.response &&
this.response.headers &&
this.response.headers["content-type"] &&
isJSONContentType(this.response.headers["content-type"])
)
},
},
methods: {
downloadResponse() {
const dataToWrite = this.responseBodyText
const file = new Blob([dataToWrite], { type: this.responseType })
const file = new Blob([dataToWrite], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
@@ -108,18 +100,12 @@ export default {
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
URL.revokeObjectURL(url)
this.downloadIcon = "save_alt"
}, 1000)
},
copyResponse() {
const aux = document.createElement("textarea")
const copy = this.responseBodyText
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
copyToClipboard(this.responseBodyText)
this.copyIcon = "done"
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",

View File

@@ -2,22 +2,22 @@
<div>
<div
class="
flex flex-1
sticky
top-23
z-10
bg-primary
border-b border-dividerLight
flex flex-1
top-20
z-10
sticky
items-center
justify-between
border-b border-dividerLight
"
>
<label for="body" class="px-4 font-semibold text-xs">
<label for="body" class="font-semibold px-4">
{{ $t("response_body") }}
</label>
<div>
<ButtonSecondary
v-if="response.body && canDownloadResponse"
v-if="response.body"
ref="downloadResponse"
v-tippy="{ theme: 'tooltip' }"
:title="$t('download_file')"
@@ -28,7 +28,7 @@
v-if="response.body"
ref="copyResponse"
v-tippy="{ theme: 'tooltip' }"
:title="$t('copy_response')"
:title="$t('action.copy')"
:icon="copyIcon"
@click.native="copyResponse"
/>
@@ -40,8 +40,8 @@
:lang="'plain_text'"
:options="{
maxLines: Infinity,
minLines: '16',
fontSize: '14px',
minLines: 16,
fontSize: '12px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
@@ -54,7 +54,7 @@
<script>
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
import { isJSONContentType } from "~/helpers/utils/contenttypes"
import { copyToClipboard } from "~/helpers/utils/clipboard"
export default {
mixins: [TextContentRendererMixin],
@@ -73,14 +73,6 @@ export default {
.split(";")[0]
.toLowerCase()
},
canDownloadResponse() {
return (
this.response &&
this.response.headers &&
this.response.headers["content-type"] &&
isJSONContentType(this.response.headers["content-type"])
)
},
},
methods: {
downloadResponse() {
@@ -99,18 +91,12 @@ export default {
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
URL.revokeObjectURL(url)
this.downloadIcon = "save_alt"
}, 1000)
},
copyResponse() {
const aux = document.createElement("textarea")
const copy = this.responseBodyText
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
copyToClipboard(this.responseBodyText)
this.copyIcon = "done"
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",

View File

@@ -2,17 +2,17 @@
<div>
<div
class="
flex flex-1
sticky
top-23
z-10
bg-primary
border-b border-dividerLight
flex flex-1
top-20
z-10
sticky
items-center
justify-between
border-b border-dividerLight
"
>
<label for="body" class="px-4 font-semibold text-xs">
<label for="body" class="font-semibold px-4">
{{ $t("response_body") }}
</label>
<div>
@@ -28,7 +28,7 @@
v-if="response.body"
ref="copyResponse"
v-tippy="{ theme: 'tooltip' }"
:title="$t('copy_response')"
:title="$t('action.copy')"
:icon="copyIcon"
@click.native="copyResponse"
/>
@@ -40,8 +40,8 @@
:lang="'xml'"
:options="{
maxLines: Infinity,
minLines: '16',
fontSize: '14px',
minLines: 16,
fontSize: '12px',
autoScrollEditorIntoView: true,
readOnly: true,
showPrintMargin: false,
@@ -54,6 +54,7 @@
<script>
import TextContentRendererMixin from "./mixins/TextContentRendererMixin"
import { copyToClipboard } from "~/helpers/utils/clipboard"
export default {
mixins: [TextContentRendererMixin],
@@ -90,18 +91,12 @@ export default {
})
setTimeout(() => {
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
URL.revokeObjectURL(url)
this.downloadIcon = "save_alt"
}, 1000)
},
copyResponse() {
const aux = document.createElement("textarea")
const copy = this.responseBodyText
aux.innerText = copy
document.body.appendChild(aux)
aux.select()
document.execCommand("copy")
document.body.removeChild(aux)
copyToClipboard(this.responseBodyText)
this.copyIcon = "done"
this.$toast.success(this.$t("copied_to_clipboard"), {
icon: "done",

View File

@@ -1,21 +1,26 @@
<template>
<div class="relative flex items-center justify-center h-5 w-5 cursor-pointer">
<div class="cursor-pointer flex h-5 w-5 relative items-center justify-center">
<img
class="
absolute
object-cover object-center
transition
bg-primaryDark bg-primaryLight
rounded-full
bg-primaryDark
object-cover object-center
h-5
transition
w-5
bg-primaryLight
absolute
"
:src="url"
:alt="alt"
loading="lazy"
/>
<div class="absolute inset-0 rounded-lg shadow-inner"></div>
<div class="rounded-full shadow-inner inset-0 absolute"></div>
<span
:class="[
'border-primary rounded-full border-2 h-3 -top-1 -right-1 w-3 absolute',
indicator,
]"
></span>
</div>
</template>
@@ -32,6 +37,10 @@ export default {
type: String,
default: "Profile picture",
},
indicator: {
type: String,
default: "bg-green-500",
},
},
}
</script>

View File

@@ -1,8 +1,22 @@
<template>
<div class="flex flex-col">
<label for="log">{{ title }}</label>
<div
class="
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-0
z-10
sticky
items-center
justify-between
"
>
<label for="log" class="font-semibold py-2">{{ title }}</label>
</div>
<div ref="log" name="log" class="realtime-log">
<span v-if="log">
<span v-if="log" class="space-y-2">
<span
v-for="(entry, index) in log"
:key="`entry-${index}`"
@@ -51,7 +65,7 @@ export default {
&,
span {
@apply font-mono;
@apply font-mono font-semibold;
@apply select-text;
}

View File

@@ -1,140 +1,150 @@
<template>
<div>
<Splitpanes vertical :dbl-click-splitter="false">
<Pane class="overflow-auto">
<Splitpanes horizontal :dbl-click-splitter="false">
<Pane class="overflow-auto">
<Splitpanes :dbl-click-splitter="false" vertical>
<Pane class="hide-scrollbar !overflow-auto">
<Splitpanes :dbl-click-splitter="false" horizontal>
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="request">
<ul>
<li>
<label for="mqtt-url">{{ $t("url") }}</label>
<div class="bg-primary flex p-4 top-0 z-10 sticky">
<div class="flex-1 inline-flex">
<input
id="mqtt-url"
v-model="url"
type="url"
spellcheck="false"
class="input md:rounded-bl-lg"
class="
bg-primaryLight
border border-divider
rounded-l
font-semibold font-mono
text-secondaryDark
w-full
py-1
px-4
transition
truncate
focus:outline-none focus:border-accent
"
:placeholder="$t('url')"
/>
</li>
<div>
<li>
<ButtonSecondary
<ButtonPrimary
id="connect"
:disabled="!validUrl"
class="
button
rounded-b-lg
md:rounded-bl-none md:rounded-br-lg
"
:icon="!connectionState ? 'sync' : 'sync_disabled'"
:label="
connectionState ? $t('disconnect') : $t('connect')
"
reverse
class="rounded-l-none w-28"
:label="connectionState ? $t('disconnect') : $t('connect')"
:loading="connectingState"
@click.native="toggleConnection"
/>
</li>
</div>
</ul>
</div>
</AppSection>
</Pane>
<Pane class="overflow-auto">
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="response">
<ul>
<li>
<RealtimeLog :title="$t('log')" :log="log" />
</li>
</ul>
</AppSection>
</Pane>
</Splitpanes>
</Pane>
<Pane max-size="35" min-size="20" class="overflow-auto">
<Pane
v-if="RIGHT_SIDEBAR"
max-size="35"
size="25"
min-size="20"
class="hide-scrollbar !overflow-auto"
>
<AppSection label="messages">
<ul>
<li>
<label for="pub_topic">{{ $t("mqtt_topic") }}</label>
<div class="flex flex-col flex-1 p-4 inline-flex">
<label for="pub_topic" class="font-semibold">
{{ $t("mqtt.topic") }}
</label>
</div>
<div class="flex px-4">
<input
id="pub_topic"
v-model="pub_topic"
class="input"
:placeholder="$t('mqtt.topic_name')"
type="text"
spellcheck="false"
/>
</li>
<li>
<label for="mqtt-message">{{ $t("message") }}</label>
</div>
<div class="bg-primary flex flex-1 p-4 items-center justify-between">
<label for="mqtt-message" class="font-semibold">{{
$t("communication")
}}</label>
</div>
<div class="flex px-4">
<input
id="mqtt-message"
v-model="msg"
class="input !rounded-r-none"
type="text"
:placeholder="$t('message')"
spellcheck="false"
class="input border-dashed md:border-l border-divider"
/>
</li>
<div>
<li>
<ButtonSecondary
<ButtonPrimary
id="publish"
class="button"
name="get"
class="rounded-l-none"
:disabled="!canpublish"
icon="send"
:label="$t('mqtt_publish')"
:label="$t('mqtt.publish')"
@click.native="publish"
/>
</li>
</div>
</ul>
<ul>
<li>
<label for="sub_topic">{{ $t("mqtt_topic") }}</label>
<div
class="
border-t border-dividerLight
flex flex-col flex-1
mt-4
p-4
inline-flex
"
>
<label for="sub_topic" class="font-semibold">{{
$t("mqtt.topic")
}}</label>
</div>
<div class="flex px-4">
<input
id="sub_topic"
v-model="sub_topic"
type="text"
:placeholder="$t('mqtt.topic_name')"
spellcheck="false"
class="input md:rounded-bl-lg"
class="input !rounded-r-none"
/>
</li>
<div>
<li>
<ButtonSecondary
<ButtonPrimary
id="subscribe"
name="get"
:disabled="!cansubscribe"
class="
button
rounded-b-lg
md:rounded-bl-none md:rounded-br-lg
"
:icon="subscriptionState ? 'sync_disabled' : 'sync'"
class="rounded-l-none"
:label="
subscriptionState
? $t('mqtt_unsubscribe')
: $t('mqtt_subscribe')
subscriptionState ? $t('mqtt.unsubscribe') : $t('mqtt.subscribe')
"
reverse
@click.native="toggleSubscription"
/>
</li>
</div>
</ul>
</AppSection>
</Pane>
</Splitpanes>
</div>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import { Splitpanes, Pane } from "splitpanes"
import Paho from "paho-mqtt"
import debounce from "~/helpers/utils/debounce"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import { useSetting } from "~/newstore/settings"
export default {
export default defineComponent({
components: { Splitpanes, Pane },
setup() {
return {
RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
}
},
data() {
return {
url: "wss://test.mosquitto.org:8081",
@@ -144,6 +154,7 @@ export default {
sub_topic: "",
msg: "",
connectionState: false,
connectingState: false,
log: null,
manualDisconnect: false,
subscriptionState: false,
@@ -182,6 +193,7 @@ export default {
if (data.url === this.url) this.isUrlValid = data.result
},
connect() {
this.connectingState = true
this.log = [
{
payload: this.$t("connecting_to", { name: this.url }),
@@ -209,6 +221,7 @@ export default {
})
},
onConnectionFailure() {
this.connectingState = false
this.connectionState = false
this.log.push({
payload: this.$t("error_occurred"),
@@ -218,6 +231,7 @@ export default {
})
},
onConnectionSuccess() {
this.connectingState = false
this.connectionState = true
this.log.push({
payload: this.$t("connected_to", { name: this.url }),
@@ -255,6 +269,7 @@ export default {
})
},
onConnectionLost() {
this.connectingState = false
this.connectionState = false
if (this.manualDisconnect) {
this.$toast.error(this.$t("disconnected"), {
@@ -342,5 +357,5 @@ export default {
})
},
},
}
})
</script>

View File

@@ -1,167 +1,173 @@
<template>
<div>
<Splitpanes vertical :dbl-click-splitter="false">
<Pane class="overflow-auto">
<Splitpanes horizontal :dbl-click-splitter="false">
<Pane class="overflow-auto">
<Splitpanes :dbl-click-splitter="false" vertical>
<Pane class="hide-scrollbar !overflow-auto">
<Splitpanes :dbl-click-splitter="false" horizontal>
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="request">
<ul>
<li>
<label for="socketio-url">{{ $t("url") }}</label>
<div class="bg-primary flex p-4 top-0 z-10 sticky">
<div class="flex-1 inline-flex">
<input
id="socketio-url"
v-model="url"
type="url"
spellcheck="false"
:class="{ error: !urlValid }"
class="input md:rounded-bl-lg"
class="
bg-primaryLight
border border-divider
rounded-l
flex
font-semibold font-mono
flex-1
text-secondaryDark
w-full
py-1
px-4
transition
truncate
focus:outline-none focus:border-accent
"
:placeholder="$t('url')"
@keyup.enter="urlValid ? toggleConnection() : null"
/>
</li>
<div>
<li>
<label for="socketio-path">{{ $t("path") }}</label>
<input
id="socketio-path"
v-model="path"
class="input"
class="
bg-primaryLight
border border-divider
flex
font-semibold font-mono
flex-1
text-secondaryDark
w-full
py-1
px-4
transition
truncate
focus:outline-none focus:border-accent
"
spellcheck="false"
/>
</li>
</div>
<div>
<li>
<ButtonSecondary
<ButtonPrimary
id="connect"
:disabled="!urlValid"
name="connect"
class="
button
rounded-b-lg
md:rounded-bl-none md:rounded-br-lg
"
:icon="!connectionState ? 'sync' : 'sync_disabled'"
:label="
!connectionState ? $t('connect') : $t('disconnect')
"
reverse
class="rounded-l-none w-28"
:label="!connectionState ? $t('connect') : $t('disconnect')"
:loading="connectingState"
@click.native="toggleConnection"
/>
</li>
</div>
</ul>
</div>
</AppSection>
</Pane>
<Pane class="overflow-auto">
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="response">
<ul>
<li>
<RealtimeLog :title="$t('log')" :log="communication.log" />
</li>
</ul>
</AppSection>
</Pane>
</Splitpanes>
</Pane>
<Pane max-size="35" min-size="20" class="overflow-auto">
<Pane
v-if="RIGHT_SIDEBAR"
max-size="35"
size="25"
min-size="20"
class="hide-scrollbar !overflow-auto"
>
<AppSection label="messages">
<ul>
<li>
<label for="event_name">{{ $t("event_name") }}</label>
<div class="flex flex-col flex-1 p-4 inline-flex">
<label for="events" class="font-semibold">
{{ $t("events") }}
</label>
</div>
<div class="flex px-4">
<input
id="event_name"
v-model="communication.eventName"
class="input"
name="event_name"
:placeholder="$t('event_name')"
type="text"
:readonly="!connectionState"
:disabled="!connectionState"
/>
</li>
</ul>
<ul>
<li>
<div class="flex flex-1">
<label>{{ $t("message") }}s</label>
</div>
</li>
</ul>
<ul
<div class="bg-primary flex flex-1 p-4 items-center justify-between">
<label class="font-semibold">{{ $t("communication") }}</label>
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('add.new')"
icon="add"
@click.native="addCommunicationInput"
/>
</div>
</div>
<div class="flex flex-col space-y-2 px-4">
<div
v-for="(input, index) of communication.inputs"
:key="`input-${index}`"
:class="{ 'border-t': index == 0 }"
class="
border-b border-dashed
divide-y
md:divide-x
border-divider
divide-dashed divide-divider
md:divide-y-0
"
>
<li>
<div class="flex">
<input
v-model="communication.inputs[index]"
class="input"
class="input !rounded-r-none"
name="message"
:placeholder="$t('count.message', { count: index + 1 })"
type="text"
:readonly="!connectionState"
:disabled="!connectionState"
@keyup.enter="connectionState ? sendMessage() : null"
/>
</li>
<div v-if="index + 1 !== communication.inputs.length">
<li>
<ButtonSecondary
v-if="index + 1 !== communication.inputs.length"
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="delete"
class="rounded-l-none"
color="red"
outline
@click.native="removeCommunicationInput({ index })"
/>
</li>
</div>
<div v-if="index + 1 === communication.inputs.length">
<li>
<ButtonSecondary
<ButtonPrimary
v-if="index + 1 === communication.inputs.length"
id="send"
class="button"
name="send"
:disabled="!connectionState"
icon="send"
class="rounded-l-none"
:label="$t('send')"
@click.native="sendMessage"
/>
</li>
</div>
</ul>
<ul>
<li>
<ButtonSecondary
icon="add"
:label="$t('add_new')"
@click.native="addCommunicationInput"
/>
</li>
</ul>
</div>
</div>
</AppSection>
</Pane>
</Splitpanes>
</div>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import { Splitpanes, Pane } from "splitpanes"
import { io as Client } from "socket.io-client"
import wildcard from "socketio-wildcard"
import debounce from "~/helpers/utils/debounce"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import { useSetting } from "~/newstore/settings"
export default {
export default defineComponent({
components: { Splitpanes, Pane },
setup() {
return {
RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
}
},
data() {
return {
url: "wss://main-daxrc78qyb411dls-gtw.qovery.io",
path: "/socket.io",
isUrlValid: true,
connectingState: false,
connectionState: false,
io: null,
communication: {
@@ -210,6 +216,7 @@ export default {
else return this.disconnect()
},
connect() {
this.connectingState = true
this.communication.log = [
{
payload: this.$t("connecting_to", { name: this.url }),
@@ -228,6 +235,7 @@ export default {
// Add ability to listen to all events
wildcard(Client.Manager)(this.io)
this.io.on("connect", () => {
this.connectingState = false
this.connectionState = true
this.communication.log = [
{
@@ -259,6 +267,7 @@ export default {
this.handleError()
})
this.io.on("disconnect", () => {
this.connectingState = false
this.connectionState = false
this.communication.log.push({
payload: this.$t("disconnected_from", { name: this.url }),
@@ -286,6 +295,7 @@ export default {
},
handleError(error) {
this.disconnect()
this.connectingState = false
this.connectionState = false
this.communication.log.push({
payload: this.$t("error_occurred"),
@@ -332,5 +342,5 @@ export default {
}
},
},
}
})
</script>

View File

@@ -1,43 +1,44 @@
<template>
<div>
<Splitpanes horizontal :dbl-click-splitter="false">
<Pane class="overflow-auto">
<Splitpanes :dbl-click-splitter="false" horizontal>
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="request">
<ul>
<li>
<label for="server">{{ $t("server") }}</label>
<div class="bg-primary flex p-4 top-0 z-10 sticky">
<div class="flex-1 inline-flex">
<input
id="server"
v-model="server"
type="url"
:class="{ error: !serverValid }"
class="input md:rounded-bl-lg"
class="
bg-primaryLight
border border-divider
rounded-l
font-semibold font-mono
text-secondaryDark
w-full
py-1
px-4
transition
truncate
focus:outline-none focus:border-accent
"
:placeholder="$t('url')"
@keyup.enter="serverValid ? toggleSSEConnection() : null"
/>
</li>
<div>
<li>
<ButtonSecondary
<ButtonPrimary
id="start"
:disabled="!serverValid"
name="start"
class="
button
rounded-b-lg
md:rounded-bl-none md:rounded-br-lg
"
:icon="!connectionSSEState ? 'sync' : 'sync_disabled'"
class="rounded-l-none w-22"
:label="!connectionSSEState ? $t('start') : $t('stop')"
reverse
:loading="connectingState"
@click.native="toggleSSEConnection"
/>
</li>
</div>
</ul>
</div>
</AppSection>
</Pane>
<Pane class="overflow-auto">
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="response">
<ul>
<li>
@@ -48,7 +49,6 @@
</AppSection>
</Pane>
</Splitpanes>
</div>
</template>
<script>
@@ -61,6 +61,7 @@ export default {
data() {
return {
connectionSSEState: false,
connectingState: false,
server: "https://express-eventsource.herokuapp.com/events",
isUrlValid: true,
sse: null,
@@ -103,6 +104,7 @@ export default {
else return this.stop()
},
start() {
this.connectingState = true
this.events.log = [
{
payload: this.$t("connecting_to", { name: this.server }),
@@ -114,6 +116,7 @@ export default {
try {
this.sse = new EventSource(this.server)
this.sse.onopen = () => {
this.connectingState = false
this.connectionSSEState = true
this.events.log = [
{

View File

@@ -1,183 +1,217 @@
<template>
<div>
<Splitpanes vertical :dbl-click-splitter="false">
<Pane class="overflow-auto">
<Splitpanes horizontal :dbl-click-splitter="false">
<Pane class="overflow-auto">
<Splitpanes :dbl-click-splitter="false" vertical>
<Pane class="hide-scrollbar !overflow-auto">
<Splitpanes :dbl-click-splitter="false" horizontal>
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="request">
<ul>
<li>
<label for="websocket-url">{{ $t("url") }}</label>
<div class="bg-primary flex p-4 top-0 z-10 sticky">
<div class="flex-1 inline-flex">
<input
id="websocket-url"
v-model="url"
class="input"
class="
bg-primaryLight
border border-divider
rounded-l
font-semibold font-mono
text-secondaryDark
w-full
py-1
px-4
transition
truncate
focus:outline-none focus:border-accent
"
type="url"
spellcheck="false"
:class="{ error: !urlValid }"
:placeholder="$t('url')"
@keyup.enter="urlValid ? toggleConnection() : null"
/>
</li>
<div>
<li>
<ButtonSecondary
<ButtonPrimary
id="connect"
:disabled="!urlValid"
class="button"
class="rounded-l-none w-28"
name="connect"
:icon="!connectionState ? 'sync' : 'sync_disabled'"
:label="
!connectionState ? $t('connect') : $t('disconnect')
"
reverse
:label="!connectionState ? $t('connect') : $t('disconnect')"
:loading="connectingState"
@click.native="toggleConnection"
/>
</li>
</div>
</ul>
<ul>
<li>
<div class="flex flex-1">
<label>{{ $t("protocols") }}</label>
</div>
</li>
</ul>
<ul
v-for="(protocol, index) of protocols"
:key="`protocol-${index}`"
:class="{ 'border-t': index == 0 }"
</AppSection>
<div
class="
border-b border-dashed
divide-y
md:divide-x
border-divider
divide-dashed divide-divider
md:divide-y-0
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-16
z-10
sticky
items-center
justify-between
"
>
<li>
<label class="font-semibold">
{{ $t("websocket.protocols") }}
</label>
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('clear_all')"
icon="clear_all"
@click.native="clearContent"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('add.new')"
icon="add"
@click.native="addProtocol"
/>
</div>
</div>
<div
v-for="(protocol, index) of protocols"
:key="`protocol-${index}`"
class="
divide-x divide-dividerLight
border-b border-dividerLight
flex
"
:class="{ 'border-t': index == 0 }"
>
<input
v-model="protocol.value"
class="input"
:placeholder="$t('protocol_count', { count: index + 1 })"
class="
bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none
"
:placeholder="$t('count.protocol', { count: index + 1 })"
name="message"
type="text"
/>
</li>
<div>
<li>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="
protocol.hasOwnProperty('active')
? protocol.active
? $t('turn_off')
: $t('turn_on')
: $t('turn_off')
? $t('action.turn_off')
: $t('action.turn_on')
: $t('action.turn_off')
"
:icon="
protocol.hasOwnProperty('active')
? protocol.active
? 'check_box'
: 'check_box_outline_blank'
: 'check_box'
"
color="green"
@click.native="
protocol.active = protocol.hasOwnProperty('active')
? !protocol.active
: false
"
/>
<i class="material-icons">
{{
protocol.hasOwnProperty("active")
? protocol.active
? "check_box"
: "check_box_outline_blank"
: "check_box"
}}
</i>
</li>
</div>
<div>
<li>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')"
icon="delete"
color="red"
@click.native="deleteProtocol({ index })"
/>
</li>
</div>
</ul>
<ul>
<li>
<ButtonSecondary
icon="add"
:label="$t('add_new')"
@click.native="addProtocol"
/>
</li>
</ul>
</AppSection>
</div>
<div
v-if="protocols.length === 0"
class="
flex flex-col
text-secondaryLight
p-4
items-center
justify-center
"
>
<i class="opacity-75 pb-2 material-icons">topic</i>
<span class="text-center">
{{ $t("empty.protocols") }}
</span>
</div>
</Pane>
<Pane class="overflow-auto">
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="response">
<ul>
<li>
<RealtimeLog :title="$t('log')" :log="communication.log" />
</li>
</ul>
</AppSection>
</Pane>
</Splitpanes>
</Pane>
<Pane max-size="35" min-size="20" class="overflow-auto">
<Pane
v-if="RIGHT_SIDEBAR"
max-size="35"
size="25"
min-size="20"
class="hide-scrollbar !overflow-auto"
>
<AppSection label="messages">
<ul>
<li>
<label for="websocket-message">{{ $t("message") }}</label>
<div class="flex flex-col flex-1 p-4 inline-flex">
<label for="websocket-message" class="font-semibold">
{{ $t("communication") }}
</label>
</div>
<div class="flex px-4">
<input
id="websocket-message"
v-model="communication.input"
name="message"
type="text"
:readonly="!connectionState"
class="input md:rounded-bl-lg"
:disabled="!connectionState"
:placeholder="$t('message')"
class="input !rounded-r-none"
@keyup.enter="connectionState ? sendMessage() : null"
@keyup.up="connectionState ? walkHistory('up') : null"
@keyup.down="connectionState ? walkHistory('down') : null"
/>
</li>
<div>
<li>
<ButtonSecondary
<ButtonPrimary
id="send"
name="send"
:disabled="!connectionState"
class="
button
rounded-b-lg
md:rounded-bl-none md:rounded-br-lg
"
icon="send"
class="rounded-l-none"
:label="$t('send')"
@click.native="sendMessage"
/>
</li>
</div>
</ul>
</AppSection>
</Pane>
</Splitpanes>
</div>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import { Splitpanes, Pane } from "splitpanes"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import debounce from "~/helpers/utils/debounce"
import "splitpanes/dist/splitpanes.css"
export default {
import { useSetting } from "~/newstore/settings"
export default defineComponent({
components: { Splitpanes, Pane },
setup() {
return {
RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
}
},
data() {
return {
connectionState: false,
connectingState: false,
url: "wss://echo.websocket.org",
isUrlValid: true,
socket: null,
@@ -222,6 +256,9 @@ export default {
this.worker.terminate()
},
methods: {
clearContent() {
this.protocols = []
},
debouncer: debounce(function () {
this.worker.postMessage({ type: "ws", url: this.url })
}, 1000),
@@ -243,8 +280,10 @@ export default {
},
]
try {
this.connectingState = true
this.socket = new WebSocket(this.url, this.activeProtocols)
this.socket.onopen = () => {
this.connectingState = false
this.connectionState = true
this.communication.log = [
{
@@ -294,6 +333,8 @@ export default {
disconnect() {
if (this.socket) {
this.socket.close()
this.connectionState = false
this.connectingState = false
}
},
handleError(error) {
@@ -378,5 +419,5 @@ export default {
})
},
},
}
})
</script>

View File

@@ -1,21 +1,22 @@
<template>
<div class="flex">
<!-- text-blue-400 -->
<!-- text-green-400 -->
<!-- text-teal-400 -->
<!-- text-indigo-400 -->
<!-- text-purple-400 -->
<!-- text-orange-400 -->
<!-- text-pink-400 -->
<!-- text-red-400 -->
<!-- text-yellow-400 -->
<!-- text-green-500 -->
<!-- text-teal-500 -->
<!-- text-blue-500 -->
<!-- text-indigo-500 -->
<!-- text-purple-500 -->
<!-- text-yellow-500 -->
<!-- text-orange-500 -->
<!-- text-red-500 -->
<!-- text-pink-500 -->
<ButtonSecondary
v-for="(color, index) of accentColors"
:key="`color-${index}`"
v-tippy="{ theme: 'tooltip' }"
:title="`${color.charAt(0).toUpperCase()}${color.slice(1)}`"
:class="[`text-${color}-400`, { 'bg-primary': color === active }]"
:class="[{ 'bg-primaryLight': color === active }]"
icon="lens"
:color="color"
@click.native="setActiveColor(color)"
/>
</div>

View File

@@ -2,18 +2,19 @@
<div class="show-if-initialized" :class="{ initialized }">
<pre ref="editor" :class="styles"></pre>
<div
v-if="lang == 'json'"
v-if="provideJSONOutline"
class="
sticky
bottom-0
z-10
flex flex-nowrap flex-1
overflow-auto
font-mono
shadow-lg
px-4
bg-primaryLight
border-t border-divider
flex flex-nowrap
font-mono
flex-1
py-1
px-4
bottom-0
z-10
sticky
overflow-auto
hide-scrollbar
"
>
@@ -21,20 +22,19 @@
v-for="(p, index) in currentPath"
:key="`p-${index}`"
class="
inline-flex
items-center
flex-grow-0 flex-shrink-0
text-secondaryLight
hover:text-secondary
cursor-pointer
font-semibold
text-xs
flex-grow-0 flex-shrink-0
text-secondaryLight
inline-flex
items-center
hover:text-secondary
"
>
<span @click="onBlockClick(index)">
{{ p }}
</span>
<i v-if="index + 1 !== currentPath.length" class="material-icons mx-2">
<i v-if="index + 1 !== currentPath.length" class="mx-2 material-icons">
chevron_right
</i>
<tippy

View File

@@ -8,7 +8,7 @@
color
? `text-${color}-500 hover:text-${color}-600 focus:text-${color}-600`
: 'hover:text-secondaryDark focus:text-secondaryDark',
{ 'opacity-50 cursor-not-allowed': disabled },
{ 'opacity-75 cursor-not-allowed': disabled },
{ 'flex-row-reverse': reverse },
]"
:disabled="disabled"

View File

@@ -3,15 +3,6 @@
<input
ref="acInput"
v-model="text"
class="
px-4
py-3
text-xs
flex flex-1
font-semibold
bg-primaryLight
focus:outline-none
"
type="text"
:placeholder="placeholder"
:spellcheck="spellcheck"
@@ -197,7 +188,7 @@ export default {
.autocomplete-wrapper {
@apply relative;
@apply flex;
@apply flex-1;
@apply w-full;
input:focus + ul.suggestions,
ul.suggestions:hover {
@@ -213,15 +204,15 @@ export default {
@apply z-50;
@apply shadow-lg;
top: calc(100% - 8px);
top: calc(100% - 4px);
border-radius: 0 0 8px 8px;
li {
@apply w-full;
@apply block;
@apply py-2 px-4;
@apply text-xs;
@apply font-mono;
@apply text-secondaryLight;
@apply font-semibold font-mono;
&:last-child {
border-radius: 0 0 8px 8px;

View File

@@ -1,5 +1,5 @@
<template>
<span>
<span class="inline-flex">
<tippy
ref="language"
interactive
@@ -10,23 +10,26 @@
:animate-fill="false"
>
<template #trigger>
<TabPrimary
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('choose_language')"
class="font-medium focus:outline-none"
outline
:label="`${
$i18n.locales.find(({ code }) => code == $i18n.locale).country
} ${$i18n.locales.find(({ code }) => code == $i18n.locale).name}`"
$i18n.locales.find(({ code }) => code == $i18n.locale).name
}`"
/>
</template>
<SmartItem
<nuxt-link
v-for="(locale, index) in $i18n.locales.filter(
({ code }) => code !== $i18n.locale
)"
:key="`locale-${index}`"
:to="switchLocalePath(locale.code).toString()"
:label="`${locale.country} ${locale.name}`"
@click.native="$refs.language.tippy().hide()"
/>
:to="switchLocalePath(locale.code)"
@click="$refs.language.tippy().hide()"
>
<SmartItem :label="locale.name" />
</nuxt-link>
</tippy>
</span>
</template>

View File

@@ -5,10 +5,9 @@
:key="`color-${index}`"
v-tippy="{ theme: 'tooltip' }"
:title="`${color.charAt(0).toUpperCase()}${color.slice(1)}`"
:class="[
{ 'bg-primary': color === activeColor },
{ 'text-accent hover:text-accent': color === activeColor },
]"
:class="{
'bg-primaryLight !text-accent hover:text-accent': color === active,
}"
:icon="getIcon(color)"
@click.native="setBGMode(color)"
/>
@@ -32,7 +31,7 @@ export default Vue.extend({
},
subscriptions() {
return {
activeColor: getSettingSubject("BG_COLOR"),
active: getSettingSubject("BG_COLOR"),
}
},
methods: {

View File

@@ -7,8 +7,8 @@
</div>
</template>
<template #body>
<div class="px-2 flex flex-col">
<label class="font-semibold text-xs">
<div class="flex flex-col px-2">
<label class="font-semibold">
{{ title }}
</label>
</div>

View File

@@ -14,12 +14,12 @@
@apply inline-flex;
@apply items-center;
@apply justify-center;
@apply rounded-lg;
@apply rounded;
@apply m-1;
@apply pl-4;
@apply bg-primaryDark;
@apply text-secondary;
@apply font-mono;
@apply font-mono font-semibold;
@apply border border-divider;
}

View File

@@ -5,14 +5,32 @@
<template>
<div class="url-field-container">
<div ref="editor" class="url-field" contenteditable="true"></div>
<div
ref="editor"
:placeholder="placeholder"
class="url-field"
:class="styles"
contenteditable="true"
@keydown.enter.prevent="$emit('enter', $event)"
@change="$emit('change', $event)"
@keyup="$emit('keyup', $event)"
@click="$emit('click', $event)"
@keydown="$emit('keydown', $event)"
></div>
</div>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import IntervalTree from "node-interval-tree"
import debounce from "lodash/debounce"
import isUndefined from "lodash/isUndefined"
import { tippy } from "vue-tippy"
import {
currentEnvironment$,
getCurrentEnvironment,
} from "~/newstore/environments"
import { useReadonlyStream } from "~/helpers/utils/composables"
const tagsToReplace = {
"&": "&amp;",
@@ -20,12 +38,30 @@ const tagsToReplace = {
">": "&gt;",
}
export default {
export default defineComponent({
props: {
value: {
type: String,
default: "",
},
placeholder: {
type: String,
default: "",
},
styles: {
type: String,
default: "",
},
},
setup() {
const currentEnvironment = useReadonlyStream(
currentEnvironment$,
getCurrentEnvironment()
)
return {
currentEnvironment,
}
},
data() {
return {
@@ -35,7 +71,8 @@ export default {
highlight: [
{
text: /(<<\w+>>)/g,
style: "VAR",
style:
"text-white cursor-help rounded px-1 focus:outline-none mx-0.5",
},
],
highlightEnabled: true,
@@ -45,7 +82,11 @@ export default {
fireOnEnabled: true,
}
},
watch: {
currentEnvironment() {
this.processHighlights()
},
highlightStyle() {
this.processHighlights()
},
@@ -70,6 +111,7 @@ export default {
this.restoreSelection(this.$refs.editor, selection)
},
},
mounted() {
if (this.fireOnEnabled)
this.$refs.editor.addEventListener(this.fireOn, this.handleChange)
@@ -155,14 +197,21 @@ export default {
result += this.safe_tags_replace(
this.internalValue.substring(startingPosition, position.start)
)
result +=
"<span class='" +
highlightPositions[k].style +
"'>" +
this.safe_tags_replace(
const envVar = this.internalValue
.substring(position.start, position.end + 1)
.slice(2, -2)
result += `<span class="${highlightPositions[k].style} ${
this.currentEnvironment.variables.find((k) => k.key === envVar)
?.value === undefined
? "bg-red-500"
: "bg-accentDark"
}" v-tippy data-tippy-content="environment: ${
this.currentEnvironment.name
} value: ${
this.currentEnvironment.variables.find((k) => k.key === envVar)?.value
}">${this.safe_tags_replace(
this.internalValue.substring(position.start, position.end + 1)
) +
"</span>"
)}</span>`
startingPosition = position.end + 1
}
if (startingPosition < this.internalValue.length)
@@ -177,8 +226,32 @@ export default {
result += "&nbsp;"
}
this.htmlOutput = result
this.$nextTick(() => {
this.renderTippy()
})
this.$emit("input", this.internalValue)
},
renderTippy() {
const tippable = document.querySelectorAll("[v-tippy]")
tippable.forEach((t) => {
tippy(t, {
content: t.dataset["tippy-content"],
theme: "tooltip",
popperOptions: {
modifiers: {
preventOverflow: {
enabled: false,
},
hide: {
enabled: false,
},
},
},
})
})
},
insertRange(start, end, highlightObj, intervalTree) {
const overlap = intervalTree.search(start, end)
const maxLengthOverlap = overlap.reduce((max, o) => {
@@ -386,25 +459,37 @@ export default {
}
},
},
}
})
</script>
<style lang="scss">
.VAR {
@apply font-bold;
@apply text-accent;
<style lang="scss" scoped>
[contenteditable="true"] {
&:empty {
line-height: 1.9;
&::before {
@apply text-secondary;
@apply opacity-75;
@apply pointer-events-none;
content: attr(placeholder);
}
}
}
.url-field-container {
@apply inline-grid;
@apply w-full;
}
.url-field {
@apply border-dashed border-divider;
@apply flex;
@apply items-center;
@apply justify-items-start;
@apply whitespace-nowrap;
@apply overflow-x-auto;
@apply overflow-y-hidden;
@apply resize-none;
@apply md:border-l;
}
.url-field::-webkit-scrollbar {

Some files were not shown because too many files have changed in this diff Show More