KeyValueArray.tsx 24 KB

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