lodash#reject TypeScript Examples

The following examples show how to use lodash#reject. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: api.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
function filterBySettings(
  suggestions: UI5XMLViewCompletion[],
  settings: CodeAssistSettings
): UI5XMLViewCompletion[] {
  let filteredSuggestions = suggestions;
  if (!settings.codeAssist.deprecated) {
    filteredSuggestions = reject(
      filteredSuggestions,
      (suggestion) =>
        isUI5NodeXMLViewCompletion(suggestion) &&
        suggestion.ui5Node.deprecatedInfo?.isDeprecated === true
    );
  }
  if (!settings.codeAssist.experimental) {
    filteredSuggestions = reject(
      filteredSuggestions,
      (suggestions) =>
        isUI5NodeXMLViewCompletion(suggestions) &&
        suggestions.ui5Node.experimentalInfo?.isExperimental === true
    );
  }
  return filteredSuggestions;
}
Example #2
Source File: app.ts    From PrettyScribe with ISC License 6 votes vote down vote up
function handleFileSelect(event: Event) {

  let input;
  let files;

  if (event?.type === "resize") input = fileChangeEvent?.target as HTMLInputElement;
  else input = event?.target as HTMLInputElement;

  files = input?.files;

  cleanup();

  if (files) {
    if (event?.type !== "resize") fileChangeEvent = event;

    const reader = new FileReader();
    reader.onerror = () => {
      reader.abort();
      reject(new DOMException('Failed to read roster file.'));
    }
    reader.onloadend = async () => {
      const content = reader.result as string;
      const xmldata = await unzip(content);
      parseXML(xmldata);
    }
    reader.readAsBinaryString(files[0]);
  }
}
Example #3
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
handleDelete = (uniKey: string) => {
    const dataSource = [...this.state.dataSource];
    const onDel = this.props.onDel || noop;
    this.setState(
      {
        dataSource: reject(dataSource, { uniKey }),
      },
      () => {
        onDel(Array.isArray(this.props.data) ? this.state.dataSource : this.getTableData());
      },
    );
  };
Example #4
Source File: non-unique-id.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
function buildIssuesForSingleID(
  duplicatedAttributes: DuplicatedIDXMLAttribute[],
  id: string
): NonUniqueIDIssue[] {
  const issuesForID = map(
    duplicatedAttributes,
    (currDupAttrib, currAttribIdx) => {
      const currDupIdValToken = currDupAttrib.syntax.value;
      // Related issues must not include the "main" issue attribute
      const relatedOtherDupIDAttribs = reject(
        duplicatedAttributes,
        (_, arrIdx) => arrIdx === currAttribIdx
      );

      return {
        kind: "NonUniqueIDIssue" as const,
        message: buildMessage(NON_UNIQUE_ID.msg, id),
        severity: "error" as const,
        offsetRange: {
          start: currDupIdValToken.startOffset,
          end: currDupIdValToken.endOffset,
        },
        identicalIDsRanges: map(relatedOtherDupIDAttribs, (_) => ({
          start: _.syntax.value.startOffset,
          end: _.syntax.value.endOffset,
        })),
      };
    }
  );

  return issuesForID;
}
Example #5
Source File: namespace.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
/**
 * Suggests Namespaces inside Element
 * For example xmlns:m should provide list of namespaces ending with .m.. (like "sap.m", "sap.ui.core.mvc")
 * attribute is namespace attribute if it's equal to "xmlns" or starts with "xmlns:"
 * in first case all possible namespaces of semantic module will be provided excluding pre-existing ones
 */
export function namespaceKeysSuggestions(
  opts: UI5AttributeNameCompletionOptions
): UI5NamespacesInXMLAttributeKeyCompletion[] {
  const ui5Model = opts.context;
  const xmlElement = opts.element;

  if (opts.prefix === undefined) {
    return [];
  }

  const xmlnsPrefix = getXMLNamespaceKeyPrefix(opts.prefix);
  if (xmlnsPrefix === undefined) {
    return [];
  }

  const existingNamespacesAttributes: XMLAttribute[] = filter(
    xmlElement.attributes,
    isExistingNamespaceAttribute
  );

  const existingNamespacesNames = compact(
    map(existingNamespacesAttributes, (_) => _.value)
  );

  const applicableNamespaces = filter(ui5Model.namespaces, (_) =>
    isNamespaceApplicable(_, xmlnsPrefix)
  );

  const suggestedNamespaces = reject(applicableNamespaces, (_) =>
    includes(existingNamespacesNames, ui5NodeToFQN(_))
  );

  return map(suggestedNamespaces, (_) => ({
    type: "UI5NamespacesInXMLAttributeKey",
    ui5Node: _,
    astNode: opts.attribute as XMLAttribute,
  }));
}
Example #6
Source File: turn.ts    From fishbowl with MIT License 5 votes vote down vote up
export function drawableCardsWithoutCompletedCardsInActiveTurn(
  cards: CurrentGameSubscription["games"][0]["cards"],
  completedCardIdsInActiveTurn: Array<number>
) {
  return reject(cards, (card) => completedCardIdsInActiveTurn.includes(card.id))
}
Example #7
Source File: filter-members.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
export function filterMembersByNames<T extends WithName>(
  members: T[],
  preExistingNames: string[]
): T[] {
  return reject(members, (_) => includes(preExistingNames, _.name));
}
Example #8
Source File: aggregation.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
/**
 * Suggests Aggregation inside sap.ui.core.Element
 * For example: 'content' and 'footer' inside `sap.m.Page`
 */
export function aggregationSuggestions(
  opts: UI5ElementNameCompletionOptions
): UI5AggregationsInXMLTagNameCompletion[] {
  const ui5Model = opts.context;
  const prefix = opts.prefix ?? "";
  const xmlElement = opts.element;
  const parentXMLElement = xmlElement.parent;

  // The top level element cannot be an aggregation
  if (parentXMLElement.type === "XMLDocument") {
    return [];
  }

  const prefixParts = splitQNameByNamespace(prefix);

  // An aggregation must be on the parent tag namespace.
  // We suggest the completion even if the namespace was not defined.
  if (
    prefixParts.prefix !== undefined &&
    !isSameXMLNSFromPrefix(
      parentXMLElement.ns,
      parentXMLElement,
      prefixParts.prefix,
      xmlElement
    )
  ) {
    return [];
  }

  const parentUI5Class = getUI5ClassByXMLElement(parentXMLElement, ui5Model);
  if (!isElementSubClass(parentUI5Class)) {
    return [];
  }

  const existingAggregations = compact(
    uniq(map(parentXMLElement.subElements, (_) => _.name))
  );
  const existingAggregationsWithoutCurrent =
    xmlElement.name === null
      ? existingAggregations
      : reject(existingAggregations, (name) => name === xmlElement.name);

  const uniquePrefixMatchingAggregations = filterMembersForSuggestion(
    flattenAggregations(parentUI5Class),
    prefixParts.localName,
    existingAggregationsWithoutCurrent
  );

  return map(uniquePrefixMatchingAggregations, (_) => ({
    type: "UI5AggregationsInXMLTagName",
    ui5Node: _,
    astNode: xmlElement,
  }));
}
Example #9
Source File: prop-event-assoc.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
/**
 * Suggests Properties and Events inside Element
 * For example: 'backgroundDesign' and 'icon' in `sap.m.Page` element
 */
