Compare commits

...

5 Commits

Author SHA1 Message Date
Liyas Thomas
c11b592543 feat: ability to export a single environment 2022-12-05 18:11:05 +05:30
Liyas Thomas
a1d69b3210 chore: minor ui improvements 2022-12-03 13:01:47 +05:30
Liyas Thomas
dcbc2f1145 ci: use latest workflow versions 2022-12-03 00:51:49 +05:30
Andrew Bastin
36903b338a fix: broken Dockerfile and final start command 2022-12-02 13:34:46 -05:00
Andrew Bastin
9d8d6832af chore: fix broken ci 2022-12-02 11:01:53 -05:00
25 changed files with 252 additions and 233 deletions

View File

@@ -1,9 +1,9 @@
{ {
"name": "Hoppscotch", "name": "Hoppscotch",
"image": "mcr.microsoft.com/devcontainers/typescript-node:18", "image": "mcr.microsoft.com/devcontainers/typescript-node:18",
"forwardPorts": [3000], "forwardPorts": [3000],
"features": { "features": {
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {} "ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
}, },
"postCreateCommand": "cp packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env && pnpm i" "postCreateCommand": "mv .env.example .env && pnpm i"
} }

View File

@@ -13,10 +13,10 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Environment - name: Setup Environment
run: mv packages/hoppscotch-web/.env.example packages/hoppscotch-web/.env run: mv .env.example .env
- name: Setup and run pnpm install - name: Setup and run pnpm install
uses: pnpm/action-setup@v2.2.2 uses: pnpm/action-setup@v2.2.4
with: with:
version: 7 version: 7
run_install: true run_install: true

View File

@@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup and run pnpm install - name: Setup and run pnpm install
uses: pnpm/action-setup@v2.2.2 uses: pnpm/action-setup@v2.2.4
env: env:
VITE_BACKEND_GQL_URL: ${{ secrets.STAGING_BACKEND_GQL_URL }} VITE_BACKEND_GQL_URL: ${{ secrets.STAGING_BACKEND_GQL_URL }}
with: with:

View File

@@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup and run pnpm install - name: Setup and run pnpm install
uses: pnpm/action-setup@v2.2.2 uses: pnpm/action-setup@v2.2.4
env: env:
VITE_BACKEND_GQL_URL: ${{ secrets.STAGING_BACKEND_GQL_URL }} VITE_BACKEND_GQL_URL: ${{ secrets.STAGING_BACKEND_GQL_URL }}
with: with:

View File

@@ -19,10 +19,10 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Environment - name: Setup Environment
run: mv packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env run: mv .env.example .env
- name: Setup and run pnpm install - name: Setup and run pnpm install
uses: pnpm/action-setup@v2.2.2 uses: pnpm/action-setup@v2.2.4
with: with:
version: 7 version: 7
run_install: true run_install: true

View File

@@ -17,7 +17,7 @@ COPY . .
RUN npm install -g pnpm RUN npm install -g pnpm
RUN mv packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env RUN mv .env.example .env
RUN pnpm i --unsafe-perm=true RUN pnpm i --unsafe-perm=true

View File

