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

View File

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

View File

@@ -1,22 +1,22 @@
<template> <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"> <span class="flex">
<SmartLink <SmartLink
to="https://forms.gle/8yFiEynXB7h477Ns6" to="https://forms.gle/8yFiEynXB7h477Ns6"
blank blank
class=" class="
relative
flex flex
py-2
px-4
transition
relative
items-center items-center
justify-center justify-center
px-4
py-3
transition
group group
" "
> >
<i class="material-icons mr-4">science</i> <i class="mr-4 material-icons">science</i>
<span class="text-secondaryDark text-xs"> <span class="text-secondaryDark">
<span class="md:hidden"> Beta Layout </span> <span class="md:hidden"> Beta Layout </span>
<span class="hidden md:inline"> <span class="hidden md:inline">
You're currently viewing an experimental beta layout You're currently viewing an experimental beta layout
@@ -24,17 +24,16 @@
</span> </span>
<span <span
class=" class="
border-l border-divider
flex flex
font-semibold
text-accent
ml-4
pl-4
transition
items-center items-center
justify-center justify-center
pl-4
ml-4
font-semibold
transition
border-l
group-hover:text-accentDark group-hover:text-accentDark
border-divider
text-accent text-xs
" "
> >
<span class="md:hidden"> Give Feedback </span> <span class="md:hidden"> Give Feedback </span>
@@ -43,17 +42,13 @@
</SmartLink> </SmartLink>
<SmartLink <SmartLink
to="https://hoppscotch.io" 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 Switch back to the Hoppscotch website
</span> </span>
</SmartLink> </SmartLink>
</span> </span>
<ButtonSecondary <ButtonSecondary icon="close" />
v-tippy="{ theme: 'tooltip' }"
icon="close"
:title="$t('close')"
/>
</div> </div>
</template> </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> <template>
<header class="flex items-center justify-between p-2 flex-1"> <header class="flex flex-1 py-2 px-4 items-center justify-between">
<div class="inline-flex space-x-2 items-center font-bold flex-shrink-0"> <div
<AppLogo class="h-6 mx-4" /> Hoppscotch class="
font-extrabold
space-x-2
flex-shrink-0
text-sm
inline-flex
items-center
"
>
<AppLogo />
</div> </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" /> <AppGitHubStarButton class="mt-1 mr-2" />
<TabPrimary <TabPrimary
id="installPWA" id="installPWA"
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('install_pwa')" :title="$t('header.install_pwa')"
icon="offline_bolt" icon="offline_bolt"
@click.native="showInstallPrompt()" @click.native="showInstallPrompt()"
/> />
<span tabindex="-1"> <span tabindex="-1">
<ButtonPrimary <ButtonPrimary
v-if="currentUser === null" v-if="currentUser === null"
label="Get Started" label="Login"
@click.native="showLogin = true" @click.native="showLogin = true"
/> />
<tippy <tippy
@@ -34,11 +43,11 @@
:url="currentUser.photoURL" :url="currentUser.photoURL"
:alt="currentUser.displayName" :alt="currentUser.displayName"
:title=" :title="
(currentUser.displayName || `${currentUser.displayName || 'Name not found'}` +
'<label><i>Name not found</i></label>') +
'<br>' + '<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 <TabPrimary
v-else v-else
@@ -47,12 +56,6 @@
icon="account_circle" icon="account_circle"
/> />
</template> </template>
<SmartItem
to="/profile"
icon="person"
:label="$t('profile')"
@click.native="$refs.user.tippy().hide()"
/>
<SmartItem <SmartItem
to="/settings" to="/settings"
icon="settings" icon="settings"
@@ -62,56 +65,8 @@
<FirebaseLogout @confirm-logout="$refs.user.tippy().hide()" /> <FirebaseLogout @confirm-logout="$refs.user.tippy().hide()" />
</tippy> </tippy>
</span> </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> </div>
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" /> <FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
<AppExtensions
:show="showExtensions"
@hide-modal="showExtensions = false"
/>
<AppShortcuts :show="showShortcuts" @hide-modal="showShortcuts = false" />
</header> </header>
</template> </template>
@@ -119,7 +74,6 @@
import intializePwa from "~/helpers/pwa" import intializePwa from "~/helpers/pwa"
import { currentUser$ } from "~/helpers/fb/auth" import { currentUser$ } from "~/helpers/fb/auth"
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence" import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
// import { hasExtensionInstalled } from "~/helpers/strategies/ExtensionStrategy"
export default { export default {
data() { data() {
@@ -129,9 +83,7 @@ export default {
// prompt. // prompt.
showInstallPrompt: null, showInstallPrompt: null,
showLogin: false, showLogin: false,
showExtensions: false, isOnLine: navigator.onLine,
showShortcuts: false,
navigatorShare: navigator.share,
} }
}, },
subscriptions() { subscriptions() {
@@ -140,6 +92,13 @@ export default {
} }
}, },
async mounted() { async mounted() {
window.addEventListener("online", () => {
this.isOnLine = true
})
window.addEventListener("offline", () => {
this.isOnLine = false
})
// Initializes the PWA code - checks if the app is installed, // Initializes the PWA code - checks if the app is installed,
// etc. // etc.
this.showInstallPrompt = await intializePwa() this.showInstallPrompt = await intializePwa()
@@ -151,7 +110,7 @@ export default {
theme: "toasted-primary", theme: "toasted-primary",
action: [ action: [
{ {
text: this.$t("dismiss"), text: this.$t("action.dismiss"),
onClick: (_, toastObject) => { onClick: (_, toastObject) => {
setLocalConfig("cookiesAllowed", "yes") setLocalConfig("cookiesAllowed", "yes")
toastObject.goAway(0) 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> </script>

View File

@@ -1,19 +1,7 @@
<template> <template>
<svg <svg class="logo" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
class="logo fill-current" <circle class="fill-current" r="8" cx="50%" cy="50%" />
xmlns="http://www.w3.org/2000/svg" <circle class="fill-primary" r="6" cx="50%" cy="50%" />
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> </svg>
</template> </template>

View File

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

View File

@@ -1,44 +1,76 @@
<template> <template>
<SmartModal v-if="show" @close="hideModal"> <AppSlideOver :show="show" @close="close()">
<template #header> <template #content>
<h3 class="heading">{{ $t("shortcuts") }}</h3>
<div>
<ButtonSecondary icon="close" @click.native="hideModal" />
</div>
</template>
<template #body>
<div class="px-2">
<div <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=" class="
py-2 bg-primary
px-4 border-b border-dividerLight
m-1 flex
text-xs p-2
border border-divider top-0
rounded-lg z-10
font-bold items-center
sticky
justify-between
" "
> >
{{ key }} <h3 class="ml-4 heading">{{ $t("shortcuts") }}</h3>
</kbd> <div>
<span class="flex text-xs ml-4"> <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 }} {{ shortcut.label }}
</span> </span>
<span
v-for="(key, keyIndex) in shortcut.keys"
:key="`map-${mapIndex}-shortcut-${shortcutIndex}-key-${keyIndex}`"
class="shortcut-key"
>
{{ key }}
</span>
</div>
</div> </div>
</div> </div>
</template> </template>
</SmartModal> </AppSlideOver>
</template> </template>
<script> <script>
import { getPlatformSpecialKey } from "~/helpers/platformutils" import {
getPlatformSpecialKey,
getPlatformAlternateKey,
} from "~/helpers/platformutils"
export default { export default {
props: { props: {
@@ -46,59 +78,85 @@ export default {
}, },
data() { data() {
return { return {
filterText: "",
mappings: [
{
section: "General",
shortcuts: [ shortcuts: [
{ {
keys: [this.getSpecialKey(), "G"], keys: [getPlatformSpecialKey(), "G"],
label: this.$t("send_request"), label: this.$t("shortcut.send_request"),
}, },
{ {
keys: [this.getSpecialKey(), "S"], keys: [getPlatformSpecialKey(), "S"],
label: this.$t("save_to_collections"), label: this.$t("shortcut.save_to_collections"),
}, },
{ {
keys: [this.getSpecialKey(), "K"], keys: [getPlatformSpecialKey(), "K"],
label: this.$t("copy_request_link"), label: this.$t("shortcut.copy_request_link"),
}, },
{ {
keys: [this.getSpecialKey(), "I"], keys: [getPlatformSpecialKey(), "I"],
label: this.$t("reset_request"), label: this.$t("shortcut.reset_request"),
},
],
}, },
{ {
keys: ["Alt", "▲"], section: "Request",
label: this.$t("select_next_method"), shortcuts: [
{
keys: [getPlatformAlternateKey(), "↑"],
label: this.$t("shortcut.next_method"),
}, },
{ {
keys: ["Alt", ""], keys: [getPlatformAlternateKey(), ""],
label: this.$t("select_previous_method"), label: this.$t("shortcut.previous_method"),
}, },
{ {
keys: ["Alt", "G"], keys: [getPlatformAlternateKey(), "G"],
label: this.$t("select_get_method"), label: this.$t("shortcut.get_method"),
}, },
{ {
keys: ["Alt", "H"], keys: [getPlatformAlternateKey(), "H"],
label: this.$t("select_head_method"), label: this.$t("shortcut.head_method"),
}, },
{ {
keys: ["Alt", "P"], keys: [getPlatformAlternateKey(), "P"],
label: this.$t("select_post_method"), label: this.$t("shortcut.post_method"),
}, },
{ {
keys: ["Alt", "U"], keys: [getPlatformAlternateKey(), "U"],
label: this.$t("select_put_method"), label: this.$t("shortcut.put_method"),
}, },
{ {
keys: ["Alt", "X"], keys: [getPlatformAlternateKey(), "X"],
label: this.$t("select_delete_method"), label: this.$t("shortcut.delete_method"),
},
],
}, },
], ],
} }
}, },
watch: {
$route() {
this.$emit("close")
},
},
methods: { methods: {
getSpecialKey: getPlatformSpecialKey, close() {
hideModal() { this.$emit("close")
this.$emit("hide-modal")
}, },
}, },
} }
</script> </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)" :to="localePath(navigation.target)"
class="nav-link" 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> <span>{{ navigation.title }}</span>
</nuxt-link> </nuxt-link>
</nav> </nav>
@@ -19,12 +24,31 @@ export default {
data() { data() {
return { return {
primaryNavigation: [ primaryNavigation: [
{ target: "index", icon: "home", title: "Home" }, {
{ target: "realtime", icon: "language", title: "Realtime" }, target: "index",
{ target: "graphql", icon: "code", title: "GraphQL" }, icon: "settings_ethernet",
{ target: "doc", icon: "book", title: "Docs" }, title: this.$t("navigation.rest"),
{ target: "profile", icon: "person", title: "Profile" }, },
{ target: "settings", icon: "settings", title: "Settings" }, {
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"> <style scoped lang="scss">
.nav-link { .nav-link {
@apply p-4; @apply p-4;
@apply flex-col; @apply flex flex-col flex-1;
@apply flex-1;
@apply hover:bg-primaryDark;
@apply hover:text-secondaryDark;
@apply items-center; @apply items-center;
@apply justify-center; @apply justify-center;
@apply transition; @apply transition;
@apply hover:bg-primaryDark;
@apply hover:text-secondaryDark;
.material-icons { .material-icons,
@apply transition-opacity; .svg-icons {
@apply opacity-50; @apply opacity-75;
} }
span { span {
@apply mt-2; @apply mt-2;
@apply text-xs;
@apply font-semibold; @apply font-semibold;
} }
@@ -57,7 +79,8 @@ export default {
@apply text-accent; @apply text-accent;
@apply hover:text-accent; @apply hover:text-accent;
.material-icons { .material-icons,
.svg-icons {
@apply opacity-100; @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" :exact="exact"
:blank="blank" :blank="blank"
class=" class="
font-bold
py-2
transition
inline-flex inline-flex
items-center items-center
justify-center justify-center
py-2
font-semibold
transition
focus:outline-none focus:outline-none
" "
:class="[ :class="[
color color
? `text-${color}-800 bg-${color}-200 hover:text-${color}-900 hover:bg-${color}-300 focus:text-${color}-900 focus:bg-${color}-300` ? `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', label ? 'px-4' : 'px-2',
rounded ? 'rounded-full' : 'rounded-lg', rounded ? 'rounded-full' : 'rounded',
{ 'opacity-50 cursor-not-allowed': disabled }, { 'opacity-75 cursor-not-allowed': disabled },
{ 'pointer-events-none': loading }, { 'pointer-events-none': loading },
{ 'px-4 py-4 text-lg': large }, { 'px-6 py-4 text-lg': large },
{ 'shadow-lg hover:shadow-xl': shadow }, { 'shadow-lg hover:shadow-xl': shadow },
{ {
'text-white bg-gradient-to-tr from-gradientFrom via-gradientVia to-gradientTo': 'text-white bg-gradient-to-tr from-gradientFrom via-gradientVia to-gradientTo':
gradient, gradient,
}, },
{
'border border-accent hover:border-accentDark focus:border-accentDark':
outline,
},
]" ]"
:disabled="disabled" :disabled="disabled"
:tabindex="loading ? '-1' : '0'" :tabindex="loading ? '-1' : '0'"
@@ -37,27 +41,41 @@
> >
<i <i
v-if="icon" v-if="icon"
:class="label ? (reverse ? 'ml-2' : 'mr-2') : ''" :class="[
class="material-icons" 'material-icons',
{ '!text-2xl': large },
label ? (reverse ? 'ml-2' : 'mr-2') : '',
]"
> >
{{ icon }} {{ icon }}
</i> </i>
<SmartIcon <SmartIcon
v-if="svg" v-if="svg"
:name="svg" :name="svg"
:class="label ? (reverse ? 'ml-4' : 'mr-4') : ''" :class="[
class="svg-icons" 'svg-icons',
{ '!h-6 !w-6': large },
label ? (reverse ? 'ml-2' : 'mr-2') : '',
]"
/> />
{{ label }} {{ label }}
<span v-if="shortkey" class="px-1 ml-2 rounded bg-accentLight">{{ <div v-if="shortcut.length && SHORTCUT_INDICATOR" class="ml-2">
shortkey <kbd
}}</span> v-for="(key, index) in shortcut"
:key="`key-${index}`"
class="bg-accentLight rounded ml-1 px-1 inline-flex"
>
{{ key }}
</kbd>
</div>
</span> </span>
<SmartSpinner v-else /> <SmartSpinner v-else />
</SmartLink> </SmartLink>
</template> </template>
<script> <script>
import { getSettingSubject } from "~/newstore/settings"
export default { export default {
props: { props: {
to: { to: {
@@ -116,10 +134,24 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
shortkey: { outline: {
type: String, type: Boolean,
default: "", default: false,
}, },
shortcut: {
type: Array,
default: () => [],
},
},
data() {
return {
SHORTCUT_INDICATOR: null,
}
},
subscriptions() {
return {
SHORTCUT_INDICATOR: getSettingSubject("SHORTCUT_INDICATOR"),
}
}, },
} }
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,15 +2,14 @@
<div class="flex items-center group"> <div class="flex items-center group">
<span <span
class=" class="
font-mono font-bold cursor-pointer
flex flex
font-mono font-bold
mx-2
w-12
justify-center justify-center
items-center items-center
text-xs
w-12
mx-2
truncate truncate
cursor-pointer
" "
:class="entryStatus.className" :class="entryStatus.className"
data-testid="restore_history_entry" data-testid="restore_history_entry"
@@ -21,15 +20,15 @@
</span> </span>
<span <span
class=" class="
py-3
cursor-pointer cursor-pointer
pr-2 flex
flex flex-1
min-w-0
text-xs
group-hover:text-secondaryDark
transition
font-semibold font-semibold
flex-1
min-w-0
py-2
pr-2
transition
group-hover:text-secondaryDark
" "
data-testid="restore_history_entry" data-testid="restore_history_entry"
:title="duration" :title="duration"
@@ -42,15 +41,15 @@
<ButtonSecondary <ButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
icon="delete" icon="delete"
color="red"
:title="$t('delete')" :title="$t('delete')"
class="group-hover:inline-flex hidden" class="group-hover:inline-flex hidden"
color="red"
data-testid="delete_history_entry" data-testid="delete_history_entry"
@click.native="$emit('delete-entry')" @click.native="$emit('delete-entry')"
/> />
<ButtonSecondary <ButtonSecondary
v-tippy="{ theme: 'tooltip' }" 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 }" :class="{ 'group-hover:inline-flex hidden': !entry.star }"
:icon="entry.star ? 'star' : 'star_border'" :icon="entry.star ? 'star' : 'star_border'"
color="yellow" 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"> <AppSection label="bodyParameters">
<div <div
class=" class="
sticky
top-110px
z-10
bg-primary bg-primary
border-b border-dividerLight
flex flex-1 flex flex-1
pl-4
top-24
z-10
sticky
items-center items-center
justify-between 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") }} {{ $t("request_body") }}
</label> </label>
<div class="flex">
<ButtonSecondary <ButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('clear')" :title="$t('clear')"
@@ -23,29 +24,24 @@
@click.native="clearContent('bodyParams', $event)" @click.native="clearContent('bodyParams', $event)"
/> />
</div> </div>
</div>
<div <div
v-for="(param, index) in bodyParams" v-for="(param, index) in bodyParams"
:key="`param-${index}`" :key="`param-${index}`"
class=" class="divide-x divide-dividerLight border-b border-dividerLight flex"
flex
border-b border-dashed
divide-x
border-divider
divide-dashed divide-divider
"
:class="{ 'border-t': index == 0 }" :class="{ 'border-t': index == 0 }"
> >
<input <input
class=" class="
px-4
py-3
text-xs
flex flex-1
font-semibold
bg-primaryLight bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none focus:outline-none
" "
:placeholder="$t('parameter_count', { count: index + 1 })" :placeholder="$t('count.parameter', { count: index + 1 })"
:name="'param' + index" :name="'param' + index"
:value="param.key" :value="param.key"
autofocus autofocus
@@ -55,15 +51,15 @@
<input <input
v-if="!requestBodyParamIsFile(index)" v-if="!requestBodyParamIsFile(index)"
class=" class="
px-4
py-3
text-xs
flex flex-1
font-semibold
bg-primaryLight bg-primaryLight
flex
font-semibold font-mono
flex-1
py-2
px-4
focus:outline-none focus:outline-none
" "
:placeholder="$t('value_count', { count: index + 1 })" :placeholder="$t('count.value', { count: index + 1 })"
:name="'value' + index" :name="'value' + index"
:value="param.value" :value="param.value"
@change=" @change="
@@ -90,9 +86,9 @@
:title=" :title="
param.hasOwnProperty('active') param.hasOwnProperty('active')
? param.active ? param.active
? $t('turn_off') ? $t('action.turn_off')
: $t('turn_on') : $t('action.turn_on')
: $t('turn_off') : $t('action.turn_off')
" "
:icon=" :icon="
param.hasOwnProperty('active') param.hasOwnProperty('active')
@@ -101,10 +97,11 @@
: 'check_box_outline_blank' : 'check_box_outline_blank'
: 'check_box' : 'check_box'
" "
color="green"
@click.native="toggleActive(index, param)" @click.native="toggleActive(index, param)"
/> />
</div> </div>
<div v-if="contentType === 'multipart/form-data'"> <div>
<label for="attachment" class="p-0"> <label for="attachment" class="p-0">
<ButtonSecondary <ButtonSecondary
class="w-full" class="w-full"
@@ -126,6 +123,7 @@
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')" :title="$t('delete')"
icon="delete" icon="delete"
color="red"
@click.native="removeRequestBodyParam(index)" @click.native="removeRequestBodyParam(index)"
/> />
</div> </div>

View File

@@ -5,8 +5,8 @@
<ButtonSecondary icon="close" @click.native="hideModal" /> <ButtonSecondary icon="close" @click.native="hideModal" />
</template> </template>
<template #body> <template #body>
<div class="px-2 flex flex-col"> <div class="flex flex-col px-2">
<label for="requestType" class="px-4 font-semibold pb-4 text-xs"> <label for="requestType" class="font-semibold px-4 pb-4">
{{ $t("choose_language") }} {{ $t("choose_language") }}
</label> </label>
<div class="flex flex-1"> <div class="flex flex-1">
@@ -22,20 +22,19 @@
<template #trigger> <template #trigger>
<span <span
class=" class="
flex
w-full
px-4
text-xs
py-3
rounded-lg
font-semibold
focus:outline-none
border-b border-dividerLight
bg-primaryLight bg-primaryLight
border border-dividerLight
rounded
cursor-pointer 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> </span>
</template> </template>
<SmartItem <SmartItem
@@ -43,72 +42,109 @@
:key="`gen-${index}`" :key="`gen-${index}`"
:label="gen.name" :label="gen.name"
@click.native=" @click.native="
requestType = gen.id codegenType = gen.id
$refs.options.tippy().hide() $refs.options.tippy().hide()
" "
/> />
</tippy> </tippy>
</span> </span>
</div> </div>
<div class="flex justify-between flex-1"> <div class="flex flex-1 justify-between">
<label <label for="generatedCode" class="font-semibold px-4 pt-4 pb-4">
for="generatedCode"
class="px-4 pt-4 font-semibold pb-4 text-xs"
>
{{ $t("generated_code") }} {{ $t("generated_code") }}
</label> </label>
<ButtonSecondary
ref="copyRequestCode"
v-tippy="{ theme: 'tooltip' }"
:title="$t('copy_code')"
:icon="copyIcon"
@click.native="copyRequestCode"
/>
</div> </div>
<SmartAceEditor <SmartAceEditor
v-if="requestType" v-if="codegenType"
ref="generatedCode" ref="generatedCode"
:value="requestCode" :value="requestCode"
:lang="codegens.find((x) => x.id === requestType).language" :lang="codegens.find((x) => x.id === codegenType).language"
:options="{ :options="{
maxLines: '10', maxLines: 16,
minLines: '10', minLines: 8,
fontSize: '14px', fontSize: '12px',
autoScrollEditorIntoView: true, autoScrollEditorIntoView: true,
readOnly: true, readOnly: true,
showPrintMargin: false, showPrintMargin: false,
useWorker: false, useWorker: false,
}" }"
styles="rounded-lg" styles="rounded"
/> />
</div> </div>
</template> </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> </SmartModal>
</template> </template>
<script> <script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { codegens } from "~/helpers/codegen/codegen" 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: { props: {
show: Boolean, show: Boolean,
requestCode: { type: String, default: null },
requestTypeProp: { type: String, default: "curl" },
}, },
data() { data() {
return { return {
codegens, codegens,
copyIcon: "content_copy", copyIcon: "content_copy",
request: getRESTRequest(),
codegenType: "curl",
} }
}, },
computed: { computed: {
requestType: { requestCode(): string {
get() { const effectiveRequest = getEffectiveRESTRequest(
return this.requestTypeProp 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: { methods: {
@@ -119,15 +155,13 @@ export default {
this.$emit("handle-import") this.$emit("handle-import")
}, },
copyRequestCode() { copyRequestCode() {
this.$refs.generatedCode.editor.selectAll() copyToClipboard(this.requestCode)
this.$refs.generatedCode.editor.focus()
document.execCommand("copy")
this.copyIcon = "done" this.copyIcon = "done"
this.$toast.success(this.$t("copied_to_clipboard"), { this.$toast.success(this.$t("copied_to_clipboard").toString(), {
icon: "done", icon: "done",
}) })
setTimeout(() => (this.copyIcon = "content_copy"), 1000) setTimeout(() => (this.copyIcon = "content_copy"), 1000)
}, },
}, },
} })
</script> </script>

View File

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

View File

@@ -1,41 +1,134 @@
<template> <template>
<SmartModal v-if="show" @close="hideModal"> <SmartModal v-if="show" @close="hideModal">
<template #header> <template #header>
<h3 class="heading">{{ $t("import_curl") }}</h3> <h3 class="heading">{{ $t("import.curl") }}</h3>
<div> <div>
<ButtonSecondary icon="close" @click.native="hideModal" /> <ButtonSecondary icon="close" @click.native="hideModal" />
</div> </div>
</template> </template>
<template #body> <template #body>
<div class="flex flex-col px-2">
<textarea <textarea
id="import-curl" id="import-curl"
v-model="curl"
class="textarea" class="textarea"
autofocus autofocus
rows="8" rows="8"
:placeholder="$t('enter_curl')" :placeholder="$t('enter_curl')"
></textarea> ></textarea>
</div>
</template> </template>
<template #footer> <template #footer>
<span> <span>
<ButtonPrimary :label="$t('import')" @click.native="handleImport" /> <ButtonPrimary
:label="$t('import.title')"
@click.native="handleImport"
/>
<ButtonSecondary :label="$t('cancel')" @click.native="hideModal" /> <ButtonSecondary :label="$t('cancel')" @click.native="hideModal" />
</span> </span>
</template> </template>
</SmartModal> </SmartModal>
</template> </template>
<script> <script lang="ts">
export default { 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: { props: {
show: Boolean, show: Boolean,
}, },
emits: ["hide-modal"],
data() {
return {
curl: "",
}
},
methods: { methods: {
hideModal() { hideModal() {
this.$emit("hide-modal") this.$emit("hide-modal")
}, },
handleImport() { 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> </script>

View File

@@ -2,21 +2,21 @@
<AppSection label="parameters"> <AppSection label="parameters">
<div <div
class=" class="
sticky
top-110px
z-10
bg-primary bg-primary
border-b border-dividerLight
flex flex-1 flex flex-1
pl-4
top-24
z-10
sticky
items-center items-center
justify-between justify-between
pl-4
border-b border-dividerLight
" "
> >
<label for="paramList" class="font-semibold text-xs"> <label class="font-semibold">
{{ $t("parameter_list") }} {{ $t("parameter_list") }}
</label> </label>
<div> <div class="flex">
<ButtonSecondary <ButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('clear_all')" :title="$t('clear_all')"
@@ -25,7 +25,7 @@
/> />
<ButtonSecondary <ButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('add_new')" :title="$t('add.new')"
icon="add" icon="add"
@click.native="addParam" @click.native="addParam"
/> />
@@ -34,26 +34,42 @@
<div <div
v-for="(param, index) in params$" v-for="(param, index) in params$"
:key="`param-${index}`" :key="`param-${index}`"
class=" class="divide-x divide-dividerLight border-b border-dividerLight flex"
flex
border-b border-dashed
divide-x
border-divider
divide-dashed divide-divider
"
:class="{ 'border-t': index == 0 }" :class="{ 'border-t': index == 0 }"
> >
<input <SmartEnvInput
class=" v-if="EXPERIMENTAL_URL_BAR_ENABLED"
px-4 v-model="param.key"
py-3 :placeholder="$t('count.parameter', { count: index + 1 })"
text-xs styles="
flex flex-1
font-semibold
bg-primaryLight bg-primaryLight
flex
font-semibold font-mono
flex-1
py-1
px-4
focus:outline-none 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" :name="'param' + index"
:value="param.key" :value="param.key"
autofocus autofocus
@@ -65,17 +81,39 @@
}) })
" "
/> />
<input <SmartEnvInput
class=" v-if="EXPERIMENTAL_URL_BAR_ENABLED"
px-4 v-model="param.value"
py-3 :placeholder="$t('count.value', { count: index + 1 })"
text-xs styles="
flex flex-1
font-semibold
bg-primaryLight bg-primaryLight
flex
font-semibold font-mono
flex-1
py-1
px-4
focus:outline-none 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" :name="'value' + index"
:value="param.value" :value="param.value"
@change=" @change="
@@ -92,9 +130,9 @@
:title=" :title="
param.hasOwnProperty('active') param.hasOwnProperty('active')
? param.active ? param.active
? $t('turn_off') ? $t('action.turn_off')
: $t('turn_on') : $t('action.turn_on')
: $t('turn_off') : $t('action.turn_off')
" "
:icon=" :icon="
param.hasOwnProperty('active') param.hasOwnProperty('active')
@@ -103,6 +141,7 @@
: 'check_box_outline_blank' : 'check_box_outline_blank'
: 'check_box' : 'check_box'
" "
color="green"
@click.native=" @click.native="
updateParam(index, { updateParam(index, {
key: param.key, key: param.key,
@@ -117,10 +156,25 @@
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')" :title="$t('delete')"
icon="delete" icon="delete"
color="red"
@click.native="deleteParam(index)" @click.native="deleteParam(index)"
/> />
</div> </div>
</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> </AppSection>
</template> </template>
@@ -132,6 +186,7 @@ import {
deleteRESTParam, deleteRESTParam,
deleteAllRESTParams, deleteAllRESTParams,
} from "~/newstore/RESTSession" } from "~/newstore/RESTSession"
import { getSettingSubject } from "~/newstore/settings"
export default { export default {
data() { data() {
@@ -142,20 +197,24 @@ export default {
subscriptions() { subscriptions() {
return { return {
params$: restParams$, params$: restParams$,
EXPERIMENTAL_URL_BAR_ENABLED: getSettingSubject(
"EXPERIMENTAL_URL_BAR_ENABLED"
),
} }
}, },
// watch: { watch: {
// params$: { params$: {
// handler(newValue) { handler(newValue) {
// if ( if (
// newValue[newValue.length - 1]?.key !== "" || (newValue[newValue.length - 1]?.key !== "" ||
// newValue[newValue.length - 1]?.value !== "" newValue[newValue.length - 1]?.value !== "") &&
// ) newValue.length
// this.addParam() )
// }, this.addParam()
// deep: true, },
// }, deep: true,
// }, },
},
mounted() { mounted() {
if (!this.params$?.length) { if (!this.params$?.length) {
this.addParam() 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>
<div <div
class=" class="
sticky
top-110px
z-10
bg-primary bg-primary
border-b border-dividerLight
flex flex-1 flex flex-1
pl-4
top-24
z-10
sticky
items-center items-center
justify-between 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") }} {{ $t("raw_request_body") }}
</label> </label>
<div> <div class="flex">
<ButtonSecondary <ButtonSecondary
v-if="rawInput && contentType.endsWith('json')" v-if="contentType.endsWith('json')"
ref="prettifyRequest" ref="prettifyRequest"
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('prettify_body')" :title="$t('prettify_body')"
@@ -28,7 +28,7 @@
<label for="payload"> <label for="payload">
<ButtonSecondary <ButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('import_json')" :title="$t('import.json')"
icon="post_add" icon="post_add"
@click.native="$refs.payload.click()" @click.native="$refs.payload.click()"
/> />
@@ -53,9 +53,9 @@
v-model="rawParamsBody" v-model="rawParamsBody"
:lang="rawInputEditorLang" :lang="rawInputEditorLang"
:options="{ :options="{
maxLines: '16', maxLines: 16,
minLines: '8', minLines: 8,
fontSize: '14px', fontSize: '12px',
autoScrollEditorIntoView: true, autoScrollEditorIntoView: true,
showPrintMargin: false, showPrintMargin: false,
useWorker: false, useWorker: false,
@@ -66,43 +66,39 @@
</template> </template>
<script> <script>
import { defineComponent } from "@nuxtjs/composition-api"
import { getEditorLangForMimeType } from "~/helpers/editorutils" import { getEditorLangForMimeType } from "~/helpers/editorutils"
import { pluckRef } from "~/helpers/utils/composables"
import { useRESTRequestBody } from "~/newstore/RESTSession"
export default { export default defineComponent({
props: { props: {
rawParams: { type: String, default: null }, contentType: {
contentType: { type: String, default: null }, type: String,
rawInput: { type: Boolean, default: false }, required: true,
}, },
data() { },
setup() {
return { return {
rawParamsBody: pluckRef(useRESTRequestBody(), "body"),
prettifyIcon: "photo_filter", prettifyIcon: "photo_filter",
} }
}, },
computed: { computed: {
rawParamsBody: {
get() {
return this.rawParams
},
set(value) {
this.$emit("update-raw-body", value)
},
},
rawInputEditorLang() { rawInputEditorLang() {
return getEditorLangForMimeType(this.contentType) return getEditorLangForMimeType(this.contentType)
}, },
}, },
methods: { methods: {
clearContent(bodyParams, $event) { clearContent() {
this.$emit("clear-content", bodyParams, $event) this.rawParamsBody = ""
}, },
uploadPayload() { uploadPayload() {
this.$emit("update-raw-input", true)
const file = this.$refs.payload.files[0] const file = this.$refs.payload.files[0]
if (file !== undefined && file !== null) { if (file !== undefined && file !== null) {
const reader = new FileReader() const reader = new FileReader()
reader.onload = ({ target }) => { reader.onload = ({ target }) => {
this.$emit("update-raw-body", target.result) this.rawParamsBody = target.result
} }
reader.readAsText(file) reader.readAsText(file)
this.$toast.info(this.$t("file_imported"), { this.$toast.info(this.$t("file_imported"), {
@@ -128,5 +124,5 @@ export default {
} }
}, },
}, },
} })
</script> </script>

View File

@@ -1,9 +1,9 @@
<template> <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"> <div class="relative inline-flex">
<span class="select-wrapper"> <span class="select-wrapper">
<tippy <tippy
ref="options" ref="methodOptions"
interactive interactive
tabindex="-1" tabindex="-1"
trigger="click" trigger="click"
@@ -14,23 +14,23 @@
<input <input
id="method" id="method"
class=" class="
flex
rounded-l-lg
bg-primaryLight bg-primaryLight
font-mono
w-32
px-4
py-2
truncate
text-secondaryDark
font-semibold
border border-divider border border-divider
transition rounded-l
focus:outline-none focus:border-accent
cursor-pointer 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$" :value="newMethod"
autofocus readonly
/> />
</template> </template>
<SmartItem <SmartItem
@@ -38,79 +38,64 @@
:key="`method-${index}`" :key="`method-${index}`"
:label="method" :label="method"
class="font-mono" class="font-mono"
@click.native=" @click.native="onSelectMethod(method)"
updateMethod(method)
$refs.options.tippy().hide()
"
/> />
</tippy> </tippy>
</span> </span>
</div> </div>
<div class="flex-1 inline-flex"> <div class="flex-1 inline-flex">
<input <SmartEnvInput
id="url" v-if="EXPERIMENTAL_URL_BAR_ENABLED"
v-model="newEndpoint$" v-model="newEndpoint"
class=" :placeholder="$t('url')"
w-full styles="
font-mono font-semibold
truncate
text-secondaryDark
px-4
py-2
border border-divider
bg-primaryLight bg-primaryLight
border border-divider
flex
font-semibold font-mono
flex-1
text-secondaryDark
py-1
px-4
transition 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 focus:outline-none focus:border-accent
" "
name="url" name="url"
type="text" type="text"
spellcheck="false" spellcheck="false"
:placeholder="$t('url')" :placeholder="$t('url')"
autofocus
@keyup.enter="newSendRequest()" @keyup.enter="newSendRequest()"
/> />
<!-- <SmartUrlField v-else v-model="uri" /> -->
</div> </div>
<div class="flex"> <div class="flex">
<span <ButtonPrimary
id="send" id="send"
class=" class="rounded-none min-w-20"
px-4 :label="!loading ? $t('send') : $t('cancel')"
py-2 @click.native="!loading ? newSendRequest() : cancelRequest()"
border border-accent />
font-mono <span class="inline-flex">
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> -->
<tippy <tippy
ref="sendOptions" ref="sendOptions"
interactive interactive
@@ -120,40 +105,22 @@
arrow arrow
> >
<template #trigger> <template #trigger>
<span <ButtonPrimary class="rounded-l-none" icon="keyboard_arrow_down" />
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>
</template> </template>
<SmartItem <SmartItem
:label="$t('import_curl')" :label="$t('import.curl')"
icon="import_export" icon="import_export"
@click.native=" @click.native="
showCurlImportModal = !showCurlImportModal showCurlImportModal = !showCurlImportModal
$refs.sendOptions.tippy().hide() sendOptions.tippy().hide()
" "
/> />
<SmartItem <SmartItem
:label="$t('show_code')" :label="$t('show.code')"
icon="code" icon="code"
@click.native=" @click.native="
showCodegenModal = !showCodegenModal showCodegenModal = !showCodegenModal
$refs.sendOptions.tippy().hide() sendOptions.tippy().hide()
" "
/> />
<SmartItem <SmartItem
@@ -161,30 +128,20 @@
:label="$t('clear_all')" :label="$t('clear_all')"
icon="clear_all" icon="clear_all"
@click.native=" @click.native="
clearContent('', $event) clearContent()
$refs.sendOptions.tippy().hide() sendOptions.tippy().hide()
" "
/> />
</tippy> </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> </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 <tippy
ref="saveOptions" ref="saveOptions"
interactive interactive
@@ -194,72 +151,76 @@
arrow arrow
> >
<template #trigger> <template #trigger>
<span <ButtonSecondary
class=" icon="keyboard_arrow_down"
px-1 outline
py-2 class="rounded-l-none h-8"
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>
</template> </template>
<SmartItem :description="$t('token_req_name')" />
<input <input
id="request-name" id="request-name"
v-model="name" v-model="requestName"
:placeholder="$t('request_name')"
name="request-name" name="request-name"
type="text" type="text"
class="input text-sm" class="mb-2 input"
/> />
<SmartItem <SmartItem
ref="copyRequest" ref="copyRequest"
:label="$t('copy_request_link')" :label="$t('request.copy_link')"
:icon="navigatorShare ? 'share' : 'content_copy'" :icon="hasNavigatorShare ? 'share' : 'content_copy'"
@click.native=" @click.native="
copyRequest() copyRequest()
$refs.saveOptions.tippy().hide() saveOptions.tippy().hide()
" "
/> />
<SmartItem <SmartItem
ref="saveRequest" ref="saveRequest"
:label="$t('save_to_collections')" :label="$t('request.save_as')"
icon="create_new_folder" icon="create_new_folder"
@click.native=" @click.native="
saveRequest() showSaveRequestModal = true
$refs.saveOptions.tippy().hide() saveOptions.tippy().hide()
" "
/> />
</tippy> </tippy>
</span>
</div> </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> </div>
</template> </template>
<script> <script lang="ts">
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api"
import { import {
updateRESTResponse, updateRESTResponse,
restRequest$,
restEndpoint$, restEndpoint$,
setRESTEndpoint, setRESTEndpoint,
restMethod$, restMethod$,
updateRESTMethod, updateRESTMethod,
resetRESTRequest,
useRESTRequestName,
} from "~/newstore/RESTSession" } from "~/newstore/RESTSession"
import { createRESTNetworkRequestStream } from "~/helpers/network" import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { currentEnvironment$ } from "~/newstore/environments" import { runRESTRequest$ } from "~/helpers/RequestRunner"
import { getEffectiveRESTRequestStream } from "~/helpers/utils/EffectiveURL" 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 { const methods = [
data() {
return {
newMethod$: "",
methods: [
"GET", "GET",
"HEAD", "HEAD",
"POST", "POST",
@@ -270,47 +231,162 @@ export default {
"TRACE", "TRACE",
"PATCH", "PATCH",
"CUSTOM", "CUSTOM",
], ]
name: "",
newEndpoint$: "", export default defineComponent({
showCurlImportModal: false, setup() {
showCodegenModal: false, const {
navigatorShare: navigator.share, $toast,
effectiveStream$: null, app: { i18n },
} } = useContext()
}, const t = i18n.t.bind(i18n)
subscriptions() { const { subscribeToStream } = useStreamSubscriber()
return {
newMethod$: restMethod$, const newEndpoint = useStream(restEndpoint$, "", setRESTEndpoint)
newEndpoint$: restEndpoint$, const newMethod = useStream(restMethod$, "", updateRESTMethod)
effectiveStream$: getEffectiveRESTRequestStream(
restRequest$, const loading = ref(false)
currentEnvironment$
), const showCurlImportModal = ref(false)
} const showCodegenModal = ref(false)
}, const showSaveRequestModal = ref(false)
watch: {
newEndpoint$(newVal) { const hasNavigatorShare = !!navigator.share
setRESTEndpoint(newVal)
}, // Template refs
}, //
mounted() {}, const methodOptions = ref<any | null>(null)
methods: { const saveOptions = ref<any | null>(null)
updateMethod(method) { const sendOptions = ref<any | null>(null)
updateRESTMethod(method)
}, const newSendRequest = () => {
newSendRequest() { loading.value = true
this.$subscribeTo(
createRESTNetworkRequestStream( subscribeToStream(
this.effectiveStream$, runRESTRequest$(),
currentEnvironment$
),
(responseState) => { (responseState) => {
console.log(responseState) console.log(responseState)
if (loading.value) {
// Check exists because, loading can be set to false
// when cancelled
updateRESTResponse(responseState) 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> </script>

View File

@@ -1,20 +1,46 @@
<template> <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 <div
v-if="response == null" v-if="response == null"
class=" class="
flex flex-1 flex flex-col flex-1
items-center
text-secondaryLight text-secondaryLight
flex-col items-center
p-4
justify-center justify-center
" "
> >
<i class="material-icons opacity-50 pb-2">send</i> <div class="flex space-x-2 pb-8">
<span class="text-xs text-center"> <div class="flex flex-col space-y-4 items-end">
{{ $t("waiting_send_req") }} <span class="flex flex-1 items-center">
{{ $t("shortcut.send_request") }}
</span> </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>
<div v-else> <div v-else>
<i v-if="response.type === 'loading'" class="animate-spin material-icons"> <i v-if="response.type === 'loading'" class="animate-spin material-icons">
@@ -29,11 +55,11 @@
<span class="text-secondaryDark"> Status: </span> <span class="text-secondaryDark"> Status: </span>
{{ response.statusCode || $t("waiting_send_req") }} {{ response.statusCode || $t("waiting_send_req") }}
</span> </span>
<span v-if="response.meta.responseDuration" class="text-xs"> <span v-if="response.meta && response.meta.responseDuration">
<span class="text-secondaryDark"> Time: </span> <span class="text-secondaryDark"> Time: </span>
{{ `${response.meta.responseDuration} ms` }} {{ `${response.meta.responseDuration} ms` }}
</span> </span>
<span v-if="response.meta.responseSize" class="text-xs"> <span v-if="response.meta && response.meta.responseSize">
<span class="text-secondaryDark"> Size: </span> <span class="text-secondaryDark"> Size: </span>
{{ `${response.meta.responseSize} B` }} {{ `${response.meta.responseSize} B` }}
</span> </span>
@@ -44,6 +70,7 @@
<script> <script>
import findStatusGroup from "~/helpers/findStatusGroup" import findStatusGroup from "~/helpers/findStatusGroup"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
export default { export default {
props: { props: {
@@ -57,5 +84,19 @@ export default {
return findStatusGroup(this.response.statusCode) return findStatusGroup(this.response.statusCode)
}, },
}, },
methods: {
getSpecialKey: getPlatformSpecialKey,
},
} }
</script> </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' }" v-tippy="{ theme: 'tooltip' }"
:title="$t('delete')" :title="$t('delete')"
icon="delete" icon="delete"
color="red"
@click.native="removeOAuthToken(index)" @click.native="removeOAuthToken(index)"
/> />
</li> </li>
</div> </div>
</ul> </ul>
<p v-if="tokens.length === 0"> <p v-if="tokens.length === 0">
{{ $t("empty") }} {{ $t("empty.protocols") }}
</p> </p>
</template> </template>
</SmartModal> </SmartModal>

View File

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

View File

@@ -1,26 +1,25 @@
<template> <template>
<div class="flex flex-col p-4"> <div class="flex flex-col p-4">
<div class="flex flex-col items-center"> <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 FEATURES
</p> </p>
</div> </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 <div
v-for="(feature, index) in features" v-for="(feature, index) in features"
:key="`feature-${index}`" :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"> <div class="flex-grow">
<h2 <h2
class=" class="
mt-4
mb-2
text-lg
font-semibold font-semibold
mt-4
text-lg text-secondaryDark
mb-2
transition transition
text-secondaryDark
" "
> >
{{ feature.title }} {{ feature.title }}
@@ -47,44 +46,44 @@ export default {
features: [ features: [
{ {
icon: "offline_bolt", icon: "offline_bolt",
title: "SaaS Executives", title: "Feature",
description: 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" }, link: { title: "Learn more", target: "/settings" },
}, },
{ {
icon: "stars", icon: "stars",
title: "Product Managers", title: "Feature",
description: 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" }, link: { title: "Learn more", target: "/settings" },
}, },
{ {
icon: "supervised_user_circle", icon: "supervised_user_circle",
title: "Creators", title: "Feature",
description: 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" }, link: { title: "Learn more", target: "/settings" },
}, },
{ {
icon: "build_circle", icon: "build_circle",
title: "Developers", title: "Feature",
description: 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" }, link: { title: "Learn more", target: "/settings" },
}, },
{ {
icon: "monetization_on", icon: "monetization_on",
title: "Finance", title: "Feature",
description: 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" }, link: { title: "Learn more", target: "/settings" },
}, },
{ {
icon: "group_work", icon: "group_work",
title: "Enterprise", title: "Feature",
description: 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" }, link: { title: "Learn more", target: "/settings" },
}, },
], ],

View File

@@ -1,28 +1,19 @@
<template> <template>
<footer class="flex flex-col p-4"> <footer class="flex flex-col p-6">
<nav class="grid grid-cols-2 gap-4 md:grid-cols-4"> <nav class="grid gap-4 grid-cols-2 md:grid-cols-4">
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
<span> <h4 class="font-semibold my-2">Hoppscotch</h4>
<AppLogo class="h-8" /> <ul class="space-y-4">
</span>
<span class="font-bold"> Hoppscotch </span>
<SmartChangeLanguage />
<ul class="space-y-2">
<li> <li>
<SmartAnchor label="Terms" to="/about/terms" class="footer-nav" /> <SmartChangeLanguage />
</li> </li>
<li> <li>
<SmartAnchor <SmartColorModePicker />
label="Privacy"
to="/about/privacy"
class="footer-nav"
/>
</li> </li>
</ul> </ul>
<SmartColorModePicker />
</div> </div>
<div class="flex flex-col space-y-2"> <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"> <ul class="space-y-2">
<li <li
v-for="(item, index) in navigation.solutions" v-for="(item, index) in navigation.solutions"
@@ -37,7 +28,7 @@
</ul> </ul>
</div> </div>
<div class="flex flex-col space-y-2"> <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"> <ul class="space-y-2">
<li <li
v-for="(item, index) in navigation.platform" v-for="(item, index) in navigation.platform"
@@ -52,7 +43,7 @@
</ul> </ul>
</div> </div>
<div class="flex flex-col space-y-2"> <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"> <ul class="space-y-2">
<li <li
v-for="(item, index) in navigation.company" v-for="(item, index) in navigation.company"
@@ -77,72 +68,84 @@ export default {
navigation: { navigation: {
solutions: [ solutions: [
{ {
name: "SaaS", name: "RESTful",
link: "/settings", link: "/",
}, },
{ {
name: "Products", name: "WebSocket",
link: "/settings", link: "/realtime",
}, },
{ {
name: "Creators", name: "SSE",
link: "/settings", link: "/realtime",
}, },
{ {
name: "Developers", name: "Socket.IO",
link: "/settings", link: "/realtime",
}, },
{ {
name: "Finance", name: "MQTT",
link: "/settings", link: "/realtime",
}, },
{ {
name: "Enterprise", name: "GraphQL",
link: "/settings", link: "/graphql",
}, },
], ],
platform: [ platform: [
{ {
name: "Payments", name: "API Designing",
link: "/settings", link: "/",
}, },
{ {
name: "Subscriptions", name: "API Development",
link: "/settings", link: "/",
}, },
{ {
name: "API", name: "API Testing",
link: "https://docs.kooli.tech/api", link: "/",
}, },
{ {
name: "Guides", name: "API Deployment",
link: "https://docs.kooli.tech/guides", link: "/",
},
{
name: "API Documentation",
link: "/documentation",
},
{
name: "Integrations",
link: "/",
}, },
], ],
company: [ company: [
{ {
name: "About", name: "About",
link: "/about", link: "/",
}, },
{ {
name: "Jobs", name: "Careers",
link: "/about/jobs", link: "/careers",
},
{
name: "Integrations",
link: "/about/integrations",
}, },
{ {
name: "Support", name: "Support",
link: "", link: "/",
}, },
{ {
name: "Contact", name: "Contact",
link: "/about/contact", link: "/",
}, },
{ {
name: "Blog", 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> </script>
<style lang="scss" scoped> <style scoped lang="scss">
.footer-nav { .footer-nav {
&:hover, @apply px-2 py-1;
&:focus { @apply -mx-2 -my-1;
@apply text-secondaryDark; @apply hover:text-secondaryDark;
} @apply focus:text-secondaryDark;
} }
</style> </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> <template>
<div class="flex p-4 relative"> <div class="flex flex-col p-6 relative">
<div class="relative my-16 z-10 max-w-3xl"> <div class="flex flex-col mt-16 items-center justify-center">
<h2 <h2
class=" class="
font-extrabold
text-accent text-center
leading-none leading-none
tracking-tighter tracking-tighter
font-semibold text-4xl
text-accent text-4xl md:text-6xl
md:text-5xl lg:text-8xl
lg:text-6xl
" "
> >
Open Source Open Source
</h2> </h2>
<h3 <h3
class=" class="
text-3xl font-extrabold
my-4 my-4
font-mono text-center text-secondaryDark
text-secondaryDark leading-none
tracking-tighter
text-3xl
md:text-4xl md:text-4xl
lg:text-4xl lg:text-5xl
font-semibold
" "
> >
API Development Ecosystem API Development Ecosystem
</h3> </h3>
<p class="text-lg my-4 text-secondaryLight max-w-4/5"> <p class="my-4 text-lg text-center max-w-2xl">
Millions of developers and companies build, ship, and maintain their Thousands of developers and companies build, ship, and maintain their
APIs on Hoppscotch the largest and most advanced development platform APIs on Hoppscotch the transparent and most flexible API development
in the world. ecosystem in the world.
</p> </p>
<div class="my-8 flex items-center"> <div class="flex space-x-4 my-8 justify-center items-center">
<ButtonPrimary <ButtonPrimary
label="Dashboard" label="Get Started"
icon="chevron_right" icon="arrow_forward"
class="my-4" rounded
large
reverse 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> </div>
<!-- <LandingStats /> -->
</div> </div>
<div class="lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2"> <div class="flex flex-col items-center justify-center"></div>
<LandingGlobe class="h-64 w-full sm:h-72 md:h-96 lg:h-full" /> <FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
</div>
</div> </div>
</template> </template>
<script>
export default {
data() {
return {
showLogin: false,
}
},
}
</script>

View File

@@ -1,7 +1,7 @@
<template> <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}`"> <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> {{ stat.count }}<span class="text-secondaryLight">+</span>
</span> </span>
<br /> <br />
@@ -18,7 +18,7 @@ export default {
return { return {
stats: [ stats: [
{ count: "350k", audience: "Developers" }, { count: "350k", audience: "Developers" },
{ count: "10k", audience: "Organizations" }, { count: "5k", audience: "Organizations" },
{ count: "1m", audience: "Requests" }, { count: "1m", audience: "Requests" },
], ],
} }

View File

@@ -1,21 +1,21 @@
<template> <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"> <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 EMPOWERING DEVELOPERS FROM
</p> </p>
</div> </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 <div
v-for="(user, index) in users" v-for="(user, index) in users"
:key="`user-${index}`" :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 <img
:src="`/images/users/${user.image}`" :src="`/images/users/${user.image}`"
alt="Profile picture" alt="Profile picture"
loading="lazy" 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>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,26 @@
<template> <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 <img
class=" class="
absolute bg-primaryDark bg-primaryLight
object-cover object-center
transition
rounded-full rounded-full
bg-primaryDark object-cover object-center
h-5 h-5
transition
w-5 w-5
bg-primaryLight absolute
" "
:src="url" :src="url"
:alt="alt" :alt="alt"
loading="lazy" 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> </div>
</template> </template>
@@ -32,6 +37,10 @@ export default {
type: String, type: String,
default: "Profile picture", default: "Profile picture",
}, },
indicator: {
type: String,
default: "bg-green-500",
},
}, },
} }
</script> </script>

View File

@@ -1,8 +1,22 @@
<template> <template>
<div class="flex flex-col"> <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"> <div ref="log" name="log" class="realtime-log">
<span v-if="log"> <span v-if="log" class="space-y-2">
<span <span
v-for="(entry, index) in log" v-for="(entry, index) in log"
:key="`entry-${index}`" :key="`entry-${index}`"
@@ -51,7 +65,7 @@ export default {
&, &,
span { span {
@apply font-mono; @apply font-mono font-semibold;
@apply select-text; @apply select-text;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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