KeyValueArray.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  1. import React, { useContext, useEffect, useState } from "react";
  2. import {
  3. GetFinalVariablesFunction,
  4. GetMetadataFunction,
  5. KeyValueArrayField,
  6. KeyValueArrayFieldState,
  7. PartialEnvGroup,
  8. PopulatedEnvGroup,
  9. } from "../types";
  10. import sliders from "../../../assets/sliders.svg";
  11. import upload from "../../../assets/upload.svg";
  12. import styled, { keyframes } from "styled-components";
  13. import useFormField from "../hooks/useFormField";
  14. import Modal from "../../../main/home/modals/Modal";
  15. import LoadEnvGroupModal from "../../../main/home/modals/LoadEnvGroupModal";
  16. import EnvEditorModal from "../../../main/home/modals/EnvEditorModal";
  17. import { hasSetValue } from "../utils";
  18. import _, { isObject, differenceBy, omit } from "lodash";
  19. import Helper from "components/form-components/Helper";
  20. import Heading from "components/form-components/Heading";
  21. import Loading from "components/Loading";
  22. import api from "shared/api";
  23. import { Context } from "shared/Context";
  24. import { dotenv_parse } from "shared/string_utils";
  25. interface Props extends KeyValueArrayField {
  26. id: string;
  27. }
  28. const KeyValueArray: React.FC<Props> = (props) => {
  29. const { state, setState, variables } = useFormField<KeyValueArrayFieldState>(
  30. props.id,
  31. {
  32. initState: () => {
  33. let values = {}
  34. if (props?.value?.length > 0) {
  35. values = props.value[0]
  36. }
  37. const normalValues = Object.entries(values?.normal || {});
  38. values = omit(values, ["normal", "synced", "build"]);
  39. return {
  40. values: hasSetValue(props)
  41. ? ([...Object.entries(values), ...normalValues]?.map(([k, v]) => {
  42. return { key: k, value: v };
  43. }) as any[])
  44. : [],
  45. showEnvModal: false,
  46. showEditorModal: false,
  47. synced_env_groups: props.settings?.options?.enable_synced_env_groups
  48. ? null
  49. : [],
  50. };
  51. },
  52. }
  53. );
  54. const { currentProject } = useContext(Context);
  55. // If the variable includes normal it means that the form corresponds to an old job template version
  56. // The "normal" keyword doesn't exist for applications as well as the enable_synced_env_groups setting.
  57. // This is why we have to check if the form corresponds to a job or not.
  58. const enableSyncedEnvGroups = props.variable.includes("normal")
  59. ? !!props.settings?.options?.enable_synced_env_groups
  60. : true;
  61. useEffect(() => {
  62. if (hasSetValue(props) && !Array.isArray(state?.synced_env_groups)) {
  63. const values = props.value[0];
  64. const envGroups: PartialEnvGroup[] = values?.synced || [];
  65. if (Array.isArray(props.injectedProps?.availableSyncEnvGroups)) {
  66. const availableEnvGroups = props.injectedProps.availableSyncEnvGroups;
  67. const populatedEnvGroups = envGroups
  68. .map((envGroup) => {
  69. return availableEnvGroups.find(
  70. (availableEnvGroup) => availableEnvGroup.name === envGroup.name
  71. );
  72. })
  73. .filter(Boolean);
  74. setState(() => ({
  75. synced_env_groups: currentProject?.stacks_enabled ?
  76. (Array.isArray(populatedEnvGroups)
  77. ? populatedEnvGroups
  78. : []) :
  79. (Array.isArray(populatedEnvGroups)
  80. ? populatedEnvGroups
  81. : [])
  82. }));
  83. return;
  84. }
  85. const promises = Promise.all(
  86. envGroups.map(async (envGroup: any) => {
  87. const res = await api.getEnvGroup(
  88. "<token>",
  89. {},
  90. {
  91. id: currentProject.id,
  92. cluster_id: variables.clusterId,
  93. namespace: variables.namespace,
  94. name: envGroup?.name,
  95. version: envGroup.version,
  96. }
  97. );
  98. return res.data;
  99. })
  100. );
  101. promises.then((populatedEnvGroups) => {
  102. setState(() => ({
  103. synced_env_groups: Array.isArray(populatedEnvGroups)
  104. ? populatedEnvGroups
  105. : [],
  106. }));
  107. });
  108. }
  109. }, [
  110. props.injectedProps,
  111. variables?.clusterId,
  112. variables?.namespace,
  113. currentProject?.id,
  114. ]);
  115. if (state == undefined) return <></>;
  116. if (!Array.isArray(state.synced_env_groups) && enableSyncedEnvGroups) {
  117. return <Loading />;
  118. }
  119. const parseEnv = (src: any, options: any) => {
  120. return dotenv_parse(src);
  121. };
  122. const readFile = (env: string) => {
  123. let envObj = parseEnv(env, null);
  124. let push = true;
  125. for (let key in envObj) {
  126. for (var i = 0; i < state.values.length; i++) {
  127. let existingKey = state.values[i]["key"];
  128. if (key === existingKey) {
  129. state.values[i]["value"] = envObj[key];
  130. push = false;
  131. }
  132. }
  133. if (push) {
  134. setState((prev) => {
  135. return {
  136. values: [...prev.values, { key, value: envObj[key] }],
  137. };
  138. });
  139. }
  140. }
  141. };
  142. const renderEditorModal = () => {
  143. if (state.showEditorModal) {
  144. return (
  145. <Modal
  146. onRequestClose={() =>
  147. setState(() => {
  148. return { showEditorModal: false };
  149. })
  150. }
  151. width="60%"
  152. height="80%"
  153. >
  154. <EnvEditorModal
  155. closeModal={() =>
  156. setState(() => {
  157. return { showEditorModal: false };
  158. })
  159. }
  160. setEnvVariables={(envFile: string) => readFile(envFile)}
  161. />
  162. </Modal>
  163. );
  164. }
  165. };
  166. const getProcessedValues = (
  167. objectArray: { key: string; value: string }[]
  168. ): any => {
  169. let obj = {} as any;
  170. objectArray?.forEach(({ key, value }) => {
  171. obj[key] = value;
  172. });
  173. return obj;
  174. };
  175. const renderEnvModal = () => {
  176. if (state.showEnvModal) {
  177. return (
  178. <Modal
  179. onRequestClose={() =>
  180. setState(() => {
  181. return { showEnvModal: false };
  182. })
  183. }
  184. width="800px"
  185. height="542px"
  186. >
  187. <LoadEnvGroupModal
  188. existingValues={getProcessedValues(state.values)}
  189. enableSyncedEnvGroups={enableSyncedEnvGroups}
  190. syncedEnvGroups={state.synced_env_groups}
  191. availableEnvGroups={props.injectedProps?.availableSyncEnvGroups}
  192. namespace={variables.namespace}
  193. clusterId={variables.clusterId}
  194. closeModal={() =>
  195. setState(() => {
  196. return {
  197. showEnvModal: false,
  198. };
  199. })
  200. }
  201. setSyncedEnvGroups={(value) => {
  202. setState((prev) => {
  203. return {
  204. synced_env_groups: [...(prev.synced_env_groups || []), value],
  205. };
  206. });
  207. }}
  208. setValues={(values) => {
  209. setState((prev) => {
  210. // Transform array to object similar on what we receive from setValues
  211. const prevValues = prev.values.reduce((acc, currentValue) => {
  212. acc[currentValue.key] = currentValue.value;
  213. return acc;
  214. }, {} as Record<string, string>);
  215. // Deconstruct the two records/objects inside one to merge their values (this will override the old duped vars too)
  216. // and convert the new object back to an array usable for the component
  217. const newValues = Object.entries({
  218. ...prevValues,
  219. ...values,
  220. })?.map(([k, v]) => {
  221. return {
  222. key: k,
  223. value: v,
  224. };
  225. });
  226. return {
  227. values: [...newValues],
  228. };
  229. });
  230. }}
  231. />
  232. </Modal>
  233. );
  234. }
  235. };
  236. const renderDeleteButton = (i: number) => {
  237. if (!props.isReadOnly) {
  238. return (
  239. <DeleteButton
  240. onClick={() => {
  241. state.values.splice(i, 1);
  242. setState((prev) => {
  243. return {
  244. values: prev.values
  245. .slice(0, i + 1)
  246. .concat(prev.values.slice(i + 1, prev.values.length)),
  247. };
  248. });
  249. }}
  250. >
  251. <i className="material-icons">cancel</i>
  252. </DeleteButton>
  253. );
  254. }
  255. };
  256. const renderHiddenOption = (hidden: boolean, i: number) => {
  257. if (props.secretOption && hidden) {
  258. return (
  259. <HideButton>
  260. <i className="material-icons">lock</i>
  261. </HideButton>
  262. );
  263. }
  264. };
  265. const checkOverridedKey = (key: string) => {
  266. const env_group = state.synced_env_groups.find(
  267. (env) => env?.variables && env?.variables[key]
  268. );
  269. if (env_group) {
  270. return (
  271. <Wrapper>
  272. <Helper color="#f5cb42" style={{ marginLeft: "10px" }}>
  273. Overridden by the env group "{env_group?.name}"
  274. </Helper>
  275. </Wrapper>
  276. );
  277. }
  278. return null;
  279. };
  280. const renderInputList = () => {
  281. return (
  282. <>
  283. {state.values?.map((entry: any, i: number) => {
  284. // Preprocess non-string env values set via raw Helm values
  285. let { value } = entry;
  286. if (typeof value === "object") {
  287. value = JSON.stringify(value);
  288. } else if (typeof value === "number" || typeof value === "boolean") {
  289. value = value.toString();
  290. }
  291. return (
  292. <InputWrapper key={i}>
  293. <KeyInput
  294. placeholder="ex: key"
  295. width="270px"
  296. value={entry.key}
  297. onChange={(e: any) => {
  298. e.persist();
  299. setState((prev) => {
  300. return {
  301. values: prev.values?.map((t, j) => {
  302. if (j == i) {
  303. return {
  304. ...t,
  305. key: e.target.value,
  306. };
  307. }
  308. return t;
  309. }),
  310. };
  311. });
  312. }}
  313. disabled={props.isReadOnly || value.includes("PORTERSECRET")}
  314. spellCheck={false}
  315. borderColor={
  316. checkOverridedKey(entry.key) ? "#f5cb42" : undefined
  317. }
  318. />
  319. <Spacer />
  320. {value?.includes("PORTERSECRET") ? (
  321. <KeyInput
  322. placeholder="ex: value"
  323. width="270px"
  324. disabled
  325. type={"password"}
  326. spellCheck={false}
  327. value={value}
  328. />
  329. ) : (
  330. <MultiLineInput
  331. placeholder="ex: value"
  332. width="270px"
  333. value={value}
  334. onChange={(e: any) => {
  335. e.persist();
  336. setState((prev) => {
  337. return {
  338. values: prev.values?.map((t, j) => {
  339. if (j == i) {
  340. return {
  341. ...t,
  342. value: e.target.value,
  343. };
  344. }
  345. return t;
  346. }),
  347. };
  348. });
  349. }}
  350. disabled={props.isReadOnly}
  351. spellCheck={false}
  352. rows={value?.split("\n").length}
  353. />
  354. )}
  355. {renderDeleteButton(i)}
  356. {renderHiddenOption(value.includes("PORTERSECRET"), i)}
  357. {checkOverridedKey(entry.key)}
  358. </InputWrapper>
  359. );
  360. })}
  361. </>
  362. );
  363. };
  364. return (
  365. <>
  366. <StyledInputArray>
  367. <Label>{props.label}</Label>
  368. {state.values.length === 0 ? <></> : renderInputList()}
  369. {props.isReadOnly ? (
  370. <></>
  371. ) : (
  372. <InputWrapper>
  373. <AddRowButton
  374. onClick={() => {
  375. setState((prev) => {
  376. return {
  377. values: [...prev.values, { key: "", value: "" }],
  378. };
  379. });
  380. }}
  381. >
  382. <i className="material-icons">add</i> Add Row
  383. </AddRowButton>
  384. <Spacer />
  385. {variables.namespace && props.envLoader && (
  386. <LoadButton
  387. onClick={() =>
  388. setState((prev) => {
  389. return {
  390. showEnvModal: !prev.showEnvModal,
  391. };
  392. })
  393. }
  394. >
  395. <img src={sliders} /> Load from Env Group
  396. </LoadButton>
  397. )}
  398. {props.fileUpload && (
  399. <UploadButton
  400. onClick={() => {
  401. setState((prev) => {
  402. return {
  403. showEditorModal: true,
  404. };
  405. });
  406. }}
  407. >
  408. <img src={upload} /> Copy from File
  409. </UploadButton>
  410. )}
  411. </InputWrapper>
  412. )}
  413. {enableSyncedEnvGroups && !!state.synced_env_groups?.length && (
  414. <>
  415. <Heading>Synced environment groups</Heading>
  416. <Br />
  417. {state.synced_env_groups?.map((envGroup: any) => {
  418. return (
  419. <ExpandableEnvGroup
  420. key={envGroup?.name}
  421. envGroup={envGroup}
  422. onDelete={() => {
  423. setState((prev) => {
  424. const synced = prev.synced_env_groups?.filter(
  425. (env) => env.name !== envGroup.name
  426. );
  427. return {
  428. ...prev,
  429. synced_env_groups: synced,
  430. };
  431. });
  432. }}
  433. />
  434. );
  435. })}
  436. </>
  437. )}
  438. </StyledInputArray>
  439. {renderEnvModal()}
  440. {renderEditorModal()}
  441. </>
  442. );
  443. };
  444. export const getFinalVariablesForKeyValueArray: GetFinalVariablesFunction = (
  445. vars,
  446. props: KeyValueArrayField,
  447. state: KeyValueArrayFieldState
  448. ) => {
  449. if (!state) {
  450. return {
  451. [props.variable]: hasSetValue(props) ? props.value[0] : [],
  452. };
  453. }
  454. const isNumber = (s: string) => {
  455. return !isNaN(!s ? NaN : Number(String(s).trim()));
  456. };
  457. const rg = /(?:^|[^\\])(\\n)/g;
  458. const fixNewlines = (s: string) => {
  459. while (rg.test(s)) {
  460. s = s.replace(rg, (str) => {
  461. if (str.length == 2) return "\n";
  462. if (str[0] != "\\") return str[0] + "\n";
  463. return "\\n";
  464. });
  465. }
  466. return s;
  467. };
  468. if (props.variable.includes("env")) {
  469. let obj = {
  470. normal: {},
  471. } as any;
  472. state.values.forEach((entry: any, i: number) => {
  473. if (isNumber(entry.value)) {
  474. obj.normal[entry.key] = entry.value;
  475. } else {
  476. obj.normal[entry.key] = fixNewlines(entry.value);
  477. }
  478. });
  479. if (Array.isArray(props.value) && props.value[0]?.build) {
  480. obj.build = props.value[0].build;
  481. }
  482. if (state.synced_env_groups?.length) {
  483. obj.synced = state.synced_env_groups.map((envGroup) => ({
  484. name: envGroup?.name,
  485. version: envGroup?.version,
  486. keys: Object.entries(envGroup?.variables || {}).map(([key, val]) => ({
  487. name: key,
  488. secret: val.includes("PORTERSECRET"),
  489. })),
  490. }));
  491. }
  492. const variableContent = props.variable.split(".");
  493. let variable = props.variable;
  494. if (variable.includes("normal")) {
  495. variable = `${variableContent[0]}.${variableContent[1]}`;
  496. }
  497. return {
  498. [variable]: obj,
  499. };
  500. } else {
  501. let obj = {} as any;
  502. state.values.forEach((entry: any, i: number) => {
  503. if (isNumber(entry.value)) {
  504. obj[entry.key] = entry.value;
  505. } else {
  506. obj[entry.key] = fixNewlines(entry.value);
  507. }
  508. });
  509. return {
  510. [props.variable]: obj,
  511. };
  512. }
  513. };
  514. type KeyValueArrayMetadata = {
  515. [variable: string]: {
  516. added: { name: string }[];
  517. deleted: { name: string }[];
  518. };
  519. };
  520. export const getMetadata: GetMetadataFunction<KeyValueArrayMetadata> = (
  521. vars,
  522. props: KeyValueArrayField,
  523. state: KeyValueArrayFieldState
  524. ) => {
  525. // We don't need any metadata for other key-value-array fields yet so we return null for that variable
  526. if (!state || !props?.variable?.includes("env")) {
  527. return {
  528. [props.variable]: null,
  529. };
  530. }
  531. const originalSyncedEnvGroups: { name: string }[] =
  532. props.value[0]?.synced || [];
  533. const currSynced = state?.synced_env_groups || [];
  534. let obj: KeyValueArrayMetadata[""] = {
  535. added: [],
  536. deleted: [],
  537. };
  538. obj.added = differenceBy(currSynced, originalSyncedEnvGroups, "name");
  539. obj.deleted = differenceBy(originalSyncedEnvGroups, currSynced, "name");
  540. // This will assure that the variable is always "container.env" and not "container.env.normal" as it is
  541. // for some old versions of the jobs chart.
  542. const variableContent = props.variable.split(".");
  543. let variable = props.variable;
  544. if (variable.includes("normal")) {
  545. variable = `${variableContent[0]}.${variableContent[1]}`;
  546. }
  547. return {
  548. [variable]: obj,
  549. };
  550. };
  551. export default KeyValueArray;
  552. const ExpandableEnvGroup: React.FC<{
  553. envGroup: PopulatedEnvGroup;
  554. onDelete: () => void;
  555. }> = ({ envGroup, onDelete }) => {
  556. const [isExpanded, setIsExpanded] = useState(false);
  557. return (
  558. <>
  559. <StyledCard>
  560. <Flex>
  561. <ContentContainer>
  562. <EventInformation>
  563. <EventName>{envGroup.name}</EventName>
  564. </EventInformation>
  565. </ContentContainer>
  566. <ActionContainer>
  567. <ActionButton onClick={() => onDelete()}>
  568. <span className="material-icons">delete</span>
  569. </ActionButton>
  570. <ActionButton onClick={() => setIsExpanded((prev) => !prev)}>
  571. <i className="material-icons">
  572. {isExpanded ? "arrow_drop_up" : "arrow_drop_down"}
  573. </i>
  574. </ActionButton>
  575. </ActionContainer>
  576. </Flex>
  577. {isExpanded && (
  578. <>
  579. <Buffer />
  580. {isObject(envGroup.variables) ? (
  581. <>
  582. {Object.entries(envGroup.variables || {})?.map(
  583. ([key, value], i: number) => {
  584. // Preprocess non-string env values set via raw Helm values
  585. if (typeof value === "object") {
  586. value = JSON.stringify(value);
  587. } else {
  588. value = String(value);
  589. }
  590. return (
  591. <InputWrapper key={i}>
  592. <KeyInput
  593. placeholder="ex: key"
  594. width="270px"
  595. value={key}
  596. disabled
  597. />
  598. <Spacer />
  599. {value?.includes("PORTERSECRET") ? (
  600. <KeyInput
  601. placeholder="ex: value"
  602. width="270px"
  603. value={value}
  604. disabled
  605. type={
  606. value.includes("PORTERSECRET")
  607. ? "password"
  608. : "text"
  609. }
  610. />
  611. ) : (
  612. <MultiLineInput
  613. placeholder="ex: value"
  614. width="270px"
  615. value={value}
  616. disabled
  617. rows={value?.split("\n").length}
  618. spellCheck={false}
  619. ></MultiLineInput>
  620. )}
  621. </InputWrapper>
  622. );
  623. }
  624. )}
  625. </>
  626. ) : (
  627. <NoVariablesTextWrapper>
  628. This env group has no variables yet
  629. </NoVariablesTextWrapper>
  630. )}
  631. <Br />
  632. </>
  633. )}
  634. </StyledCard>
  635. </>
  636. );
  637. };
  638. const Br = styled.div`
  639. width: 100%;
  640. height: 1px;
  641. `;
  642. const Buffer = styled.div`
  643. width: 100%;
  644. height: 10px;
  645. `;
  646. const Spacer = styled.div`
  647. width: 10px;
  648. height: 20px;
  649. `;
  650. const AddRowButton = styled.div`
  651. display: flex;
  652. align-items: center;
  653. width: 270px;
  654. font-size: 13px;
  655. color: #aaaabb;
  656. height: 32px;
  657. border-radius: 3px;
  658. cursor: pointer;
  659. background: #ffffff11;
  660. :hover {
  661. background: #ffffff22;
  662. }
  663. > i {
  664. color: #ffffff44;
  665. font-size: 16px;
  666. margin-left: 8px;
  667. margin-right: 10px;
  668. display: flex;
  669. align-items: center;
  670. justify-content: center;
  671. }
  672. `;
  673. const LoadButton = styled(AddRowButton)`
  674. background: none;
  675. border: 1px solid #ffffff55;
  676. > i {
  677. color: #ffffff44;
  678. font-size: 16px;
  679. margin-left: 8px;
  680. margin-right: 10px;
  681. display: flex;
  682. align-items: center;
  683. justify-content: center;
  684. }
  685. > img {
  686. width: 14px;
  687. margin-left: 10px;
  688. margin-right: 12px;
  689. }
  690. `;
  691. const UploadButton = styled(AddRowButton)`
  692. background: none;
  693. position: relative;
  694. margin-left: 10px;
  695. border: 1px solid #ffffff55;
  696. > i {
  697. color: #ffffff44;
  698. font-size: 16px;
  699. margin-left: 8px;
  700. margin-right: 10px;
  701. display: flex;
  702. align-items: center;
  703. justify-content: center;
  704. }
  705. > img {
  706. width: 14px;
  707. margin-left: 10px;
  708. margin-right: 12px;
  709. }
  710. `;
  711. const DeleteButton = styled.div`
  712. width: 15px;
  713. height: 15px;
  714. display: flex;
  715. align-items: center;
  716. margin-left: 8px;
  717. margin-top: -3px;
  718. justify-content: center;
  719. > i {
  720. font-size: 17px;
  721. color: #ffffff44;
  722. display: flex;
  723. align-items: center;
  724. justify-content: center;
  725. cursor: pointer;
  726. :hover {
  727. color: #ffffff88;
  728. }
  729. }
  730. `;
  731. const HideButton = styled(DeleteButton)`
  732. margin-top: -5px;
  733. > i {
  734. font-size: 19px;
  735. cursor: default;
  736. :hover {
  737. color: #ffffff44;
  738. }
  739. }
  740. `;
  741. const Wrapper = styled.div`
  742. margin-left: 5px;
  743. height: 20px;
  744. display: flex;
  745. align-items: center;
  746. margin-top: -7px;
  747. `;
  748. const InputWrapper = styled.div`
  749. display: flex;
  750. align-items: center;
  751. margin-top: 5px;
  752. `;
  753. type InputProps = {
  754. disabled?: boolean;
  755. width: string;
  756. borderColor?: string;
  757. };
  758. const KeyInput = styled.input<InputProps>`
  759. outline: none;
  760. border: none;
  761. margin-bottom: 5px;
  762. font-size: 13px;
  763. background: #ffffff11;
  764. border: 1px solid
  765. ${(props) => (props.borderColor ? props.borderColor : "#ffffff55")};
  766. border-radius: 3px;
  767. width: ${(props) => (props.width ? props.width : "270px")};
  768. color: ${(props) => (props.disabled ? "#ffffff44" : "white")};
  769. padding: 5px 10px;
  770. height: 35px;
  771. `;
  772. export const MultiLineInput = styled.textarea<InputProps>`
  773. outline: none;
  774. border: none;
  775. margin-bottom: 5px;
  776. font-size: 13px;
  777. background: #ffffff11;
  778. border: 1px solid
  779. ${(props) => (props.borderColor ? props.borderColor : "#ffffff55")};
  780. border-radius: 3px;
  781. min-width: ${(props) => (props.width ? props.width : "270px")};
  782. max-width: ${(props) => (props.width ? props.width : "270px")};
  783. color: ${(props) => (props.disabled ? "#ffffff44" : "white")};
  784. padding: 8px 10px 5px 10px;
  785. min-height: 35px;
  786. max-height: 100px;
  787. white-space: nowrap;
  788. ::-webkit-scrollbar {
  789. width: 8px;
  790. :horizontal {
  791. height: 8px;
  792. }
  793. }
  794. ::-webkit-scrollbar-corner {
  795. width: 10px;
  796. background: #ffffff11;
  797. color: white;
  798. }
  799. ::-webkit-scrollbar-track {
  800. width: 10px;
  801. -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
  802. box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
  803. }
  804. ::-webkit-scrollbar-thumb {
  805. background-color: darkgrey;
  806. outline: 1px solid slategrey;
  807. }
  808. `;
  809. const Label = styled.div`
  810. color: #ffffff;
  811. margin-bottom: 10px;
  812. `;
  813. const StyledInputArray = styled.div`
  814. margin-bottom: 15px;
  815. margin-top: 22px;
  816. `;
  817. const fadeIn = keyframes`
  818. from {
  819. opacity: 0;
  820. }
  821. to {
  822. opacity: 1;
  823. }
  824. `;
  825. const StyledCard = styled.div`
  826. border: 1px solid #ffffff44;
  827. background: #ffffff11;
  828. margin-bottom: 5px;
  829. border-radius: 8px;
  830. margin-top: 15px;
  831. padding: 10px 14px;
  832. overflow: hidden;
  833. font-size: 13px;
  834. animation: ${fadeIn} 0.5s;
  835. `;
  836. const Flex = styled.div`
  837. display: flex;
  838. height: 25px;
  839. align-items: center;
  840. justify-content: space-between;
  841. `;
  842. const ContentContainer = styled.div`
  843. display: flex;
  844. height: 40px;
  845. width: 100%;
  846. align-items: center;
  847. `;
  848. const EventInformation = styled.div`
  849. display: flex;
  850. flex-direction: column;
  851. justify-content: space-around;
  852. height: 100%;
  853. `;
  854. const EventName = styled.div`
  855. font-family: "Work Sans", sans-serif;
  856. font-weight: 500;
  857. color: #ffffff;
  858. `;
  859. const ActionContainer = styled.div`
  860. display: flex;
  861. align-items: center;
  862. white-space: nowrap;
  863. height: 100%;
  864. `;
  865. const ActionButton = styled.button`
  866. position: relative;
  867. border: none;
  868. background: none;
  869. color: white;
  870. padding: 5px;
  871. width: 30px;
  872. height: 30px;
  873. margin-left: 5px;
  874. display: flex;
  875. justify-content: center;
  876. align-items: center;
  877. border-radius: 50%;
  878. cursor: pointer;
  879. color: #aaaabb;
  880. border: 1px solid #ffffff00;
  881. :hover {
  882. background: #ffffff11;
  883. border: 1px solid #ffffff44;
  884. }
  885. > span {
  886. font-size: 20px;
  887. }
  888. `;
  889. const NoVariablesTextWrapper = styled.div`
  890. display: flex;
  891. align-items: center;
  892. justify-content: center;
  893. color: #ffffff99;
  894. `;