export function propEventAssocSuggestions(
  opts: UI5AttributeNameCompletionOptions
): PropEventsAssocInXMLAttributeKeyCompletion[] {
  const ui5Model = opts.context;
  const xmlElement = opts.element;

  const elementClass = getUI5ClassByXMLElement(xmlElement, ui5Model);
  if (!isElementSubClass(elementClass)) {
    return [];
  }
  const allProps: (UI5Prop | UI5Event | UI5Association)[] = flattenProperties(
    elementClass
  );
  const allEvents = flattenEvents(elementClass);
  const allAssociations = flattenAssociations(elementClass);
  const allPropertiesEventsAssociations = allProps
    .concat(allEvents)
    .concat(allAssociations);

  const prefix = opts.prefix ?? "";
  const existingAttributeNames = compact(
    uniq(map(xmlElement.attributes, (_) => _.key))
  );
  const currentAttributeKey = opts.attribute?.key;
  const existingAttributeNamesWithoutCurrent =
    currentAttributeKey === undefined
      ? existingAttributeNames
      : reject(existingAttributeNames, (name) => name === currentAttributeKey);

  const uniquePrefixMatchingAttributes = filterMembersForSuggestion(
    allPropertiesEventsAssociations,
    prefix,
    existingAttributeNamesWithoutCurrent
  );

  const suggestions = map(uniquePrefixMatchingAttributes, (_) => ({
    type: NodeKindToSuggestionType[_.kind],
    ui5Node: _,
    astNode:
      (opts.attribute as XMLAttribute) ?? createDummyAttribute(xmlElement),
  }));

  // Using casing due to dynamic assignment to the `type` property of each suggestion
  return suggestions as PropEventsAssocInXMLAttributeKeyCompletion[];
}
Example #10
Source File: WaitingForSubmissions.tsx    From fishbowl with MIT License 4 votes vote down vote up
function WaitingForSubmissions() {
  const { t } = useTranslation()
  const currentGame = React.useContext(CurrentGameContext)
  const currentPlayer = React.useContext(CurrentPlayerContext)

  const numEntriesPerPlayer = currentGame.num_entries_per_player
  const numPlayers = currentGame.players.length

  if (!numEntriesPerPlayer || !numPlayers) {
    return null
  }

  const total = numEntriesPerPlayer * numPlayers
  const submittedOrAcceptedSoFar = currentGame.cards.length

  const waitingForPlayers = reject(currentGame.players, (player) => {
    return (
      filter(currentGame.cards, (card) => card.player_id === player.id)
        .length === numEntriesPerPlayer
    )
  })

  if (!submittedOrAcceptedSoFar) {
    return null
  }

  let state: WaitingForSubmissionsState

  if (total === 0 || submittedOrAcceptedSoFar !== total) {
    state = WaitingForSubmissionsState.Waiting
  } else if (PlayerRole.Host === currentPlayer.role) {
    state = WaitingForSubmissionsState.SubmittedAssign
  } else {
    state = WaitingForSubmissionsState.SubmittedWait
  }

  const unscreenedCards =
    PlayerRole.Host === currentPlayer.role && currentGame.screen_cards
      ? currentGame.cards.filter(
          (card) =>
            card.is_allowed === null && card.player_id !== currentPlayer.id
        )
      : []

  return (
    <>
      <Grid item>
        <Title text={t("cardSubmission.waiting.title", "Well done!")} />
      </Grid>

      {
        {
          [WaitingForSubmissionsState.Waiting]: (
            <>
              <Grid item container justify="center">
                {t(
                  "cardSubmission.waiting.description",
                  "Just waiting for everyone else..."
                )}
                <div style={{ width: 4 }} />
                {waitingForPlayers.map((player) => (
                  <>
                    <PlayerChip username={player.username || ""} />
                    <div style={{ width: 4 }} />
                  </>
                ))}
              </Grid>
              <Grid item>
                <Typography variant="h5">
                  {t(
                    "cardSubmission.waiting.progress",
                    "{{ progress }} cards so far",
                    {
                      progress: `${submittedOrAcceptedSoFar}/${total}`,
                    }
                  )}
                </Typography>
              </Grid>
            </>
          ),
          [WaitingForSubmissionsState.SubmittedAssign]: (
            <Grid item>
              {t(
                "cardSubmission.waiting.submitted.host",
                "All players submitted! As the host, you can now assign teams."
              )}
            </Grid>
          ),
          [WaitingForSubmissionsState.SubmittedWait]: (
            <Grid item>
              {t(
                "cardSubmission.waiting.submitted.player",
                "All players submitted {{ cardCount }} cards in total. Now we are waiting on the host to start the game!",
                { cardCount: submittedOrAcceptedSoFar }
              )}
            </Grid>
          ),
        }[state]
      }

      {PlayerRole.Host === currentPlayer.role && currentGame.screen_cards && (
        <Grid item container direction="column" spacing={2} alignItems="center">
          {unscreenedCards.map((card, index) => (
            <Grid item key={index}>
              <ScreenCard card={card} />
            </Grid>
          ))}
        </Grid>
      )}

      {WaitingForSubmissionsState.SubmittedAssign === state ? (
        <div style={{ marginTop: 30 }}>
          <AssignTeamsButton />
        </div>
      ) : !unscreenedCards.length ? (
        <div style={{ marginTop: 50 }}>
          <Fishbowl />
        </div>
      ) : null}
    </>
  )
}
Example #11
Source File: index.tsx    From fishbowl with MIT License 4 votes vote down vote up
function EndGame() {
  const { t } = useTranslation()
  const currentGame = React.useContext(CurrentGameContext)
  const currentPlayer = React.useContext(CurrentPlayerContext)
  const titleClasses = useTitleStyle()
  const [redirectHome, setRedirectHome] = React.useState(false)

  const turnsByPlayer = new Map()
  currentGame.turns.forEach((turn) => {
    turnsByPlayer.set(
      turn.player_id,
      reject(
        (turnsByPlayer.get(turn.player_id) || []).concat([
          turn.completed_card_ids,
        ]),
        (arr) => isEmpty(arr)
      )
    )
  })

  const scoresByPlayer = new Map()
  turnsByPlayer.forEach((value, key) => {
    scoresByPlayer.set(key, flatMap(value).length)
  })

  let highScore = -1
  scoresByPlayer.forEach((value, key) => {
    if (value > highScore) {
      highScore = value
    }
  })

  const highScorePlayers = filter(
    currentGame.players,
    (player) => scoresByPlayer.get(player.id) === highScore
  )

  const redScore = teamScore(Team.Red, currentGame.turns, currentGame.players)
  const blueScore = teamScore(Team.Blue, currentGame.turns, currentGame.players)

  const tie = redScore === blueScore
  const winningTeam = redScore > blueScore ? Team.Red : Team.Blue

  const shareContent = t(
    "end.shareContent",
    "Just had a great time playing {{ url }} online, you should check it out!",
    { url: "fishbowl-game.com" }
  )

  return (
    <>
      {redirectHome && <Redirect to={routes.root}></Redirect>}
      <Grid container direction="column" spacing={2}>
        <Grid item style={{ textAlign: "center" }}>
          <Typography variant="h4" className={titleClasses.title}>
            {t("end.title", "Game Over")}
          </Typography>
        </Grid>
        <Grid item style={{ textAlign: "center" }}>
          <Box style={{ fontSize: "24px", lineHeight: "0.9" }}>
            {<span style={{ color: TeamColor[Team.Red] }}>{redScore}</span>}
            {" - "}
            {<span style={{ color: TeamColor[Team.Blue] }}>{blueScore}</span>}
          </Box>
        </Grid>
        <Grid item>
          <Divider variant="fullWidth"></Divider>
        </Grid>
        <Grid item>
          {tie
            ? t("end.result.tie", "It's a tie! Play again to break it.")
            : t("end.result.win", "{{ teamName }} wins! Bask in the glory.", {
                teamName: t(
                  `teams.teamName.${winningTeam}`,
                  `Team ${winningTeam}`
                ).toLocaleUpperCase(),
              })}
        </Grid>
        {!isEmpty(highScorePlayers) && (
          <Grid item>
            <Trans
              t={t}
              i18nKey="end.highScore"
              count={highScorePlayers.length}
              values={{ highScore }}
              tOptions={{
                defaultValue_plural:
                  "<0>{{playerUsernames}}</0> put the team on their back. They got their team to guess the most number of cards ({{ highScore }}!), across all rounds.",
              }}
            >
              <PlayerChipList players={highScorePlayers}>
                {{ playerUsernames: null }}
              </PlayerChipList>
              {
                " put the team on their back. They got their team to guess the most number of cards ({{ highScore }}!), across all rounds."
              }
            </Trans>
          </Grid>
        )}
        <Grid item>
          {t("end.yourScore", "You scored {{ score }} across all rounds.", {
            score: scoresByPlayer.get(currentPlayer.id) || 0,
          })}
        </Grid>
        <Grid item>
          <Divider variant="fullWidth"></Divider>
        </Grid>
        <Grid
          item
          container
          direction="column"
          justify="center"
          alignItems="center"
        >
          <Box pb={2}>
            {t(
              "end.thanks.share",
              "Thanks for playing -- if you had fun, share it with your friends!"
            )}
          </Box>

          <Grid container justify="center" spacing={2}>
            <Grid item>
              <TwitterShareButton
                url={"fishbowl-game.com"}
                title={shareContent}
              >
                <TwitterIcon size={50} round />
              </TwitterShareButton>
            </Grid>
            <Grid item>
              <FacebookShareButton
                url={"fishbowl-game.com"}
                title={shareContent}
              >
                <FacebookIcon size={50} round />
              </FacebookShareButton>
            </Grid>
          </Grid>
          <Trans t={t} i18nKey="end.thanks.support">
            <Box pb={1} pt={2}>
              Or support the project by
            </Box>
            <Box py={2}>
              <BuyMeACoffeeButton>Buying us a coffee</BuyMeACoffeeButton>
            </Box>
            <Box py={1}>
              <Link href="https://forms.gle/L9qWMsnAUghXqqxE9" target="_blank">
                sharing your feedback
              </Link>
              , and playing again soon!
            </Box>
          </Trans>
        </Grid>
        <Grid item>
          <Divider variant="fullWidth"></Divider>
        </Grid>
        <Grid item container justify="center">
          <Box py={1}>
            <Button variant="outlined" onClick={() => setRedirectHome(true)}>
              {t("end.playAgainButton", "Play Again")}
            </Button>
          </Box>
        </Grid>
      </Grid>
    </>
  )
}
Example #12
Source File: ControllableRoundSettings.tsx    From fishbowl with MIT License 4 votes vote down vote up
function ControllableRoundSettings() {
  const currentGame = React.useContext(CurrentGameContext)
  const [updateAllRounds] = useUpdateAllRoundsMutation()
  const [deleteRound] = useDeleteRoundMutation()
  const [addRound] = useAddRoundMutation()
  const [showAddRoundForm, setShowAddRoundForm] = React.useState(false)
  const [addRoundValue, setAddRoundValue] = React.useState("")
  const ref = React.useRef<HTMLDivElement>(null)

  useOnClickOutside(ref, () => {
    if (showAddRoundForm) {
      setShowAddRoundForm(false)
      setAddRoundValue("")
    }
  })

  return (
    <div ref={ref}>
      <RoundSettingsList>
        <>
          {currentGame.rounds.map((round, index) => {
            return (
              <ListItem key={round.id}>
                <ListItemIcon>
                  <>
                    <IconButton
                      color="primary"
                      size="small"
                      disabled={index === 0}
                      onClick={() => {
                        const updatedRounds = arrayMove(
                          currentGame.rounds,
                          index,
                          index - 1
                        )
                        updateAllRounds({
                          variables: {
                            gameId: currentGame.id,
                            rounds: updatedRounds.map(
                              (updatedRound, updatedIndex) => {
                                return {
                                  id: updatedRound.id,
                                  value: updatedRound.value,
                                  order_sequence: updatedIndex,
                                }
                              }
                            ),
                          },
                        })
                      }}
                    >
                      <ArrowDropUpIcon></ArrowDropUpIcon>
                    </IconButton>
                    <IconButton
                      color="primary"
                      size="small"
                      disabled={index === currentGame.rounds.length - 1}
                      onClick={() => {
                        const updatedRounds = arrayMove(
                          currentGame.rounds,
                          index,
                          index + 1
                        )
                        updateAllRounds({
                          variables: {
                            gameId: currentGame.id,
                            rounds: updatedRounds.map(
                              (updatedRound, updatedIndex) => {
                                return {
                                  id: updatedRound.id,
                                  value: updatedRound.value,
                                  order_sequence: updatedIndex,
                                }
                              }
                            ),
                          },
                        })
                      }}
                    >
                      <ArrowDropDownIcon></ArrowDropDownIcon>
                    </IconButton>
                  </>
                </ListItemIcon>
                <ListItemText>
                  <Box pl={2}>
                    {index + 1}. {capitalize(round.value)}
                  </Box>
                </ListItemText>
                <ListItemSecondaryAction>
                  <IconButton
                    size="small"
                    onClick={async () => {
                      const updatedRounds = reject(
                        currentGame.rounds,
                        (_r) => _r.id === round.id
                      )
                      await deleteRound({
                        variables: {
                          id: round.id,
                          gameId: currentGame.id,
                          rounds: updatedRounds.map(
                            (updatedRound, updatedIndex) => {
                              return {
                                id: updatedRound.id,
                                value: updatedRound.value,
                                order_sequence: updatedIndex,
                              }
                            }
                          ),
                        },
                      })
                    }}
                  >
                    <CloseIcon></CloseIcon>
                  </IconButton>
                </ListItemSecondaryAction>
              </ListItem>
            )
          })}
          <ListItem>
            <ListItemText style={{ paddingLeft: "76px" }}>
              {showAddRoundForm ? (
                <TextField
                  value={addRoundValue}
                  onChange={({ target: { value } }) => setAddRoundValue(value)}
                  size="small"
                  autoFocus
                  InputProps={{
                    startAdornment: (
                      <InputAdornment position="start">
                        {currentGame.rounds.length + 1}.
                      </InputAdornment>
                    ),
                  }}
                ></TextField>
              ) : (
                <Box color="#fafafa">.</Box>
              )}
            </ListItemText>
            <ListItemSecondaryAction>
              <IconButton
                size="small"
                disabled={showAddRoundForm && addRoundValue.length === 0}
                color={addRoundValue.length > 0 ? "primary" : "default"}
                onClick={() => {
                  if (showAddRoundForm && addRoundValue.length > 0) {
                    addRound({
                      variables: {
                        object: {
                          game_id: currentGame.id,
                          value: lowerFirst(addRoundValue),
                          order_sequence: currentGame.rounds.length,
                        },
                      },
                    })
                    setShowAddRoundForm(false)
                    setAddRoundValue("")
                  } else {
                    setShowAddRoundForm(true)
                  }
                }}
              >
                <AddCircleIcon></AddCircleIcon>
              </IconButton>
            </ListItemSecondaryAction>
          </ListItem>
        </>
      </RoundSettingsList>
    </div>
  )
}
Example #13
Source File: WaitingRoom.tsx    From fishbowl with MIT License 4 votes vote down vote up
function WaitingRoom(props: {
  cardPlayStyle: GameCardPlayStyleEnum
  wordList?: string
}) {
  const { t } = useTranslation()
  const MIN_NUMBER_OF_PLAYERS = 2 // TODO: Update to 4.
  const currentGame = React.useContext(CurrentGameContext)
  const currentPlayer = React.useContext(CurrentPlayerContext)
  const [updateGameState] = useUpdateGameStateMutation()
  const [removePlayer] = useRemovePlayerMutation()
  const [loadWords] = useLoadWordsMutation()
  const [updateAllPlayers] = useUpdateAllPlayersMutation()

  const playersWithUsernames =
    reject(currentGame.players, (player) => isEmpty(player.username)) || []
  const playersWithoutUsernames =
    filter(currentGame.players, (player) => isEmpty(player.username)) || []
  const canSeeStartGameButton = currentPlayer.role === PlayerRole.Host
  const canStartGame =
    canSeeStartGameButton &&
    currentGame.seconds_per_turn &&
    find(playersWithUsernames, (player) => player.id === currentPlayer.id) &&
    playersWithUsernames.length >= MIN_NUMBER_OF_PLAYERS &&
    ((props.cardPlayStyle === GameCardPlayStyleEnum.PlayersSubmitWords &&
      currentGame.num_entries_per_player) ||
      (props.cardPlayStyle === GameCardPlayStyleEnum.HostProvidesWords &&
        Boolean(props.wordList)))

  return (
    <>
      <Grid item>
        <PlayerArena
          players={playersWithUsernames}
          hostCanRemovePlayer={currentPlayer.role === PlayerRole.Host}
        ></PlayerArena>
        {currentPlayer.role === PlayerRole.Host && (
          <>
            <Box mt={2} color={grey[600]}>
              {t(
                "lobby.hostHelper.removeHost",
                "In case someone is switching devices or browsers, you can remove them as the host."
              )}
            </Box>
            <Box my={2} color={grey[600]}>
              <Trans t={t} i18nKey="lobby.hostHelper.lateJoining">
                <b>Once you start the game, new players cannot join.</b> We'll
                add support for players joining late soon!
              </Trans>
            </Box>
          </>
        )}
      </Grid>
      <Grid item style={{ textAlign: "center" }}>
        {canSeeStartGameButton && (
          <Button
            onClick={async () => {
              await Promise.all(
                playersWithoutUsernames.map((player) => {
                  return removePlayer({
                    variables: {
                      id: player.id,
                    },
                  })
                })
              )
              if (
                props.cardPlayStyle ===
                  GameCardPlayStyleEnum.HostProvidesWords &&
                props.wordList
              ) {
                await loadWords({
                  variables: {
                    objects: parseWordList(props.wordList).map((word) => {
                      return {
                        word,
                        game_id: currentGame.id,
                        player_id: currentPlayer.id,
                        is_allowed: true,
                      }
                    }),
                  },
                })
                const players = teamsWithSequence(currentGame.players)
                await updateAllPlayers({
                  variables: {
                    gameId: currentGame.id,
                    players: players.map(({ id, team, team_sequence }) => ({
                      id,
                      team,
                      team_sequence,
                    })),
                  },
                })
                updateGameState({
                  variables: {
                    id: currentGame.id,
                    state: GameStateEnum.TeamAssignment,
                  },
                })
              } else if (
                props.cardPlayStyle === GameCardPlayStyleEnum.PlayersSubmitWords
              ) {
                await updateGameState({
                  variables: {
                    id: currentGame.id,
                    state: GameStateEnum.CardSubmission,
                  },
                })
              }
            }}
            disabled={!canStartGame}
            variant="contained"
            color="primary"
          >
            {t("lobby.everyoneHereButton", "Everyone's Here!")}
          </Button>
        )}
      </Grid>
    </>
  )
}
Example #14
Source File: YourTurnContent.tsx    From fishbowl with MIT License 4 votes vote down vote up
function YourTurnContent(props: {
  yourTeamPlayers: CurrentGameSubscription["games"][0]["players"]
  cardsInBowl: CurrentGameSubscription["games"][0]["cards"]
  activePlayer: CurrentGameSubscription["games"][0]["players"][0]
  activeTurn: CurrentGameSubscription["games"][0]["turns"][0]
  activeTurnPlayState: ActiveTurnPlayState
  secondsLeft: number
  currentRoundId: Rounds["id"]
  nextRoundId?: Rounds["id"]
  onStart: () => void
  onOutOfCards: () => void
}) {
  const { t } = useTranslation()
  const { serverTimeOffset } = React.useContext(CurrentPlayerContext)
  const currentGame = React.useContext(CurrentGameContext)
  const [startTurn] = useStartTurnMutation()
  const [endTurn] = useEndCurrentTurnAndStartNextTurnMutation()

  const [startingTurn, setStartingTurn] = React.useState(false)
  const [endingTurn, setEndingTurn] = React.useState(false)
  const [skippingTurn, setSkippingTurn] = React.useState(false)

  const [activeCard, setActiveCard] = React.useState<
    CurrentGameSubscription["games"][0]["cards"][0] | null
  >(null)

  const [shownCardsInActiveTurn, setShownCardsInActiveTurn] = React.useState<
    Map<number, { status: ShownCardStatus; startedAt: Date; endedAt: Date }>
  >(new Map())

  // Attach keyboard shortcuts to the Correct and Skip actions
  const SHORTCUTS_COMPLETE = [" ", "c"]
  const SHORTCUTS_SKIP = ["s"]
  const upHandler = (event: KeyboardEvent) => {
    if (includes(SHORTCUTS_COMPLETE, event.key)) {
      onNextCardClick(ShownCardStatus.Complete)
    } else if (
      currentGame.allow_card_skips &&
      includes(SHORTCUTS_SKIP, event.key)
    ) {
      onNextCardClick(ShownCardStatus.Skipped)
    }
  }
  React.useEffect(() => {
    window.addEventListener("keyup", upHandler)
    return () => {
      window.removeEventListener("keyup", upHandler)
    }
  }, [activeCard])

  React.useEffect(() => {
    if (activeCard && props.secondsLeft <= 0) {
      const shownCard = shownCardsInActiveTurn.get(activeCard.id)
      if (shownCard) {
        setShownCardsInActiveTurn(
          new Map(
            shownCardsInActiveTurn.set(activeCard.id, {
              ...shownCard,
              endedAt: new Date(),
            })
          )
        )
      }
    }
  }, [props.secondsLeft, activeCard])

  const [play] = useSound(bell)

  React.useEffect(() => {
    if (props.secondsLeft === 0) {
      play()
    }
  }, [props.secondsLeft])

  const onNextCardClick = (status: ShownCardStatus) => {
    if (activeCard) {
      // mark the active card as "complete" or "skipped"
      const shownCard = shownCardsInActiveTurn.get(activeCard.id)
      if (shownCard) {
        setShownCardsInActiveTurn(
          new Map(
            shownCardsInActiveTurn.set(activeCard.id, {
              ...shownCard,
              status: status,
              endedAt: new Date(),
            })
          )
        )
      }

      const nextSet = drawableCardsWithoutCompletedCardsInActiveTurn(
        props.cardsInBowl,
        [...shownCardsInActiveTurn.keys()]
      )
      const outOfCards = nextSet.length === 0
      if (outOfCards) {
        props.onOutOfCards()
      } else {
        const nextActiveCard = sample(nextSet) || null
        if (nextActiveCard) {
          setActiveCard(nextActiveCard)
          setShownCardsInActiveTurn(
            new Map(
              shownCardsInActiveTurn.set(nextActiveCard.id, {
                status: ShownCardStatus.Incomplete,
                startedAt: new Date(),
                endedAt: new Date(),
              })
            )
          )
        }
      }
    }
  }

  const yourTeammates = React.useMemo(
    () =>
      reject(
        props.yourTeamPlayers,
        (player) => player.id === props.activePlayer.id
      ),
    [props.activePlayer.id, props.yourTeamPlayers]
  )

  return (
    <Box p={2}>
      <Grid container direction="column" spacing={4} alignItems="center">
        {props.activeTurnPlayState === ActiveTurnPlayState.Reviewing &&
        props.secondsLeft >= 0 ? (
          <Grid item>
            {t(
              "play.yourTurn.leftoverSeconds",
              "You'll be starting the next round with {{ count }} second leftover from this turn!",
              {
                count: props.secondsLeft,
                defaultValue_plural:
                  "You'll be starting the next round with {{ count }} seconds leftover from this turn!",
              }
            )}
          </Grid>
        ) : null}

        {/* Cards */}
        {[ActiveTurnPlayState.Waiting, ActiveTurnPlayState.Playing].includes(
          props.activeTurnPlayState
        ) && (
          <>
            {!!yourTeammates.length && (
              <Grid item>
                <Trans
                  t={t}
                  i18nKey="play.yourTurn.context"
                  count={yourTeammates.length}
                  tOptions={{
                    defaultValue_plural:
                      "<0>{{playerUsernames}}</0> from your team are guessing!",
                  }}
                >
                  <PlayerChipList players={yourTeammates}>
                    {{ playerUsernames: null }}
                  </PlayerChipList>
                  {" from your team is guessing!"}
                </Trans>
              </Grid>
            )}

            <Grid item>
              <BowlCard>
                {activeCard ? (
                  <Typography variant="h5">{activeCard.word}</Typography>
                ) : (
                  <div style={{ textAlign: "center", color: grey[600] }}>
                    {t("play.yourTurn.emptyCard", "You'll see cards here!")}
                  </div>
                )}
              </BowlCard>
            </Grid>
          </>
        )}
        {props.activeTurnPlayState === ActiveTurnPlayState.Reviewing && (
          <>
            <Grid item>
              {t(
                "play.yourTurn.reviewHelper.default",
                "Review the cards you went through this turn."
              )}{" "}
              {currentGame.allow_card_skips
                ? t(
                    "play.yourTurn.reviewHelper.withSkips",
                    "If you skipped or missed any, just uncheck them."
                  )
                : t(
                    "play.yourTurn.reviewHelper.withoutSkips",
                    "If you missed any, just uncheck them."
                  )}
            </Grid>
            <Grid item container direction="column" spacing={2}>
              {[...shownCardsInActiveTurn.keys()].map((cardId) => {
                return (
                  <Grid
                    key={cardId}
                    item
                    container
                    direction="row"
                    justify="center"
                    alignItems="center"
                    spacing={2}
                  >
                    <Grid item>
                      <Box>
                        <GreenCheckbox
                          checked={
                            shownCardsInActiveTurn.get(cardId)?.status ===
                            ShownCardStatus.Complete
                          }
                          onChange={({ target: { checked } }) => {
                            const shownCard = shownCardsInActiveTurn.get(cardId)
                            if (shownCard) {
                              setShownCardsInActiveTurn(
                                new Map(
                                  shownCardsInActiveTurn.set(cardId, {
                                    ...shownCard,
                                    status: checked
                                      ? ShownCardStatus.Complete
                                      : ShownCardStatus.Incomplete,
                                  })
                                )
                              )
                            }
                          }}
                        ></GreenCheckbox>
                      </Box>
                      {shownCardsInActiveTurn.get(cardId)?.status ===
                        ShownCardStatus.Skipped && (
                        <Box color={grey[600]}>
                          (
                          {t(
                            "play.yourTurn.cardButton.skip",
                            "Skip"
                          ).toLocaleLowerCase()}
                          )
                        </Box>
                      )}
                    </Grid>
                    <Grid item>
                      <BowlCard>
                        {
                          props.cardsInBowl.find((card) => card.id === cardId)
                            ?.word
                        }
                      </BowlCard>
                    </Grid>
                  </Grid>
                )
              })}
            </Grid>
          </>
        )}

        {/* Controls */}
        <Grid item container justify="space-around">
          {props.activeTurnPlayState === ActiveTurnPlayState.Playing && (
            <>
              {currentGame.allow_card_skips && (
                <Grid item>
                  <Button
                    color="default"
                    onClick={async () => {
                      onNextCardClick(ShownCardStatus.Skipped)
                    }}
                  >
                    {t("play.yourTurn.cardButton.skip", "Skip")}
                  </Button>
                </Grid>
              )}
              <Grid item>
                <Button
                  variant="contained"
                  color="primary"
                  onClick={async () => {
                    onNextCardClick(ShownCardStatus.Complete)
                  }}
                >
                  {t("play.yourTurn.cardButton.correct", "Correct")}
                </Button>
              </Grid>
            </>
          )}

          {props.activeTurnPlayState === ActiveTurnPlayState.Waiting && (
            <Grid item>
              <Button
                disabled={skippingTurn}
                onClick={async () => {
                  setSkippingTurn(true)
                  if (
                    window.confirm(
                      t(
                        "play.yourTurn.skipTurnConfirmation",
                        "Are you sure you want to skip your turn?"
                      )
                    )
                  ) {
                    const response = await endTurn({
                      variables: {
                        currentTurnId: props.activeTurn.id,
                        completedCardIds: [],
                        gameId: currentGame.id,
                        currentTurnScorings: [],
                        nextTurnplayerId: nextPlayerForSameTeam(
                          props.activePlayer,
                          currentGame.players
                        ).id,
                        roundId: props.currentRoundId,
                      },
                    })
                    if (response.errors) {
                      setSkippingTurn(false)
                    }
                  } else {
                    setSkippingTurn(false)
                  }
                }}
              >
                {t("play.yourTurn.skipTurnButton", "Skip turn")}
              </Button>
            </Grid>
          )}

          {props.activeTurnPlayState === ActiveTurnPlayState.Reviewing && (
            <Grid item>
              <Button
                variant="contained"
                color="primary"
                disabled={endingTurn}
                onClick={async () => {
                  setEndingTurn(true)
                  const shownCardIds = [...shownCardsInActiveTurn.keys()]
                  const completedCardIds = filter(shownCardIds, (cardId) => {
                    return (
                      shownCardsInActiveTurn.get(cardId)?.status ===
                      ShownCardStatus.Complete
                    )
                  })
                  const continueTurnIntoNewRound =
                    completedCardIds.length === shownCardIds.length &&
                    drawableCardsWithoutCompletedCardsInActiveTurn(
                      props.cardsInBowl,
                      [...shownCardsInActiveTurn.keys()]
                    ).length === 0 &&
                    props.secondsLeft !== 0

                  const scorings = compact(
                    shownCardIds.map<TurnScoringsInsertInput | null>(
                      (cardId) => {
                        const card = shownCardsInActiveTurn.get(cardId)
                        if (card) {
                          return {
                            turn_id: props.activeTurn.id,
                            card_id: cardId,
                            score:
                              card.status === ShownCardStatus.Complete ? 1 : 0,
                            status: card.status,
                            started_at: new Date(
                              card.startedAt.getTime() + serverTimeOffset
                            ),
                            ended_at: new Date(
                              card.endedAt.getTime() + serverTimeOffset
                            ),
                          }
                        } else {
                          return null
                        }
                      }
                    )
                  )

                  const response = await endTurn({
                    variables: {
                      currentTurnId: props.activeTurn.id,
                      completedCardIds: completedCardIds,
                      gameId: currentGame.id,
                      currentTurnScorings: scorings,
                      roundId: continueTurnIntoNewRound
                        ? props.nextRoundId
                        : props.currentRoundId,
                      nextTurnplayerId: continueTurnIntoNewRound
                        ? props.activePlayer.id
                        : nextPlayerForNextTeam(
                            props.activePlayer,
                            currentGame.turns,
                            currentGame.players
                          ).id,
                      nextTurnSecondsPerTurnOverride: continueTurnIntoNewRound
                        ? Math.round(Number(props.secondsLeft))
                        : null,
                    },
                  })

                  if (response.errors) {
                    setEndingTurn(false)
                  } else if (continueTurnIntoNewRound) {
                    window.location.reload()
                  }
                }}
              >
                {t("play.yourTurn.endTurnButton", "End turn")}
              </Button>
            </Grid>
          )}

          {props.activeTurnPlayState === ActiveTurnPlayState.Waiting && (
            <Grid item>
              <Button
                variant="contained"
                size="large"
                color="primary"
                disabled={startingTurn}
                onClick={async () => {
                  setStartingTurn(true)
                  const response = await startTurn({
                    variables: {
                      currentTurnId: props.activeTurn.id,
                    },
                  })
                  if (response.errors) {
                    setStartingTurn(false)
                  } else {
                    const firstActiveCard = sample(
                      drawableCardsWithoutCompletedCardsInActiveTurn(
                        props.cardsInBowl,
                        [...shownCardsInActiveTurn.keys()]
                      )
                    )
                    if (firstActiveCard) {
                      setActiveCard(firstActiveCard)
                      setShownCardsInActiveTurn(
                        new Map([
                          [
                            firstActiveCard.id,
                            {
                              status: ShownCardStatus.Incomplete,
                              startedAt: new Date(),
                              endedAt: new Date(),
                            },
                          ],
                        ])
                      )
                    }
                    props.onStart()
                  }
                }}
              >
                {t("play.yourTurn.startTurnButton", "Start Turn")}
              </Button>
            </Grid>
          )}
        </Grid>

        {!isMobile &&
          props.activeTurnPlayState === ActiveTurnPlayState.Playing && (
            <Grid item style={{ color: grey[600] }}>
              {currentGame.allow_card_skips
                ? t(
                    "play.yourTurn.shortcutHelper.withSkips",
                    'Hint: Press the spacebar for "{{ correctButton }}", and S for "{{ skipButton }}"',
                    {
                      correctButton: t(
                        "play.yourTurn.cardButton.correct",
                        "Correct"
                      ),
                      skipButton: t("play.yourTurn.cardButton.skip", "Skip"),
                    }
                  )
                : t(
                    "play.yourTurn.shortcutHelper.withoutSkips",
                    'Hint: Press the spacebar for "{{ correctButton }}"',
                    {
                      correctButton: t(
                        "play.yourTurn.cardButton.correct",
                        "Correct"
                      ),
                    }
                  )}
            </Grid>
          )}
      </Grid>
    </Box>
  )
}
Example #15
Source File: GetStatistics.ts    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export async function GetStatistics(
  categoryGroups: CategoryGroup[] = [],
  stories: Story[] = []
): Promise<any> {
  const storyList = getAllStoryListV2(categoryGroups, stories);
  const allCommonBricks = filter(storyList, (v) => v.type === "brick");
  const allCommonTemplates = reject(storyList, (v) => v.type === "brick");

  const commonBricksName = map(allCommonBricks, "id");
  const commonTemplatesName = map(allCommonTemplates, "id");
  const bootstrap = await AuthSdk.bootstrap();
  // 不统计iframe嵌套老站点的微应用
  const microAppsWithoutLegacy = reject(bootstrap.storyboards, [
    "app.legacy",
    "iframe",
  ]);
  const microAppsBricks = microAppsWithoutLegacy.map((microApp) => {
    const allBricksName = scanBricksInStoryboard(microApp, false);
    const allTemplatesName = scanTemplatesInStoryboard(microApp, false);
    const countAndSortAllBricks = reverse(
      sortBy(
        map(
          countBy(allBricksName, (item) => {
            return item;
          }),
          (v, k) => {
            return {
              id: k,
              count: v,
              type: "brick",
              isCommon: ifCommon(k, commonBricksName),
            };
          }
        ),
        "count"
      )
    );
    const countAndSortAllTemplates = reverse(
      sortBy(
        map(
          countBy(allTemplatesName, (item) => {
            return item;
          }),
          (v, k) => {
            return {
              id: k,
              count: v,
              type: "template",
              isCommon: ifCommon(k, commonTemplatesName),
            };
          }
        ),
        "count"
      )
    );
    microApp.countBricksAndTemplates = reverse(
      sortBy([...countAndSortAllBricks, ...countAndSortAllTemplates], "count")
    );
    microApp.countCommonBricksAndTemplates = filter(
      [...countAndSortAllBricks, ...countAndSortAllTemplates],
      "isCommon"
    ).length;

    const statisticsAll = [
      ...map(allBricksName, (v) => {
        return {
          type: "brick",
          id: v,
          isCommon: ifCommon(v, commonBricksName),
          app: microApp.app,
        };
      }),
      ...map(allTemplatesName, (v) => ({
        type: "template",
        id: v,
        isCommon: ifCommon(v, commonTemplatesName),
        app: microApp.app,
      })),
    ];
    microApp.statisticsAll = statisticsAll;
    microApp.commonUsedRate = getPercentage(
      filter(statisticsAll, (item) => item.isCommon).length /
        statisticsAll.length
    );
    return microApp;
  });
  const microAppsStatisticsMap = keyBy(microAppsBricks, "app.id");
  const microAppsSubMenu = {
    title: "微应用列表",
    menuItems: map(microAppsBricks, (item) => {
      return {
        text: item.app.name,
        to: `/developers/statistics/micro-app-statistics/${item.app.id}`,
      };
    }),
  };
  const microAppStatisticsRedirectTo = `${microAppsSubMenu.menuItems[0].to}`;
  const allMicroAppsBricksAndTemplate = flatten(
    map(microAppsBricks, "statisticsAll")
  );
  const allMicroAppsBricks = map(
    filter(allMicroAppsBricksAndTemplate, (item) => item.type === "brick"),
    "id"
  );
  const allMicroAppsTemplates = map(
    filter(allMicroAppsBricksAndTemplate, (item) => item.type === "template"),
    "id"
  );

  // 统计所有构件
  const allBricksAndTemplateGroupBy = groupBy(
    allMicroAppsBricksAndTemplate,
    (item) => item.type + "," + item.id
  );
  const countAllBricksAndTemplate = map(
    uniqBy(allMicroAppsBricksAndTemplate, (item) => item.type + "," + item.id),
    (v) => {
      return {
        id: v.id,
        type: v.type,
        isCommon: v.isCommon,
        count: allBricksAndTemplateGroupBy[v.type + "," + v.id].length,
        appList: map(
          uniqBy(
            allBricksAndTemplateGroupBy[v.type + "," + v.id],
            (v) => v.app.id
          ),
          "app"
        ),
      };
    }
  );

  // 排名前二十的构件
  let countCommonBricksUsed: any[] = reverse(
    sortBy(
      filter(
        map(
          countBy(allMicroAppsBricks, (item) => {
            return includes(commonBricksName, item) && item;
          }),
          (v, k) => {
            return {
              id: k,
              count: v,
            };
          }
        ),
        (item) => item.id !== "false" && item.id !== "div"
      ),
      "count"
    )
  ).slice(0, 20);
  countCommonBricksUsed = map(countCommonBricksUsed, (item) => {
    const found = find(allCommonBricks, ["id", item.id]);
    if (found) {
      item.author = found.subTitle;
      item.type = found.type;
      item.url = "/developers/brick-book/" + item.type + "/" + item.id;
    }
    return item;
  });
  // 排名前二十的模板
  let countCommonTemplatesUsed: any[] = reverse(
    sortBy(
      filter(
        map(
          countBy(allMicroAppsTemplates, (item) => {
            return includes(commonTemplatesName, item) && item;
          }),
          (v, k) => {
            return {
              id: k,
              count: v,
            };
          }
        ),
        (item) => item.id !== "false"
      ),
      "count"
    )
  ).slice(0, 20);
  countCommonTemplatesUsed = map(countCommonTemplatesUsed, (item) => {
    const found = find(allCommonTemplates, ["id", item.id]);
    item.author = found.subTitle;
    item.type = found.type;
    item.url = "/developers/brick-book/" + item.type + "/" + item.id;
    return item;
  });
  const topBricksAndTemplates = reverse(
    sortBy([...countCommonBricksUsed, ...countCommonTemplatesUsed], "count")
  ).slice(0, 20);
  const result = {
    microAppsCount: bootstrap.storyboards.length,
    microAppsWithoutLegacyCount: microAppsWithoutLegacy.length,
    iframeMicroApps:
      bootstrap.storyboards.length - microAppsWithoutLegacy.length,
    allBricksAndTemplatesCount:
      uniq(allMicroAppsBricks).length + uniq(allMicroAppsTemplates).length,
    commonBricksAndTemplatesCount:
      commonBricksName.length + allCommonTemplates.length,
    commonBricksAndTemplatesUsedRate: getPercentage(
      (filter(allMicroAppsBricks, (item) => {
        return includes(commonBricksName, item);
      }).length +
        filter(allMicroAppsTemplates, (item) => {
          return includes(commonTemplatesName, item);
        }).length) /
        (allMicroAppsBricks.length + allMicroAppsTemplates.length)
    ),
    microAppsPieChart: {
      title: "微应用总数:" + bootstrap.storyboards.length,
      data: {
        legendList: ["全新微应用", "iframe嵌套微应用"],
        seriesData: [
          {
            name: "全新微应用",
            value: microAppsWithoutLegacy.length,
          },
          {
            name: "iframe嵌套微应用",
            value: bootstrap.storyboards.length - microAppsWithoutLegacy.length,
          },
        ],
      },
    },
    microAppsSubMenu,
    microAppStatisticsRedirectTo,
    microAppsStatisticsMap,
    topBricksAndTemplates,
    countAllBricksAndTemplate,
    packageNames: uniq(
      compact(
        [...allMicroAppsBricks, ...allMicroAppsTemplates].map(
          (v) => v.split(".")?.[0]
        )
      )
    ),
  };
  return result;
}
Example #16
Source File: UserOrUserGroupSelect.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function LegacyUserSelectFormItem(
  props: UserSelectFormItemProps,
  ref: React.Ref<HTMLDivElement>
): React.ReactElement {
  const selectRef = useRef();
  const [selectedValue, setSelectedValue] = useState([]);
  const staticValue = useRef([]);
  const userShowKey: string[] = getInstanceNameKeys(props.objectMap["USER"]);
  const userGroupShowKey: string[] = getInstanceNameKeys(
    props.objectMap["USER_GROUP"]
  );

  const { t } = useTranslation(NS_FORMS);

  const getLabel = (
    objectId: "USER" | "USER_GROUP",
    instanceData: any
  ): string => {
    const showKey = objectId === "USER" ? userShowKey : userGroupShowKey;
    if (Array.isArray(showKey)) {
      const showName = showKey
        .map((key, index) => {
          if (index === 0) {
            return instanceData[key];
          } else {
            return instanceData[key] ? "(" + instanceData[key] + ")" : "";
          }
        })
        .join("");
      return showName;
    } else {
      return instanceData[showKey];
    }
  };

  const getStaticLabel = (label: string) => (
    <div style={{ color: "var(--bg-color-button-link)" }}>{label}</div>
  );

  const isDifferent = () => {
    const userOfValues = props.value?.selectedUser || [];
    const userGroupOfValues = props.value?.selectedUserGroup || [];
    const userOfSelectedValue = map(
      filter(selectedValue, (item) => !item.key.startsWith(":")),
      "key"
    );

    const userGroupOfSelectedValue = map(
      filter(selectedValue, (item) => item.key.startsWith(":")),
      "key"
    );

    return (
      !isEqual([...userOfValues].sort(), [...userOfSelectedValue].sort()) ||
      !isEqual(
        [...userGroupOfValues].sort(),
        [...userGroupOfSelectedValue].sort()
      )
    );
  };

  const initializeStaticList = () => {
    return groupBy(props.staticList, (v) =>
      startsWith(v, ":") ? "userGroup" : "user"
    );
  };

  // 后台搜索中
  const [fetching, setFetching] = useState(false);

  const [searchValue, setSearchValue] = useState();
  const [userList, setUserList] = useState([]);
  const [userGroupList, setUserGroupList] = useState([]);

  const [modalVisible, setModalVisible] = useState(false);
  const [modalObjectId, setModalObjectId] = useState(
    props.optionsMode === "group" ? "USER_GROUP" : "USER"
  );

  const triggerChange = (changedValue: any) => {
    props.onChange?.(
      isEmpty(changedValue.selectedUser) &&
        isEmpty(changedValue.selectedUserGroup)
        ? null
        : changedValue
    );
  };

  useEffect(() => {
    const initializeSelectedValue = async () => {
      if (props.value) {
        let selectedUser: any[] = [];
        let selectedUserGroup: any[] = [];
        const staticKeys = initializeStaticList();
        const user = compact(
          uniq([].concat(staticKeys.user).concat(props.value.selectedUser))
        );

        const userGroup = compact(
          uniq(
            []
              .concat(staticKeys.userGroup)
              .concat(props.value.selectedUserGroup)
          )
        );

        if (
          (staticKeys.user &&
            some(
              staticKeys.user,
              (v) => !props.value?.selectedUser?.includes(v)
            )) ||
          (staticKeys.userGroup &&
            some(
              staticKeys.userGroup,
              (v) => !props.value?.selectedUserGroup?.includes(v)
            ))
        ) {
          triggerChange({
            selectedUser: user,
            selectedUserGroup: userGroup,
          });
        }
        const staticValueToSet = [];
        if (user.length && props.optionsMode !== "group") {
          selectedUser = (
            await InstanceApi_postSearch("USER", {
              query: {
                name: {
                  $in: user,
                },
              },

              page: 1,
              page_size: user.length,
              fields: {
                ...zipObject(
                  userShowKey,
                  map(userShowKey, (v) => true)
                ),

                name: true,
              },
            })
          ).list;
        }
        if (userGroup.length && props.optionsMode !== "user") {
          selectedUserGroup = (
            await InstanceApi_postSearch("USER_GROUP", {
              query: {
                instanceId: {
                  // 默认带为":"+instanceId,这里查询的时候去掉前面的冒号
                  $in: map(userGroup, (v) => v.slice(1)),
                },
              },

              page: 1,
              page_size: userGroup.length,
              fields: {
                ...zipObject(
                  userGroupShowKey,
                  map(userGroupShowKey, (v) => true)
                ),

                name: true,
              },
            })
          ).list;
        }
        let labelValue = [
          ...map(selectedUser, (v) => {
            const labelText = getLabel("USER", v);
            const result = {
              key: v.name,
              label: props.staticList?.includes(v.name)
                ? getStaticLabel(labelText)
                : labelText,
            };

            if (props.staticList?.includes(v.name)) {
              staticValueToSet.push(result);
            }
            return result;
          }),
          ...map(selectedUserGroup, (v) => {
            const labelText = getLabel("USER_GROUP", v);
            const result = {
              key: ":" + v.instanceId,
              label: props.staticList?.includes(":" + v.instanceId)
                ? getStaticLabel(labelText)
                : labelText,
            };

            if (props.staticList?.includes(":" + v.instanceId)) {
              staticValueToSet.push(result);
            }
            return result;
          }),
        ];

        labelValue = [
          ...staticValueToSet,
          ...filter(labelValue, (v) => !props.staticList?.includes(v.key)),
        ];

        setSelectedValue(labelValue);
        staticValue.current = staticValueToSet;
      }
    };
    if (isDifferent()) {
      initializeSelectedValue();
    }
  }, [props.value]);

  const fetchInstanceList = async (
    objectId: "USER" | "USER_GROUP",
    keyword: string
  ) => {
    const showKey = objectId === "USER" ? userShowKey : userGroupShowKey;
    const showKeyQuery = {
      $or: map(uniq([...showKey, "name"]), (v) => ({
        [v]: { $like: `%${keyword}%` },
      })),
      ...(props.hideInvalidUser
        ? {
            state: "valid",
          }
        : {}),
    };
    return (
      await InstanceApi_postSearch(objectId, {
        page: 1,
        page_size: 20,
        fields: {
          ...zipObject(
            showKey,
            map(showKey, (v) => true)
          ),

          name: true,
        },
        query:
          props.userQuery && objectId === "USER"
            ? {
                ...props.userQuery,
                ...showKeyQuery,
              }
            : props.userGroupQuery && objectId === "USER_GROUP"
            ? {
                ...props.userGroupQuery,
                ...showKeyQuery,
              }
            : props.query || showKeyQuery
            ? {
                ...props.query,
                ...showKeyQuery,
              }
            : showKeyQuery,
      })
    ).list;
  };

  const searchUser = async (value: string) => {
    setUserList(await fetchInstanceList("USER", value));
  };

  // 用户组在instanceId前面加上:
  const searchUserGroup = async (value: string) => {
    const result = await fetchInstanceList("USER_GROUP", value);
    setUserGroupList(
      result.map((v) => {
        v.instanceId = ":" + v.instanceId;
        return v;
      })
    );
  };

  const searchUserOrUserGroupInstances = async (value) => {
    setSearchValue(value);
    setFetching(true);
    await Promise.all([
      ...(props.optionsMode !== "group" ? [searchUser(value)] : []),
      ...(props.optionsMode !== "user" ? [searchUserGroup(value)] : []),
    ]);

    setFetching(false);
  };

  const handleSelectChange = (originValue) => {
    const value = filter(originValue, (item) => {
      return !find(props.staticList, (v) => v === item.key);
    });
    value.unshift(...staticValue.current);
    setSelectedValue(value);
    const resultValue = {
      selectedUser: map(
        reject(value, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),

      selectedUserGroup: map(
        filter(value, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),
    };

    triggerChange(resultValue);
    if (searchValue !== "") {
      searchUserOrUserGroupInstances("");
    }
  };

  const handleFocus = () => {
    if (isNil(searchValue) || searchValue !== "") {
      searchUserOrUserGroupInstances("");
    }
  };

  const openModal = () => {
    setModalVisible(true);
  };

  const closeModal = () => {
    setModalVisible(false);
  };

  const handleInstancesSelected = (
    value: Record<string, any>[],
    objectId: string
  ): void => {
    let labelValue: any[] = [];
    if (objectId === "USER") {
      labelValue = [
        ...map(value, (v) => {
          const labelText = getLabel("USER", v);
          return {
            key: v.name,
            label: props.staticList?.includes(v.name)
              ? getStaticLabel(labelText)
              : labelText,
          };
        }),
      ];
    } else {
      labelValue = [
        ...map(value, (v) => {
          const labelText = getLabel("USER_GROUP", v);
          return {
            key: ":" + v.instanceId,
            label: props.staticList?.includes(":" + v.instanceId)
              ? getStaticLabel(labelText)
              : labelText,
          };
        }),
      ];
    }
    const resultSelectedValue = uniqBy(
      [...selectedValue, ...labelValue],
      "key"
    );

    setSelectedValue(resultSelectedValue);
    const resultValue = {
      selectedUser: map(
        reject(resultSelectedValue, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),

      selectedUserGroup: map(
        filter(resultSelectedValue, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),
    };

    triggerChange(resultValue);
  };

  const handleModalSelected = async (selectedKeys: string[]) => {
    if (selectedKeys?.length) {
      const instances = (
        await InstanceApi_postSearch(modalObjectId, {
          query: { instanceId: { $in: selectedKeys } },
          fields: { "*": true },
          page_size: selectedKeys.length,
        })
      ).list;
      handleInstancesSelected(instances, modalObjectId);
    }
    setModalVisible(false);
  };

  const toggleObjectId = () => {
    setModalObjectId(modalObjectId === "USER" ? "USER_GROUP" : "USER");
  };

  const title = (
    <div>
      {t(K.FILTER_FROM_CMDB, {
        type: modalObjectId === "USER" ? t(K.USERS) : t(K.USER_GROUPS),
      })}{" "}
      {props.optionsMode === "all" && (
        <Button type="link" onClick={toggleObjectId}>
          {t(K.SWITCH, {
            type: modalObjectId === "USER" ? t(K.USER_GROUPS) : t(K.USERS),
          })}{" "}
        </Button>
      )}
    </div>
  );

  // 快速选择我
  const addMeQuickly = async () => {
    const myUserName = getAuth().username;
    if (find(selectedValue, (v) => v.key === myUserName)) {
      // 如果已选择项中包含我,则不重新发起请求
      return;
    }
    const myUser = (
      await InstanceApi_postSearch("USER", {
        query: {
          name: {
            $eq: myUserName,
          },
        },

        page: 1,
        page_size: 1,
        fields: {
          ...zipObject(
            userShowKey,
            map(userShowKey, (v) => true)
          ),

          name: true,
        },
      })
    ).list;
    handleInstancesSelected(myUser, "USER");
  };

  const getRight = () => {
    const btnWidth =
      !props.hideAddMeQuickly && props.optionsMode !== "group" ? -34 : 0;
    const lineWidth =
      !props.hideAddMeQuickly &&
      props.optionsMode !== "group" &&
      !props.hideSelectByCMDB
        ? -1
        : 0;
    const iconWidth = props.hideSelectByCMDB ? 0 : -32;
    return btnWidth + lineWidth + iconWidth;
  };

  return (
    <div
      ref={ref}
      data-testid="wrapper"
      className={styles.UserOrUserGroupSelectContainer}
    >
      <Select
        className={styles.customSelect}
        ref={selectRef}
        allowClear={true}
        mode="multiple"
        labelInValue
        placeholder={props.placeholder}
        filterOption={false}
        value={selectedValue}
        onChange={handleSelectChange}
        onSearch={debounce((value) => {
          searchUserOrUserGroupInstances(value as string);
        }, 500)}
        onFocus={handleFocus}
        style={{ width: "100%" }}
        loading={fetching}
      >
        {props.optionsMode !== "group" && (
          <Select.OptGroup label={t(K.USERS_RESULT_LABEL)}>
            {userList.length > 0 ? (
              userList.map((d) => (
                <Select.Option value={d.name} key={d.name}>
                  {props.staticList?.includes(d.name)
                    ? getStaticLabel(getLabel("USER", d))
                    : getLabel("USER", d)}
                </Select.Option>
              ))
            ) : (
              <Select.Option value="empty-user" key="empty-user" disabled>
                {t(K.NO_DATA)}
              </Select.Option>
            )}
          </Select.OptGroup>
        )}

        {props.optionsMode !== "user" && (
          <Select.OptGroup label={t(K.USER_GROUPS_RESULT_LABEL)}>
            {userGroupList.length > 0 ? (
              userGroupList.map((d) => (
                <Select.Option value={d.instanceId} key={d.instanceId}>
                  {props.staticList?.includes(d.instanceId)
                    ? getStaticLabel(getLabel("USER_GROUP", d))
                    : getLabel("USER_GROUP", d)}
                </Select.Option>
              ))
            ) : (
              <Select.Option
                value="empty-user-group"
                key="empty-user-group"
                disabled
              >
                {t(K.NO_DATA)}
              </Select.Option>
            )}
          </Select.OptGroup>
        )}
      </Select>
      <div className={styles.extra} style={{ right: getRight() }}>
        {!props.hideAddMeQuickly && props.optionsMode !== "group" && (
          <Button
            type="link"
            onClick={addMeQuickly}
            style={{ fontSize: "16px" }}
          >
            <GeneralIcon icon={{ lib: "easyops", icon: "quick-add-me" }} />
          </Button>
        )}

        {!props.hideAddMeQuickly &&
          props.optionsMode !== "group" &&
          !props.hideSelectByCMDB && <Divider type="vertical" />}
        {!props.hideSelectByCMDB && (
          <Button
            type="link"
            icon={<SearchOutlined />}
            onClick={openModal}
          ></Button>
        )}
      </div>
      <InstanceListModal
        objectMap={props.objectMap}
        objectId={modalObjectId}
        visible={modalVisible}
        title={title}
        onSelected={handleModalSelected}
        onCancel={closeModal}
        showSizeChanger={true}
        {...(modalObjectId === "USER" && props.hideInvalidUser
          ? {
              presetConfigs: {
                query: {
                  state: "valid",
                },
              },
            }
          : {})}
      />
    </div>
  );
}
Example #17
Source File: api-editor.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
APIBody = (props: any) => {
  const { data = {}, onChange, renderProps, processDataTemp } = props;
  const isRaw = data.type && !['none', BasicForm].includes(data.type);
  const realType = data.type || 'none';

  const updateBody = (key: string, val: any, autoSave?: boolean, resetContent?: boolean) => {
    const newBody: any = { ...data, [key]: val || '' };
    if (key === 'type' && resetContent) {
      switch (val) {
        case 'none':
          newBody.content = '';
          break;
        case BasicForm:
          newBody.content = [];
          break;
        case BODY_RAW_OPTION[0]:
          newBody.content = '';
          break;
        default:
          break;
      }
    }
    onChange('body', newBody, autoSave, (newData: any) => {
      if (!newData.headers) {
        newData.headers = [];
      }
      const { headers, body } = newData;
      const adjustHeader = (action: string, headerType: any) => {
        // 按key查找
        const exist = find(headers, { key: headerType.key });
        if (action === 'push') {
          // 有的话更新,没有就添加
          if (exist) {
            exist.value = headerType.value;
          } else {
            headers.push(headerType);
          }
        } else if (exist && action === 'remove') {
          newData.headers = reject(headers, { key: headerType.key });
        }
      };
      switch (body.type) {
        case 'application/json':
          adjustHeader('push', { key: 'Content-Type', value: 'application/json', desc: '' });
          break;
        case 'text/plain':
          adjustHeader('push', { key: 'Content-Type', value: 'text/plain', desc: '' });
          break;
        case 'Text':
          adjustHeader('remove', { key: 'Content-Type' });
          break;
        case BasicForm:
          adjustHeader('push', { key: 'Content-Type', value: body.type, desc: '' });
          break;
        default:
          break;
      }
    });
  };

  /**
   * @description 切换body类型
   * @param type {string}
   * @param autoSave {boolean}
   * @param resetContent {boolean}
   */
  const changeType = (type: string, autoSave?: boolean, resetContent?: boolean) => {
    if (!isEmpty(data.content) && resetContent) {
      Modal.confirm({
        title: i18n.t('confirm to switch Body type?'),
        onOk() {
          updateBody('type', type === 'raw' ? BODY_RAW_OPTION[0] : type, autoSave, resetContent);
        },
      });
    } else {
      // 如果切换为raw类型,使用raw的第一个选项
      updateBody('type', type === 'raw' ? BODY_RAW_OPTION[0] : type, autoSave, resetContent);
    }
  };

  const CurValueComp = ValMap[realType] || ValMap.raw;
  return (
    <div className="case-api-body">
      <div className="body-type-chosen my-2 px-3">
        <Radio.Group onChange={(e) => changeType(e.target.value, false, true)} value={isRaw ? 'raw' : realType}>
          <Radio value={'none'}>none</Radio>
          <Radio value={BasicForm}>x-www-form-urlencoded</Radio>
          <Radio value={'raw'}>raw</Radio>
        </Radio.Group>
        {isRaw ? (
          <span>
            <Select
              size="small"
              style={{ minWidth: 120 }}
              onChange={(t: string) => changeType(t, false)}
              value={realType}
              dropdownMatchSelectWidth={false}
            >
              {map(BODY_RAW_OPTION, (item) => (
                <Option key={item} value={item}>
                  {item}
                </Option>
              ))}
            </Select>
            <Tooltip title={tip} overlayStyle={{ maxWidth: 500 }}>
              <ErdaIcon className="ml-2 mt-1" type="help" />
            </Tooltip>
          </span>
        ) : null}
      </div>
      <div className="body-value-container">
        {CurValueComp && (
          <CurValueComp
            data={data}
            updateBody={updateBody}
            renderProps={renderProps}
            processDataTemp={processDataTemp}
          />
        )}
      </div>
    </div>
  );
}
Example #18
Source File: api-test.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
APIBody = (props: CompProps) => {
  const { data, onChange, disabled } = props;
  const isRaw = !['none', BasicForm].includes(data.type);
  const realType = data.type;
  const updateBody = (key: string, val: string) => {
    const newBody: Obj = { ...data, [key]: val || '' };
    if (key === 'type') {
      switch (val) {
        case 'none':
          newBody.content = '';
          break;
        case BasicForm:
          newBody.content = [];
          break;
        default:
          break;
      }
    }
    onChange('body', newBody, (newData: Obj) => {
      const { headers = [], body } = newData;
      const adjustHeader = (action: string, headerType: { key: string; value: string; desc: string }) => {
        // 按key查找
        const exist: { key: string; value: string; desc: string } = find(headers, { key: headerType.key });
        if (action === 'push') {
          // 有的话更新,没有就添加
          if (exist) {
            exist.value = headerType.value;
          } else {
            headers.push(headerType);
          }
        } else if (exist && action === 'remove') {
          set(newData, 'headers', reject(headers, { key: headerType.key }));
        }
      };
      switch (body.type) {
        case 'application/json':
          adjustHeader('push', { key: 'Content-Type', value: 'application/json', desc: '' });
          break;
        case 'Text(text/plain)':
          adjustHeader('push', { key: 'Content-Type', value: 'text/plain', desc: '' });
          break;
        case 'Text':
          adjustHeader('remove', { key: 'Content-Type' });
          break;
        case BasicForm:
          adjustHeader('push', { key: 'Content-Type', value: body.type, desc: '' });
          break;
        default:
          break;
      }
    });
  };
  const changeType = (type: string) => {
    // 如果切换为raw类型,使用raw的第一个选项
    updateBody('type', type === 'raw' ? BODY_RAW_OPTION[0] : type);
  };
  const CurValueComp = ValMap[realType] || ValMap.raw;
  return (
    <div className="api-body">
      <div className="body-type-chosen mb-2 px-3">
        <Radio.Group disabled={disabled} onChange={(e) => changeType(e.target.value)} value={isRaw ? 'raw' : realType}>
          <Radio value={'none'}>none</Radio>
          <Radio value={BasicForm}>x-www-form-urlencoded</Radio>
          <Radio value={'raw'}>raw</Radio>
        </Radio.Group>
        {isRaw ? (
          <Select
            size="small"
            style={{ width: 160 }}
            getPopupContainer={() => document.body}
            className="mt-2"
            onChange={(t) => changeType(t as string)}
            value={realType}
            disabled={disabled}
            dropdownMatchSelectWidth={false}
          >
            {map(BODY_RAW_OPTION, (item) => (
              <Option key={item} value={item}>
                {item}
              </Option>
            ))}
          </Select>
        ) : null}
      </div>
      <div className="body-value-container px-3">
        {CurValueComp && <CurValueComp disabled={disabled} data={data} updateBody={updateBody} />}
      </div>
    </div>
  );
}
Example #19
Source File: case-api.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
APIBody = (props: any) => {
  const { data, onChange } = props;
  const isRaw = !['none', BasicForm].includes(data.type);
  const realType = data.type;

  const updateBody = (key: string, val: any, autoSave?: boolean) => {
    const newBody: any = { ...data, [key]: val || '' };
    if (key === 'type') {
      switch (val) {
        case 'none':
          newBody.content = '';
          break;
        case BasicForm:
          newBody.content = [];
          break;
        default:
          break;
      }
    }
    onChange('body', newBody, autoSave, (newData: any, i: number) => {
      const { headers, body } = newData[i];
      const adjustHeader = (action: string, headerType: any) => {
        // 按key查找
        const exist = find(headers, { key: headerType.key });
        if (action === 'push') {
          // 有的话更新,没有就添加
          if (exist) {
            exist.value = headerType.value;
          } else {
            headers.push(headerType);
          }
        } else if (exist && action === 'remove') {
          // eslint-disable-next-line no-param-reassign
          newData[i].headers = reject(headers, { key: headerType.key });
        }
      };
      switch (body.type) {
        case 'JSON(application/json)':
          adjustHeader('push', { key: 'Content-Type', value: 'application/json', desc: '' });
          break;
        case 'Text(text/plain)':
          adjustHeader('push', { key: 'Content-Type', value: 'text/plain', desc: '' });
          break;
        case 'Text':
          adjustHeader('remove', { key: 'Content-Type' });
          break;
        case BasicForm:
          adjustHeader('push', { key: 'Content-Type', value: body.type, desc: '' });
          break;

        default:
          break;
      }
    });
  };

  const changeType = (type: string, autoSave?: boolean) => {
    // 如果切换为raw类型,使用raw的第一个选项
    updateBody('type', type === 'raw' ? BODY_RAW_OPTION[0] : type, autoSave);
  };

  const CurValueComp = ValMap[realType] || ValMap.raw;
  return (
    <div className="case-api-body">
      <div className="body-type-chosen mb-2 px-3">
        <Radio.Group onChange={(e) => changeType(e.target.value)} value={isRaw ? 'raw' : realType}>
          <Radio value={'none'}>none</Radio>
          <Radio value={BasicForm}>x-www-form-urlencoded</Radio>
          <Radio value={'raw'}>raw</Radio>
        </Radio.Group>
        {isRaw ? (
          <Select
            size="small"
            style={{ minWidth: 120 }}
            onChange={(t: string) => changeType(t, true)}
            value={realType}
            dropdownMatchSelectWidth={false}
          >
            {map(BODY_RAW_OPTION, (item) => (
              <Option key={item} value={item}>
                {item}
              </Option>
            ))}
          </Select>
        ) : null}
      </div>
      <div className="body-value-container">{CurValueComp && <CurValueComp data={data} updateBody={updateBody} />}</div>
    </div>
  );
}