@@ -279,7 +279,7 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
## **Developing** ## **Developing**
0. Update [`.env.example`](https://github.com/hoppscotch/hoppscotch/blob/main/.env.example) file found in the root of the repo with your own keys and rename it to `.env`. 0. Update [`.env.example`](https://github.com/hoppscotch/hoppscotch/blob/main/.env.example) file found in the root of repository with your own keys and rename it to `.env`.
_Sample keys only work with the [production build](https://hoppscotch.io)._ _Sample keys only work with the [production build](https://hoppscotch.io)._
@@ -315,7 +315,7 @@ docker run --rm --name hoppscotch -p 3000:3000 hoppscotch/hoppscotch:latest
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git. 1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
2. Install pnpm using npm by running `npm install -g pnpm`. 2. Install pnpm using npm by running `npm install -g pnpm`.
3. Install dependencies by running `pnpm install` within the directory that you cloned (probably `hoppscotch`). 3. Install dependencies by running `pnpm install` within the directory that you cloned (probably `hoppscotch`).
4. Update [`.env.example`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/.env.example) file found in `packages/hoppscotch-app` with your own keys and rename it to `.env`. 4. Update [`.env.example`](https://github.com/hoppscotch/hoppscotch/blob/main/.env.example) file found in the root of repository with your own keys and rename it to `.env`.
5. Build the release files with `pnpm run generate`. 5. Build the release files with `pnpm run generate`.
6. Find the built project in `packages/hoppscotch-app/dist`. Host these files on any [static hosting servers](https://www.pluralsight.com/blog/software-development/where-to-host-your-jamstack-site). 6. Find the built project in `packages/hoppscotch-app/dist`. Host these files on any [static hosting servers](https://www.pluralsight.com/blog/software-development/where-to-host-your-jamstack-site).

View File

@@ -11,10 +11,10 @@ if there is no existing translation, you can create a new one by following these
1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).** 1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).**
2. **Checkout the `i18n` branch for latest translations.** 2. **Checkout the `i18n` branch for latest translations.**
3. **Create a new branch for your translation with base branch `i18n`.** 3. **Create a new branch for your translation with base branch `i18n`.**
4. **Create target language file in the [`/packages/hoppscotch-app/locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-app/locales) directory.** 4. **Create target language file in the [`/packages/hoppscotch-common/locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-common/locales) directory.**
5. **Copy the contents of the source file [`/packages/hoppscotch-app/locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/locales/en.json) to the target language file.** 5. **Copy the contents of the source file [`/packages/hoppscotch-common/locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/locales/en.json) to the target language file.**
6. **Translate the strings in the target language file.** 6. **Translate the strings in the target language file.**
7. **Add your language entry to [`/packages/hoppscotch-app/languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-app/languages.json).** 7. **Add your language entry to [`/packages/hoppscotch-common/languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/languages.json).**
8. **Save & commit changes.** 8. **Save & commit changes.**
9. **Send a pull request.** 9. **Send a pull request.**

View File

@@ -5,9 +5,9 @@
}, },
"hosting": { "hosting": {
"predeploy": [ "predeploy": [
"mv packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env && npm install -g pnpm && pnpm i && pnpm run generate" "mv .env.example .env && npm install -g pnpm && pnpm i && pnpm run generate"
], ],
"public": "packages/hoppscotch-app/dist", "public": "packages/hoppscotch-web/dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"], "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [ "rewrites": [
{ {

View File

@@ -4,7 +4,7 @@
[build] [build]
base = "/" base = "/"
publish = "packages/hoppscotch-app/dist" publish = "packages/hoppscotch-web/dist"
command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run generate" command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run generate"
[[headers]] [[headers]]

View File

@@ -10,7 +10,7 @@
"prepare": "husky install", "prepare": "husky install",
"dev": "pnpm -r do-dev", "dev": "pnpm -r do-dev",
"generate": "pnpm -r do-build-prod", "generate": "pnpm -r do-build-prod",
"start": "http-server packages/hoppscotch-app/dist -p 3000", "start": "http-server packages/hoppscotch-web/dist -p 3000",
"lint": "pnpm -r do-lint", "lint": "pnpm -r do-lint",
"typecheck": "pnpm -r do-typecheck", "typecheck": "pnpm -r do-typecheck",
"lintfix": "pnpm -r do-lintfix", "lintfix": "pnpm -r do-lintfix",

View File

@@ -1,9 +1,9 @@
* { * {
@apply backface-hidden; @apply backface-hidden;
@apply before: backface-hidden; @apply before:backface-hidden;
@apply after: backface-hidden; @apply after:backface-hidden;
@apply selection: bg-accentDark; @apply selection:bg-accentDark;
@apply selection: text-accentContrast; @apply selection:text-accentContrast;
} }
:root { :root {
@@ -21,8 +21,8 @@
@apply bg-divider bg-clip-content; @apply bg-divider bg-clip-content;
@apply rounded-full; @apply rounded-full;
@apply border-solid border-transparent border-4; @apply border-solid border-transparent border-4;
@apply hover: bg-dividerDark; @apply hover:bg-dividerDark;
@apply hover: bg-clip-content; @apply hover:bg-clip-content;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
@@ -115,7 +115,7 @@ a {
@apply no-underline; @apply no-underline;
@apply transition; @apply transition;
@apply leading-body; @apply leading-body;
@apply focus: outline-none; @apply focus:outline-none;
&.link { &.link {
@apply items-center; @apply items-center;
@@ -123,10 +123,10 @@ a {
@apply -my-0.5 -mx-1; @apply -my-0.5 -mx-1;
@apply text-accent; @apply text-accent;
@apply rounded; @apply rounded;
@apply hover: text-accentDark; @apply hover:text-accentDark;
@apply focus-visible: ring; @apply focus-visible:ring;
@apply focus-visible: ring-accent; @apply focus-visible:ring-accent;
@apply focus-visible: text-accentDark; @apply focus-visible:text-accentDark;
} }
} }
@@ -163,7 +163,7 @@ a {
@apply px-1; @apply px-1;
@apply my-0 ml-1; @apply my-0 ml-1;
@apply truncate; @apply truncate;
@apply sm: inline-flex; @apply sm:inline-flex;
} }
.env-icon { .env-icon {
@@ -198,7 +198,7 @@ a {
@apply text-secondary text-body; @apply text-secondary text-body;
@apply p-2; @apply p-2;
@apply leading-normal; @apply leading-normal;
@apply focus: outline-none; @apply focus:outline-none;
scroll-behavior: smooth; scroll-behavior: smooth;
} }
@@ -243,7 +243,7 @@ hr {
@apply rounded; @apply rounded;
@apply text-secondaryDark; @apply text-secondaryDark;
@apply border border-divider; @apply border border-divider;
@apply focus-visible: border-dividerDark; @apply focus-visible:border-dividerDark;
} }
input, input,
@@ -254,8 +254,8 @@ button {
@apply transition; @apply transition;
@apply text-body; @apply text-body;
@apply leading-body; @apply leading-body;
@apply focus: outline-none; @apply focus:outline-none;
@apply disabled: cursor-not-allowed; @apply disabled:cursor-not-allowed;
} }
.input[type="file"], .input[type="file"],
@@ -316,16 +316,16 @@ pre.ace_editor {
.select-wrapper { .select-wrapper {
@apply flex flex-1; @apply flex flex-1;
@apply relative; @apply relative;
@apply after: absolute; @apply after:absolute;
@apply after: flex; @apply after:flex;
@apply after: inset-y-0; @apply after:inset-y-0;
@apply after: items-center; @apply after:items-center;
@apply after: justify-center; @apply after:justify-center;
@apply after: pointer-events-none; @apply after:pointer-events-none;
@apply after: font-icon; @apply after:font-icon;
@apply after: text-secondaryLight; @apply after:text-secondaryLight;
@apply after: right-3; @apply after:right-3;
@apply after: content-["\e313"]; @apply after:content-["\e313"];
} }
.info-response { .info-response {
@@ -366,8 +366,8 @@ pre.ace_editor {
@apply font-semibold; @apply font-semibold;
@apply transition; @apply transition;
@apply leading-body; @apply leading-body;
@apply sm: rounded; @apply sm:rounded;
@apply sm: border; @apply sm:border;
.action { .action {
@apply relative; @apply relative;
@@ -381,16 +381,16 @@ pre.ace_editor {
@apply leading-body; @apply leading-body;
@apply tracking-normal; @apply tracking-normal;
@apply rounded; @apply rounded;
@apply last: ml-4; @apply last:ml-4;
@apply sm: ml-8; @apply sm:ml-8;
@apply before: absolute; @apply before:absolute;
@apply before: bg-current; @apply before:bg-current;
@apply before: opacity-10; @apply before:opacity-10;
@apply before: inset-0; @apply before:inset-0;
@apply before: transition; @apply before:transition;
@apply before: content-DEFAULT; @apply before:content-DEFAULT;
@apply hover: no-underline; @apply hover:no-underline;
@apply hover: before:opacity-20; @apply hover:before:opacity-20;
} }
} }
@@ -417,24 +417,24 @@ pre.ace_editor {
.smart-splitter .splitpanes__splitter { .smart-splitter .splitpanes__splitter {
@apply relative; @apply relative;
@apply bg-primaryLight; @apply bg-primaryLight;
@apply before: absolute; @apply before:absolute;
@apply before: inset-0; @apply before:inset-0;
@apply before: bg-accentLight; @apply before:bg-accentLight;
@apply before: opacity-0; @apply before:opacity-0;
@apply before: z-20; @apply before:z-20;
@apply before: transition; @apply before:transition;
@apply before: content-DEFAULT; @apply before:content-DEFAULT;
@apply after: absolute; @apply after:absolute;
@apply after: inset-0; @apply after:inset-0;
@apply after: z-20; @apply after:z-20;
@apply after: transition; @apply after:transition;
@apply after: flex; @apply after:flex;
@apply after: items-center; @apply after:items-center;
@apply after: justify-center; @apply after:justify-center;
@apply after: text-dividerDark; @apply after:text-dividerDark;
@apply after: font-icon; @apply after:font-icon;
@apply hover: before:opacity-100; @apply hover:before:opacity-100;
@apply hover: after:text-accentDark; @apply hover:after:text-accentDark;
} }
.no-splitter .splitpanes__splitter { .no-splitter .splitpanes__splitter {
@@ -444,18 +444,18 @@ pre.ace_editor {
.smart-splitter.splitpanes--vertical > .splitpanes__splitter { .smart-splitter.splitpanes--vertical > .splitpanes__splitter {
@apply w-1; @apply w-1;
@apply before: -left-0.5; @apply before:-left-0.5;
@apply before: -right-0.5; @apply before:-right-0.5;
@apply before: h-full; @apply before:h-full;
@apply after: content-["\e5d4"]; @apply after:content-["\e5d4"];
} }
.smart-splitter.splitpanes--horizontal > .splitpanes__splitter { .smart-splitter.splitpanes--horizontal > .splitpanes__splitter {
@apply h-1; @apply h-1;
@apply before: -top-0.5; @apply before:-top-0.5;
@apply before: -bottom-0.5; @apply before:-bottom-0.5;
@apply before: w-full; @apply before:w-full;
@apply after: content-["\e5d3"]; @apply after:content-["\e5d3"];
} }
.no-splitter.splitpanes--vertical > .splitpanes__splitter { .no-splitter.splitpanes--vertical > .splitpanes__splitter {
@@ -507,7 +507,7 @@ pre.ace_editor {
} }
.capitalize-first { .capitalize-first {
@apply first-letter: capitalize; @apply first-letter:capitalize;
} }
details { details {
@@ -543,12 +543,16 @@ details[open] summary .indicator {
@apply bg-accent #{!important}; @apply bg-accent #{!important};
} }
input[type="color"]::-webkit-color-swatch-wrapper { .color-picker[type="color"] {
@apply rounded; @apply appearance-none;
padding: 0;
} }
input[type="color"]::-webkit-color-swatch { .color-picker[type="color"]::-webkit-color-swatch-wrapper {
@apply rounded; @apply rounded;
border: none; @apply p-0;
}
.color-picker[type="color"]::-webkit-color-swatch {
@apply rounded;
@apply border-0;
} }

View File

@@ -98,6 +98,21 @@ declare module '@vue/runtime-core' {
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default'] HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
HttpTests: typeof import('./components/http/Tests.vue')['default'] HttpTests: typeof import('./components/http/Tests.vue')['default']
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default'] HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
IconLucideInfo: typeof import('~icons/lucide/info')['default']
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideLoader: typeof import('~icons/lucide/loader')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideRss: typeof import('~icons/lucide/rss')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUser: typeof import('~icons/lucide/user')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default']
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default'] LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default'] LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']

View File

@@ -149,9 +149,11 @@
</template> </template>
</tippy> </tippy>
<ButtonSecondary <ButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip', allowHTML: true }"
:title="`${t(
'app.shortcuts'
)} <kbd>${getSpecialKey()}</kbd><kbd>K</kbd>`"
:icon="IconZap" :icon="IconZap"
:title="t('app.shortcuts')"
@click="showShortcuts = true" @click="showShortcuts = true"
/> />
<ButtonSecondary <ButtonSecondary
@@ -223,6 +225,7 @@ import { useReadonlyStream } from "@composables/stream"
import { currentUser$ } from "~/helpers/fb/auth" import { currentUser$ } from "~/helpers/fb/auth"
import { TippyComponent } from "vue-tippy" import { TippyComponent } from "vue-tippy"
import SmartItem from "@components/smart/Item.vue" import SmartItem from "@components/smart/Item.vue"
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
const t = useI18n() const t = useI18n()
const showShortcuts = ref(false) const showShortcuts = ref(false)

View File

@@ -1,6 +1,6 @@
<template> <template>
<button <button
class="flex items-center flex-1 px-6 py-3 font-medium cursor-pointer transition search-entry focus:outline-none" class="flex items-center flex-1 px-6 py-3 font-medium transition cursor-pointer search-entry focus:outline-none"
:class="{ active: active }" :class="{ active: active }"
tabindex="-1" tabindex="-1"
@click="emit('action', shortcut.action)" @click="emit('action', shortcut.action)"
@@ -8,7 +8,7 @@
> >
<component <component
:is="shortcut.icon" :is="shortcut.icon"
class="mr-4 opacity-50 transition svg-icons" class="mr-4 transition opacity-50 svg-icons"
:class="{ 'opacity-100 text-secondaryDark': active }" :class="{ 'opacity-100 text-secondaryDark': active }"
/> />
<span <span
@@ -51,24 +51,18 @@ const emit = defineEmits<{
<style lang="scss" scoped> <style lang="scss" scoped>
.search-entry { .search-entry {
@apply relative; @apply relative;
@apply after:absolute;
&::after { @apply after:top-0;
@apply absolute; @apply after:left-0;
@apply top-0; @apply after:bottom-0;
@apply left-0; @apply after:bg-transparent;
@apply bottom-0; @apply after:z-2;
@apply bg-transparent; @apply after:w-0.5;
@apply z-2; @apply after:content-DEFAULT;
@apply w-0.5;
content: "";
}
&.active { &.active {
@apply bg-primaryLight; @apply bg-primaryLight;
@apply after:bg-accentLight;
&::after {
@apply bg-accentLight;
}
} }
} }
</style> </style>

View File

@@ -78,26 +78,20 @@ const primaryNavigation = [
@apply justify-center; @apply justify-center;
@apply hover: (bg-primaryDark text-secondaryDark); @apply hover: (bg-primaryDark text-secondaryDark);
@apply focus-visible: text-secondaryDark; @apply focus-visible: text-secondaryDark;
@apply after:absolute;
&::after { @apply after:inset-x-0;
@apply absolute; @apply after:md: inset-x-auto;
@apply inset-x-0; @apply after:md: inset-y-0;
@apply md: inset-x-auto; @apply after:bottom-0;
@apply md: inset-y-0; @apply after:md: bottom-auto;
@apply bottom-0; @apply after:md: left-0;
@apply md: bottom-auto; @apply after:z-2;
@apply md: left-0; @apply after:h-0.5;
@apply z-2; @apply after:md: h-full;
@apply h-0.5; @apply after:w-full;
@apply md: h-full; @apply after:md: w-0.5;
@apply w-full; @apply after:content-DEFAULT;
@apply md: w-0.5; @apply focus: after: bg-divider;
content: "";
}
&:focus::after {
@apply bg-divider;
}
.svg-icons { .svg-icons {
@apply opacity-75; @apply opacity-75;
@@ -112,28 +106,22 @@ const primaryNavigation = [
@apply text-secondaryDark; @apply text-secondaryDark;
@apply bg-primaryLight; @apply bg-primaryLight;
@apply hover: text-secondaryDark; @apply hover: text-secondaryDark;
@apply after:bg-accent;
.svg-icons { .svg-icons {
@apply opacity-100; @apply opacity-100;
} }
&::after {
@apply bg-accent;
}
} }
&.exact-active-link { &.exact-active-link {
@apply text-secondaryDark; @apply text-secondaryDark;
@apply bg-primaryLight; @apply bg-primaryLight;
@apply hover: text-secondaryDark; @apply hover: text-secondaryDark;
@apply after:bg-accent;
.svg-icons { .svg-icons {
@apply opacity-100; @apply opacity-100;
} }
&::after {
@apply bg-accent;
}
} }
} }
</style> </style>

View File

@@ -46,6 +46,7 @@
role="menu" role="menu"
@keyup.e="edit!.$el.click()" @keyup.e="edit!.$el.click()"
@keyup.d="duplicate!.$el.click()" @keyup.d="duplicate!.$el.click()"
@keyup.x="exportAction!.$el.click()"
@keyup.delete=" @keyup.delete="
!(environmentIndex === 'Global') !(environmentIndex === 'Global')
? deleteAction!.$el.click() ? deleteAction!.$el.click()
@@ -77,6 +78,18 @@
} }
" "
/> />
<SmartItem
ref="exportAction"
:icon="IconDownload"
:label="t('export.title')"
:shortcut="['X']"
@click="
() => {
exportEnvironment()
hide()
}
"
/>
<SmartItem <SmartItem
v-if="environmentIndex !== 'Global'" v-if="environmentIndex !== 'Global'"
ref="deleteAction" ref="deleteAction"
@@ -108,6 +121,7 @@ import IconMoreVertical from "~icons/lucide/more-vertical"
import IconEdit from "~icons/lucide/edit" import IconEdit from "~icons/lucide/edit"
import IconCopy from "~icons/lucide/copy" import IconCopy from "~icons/lucide/copy"
import IconTrash2 from "~icons/lucide/trash-2" import IconTrash2 from "~icons/lucide/trash-2"
import IconDownload from "~icons/lucide/download"
import { ref } from "vue" import { ref } from "vue"
import { Environment } from "@hoppscotch/data" import { Environment } from "@hoppscotch/data"
import { cloneDeep } from "lodash-es" import { cloneDeep } from "lodash-es"
@@ -143,6 +157,7 @@ const options = ref<TippyComponent | null>(null)
const edit = ref<typeof SmartItem | null>(null) const edit = ref<typeof SmartItem | null>(null)
const duplicate = ref<typeof SmartItem | null>(null) const duplicate = ref<typeof SmartItem | null>(null)
const deleteAction = ref<typeof SmartItem | null>(null) const deleteAction = ref<typeof SmartItem | null>(null)
const exportAction = ref<typeof SmartItem | null>(null)
const removeEnvironment = () => { const removeEnvironment = () => {
if (props.environmentIndex === null) return if (props.environmentIndex === null) return
@@ -161,4 +176,22 @@ const duplicateEnvironments = () => {
) )
} else duplicateEnvironment(props.environmentIndex) } else duplicateEnvironment(props.environmentIndex)
} }
const exportEnvironment = () => {
const environmentJSON = JSON.stringify(props.environment)
const file = new Blob([environmentJSON], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
a.download = `${props.environment.name}.json`
document.body.appendChild(a)
a.click()
toast.success(t("state.download_started").toString())
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
}
</script> </script>

View File

@@ -40,6 +40,7 @@
role="menu" role="menu"
@keyup.e="edit!.$el.click()" @keyup.e="edit!.$el.click()"
@keyup.d="duplicate!.$el.click()" @keyup.d="duplicate!.$el.click()"
@keyup.x="exportAction!.$el.click()"
@keyup.delete="deleteAction!.$el.click()" @keyup.delete="deleteAction!.$el.click()"
@keyup.escape="options!.tippy().hide()" @keyup.escape="options!.tippy().hide()"
> >
@@ -67,6 +68,18 @@
} }
" "
/> />
<SmartItem
ref="exportAction"
:icon="IconDownload"
:label="t('export.title')"
:shortcut="['X']"
@click="
() => {
exportEnvironment()
hide()
}
"
/>
<SmartItem <SmartItem
ref="deleteAction" ref="deleteAction"
:icon="IconTrash2" :icon="IconTrash2"
@@ -108,6 +121,7 @@ import IconEdit from "~icons/lucide/edit"
import IconCopy from "~icons/lucide/copy" import IconCopy from "~icons/lucide/copy"
import IconTrash2 from "~icons/lucide/trash-2" import IconTrash2 from "~icons/lucide/trash-2"
import IconMoreVertical from "~icons/lucide/more-vertical" import IconMoreVertical from "~icons/lucide/more-vertical"
import IconDownload from "~icons/lucide/download"
import { TippyComponent } from "vue-tippy" import { TippyComponent } from "vue-tippy"
import SmartItem from "@components/smart/Item.vue" import SmartItem from "@components/smart/Item.vue"
@@ -130,6 +144,7 @@ const options = ref<TippyComponent | null>(null)
const edit = ref<typeof SmartItem | null>(null) const edit = ref<typeof SmartItem | null>(null)
const duplicate = ref<typeof SmartItem | null>(null) const duplicate = ref<typeof SmartItem | null>(null)
const deleteAction = ref<typeof SmartItem | null>(null) const deleteAction = ref<typeof SmartItem | null>(null)
const exportAction = ref<typeof SmartItem | null>(null)
const removeEnvironment = () => { const removeEnvironment = () => {
pipe( pipe(
@@ -173,4 +188,22 @@ const getErrorMessage = (err: GQLError<string>) => {
} }
} }
} }
const exportEnvironment = () => {
const environmentJSON = JSON.stringify(props.environment.environment)
const file = new Blob([environmentJSON], { type: "application/json" })
const a = document.createElement("a")
const url = URL.createObjectURL(file)
a.href = url
a.download = `${props.environment.environment.name}.json`
document.body.appendChild(a)
a.click()
toast.success(t("state.download_started").toString())
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 1000)
}
</script> </script>

View File

@@ -3,7 +3,7 @@
<template #body> <template #body>
<div class="flex justify-between mb-4"> <div class="flex justify-between mb-4">
<div <div
class="flex items-center border rounded divide-x border-divider divide-divider" class="flex items-center border divide-x rounded border-divider divide-divider"
> >
<label class="mx-4"> <label class="mx-4">
{{ t("mqtt.qos") }} {{ t("mqtt.qos") }}
@@ -61,7 +61,7 @@
id="select-color" id="select-color"
v-model="color" v-model="color"
type="color" type="color"
class="w-8 h-8 p-1 rounded bg-primary" class="w-8 h-8 p-1 rounded bg-primary color-picker"
/> />
</span> </span>
</div> </div>

View File

@@ -9,6 +9,7 @@
id="checkbox" id="checkbox"
type="checkbox" type="checkbox"
name="checkbox" name="checkbox"
class="checkbox"
:checked="on" :checked="on"
@change="emit('change')" @change="emit('change')"
/> />
@@ -35,7 +36,7 @@ const emit = defineEmits<{
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
input[type="checkbox"] { .checkbox[type="checkbox"] {
@apply appearance-none; @apply appearance-none;
@apply hidden; @apply hidden;
@@ -44,7 +45,7 @@ input[type="checkbox"] {
@apply cursor-pointer; @apply cursor-pointer;
&::before { &::before {
@apply border-divider border-2; @apply border-2 border-divider;
@apply rounded; @apply rounded;
@apply group-hover: border-accentDark; @apply group-hover: border-accentDark;
@apply inline-flex; @apply inline-flex;
@@ -54,9 +55,9 @@ input[type="checkbox"] {
@apply h-4; @apply h-4;
@apply w-4; @apply w-4;
@apply font-icon; @apply font-icon;
@apply mr-3; @apply mr-2;
@apply transition; @apply transition;
content: "\e876"; @apply content-["\e876"];
} }
} }

View File

@@ -31,7 +31,7 @@
> >
<Transition name="bounce" appear> <Transition name="bounce" appear>
<div <div
class="inline-block w-full overflow-hidden text-left align-bottom border shadow-lg transition-all transform border-dividerDark bg-primary sm:rounded-xl sm:align-middle" class="inline-block w-full overflow-hidden text-left align-bottom transition-all transform shadow-lg sm:border border-dividerDark bg-primary sm:rounded-xl sm:align-middle"
:class="[{ 'mt-24 md:mb-8': placement === 'top' }, styles]" :class="[{ 'mt-24 md:mb-8': placement === 'top' }, styles]"
> >
<div <div

View File

@@ -182,16 +182,6 @@ const selectTab = (id: string) => {
@apply overflow-auto; @apply overflow-auto;
@apply flex-shrink-0; @apply flex-shrink-0;
// &::after {
// @apply absolute;
// @apply inset-x-0;
// @apply bottom-0;
// @apply bg-dividerLight;
// @apply z-1;
// @apply h-0.5;
// content: "";
// }
.tab { .tab {
@apply relative; @apply relative;
@apply flex; @apply flex;
@@ -205,6 +195,15 @@ const selectTab = (id: string) => {
@apply hover: text-secondaryDark; @apply hover: text-secondaryDark;
@apply focus: outline-none; @apply focus: outline-none;
@apply focus-visible: text-secondaryDark; @apply focus-visible: text-secondaryDark;
@apply after:absolute;
@apply after:left-4;
@apply after:right-4;
@apply after:bottom-0;
@apply after:bg-transparent;
@apply after:z-2;
@apply after:h-0.5;
@apply after:content-DEFAULT;
@apply focus: after: bg-divider;
.tab-info { .tab-info {
@apply inline-flex; @apply inline-flex;
@@ -219,53 +218,29 @@ const selectTab = (id: string) => {
@apply text-secondaryLight; @apply text-secondaryLight;
} }
&::after {
@apply absolute;
@apply left-4;
@apply right-4;
@apply bottom-0;
@apply bg-transparent;
@apply z-2;
@apply h-0.5;
content: "";
}
&:focus::after {
@apply bg-divider;
}
&.active { &.active {
@apply text-secondaryDark; @apply text-secondaryDark;
@apply after:bg-accent;
.tab-info { .tab-info {
@apply text-secondary; @apply text-secondary;
@apply border-dividerDark; @apply border-dividerDark;
} }
&::after {
@apply bg-accent;
}
} }
&.vertical { &.vertical {
@apply p-2; @apply p-2;
@apply rounded; @apply rounded;
@apply focus: after: hidden;
&:focus::after {
@apply hidden;
}
&.active { &.active {
@apply text-accent; @apply text-accent;
@apply after:hidden;
.tab-info { .tab-info {
@apply text-secondary; @apply text-secondary;
@apply border-dividerDark; @apply border-dividerDark;
} }
&::after {
@apply hidden;
}
} }
} }
} }

View File

@@ -9,7 +9,7 @@
<draggable <draggable
v-bind="dragOptions" v-bind="dragOptions"
:list="tabEntries" :list="tabEntries"
:style="tabsWidth" :style="tabStyles"
:item-key="'window-'" :item-key="'window-'"
class="flex flex-shrink-0 overflow-x-auto transition divide-x divide-dividerLight" class="flex flex-shrink-0 overflow-x-auto transition divide-x divide-dividerLight"
@sort="sortTabs" @sort="sortTabs"
@@ -43,7 +43,7 @@
}" }"
:title="t('action.close')" :title="t('action.close')"
:class="[{ active: modelValue === tabID }, 'close']" :class="[{ active: modelValue === tabID }, 'close']"
class="rounded mx-2 !py-0.5 !px-1" class="mx-2 !p-0.5"
@click.stop="emit('removeTab', tabID)" @click.stop="emit('removeTab', tabID)"
/> />
</button> </button>
@@ -127,11 +127,11 @@ const emit = defineEmits<{
(e: "addTab"): void (e: "addTab"): void
}>() }>()
const tabEntries = ref<Array<[string, TabMeta]>>([]) const tabEntries = ref<Array<[string, TabMeta]>>([])
const tabsWidth = computed(() => ({ const tabStyles = computed(() => ({
maxWidth: `${tabEntries.value.length * 184}px`, maxWidth: `${tabEntries.value.length * 184}px`,
width: "100%", width: "100%",
minWidth: "0px", minWidth: "0px",
transition: "max-width 0.2s", // transition: "max-width 0.2s",
})) }))
const dragOptions = { const dragOptions = {
group: "tabs", group: "tabs",
@@ -201,16 +201,13 @@ const addTab = () => {
@apply whitespace-nowrap; @apply whitespace-nowrap;
@apply overflow-auto; @apply overflow-auto;
@apply flex-shrink-0; @apply flex-shrink-0;
@apply after:absolute;
&::after { @apply after:inset-x-0;
@apply absolute; @apply after:bottom-0;
@apply inset-x-0; @apply after:bg-dividerLight;
@apply bottom-0; @apply after:z-10;
@apply bg-dividerLight; @apply after:h-0.25;
@apply z-10; @apply after:content-DEFAULT;
@apply h-0.25;
content: "";
}
.tab { .tab {
@apply relative; @apply relative;
@@ -226,44 +223,20 @@ const addTab = () => {
@apply hover:bg-primaryDark; @apply hover:bg-primaryDark;
@apply hover:text-secondary; @apply hover:text-secondary;
@apply focus-visible:text-secondaryDark; @apply focus-visible:text-secondaryDark;
@apply before:absolute;
&::before { @apply before:left-0;
@apply absolute; @apply before:right-0;
@apply left-0; @apply before:top-0;
@apply right-0; @apply before:bg-transparent;
@apply top-0; @apply before:z-2;
@apply bg-transparent; @apply before:h-0.5;
@apply z-2; @apply before:content-DEFAULT;
@apply h-0.5; @apply focus: before: bg-divider;
content: "";
}
// &::after {
// @apply absolute;
// @apply left-0;
// @apply right-0;
// @apply bottom-0;
// @apply bg-divider;
// @apply z-2;
// @apply h-0.25;
// content: "";
// }
&:focus::before {
@apply bg-divider;
}
&.active { &.active {
@apply text-secondaryDark; @apply text-secondaryDark;
@apply bg-primary; @apply bg-primary;
@apply before:bg-accent;
&::before {
@apply bg-accent;
}
// &::after {
// @apply bg-transparent;
// }
} }
.close { .close {

View File

@@ -284,8 +284,8 @@
</SmartTabs> </SmartTabs>
</div> </div>
</div> </div>
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
</div> </div>
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
</div> </div>
</template> </template>

View File

@@ -5,7 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "node --max_old_space_size=16384 ./node_modules/vite/bin/vite.js build",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .", "lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
"lint:ts": "vue-tsc --noEmit", "lint:ts": "vue-tsc --noEmit",