lodash#findIndex JavaScript Examples

The following examples show how to use lodash#findIndex. 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: notification.js    From haven with MIT License 6 votes vote down vote up
getNotificationType = (type) => {
  const order = findIndex(orderNotificationTypes, o => o === type);
  if (order >= 0) {
    return 'order';
  }
  const peer = findIndex(peerNotificationTypes, o => o === type);
  if (peer >= 0) {
    return 'peer';
  }
  const dispute = findIndex(disputeNotificationTypes, o => o === type);
  if (dispute >= 0) {
    return 'dispute';
  }
  return 'unknown';
}
Example #2
Source File: index.js    From hzero-front with Apache License 2.0 6 votes vote down vote up
@Bind()
  handleConfig() {
    const {
      dataSource: { dataSourceDetail = {}, extConfigs = [] },
    } = this.props;
    const { extConfig = '{}' } = dataSourceDetail;
    const columnList = [];
    // 获取参数
    forIn(JSON.parse(extConfig), (value, key) => {
      if (
        findIndex(extConfigs, (o) => {
          return o.itemCode === key;
        }) === -1
      ) {
        columnList.push({
          name: key,
          value,
          columnId: uuid(),
          _status: '',
        });
      }
    });
    this.setState({
      dataSource: columnList,
    });
  }
Example #3
Source File: menuTab.js    From hzero-front with Apache License 2.0 6 votes vote down vote up
/**
 * 判断是不是合法的tabPathname
 * '' / 登录,登出 其他的都认为合法的pathname
 * '' / /user/login /user/logout
 * @param {!String} pathname - 切换的pathname
 */
function isValidTabPathname(pathname) {
  return (
    isString(pathname) &&
    findIndex(
      ['', '/', '/user/login', '/user/logout'],
      (invalidPathname) => invalidPathname === pathname
    ) === -1
  );
}
Example #4
Source File: ComposeTableEditModal.js    From hzero-front with Apache License 2.0 6 votes vote down vote up
dataSourceRemove() {
    const { dataSource, selectedRowKeys } = this.state;
    const { rowKey } = this.props;
    const nextDataSource = [];
    forEach(dataSource, r => {
      if (findIndex(selectedRowKeys, rowId => rowId === r[rowKey]) === -1) {
        nextDataSource.push(r);
      }
    });
    this.setState({
      selectedRowKeys: [],
      selectedRows: [],
      dataSource: nextDataSource,
      removeLoading: false,
    });
  }
Example #5
Source File: ComposeTableEditModal.js    From hzero-front with Apache License 2.0 6 votes vote down vote up
handleRemove() {
    const { dataSource, selectedRowKeys } = this.state;
    const { rowKey } = this.props;
    const removeRows = [];
    forEach(dataSource, r => {
      if (findIndex(selectedRowKeys, rowId => rowId === r[rowKey]) !== -1) {
        if (!r.isCreate) {
          removeRows.push(r);
        }
      }
    });
    if (removeRows.length > 0) {
      // 调用 父组件 传进来的 方法
      const { onRemove } = this.props;
      if (isFunction(onRemove)) {
        this.setState({
          removeLoading: true,
        });
        onRemove(map(removeRows, row => row[rowKey]), removeRows, { onOk: this.dataSourceRemove });
      }
    } else {
      this.dataSourceRemove();
    }
  }
Example #6
Source File: diff.js    From hivemind with Apache License 2.0 6 votes vote down vote up
async function getReversedDiff(nid, eid, fctime, lctime) {
  const until = lctime + 0.0001
  const response = await rg.get('/event/diff', {
    path: `/n/${nid}`,
    reverse: true,
    since: fctime,
    until,
  })
  const result = response.body[0]
  const idx = findIndex(result.events, { _id: eid })

  return result.commands[idx]
}
Example #7
Source File: addModerators.js    From haven with MIT License 6 votes vote down vote up
getProfileList() {
    const { storeModerators } = this.props;
    const { options } = this.state;
    const filteredOptions = filter(options, (val) => {
      const idx = findIndex(storeModerators, o => o === val);
      return idx === -1;
    });
    return filteredOptions || [];
  }
Example #8
Source File: addModerators.js    From haven with MIT License 6 votes vote down vote up
onChangeState(peerID) {
    const { selected } = this.state;
    const idx = findIndex(selected, o => o === peerID);
    if (idx >= 0) {
      const newArray = filter(selected, o => o !== peerID);
      this.setState({
        selected: newArray,
      });
    } else {
      this.setState({
        selected: [...selected, peerID],
      });
    }
  }
Example #9
Source File: MultiFilter.js    From haven with MIT License 6 votes vote down vote up
onChange(val, idx, isExclusive) {
    const { selected, options } = this.props;
    const exclusiveIdx = findIndex(options, val => get(val, 'exclusive', false));
    const exclusiveVal = get(options, `[${exclusiveIdx}].value`, 'exclusive_value');
    if (idx === -1) {
      if (isExclusive) {
        selected.length = 0;
        selected.push(val);
      } else {
        selected.push(val);
        remove(selected, o => o === exclusiveVal);
      }
    } else {
      remove(selected, o => o === val);
    }
    this.props.onChange(selected);
  }
Example #10
Source File: parseMobilityData.js    From covid with GNU General Public License v3.0 6 votes vote down vote up
parseMobilityData = (selectedGeoid, weightData, centroids) => {
    let sourceCenter = findIndex(centroids, o => o[0]===selectedGeoid)
    let sourceX = centroids[sourceCenter][1];
    let sourceY = centroids[sourceCenter][2];
    
    let length = weightData.length
    let n = 0;

    while (n < length-1) {
        centroids[n] = [centroids[n][0], centroids[n][1], centroids[n][2], sourceX, sourceY, weightData[n]]
        n++;
    }
    return centroids;
}
Example #11
Source File: bulk-commands-functions.js    From OctoFarm with GNU Affero General Public License v3.0 6 votes vote down vote up
//REFACTOR this should come from printer select to save the extra call, re-iteration and matching.
async function getCurrentlySelectedPrinterList(disabled) {
  try {
    const currentPrinterList = await OctoFarmClient.listPrinters(disabled);
    const matchedPrinters = [];
    //Grab all check boxes
    const selectedPrinters = PrinterSelectionService.getSelected();
    selectedPrinters.forEach((element) => {
      const printerID = element.id.split("-");
      const index = findIndex(currentPrinterList, function (o) {
        return o._id === printerID[1];
      });
      if (index > -1) {
        matchedPrinters.push(currentPrinterList[index]);
      }
    });
    return matchedPrinters;
  } catch (e) {
    console.error(e);
    UI.createAlert(
      "error",
      `Couldn't get selected printer list: ${e}`,
      0,
      "clicked"
    );
    return [];
  }
}
Example #12
Source File: SelectorModal.js    From haven with MIT License 6 votes vote down vote up
getOptionLabel(val) {
    const {
      options, valueKey, getTitleLabel, getLabel,
    } = this.props;
    const idx = findIndex(options, o => o[valueKey] === val);
    return idx >= 0 ? (
      getTitleLabel ? getTitleLabel(options[idx]) : getLabel(options[idx])
    ) : (
      'None'
    );
  }
Example #13
Source File: MultiFilter.js    From haven with MIT License 6 votes vote down vote up
render() {
    const {
      selected, options, title, style, hasScroll,
    } = this.props;
    const Wrapper = hasScroll ? ScrollView : View;
    return (
      <Section title={title}>
        <Wrapper style={[styles.wrapper, style]}>
          {options.map((val, key) => {
            const idx = findIndex(selected, o => o === val.value);
            const isLast = key === options.length - 1;
            return (
              <TouchableWithoutFeedback
                key={key}
                onPress={() => {
                  this.onChange(val.value, idx, get(val, 'exclusive', false));
                }}
              >
                <View style={styles.itemWrapper}>
                  {idx >= 0 ? (
                    <Ionicons name="ios-checkmark" size={28} style={styles.checkmark} />
                  ) : (
                    <Ionicons size={28} style={styles.checkmark} />
                  )}
                  <View style={[styles.itemTextWrapper, !isLast && styles.bottomBorder]}>
                    <Text style={styles.itemText}>{val.label}</Text>
                  </View>
                </View>
              </TouchableWithoutFeedback>
            );
          })}
        </Wrapper>
      </Section>
    );
  }
Example #14
Source File: index.jsx    From mui-phone-input-ssr with MIT License 5 votes vote down vote up
constructor(props) {
    super(props);
    let filteredCountries = countryData.allCountries;

    if (props.disableAreaCodes) filteredCountries = this.deleteAreaCodes(filteredCountries);
    if (props.regions) filteredCountries = this.filterRegions(props.regions, filteredCountries);

    const onlyCountries = this.excludeCountries(
      this.getOnlyCountries(props.onlyCountries, filteredCountries), props.excludeCountries,
    );

    const preferredCountries = filter(filteredCountries, (country) => some(props.preferredCountries, (preferredCountry) => preferredCountry === country.iso2));

    const inputNumber = props.value || '';

    let countryGuess;
    if (inputNumber.length > 1) {
      // Country detect by value field
      countryGuess = this.guessSelectedCountry(inputNumber.replace(/\D/g, '').substring(0, 6), onlyCountries, props.defaultCountry) || 0;
    } else if (props.defaultCountry) {
      // Default country
      countryGuess = find(onlyCountries, { iso2: props.defaultCountry }) || 0;
    } else {
      // Empty params
      countryGuess = 0;
    }

    const countryGuessIndex = findIndex(this.allCountries, countryGuess);
    const dialCode = (
      inputNumber.length < 2
      && countryGuess
      && !startsWith(inputNumber.replace(/\D/g, ''), countryGuess.dialCode)
    ) ? countryGuess.dialCode : '';

    const formattedNumber = (inputNumber === '' && countryGuess === 0) ? ''
      : this.formatNumber(
        (props.disableCountryCode ? '' : dialCode) + inputNumber.replace(/\D/g, ''),
        countryGuess.name ? countryGuess.format : undefined,
      );

    this.state = {
      formattedNumber,
      placeholder: props.placeholder,
      onlyCountries,
      preferredCountries,
      defaultCountry: props.defaultCountry,
      selectedCountry: countryGuess,
      highlightCountryIndex: countryGuessIndex,
      queryString: '',
      freezeSelection: false,
      debouncedQueryStingSearcher: debounce(this.searchCountry, 100),
      anchorEl: null,
    };
  }
Example #15
Source File: SelectorPlaceholder.js    From haven with MIT License 5 votes vote down vote up
getOptionLabel(val) {
    const { options } = this.props;
    const idx = findIndex(options, o => o.value === val);
    return idx >= 0 ? options[idx].label : 'Select';
  }
Example #16
Source File: index.js    From hzero-front with Apache License 2.0 5 votes vote down vote up
@Bind()
  onCheckboxChange(params) {
    const { receiveCode, type, flag } = params;
    const { checkedList } = this.state;
    const index = findIndex(checkedList, (v) => v.receiveCode === receiveCode);
    const checkItem = checkedList[index];

    const addOrRemove = (item) => {
      // flag为true,代表当前已经被勾选,需要去除勾选
      if (flag) {
        remove(item && item.receiveTypeList, (v) => v === type);
      } else if (
        indexOf(item && item.receiveTypeList, type) < 0 &&
        indexOf(item.defaultReceiveTypeList, type) > -1
      ) {
        (item.receiveTypeList || []).push(type);
      }
    };
    addOrRemove(checkItem);

    /**
     * 根据父节点,选择所有的子节点
     *
     * @param {*} parentId
     */
    const iterator = (parentId) => {
      const subList = [];
      forEach(checkedList, (v) => {
        if (v.parentId === parentId) {
          addOrRemove(v);
          subList.push(v);
        }
      });
      if (subList && subList.length > 0) {
        forEach(subList, (v) => iterator(v.receiveCode));
      }
    };
    iterator(checkItem.receiveCode);

    /**
     * 反向勾选,即根据子节点反向勾选父节点
     *
     * @param {*} parentId 父节点的receiveCode
     */
    const reverseCheck = (parentId) => {
      if (!parentId) {
        return;
      }
      const sameParents = checkedList.filter((v) => v.parentId === parentId) || [];
      const temp = sameParents.filter((v) => {
        if (indexOf(v.defaultReceiveTypeList, type) < 0) {
          return true;
        }
        const idx = indexOf(v && v.receiveTypeList, type);
        return flag ? idx < 0 : idx > -1;
      });
      if (sameParents.length === temp.length || (sameParents.length !== temp.length && flag)) {
        const parentIndex = findIndex(checkedList, (v) => v.receiveCode === parentId);
        const parent = checkedList[parentIndex];
        addOrRemove(parent);

        reverseCheck(parent.parentId);
      }
    };

    reverseCheck(checkItem.parentId);

    this.setState({ checkedList });
  }
Example #17
Source File: profile.js    From haven with MIT License 5 votes vote down vote up
function* fetchProfile(action) {
  const { usecache, async } = yield select(getProfileFetchMode);
  const profileQueue = yield select(getProfileQueue);
  let { peerID } = action.payload || {};
  let getLoaded = get(action.payload, 'getLoaded', false);

  if (isEmpty(peerID)) {
    peerID = '';
  }

  if (!isEmpty(peerID)) {
    const profiles = yield select(getProfiles);
    const profileInCache = profiles.profiles[peerID];
    const queueIdx = findIndex(profileQueue, pfID => pfID === peerID);
    getLoaded = getLoaded && (!isEmpty(profileInCache) || queueIdx >= 0);
  }

  if (!getLoaded) {
    if (!isEmpty(peerID)) {
      // check failed profile queue and if it failed within the last 1 hr, gracefully return
      const profileFailedQueue = yield select(getProfileFailedQueue);
      const timestamp = profileFailedQueue[peerID];
      if (timestamp) {
        if ((timeSinceInSeconds(new Date(timestamp)) < PROFILE_RECHECK_HOURS * 3600)) {
          return;
        } else {
          yield put({ type: failedQueueActions.removeFromFailedQueue, payload: peerID });
        }
      }

      yield put({ type: queueActions.addToQueue, payload: peerID });
    }

    try {
      const result = yield call(getProfile, peerID, usecache, async);
      if (isEmpty(peerID)) {
        yield put({ type: profileActions.setProfile, payload: result });
      }
      if (result) {
        yield put({ type: profilesActions.setProfiles, payload: [result] });
      } else if (!isEmpty(peerID)) {
        yield put({ type: failedQueueActions.addToFailedQueue, payload: peerID });
      }
      yield put({ type: profileActions.setProfileLoading, payload: false });
    } catch (err) {
      // console.log(`Fetch profile error: ${err}`);
    } finally {
      if (!isEmpty(peerID)) {
        yield put({ type: queueActions.removeFromQueue, payload: peerID });
      }
    }
  }
}
Example #18
Source File: ProductState.js    From haven with MIT License 5 votes vote down vote up
getProductTypeText = (type) => {
  const idx = findIndex(prdType, o => o.value === type.toLowerCase());
  if (idx >= 0) {
    return prdType[idx].label;
  }
  return 'Unknown';
}
Example #19
Source File: ProductState.js    From haven with MIT License 5 votes vote down vote up
getProductConditionText = (condition) => {
  const idx = findIndex(prdCondition, o => o.value === condition);
  if (idx >= 0) {
    return prdCondition[idx].label;
  }
  return 'Unknown';
}
Example #20
Source File: MultiSelector.js    From haven with MIT License 5 votes vote down vote up
render() {
    const { selection, showModal } = this.state;
    const { options, title, required } = this.props;
    let text = 'None';
    if (selection.length === 1) {
      if (selection[0] === 'ALL') {
        const idx = findIndex(options, option => option.value === 'ALL');
        if (idx >= 0) {
          text = options[idx].label;
        } else {
          text = 'All';
        }
      } else if (hasIn(selection, '[0].label')) {
        text = selection[0].label;
      } else {
        const idx = findIndex(options, option => option.value.toLowerCase() === selection[0].toLowerCase());
        text = options[idx].label;
      }
    } else if (selection.length > 1) {
      text = `${selection.length} selected`;
    }
    return (
      <View style={styles.wrapper}>
        <TouchableWithoutFeedback onPress={this.onShowSelector}>
          <View style={styles.optionTrigger}>
            <Text style={styles.title}>
              {title}
              <Text style={required ? { color: 'red' } : {}}>{required ? '*' : ''}</Text>
            </Text>
            <Text style={styles.input} numberOfLines={1} ellipsizeMode="tail">{text}</Text>
            <Ionicons
              name="ios-arrow-forward"
              size={18}
              color={primaryTextColor}
              style={styles.icon}
            />
          </View>
        </TouchableWithoutFeedback>
        <OBLightModal
          animationType="slide"
          transparent
          visible={showModal}
          onRequestClose={this.onClose}
        >
          <Header
            modal
            title=""
            left={<NavBackButton />}
            onLeft={this.onClose}
            right={<LinkText text="Done" />}
            onRight={this.onClose}
          />
          <FlatList
            keyExtractor={this.keyExtractor}
            renderItem={this.renderItem}
            data={options}
            extraData={selection}
          />
          {selection.length > 0 && (
            <View style={styles.selectionIndicatorWrapper}>
              <Text style={styles.selectedIndicator}>
                {selection.length} Selected
              </Text>
              <TouchableOpacity onPress={this.resetSelection}>
                <LinkText text="Reset" />
              </TouchableOpacity>
            </View>
          )}
        </OBLightModal>
      </View>
    );
  }
Example #21
Source File: profile.js    From haven with MIT License 5 votes vote down vote up
function* fetchProfiles(action) {
  const { usecache, async } = yield select(getProfileFetchMode);
  const profileFailedQueue = yield select(getProfileFailedQueue);

  const profileQueue = yield select(getProfileQueue);
  const profiles = yield select(getProfiles);

  const { peerIDs, getLoaded = true } = action.payload;
  yield put({ type: queueActions.addBatchToQueue, payload: peerIDs });

  let i;
  for (i = 0; i < peerIDs.length; i += 1) {
    const peerID = peerIDs[i];
    const profileInCache = profiles.profiles[peerID];
    const queueIdx = findIndex(profileQueue, pfID => pfID === peerID);
    const inCahceOrQueue = profileInCache || queueIdx >= 0;

    if (!getLoaded || !inCahceOrQueue) {
      // check failed profile queue and if it failed within the last 1 hr, gracefully return
      const timestamp = profileFailedQueue[peerID];
      if (timestamp) {
        if ((timeSinceInSeconds(new Date(timestamp)) < PROFILE_RECHECK_HOURS * 3600)) {
          yield put({ type: queueActions.removeFromQueue, payload: peerID });
          continue;
        } else {
          yield put({ type: failedQueueActions.removeFromFailedQueue, payload: peerID });
        }
      }

      try {
        const result = yield call(getProfile, peerID, usecache, async);
        if (result) {
          yield put({ type: profilesActions.setProfiles, payload: [result] });
        } else {
          yield put({ type: failedQueueActions.addToFailedQueue, payload: peerID });
        }
      } catch (err) {
        // console.log(err);
      } finally {
        yield put({ type: queueActions.removeFromQueue, payload: peerID });
      }
    }
  }
}
Example #22
Source File: addModerators.js    From haven with MIT License 5 votes vote down vote up
checkSelected(peerID) {
    const { selected } = this.state;
    const idx = findIndex(selected, o => o === peerID);
    return idx >= 0;
  }
Example #23
Source File: [key].js    From hivemind with Apache License 2.0 4 votes vote down vote up
Page = () => {
  const { user } = useUser()
  const router = useRouter()
  const [timestamp, setTimestamp] = useState(
    typeof window === 'undefined'
      ? null
      : parseFloat(new URLSearchParams(location.search).get('timestamp'))
  )
  const { key } = router.query
  const { data, error } = useFetch(
    user ? user : null,
    `/api/mindmaps/${key}?timestamp=${timestamp || ''}`
  )
  const { data: edata, error: eerror } = useFetch(
    user ? user : null,
    `/api/timeline/events?key=${key}`
  )
  const [title, setTitle] = useState(key)

  useEffect(() => {
    if (user) {
      mutate(
        [`/api/mindmaps/${key}?timestamp=${timestamp || ''}`, user.token],
        null,
        true
      )
    }
  }, [user, timestamp, key])

  useEffect(() => {
    if (user) {
      mutate([`/api/timeline/events?key=${key}`, user.token], null, true)
    }
  }, [user, key])

  useEffect(() => {
    if (data && data.ok) {
      setTitle(data.data.meta.name)
    }
  }, [data])

  useEffect(() => {
    const handleRouteChange = (url) => {
      const fullURL = new URL(url, location.origin)
      const toTs = fullURL.searchParams.get('timestamp')
      const toTsF = parseFloat(toTs) || null

      if ((!toTsF && timestamp) || toTsF !== timestamp) {
        setTimestamp(toTsF)
      }
    }

    router.events.on('routeChangeComplete', handleRouteChange)

    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events, timestamp])

  if (typeof user === 'undefined') {
    return <Spinner />
  }

  if (error && window.notify) {
    const options = {
      place: 'tr',
      message: 'Failed to fetch mind map!',
      type: 'danger',
      autoDismiss: 7,
    }

    window.notify(options)
  }

  if (eerror && window.notify) {
    const options = {
      place: 'tr',
      message: 'Failed to fetch events!',
      type: 'danger',
      autoDismiss: 7,
    }

    window.notify(options)
  }

  const gotEventData = !eerror && edata && edata.ok
  const cEvents = gotEventData && edata.data
  const prevDisabled = !gotEventData || timestamp === cEvents[0].lctime
  const nextDisabled = !gotEventData || timestamp === last(cEvents).lctime

  async function jump(to) {
    if (to === 'now') {
      await router.push('/mmaps/[key]', `/mmaps/${key}`, { shallow: true })
      setTimestamp(null)
    } else if (gotEventData) {
      let toTS, idx

      switch (to) {
        case 'first':
          toTS = cEvents[0].lctime
          break

        case 'prev':
          idx = timestamp
            ? findIndex(cEvents, { lctime: timestamp })
            : cEvents.length
          toTS = cEvents[idx - 1].lctime
          break

        case 'next':
          idx = timestamp
            ? findIndex(cEvents, { lctime: timestamp })
            : cEvents.length - 2
          toTS = cEvents[idx + 1].lctime
          break

        case 'last':
          toTS = last(cEvents).lctime
          break

        default:
          toTS = to
      }

      await router.push(
        '/mmaps/[key]',
        {
          pathname: `/mmaps/${key}`,
          query: { timestamp: toTS },
        },
        { shallow: true }
      )
      setTimestamp(toTS)
    }
  }

  if (user) {
    const output = [
      <Row key="title">
        <Col xs="auto" md={7}>
          <h3>
            {title}
            {timestamp ? (
              <>
                &nbsp;
                <small className={'text-muted'}>
                  {' '}
                  @ {new Date(timestamp * 1000).toLocaleString()}
                </small>
              </>
            ) : null}
          </h3>
        </Col>
        <Col xs="auto" md={5} className={'text-right'}>
          <ShowAll />
          <Fit />
          <Search />
          &nbsp;&nbsp;|&nbsp;
          <ToolTippedButton
            className="ml-1"
            outline
            color="secondary"
            id="tag"
            disabled={true}
            tooltip="Tag (Coming Soon)"
          >
            <Tag size={16} />
          </ToolTippedButton>
          <ToolTippedButton
            className="ml-1"
            outline
            color="secondary"
            id="first"
            disabled={prevDisabled}
            tooltip="First"
            onClick={() => jump('first')}
          >
            <SkipBack size={16} />
          </ToolTippedButton>
          <ToolTippedButton
            className="ml-1"
            outline
            color="secondary"
            id="prev"
            disabled={prevDisabled}
            tooltip="Previous"
            onClick={() => jump('prev')}
          >
            <Rewind size={16} />
          </ToolTippedButton>
          <ToolTippedButton
            className="ml-1"
            outline
            color="secondary"
            id="next"
            disabled={nextDisabled}
            tooltip="Next"
            onClick={() => jump('next')}
          >
            <FastForward size={16} />
          </ToolTippedButton>
          <ToolTippedButton
            className="ml-1"
            outline
            color="secondary"
            id="last"
            disabled={nextDisabled}
            tooltip="Last"
            onClick={() => jump('last')}
          >
            <SkipForward size={16} />
          </ToolTippedButton>
          &nbsp;&nbsp;|&nbsp;
          <Rename
            nameChangedCallBack={setTitle}
            disabled={!!timestamp}
            rootNode={get(data, ['data', 'meta'], {})}
          />
          <ToolTippedButton
            className="ml-1"
            outline
            color={timestamp ? 'secondary' : 'danger'}
            id="now"
            tooltip={timestamp ? 'Click to unlock' : 'Click to lock'}
            onClick={() => jump(timestamp ? 'now' : 'last')}
          >
            {timestamp ? <Lock size={16} /> : <Unlock size={16} />}
          </ToolTippedButton>
        </Col>
      </Row>,
    ]

    if (error && data) {
      output.push(
        <Row key="content">
          <Col>
            <Error statusCode={data.status} />
          </Col>
        </Row>
      )
    } else if (eerror && edata) {
      output.push(
        <Row key="content">
          <Col>
            <Error statusCode={edata.status} />
          </Col>
        </Row>
      )
    } else {
      output.push(
        <Row key="content">
          <Col>
            <MindMap
              data={data}
              edata={edata}
              timestamp={timestamp}
              jump={jump}
            />
          </Col>
        </Row>
      )
    }

    return output
  }

  return <AuthPrompt />
}
Example #24
Source File: index.js    From datapass with GNU Affero General Public License v3.0 4 votes vote down vote up
ÉquipeSection = ({
  initialContacts = {},
  responsableTechniqueNeedsMobilePhone = false,
}) => {
  const {
    isUserEnrollmentLoading,
    disabled,
    onChange,
    enrollment: { team_members = [] },
  } = useContext(FormContext);
  const { user } = useAuth();
  const contactConfiguration = useMemo(() => {
    const defaultInitialContacts = {
      demandeur: {
        header: 'Demandeur',
        description: getDefaultDemandeurDescription(),
        forceDisable: true,
      },
      responsable_traitement: {
        header: 'Responsable de traitement',
        description: getDefaultResponsableTraitementDescription(),
      },
      delegue_protection_donnees: {
        header: 'Délégué à la protection des données',
        description: getDefaultDelegueProtectionDonneesDescription(),
      },
      responsable_technique: {
        header: 'Responsable technique',
        description: getDefaultResponsableTechniqueDescription(
          responsableTechniqueNeedsMobilePhone
        ),
        displayMobilePhoneLabel: responsableTechniqueNeedsMobilePhone,
      },
    };

    return chain(defaultInitialContacts)
      .assign(initialContacts)
      .omitBy((p) => !p)
      .value();
  }, [initialContacts, responsableTechniqueNeedsMobilePhone]);

  const newTeamMembers = useNewTeamMembers({
    user,
    team_members,
    contactConfiguration,
  });

  useEffect(() => {
    if (!isUserEnrollmentLoading && !disabled && !isEmpty(newTeamMembers)) {
      onChange({
        target: {
          name: 'team_members',
          value: [...team_members, ...newTeamMembers],
        },
      });
    }
  }, [
    isUserEnrollmentLoading,
    disabled,
    onChange,
    team_members,
    newTeamMembers,
  ]);

  const displayIdForAdministrator = useMemo(
    () => user && user.roles.includes('administrator'),
    [user]
  );

  const updateWithUserInformation = useCallback(
    (index) => {
      if (team_members[index]?.email !== user.email) {
        onChange({
          target: {
            name: `team_members[${index}].email`,
            value: user.email,
          },
        });
      }
      if (team_members[index]?.given_name !== user.given_name) {
        onChange({
          target: {
            name: `team_members[${index}].given_name`,
            value: user.given_name,
          },
        });
      }
      if (team_members[index]?.family_name !== user.family_name) {
        onChange({
          target: {
            name: `team_members[${index}].family_name`,
            value: user.family_name,
          },
        });
      }
      if (team_members[index]?.phone_number !== user.phone_number) {
        onChange({
          target: {
            name: `team_members[${index}].phone_number`,
            value: user.phone_number,
          },
        });
      }
      if (team_members[index]?.job !== user.job) {
        onChange({
          target: {
            name: `team_members[${index}].job`,
            value: user.job,
          },
        });
      }
    },
    [team_members, user, onChange]
  );

  useEffect(() => {
    if (!isUserEnrollmentLoading && !disabled && !isEmpty(team_members)) {
      const currentDemandeurIndex = team_members.findIndex(
        ({ type, email }) => type === 'demandeur' && email === user.email
      );

      if (currentDemandeurIndex !== -1) {
        updateWithUserInformation(currentDemandeurIndex);
      }
    }
  }, [
    isUserEnrollmentLoading,
    disabled,
    team_members,
    user,
    updateWithUserInformation,
  ]);

  const addTeamMemberFactory = (type) => {
    const tmp_id = uniqueId(`tmp_`);
    const newTeamMember = { type, tmp_id };

    return () =>
      onChange({
        target: {
          name: 'team_members',
          value: [...team_members, newTeamMember],
        },
      });
  };

  const removeTeamMember = (index) => {
    onChange({
      target: {
        name: 'team_members',
        value: [
          ...team_members.slice(0, index),
          ...team_members.slice(index + 1),
        ],
      },
    });
  };

  return (
    <ScrollablePanel scrollableId={SECTION_ID}>
      <h2>{SECTION_LABEL}</h2>
      <ExpandableQuote title="Comment renseigner la liste des contacts ?" large>
        {Object.entries(contactConfiguration).map(([type, { description }]) => (
          <p key={type}>{description}</p>
        ))}
      </ExpandableQuote>
      <CardContainer flex={false}>
        {Object.entries(contactConfiguration).map(
          ([
            type,
            {
              header,
              forceDisable,
              displayMobilePhoneLabel,
              displayIndividualEmailLabel,
              displayGroupEmailLabel,
              contactByEmailOnly,
              multiple,
            },
          ]) => (
            <React.Fragment key={type}>
              {team_members
                .filter(({ type: t }) => t === type)
                .map(({ id, tmp_id, ...team_member }) => (
                  <Contact
                    heading={header}
                    key={id || tmp_id}
                    id={id}
                    index={findIndex(team_members, ({ id: i, tmp_id: t_i }) => {
                      if (id) {
                        // if id is defined match on id field
                        return i === id;
                      }
                      if (tmp_id) {
                        // if id is not defined and tmp_id is defined
                        // match on tmp_id
                        return t_i === tmp_id;
                      }
                      return false;
                    })}
                    {...team_member}
                    displayMobilePhoneLabel={displayMobilePhoneLabel}
                    displayIndividualEmailLabel={displayIndividualEmailLabel}
                    displayGroupEmailLabel={displayGroupEmailLabel}
                    contactByEmailOnly={contactByEmailOnly}
                    displayIdForAdministrator={displayIdForAdministrator}
                    disabled={forceDisable || disabled}
                    onChange={onChange}
                    onDelete={multiple && !id && removeTeamMember}
                    onUpdateWithUserInformation={updateWithUserInformation}
                    canUpdatePersonalInformation={
                      team_member.email === user.email &&
                      (team_member.given_name !== user.given_name ||
                        team_member.family_name !== user.family_name ||
                        team_member.phone_number !== user.phone_number ||
                        team_member.job !== user.job)
                    }
                  />
                ))}
              {!disabled && multiple && (
                <AddCard
                  label={`ajouter un ${header.toLowerCase()}`}
                  onClick={addTeamMemberFactory(type)}
                />
              )}
            </React.Fragment>
          )
        )}
      </CardContainer>
    </ScrollablePanel>
  );
}
Example #25
Source File: cadastrapp.js    From mapstore2-cadastrapp with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Holds the state of cadastrapp.
 * The shape of the state is the following:
 * ```
 * {
 *     loading: true | false // general loading flag
 *     loadingFlags: {} // object that contain loading flag, for various parts of the application.
 *     searchType: undefined // one of constant.SEARCH_TOOLS
 *     selectionType: undefined // one of constant.SELECTION_TYPE
 *     plots: [{  // an entry for each tab of the plot selection
 *          data: [{parcelle: "12345", ...}] // data of the tab
 *          selected: [parcelle1, parcelle2]
 *     }],
 *     configuration: { // the configuration from server. e.g.
 *        cadastreLayerIdParcelle: "geo_parcelle"
 *        cadastreWFSLayerName: "qgis:cadastrapp_parcelle"
 *        cadastreWFSURL: "https://domain.org/geoserver/wfs"
 *        cadastreWMSLayerName: "qgis:cadastrapp_parcelle"
 *        cadastreWMSURL: "https://domain.org/geoserver/wms"
 *        cnil1RoleName: "ROLE_EL_APPLIS_CAD_CNIL1"
 *        cnil2RoleName: "ROLE_EL_APPLIS_CAD_CNIL2"
 *        dateValiditeEDIGEO: "01/01/2018"
 *        dateValiditeMajic: "01/01/2018"
 *        maxRequest: "8"
 *        minNbCharForSearch: "3"
 *        minParacelleIdLength: "14"
 *        organisme: "Un service fourni par "
 *        pdfbasemapthumbnails: [{,…}, {,…}]
 *        pdfbasemaptitles: [{value: "Cadastre", key: "pdf.baseMap.0.title"},…]
 *        uFWFSLayerName: "qgis:cadastrapp_unite_fonciere"
 *        uFWFSURL: "https://domain.org/geoserver/wfs"
 *     }
 * }
 * ```
 *
 * @param {object} state the application state
 * @param {object} action a redux action
 */
export default function cadastrapp(state = DEFAULT_STATE, action) {
    const type = action.type;
    switch (type) {
    case SETUP:
        return set('pluginCfg', action.cfg, state);
    case SET_CONFIGURATION:
        return set('configuration', action.configuration, state);
    case LOADING: {
        let newValue = action.value;
        if (action.mode === 'count') {
            const oldValue = get(state, `loadFlags.${action.name}`) ?? 0;
            newValue = isNumber(newValue)
                ? newValue // set with passed value if number
                : newValue
                    ? oldValue + 1 // increment if true
                    : Math.max(oldValue - 1, 0); // decrement if false
        }
        // anyway sets loading to true
        return set(action.name === "loading" ? "loading" : `loadFlags.${action.name}`, newValue, state);
    }
    case TOGGLE_SELECTION: {
        const {selectionType} = action;
        // if the current selection button is clicked, it turns off selection
        return set("selectionType", selectionType, state);
    }
    case TOGGLE_SEARCH: {
        const { searchType } = action;
        // if the current search button is clicked, it closes the search section
        return set("searchType", searchType, state);
    }
    case ADD_PLOTS: {
        const { plots, target, activate = true } = action;
        const {activePlotSelection = 0 } = state;
        let targetPlotSelection = activePlotSelection;
        let newState = state;
        if (!isNil(target)) {
            const targetIndex = isObject(target) ? findIndex(state.plots, {id: target.id}) : target;
            // create
            if (targetIndex < 0) {
                newState = set(`plots`, [...state.plots, { ...EMPTY_PLOT_SELECTION, ...target }], state);
                targetPlotSelection = newState.plots.length - 1;
            } else {
                newState = set(`plots[${targetIndex}]`, {...state.plots[targetIndex], ...target});
                targetPlotSelection = targetIndex;
            }
        }
        if (activate) {
            newState = set(`activePlotSelection`, targetPlotSelection, newState);
        }
        // get the current selection or create a new one if it not exists.
        let currentSelection = newState?.plots?.[targetPlotSelection] ?? EMPTY_PLOT_SELECTION;
        // add every plot received and toggle selection if exist
        plots.map(({ parcelle, ...other}) => {
            // if exists, toggle selection
            currentSelection = toggleSelection(currentSelection, parcelle);
            // update/insert the value at the en
            currentSelection = arrayUpsert(`data`, { parcelle, ...other }, {parcelle}, currentSelection);
        });
        // update with new values the state
        return set(`plots[${targetPlotSelection}]`, currentSelection, newState);
    }
    case REMOVE_PLOTS: {
        const { parcelles = []} = action;
        const {activePlotSelection = 0 } = state;
        // get the current selection or create a new one if it not exists.
        let currentSelection = state?.plots?.[activePlotSelection] ?? EMPTY_PLOT_SELECTION;
        currentSelection = {
            ...currentSelection,
            data: currentSelection.data.filter(({ parcelle }) => !parcelles.includes(parcelle)),
            selected: currentSelection.selected.filter(parcelle => !parcelles.includes(parcelle))
        };
        // update with new values the state
        return set(`plots[${activePlotSelection}]`, currentSelection, state);
    }
    case SELECT_PLOTS:
    case DESELECT_PLOTS: {
        const { activePlotSelection = 0 } = state;
        let currentSelection = state?.plots?.[activePlotSelection] ?? EMPTY_PLOT_SELECTION;
        const {plots = []} = action;
        const parcelles = plots.map(({ parcelle }) => parcelle);
        const selected = action.type === SELECT_PLOTS
            ? uniq([...(currentSelection.selected || []), ...parcelles])
            : (currentSelection.selected || []).filter(id => !parcelles.includes(id));
        currentSelection = {
            ...currentSelection,
            selected
        };
        return set(`plots[${activePlotSelection}]`, currentSelection, state);
    }
    case ADD_PLOT_SELECTION: {
        const {plot = {}} = action;
        const currentPlots = state?.plots ?? [];
        return compose(
            set(`plots`, [...currentPlots, { ...EMPTY_PLOT_SELECTION, ...plot}]),
            set(`activePlotSelection`, state?.plots?.length ?? 0) // select the new tab
        )(state);
    }
    case REMOVE_PLOT_SELECTION: {
        const active = action.active ?? state.activePlotSelection;
        const newPlots = [...state.plots.filter((_, i) => i !== active)];
        return compose(
            set(`plots`, newPlots),
            set(`activePlotSelection`, Math.max(state.activePlotSelection - 1, 0))
        )(state);
    }
    case SET_ACTIVE_PLOT_SELECTION: {
        return set('activePlotSelection', action.active, state);
    }
    case SET_LAYER_STYLE: {
        return set(`styles.${action.styleType}`, action.value, state);
    }
    case UPDATE_LAYER_STYLE: {
        return set(`styles.${action.styleType}`, {...(state?.styles?.[action.styleType] ?? {}), ...action.values}, state);
    }
    case SET_STYLES: {
        return set('styles', action.styles, state);
    }
    case SHOW_OWNERS: {
        return compose(
            set('owners.data', action.owners),
            set('owners.show', true)
        )(state);
    }
    case CLEAR_OWNERS: {
        return set('owners', undefined, state);
    }
    case SHOW_LANDED_PROPERTIES_INFORMATION: {
        return set('landedProperty.parcelle', action.parcelle, state);
    }
    case INFORMATION_UPDATE: {
        return set(
            `informationData["${action.parcelle}"]${action.path ? '.' + action.path : ''}`, action.data, state
        );
    }
    case INFORMATION_CLEAR: {
        return set(
            `informationData`, undefined, state
        );
    }
    case SAVE_BUBBLE_INFO: {
        return set('infoBulle', action.data, state);
    }
    case PRINT_RESPONSE: {
        return {...state, requestFormData: {...state.requestFormData, allowDocument: action.allowDocument, requestId: action.requestId}};
    }

    default:
        return state;
    }
}
Example #26
Source File: index.js    From hzero-front with Apache License 2.0 4 votes vote down vote up
/**
   * 渲染最终的字段
   * @param {Object} field - 字段
   */
  renderComposeFormField({ field }) {
    const { disableStyle, fieldLabelWidth, col, editable, organizationId, context } = this.props;
    const formItemProps = {
      labelCol: {
        style: { width: fieldLabelWidth, minWidth: fieldLabelWidth, maxWidth: fieldLabelWidth },
      },
      wrapperCol: { style: { flex: 'auto' } },
    };
    const colProps = getColLayout(col);
    const fieldColProps = getColLayout(col, field.colspan);
    const leftEmptyCols = [];
    const rightEmptyCols = [];
    if (inRange(field.leftOffset, 1, col)) {
      for (let i = 0; i < field.leftOffset; i++) {
        leftEmptyCols.push(<Col {...colProps} key={`${field.fieldCode}#left-offset-${i}`} />);
      }
    }
    if (inRange(field.rightOffset, 1, col)) {
      for (let i = 0; i < field.rightOffset; i++) {
        rightEmptyCols.push(<Col {...colProps} key={`${field.fieldCode}#right-offset-${i}`} />);
      }
    }
    const ComponentType = getComponentType(field);
    const componentProps = getComponentProps({
      field,
      componentType: field.componentType,
      context,
    });

    const otherFormItemOptions = {};
    let isViewOnly = false; // 附件是否为只读
    const getValueFromEvent = getGetValueFromEventFunc(field.componentType);
    const getValueProps = getGetValuePropFunc(field);
    if (field.componentType === 'Upload') {
      otherFormItemOptions.valuePropName = 'attachmentUUID';
      if (field.props) {
        const propsIndex = findIndex(field.props, ['attributeName', 'viewOnly']);
        if (propsIndex >= 0) {
          isViewOnly = field.props[propsIndex].attributeValue;
        }
      }
    }
    if (getValueFromEvent) {
      otherFormItemOptions.getValueFromEvent = getValueFromEvent;
    }
    if (getValueProps) {
      // 不影响存的值, 只影响传递给组件的值
      otherFormItemOptions.getValueProps = getValueProps;
    }
    const composeFormItem = (
      <Col {...fieldColProps} key={field.fieldCode}>
        <ComposeFormContext.Consumer>
          {({ form, dataSource }) => {
            const otherComponentProps = {}; // 为 lov 和 valueList 准备的属性
            switch (field.componentType) {
              case 'Lov':
              case 'ValueList':
                otherComponentProps.textValue = getDisplayValue(field, dataSource);
                break;
              default:
                break;
            }
            return editable ? (
              <FormItem
                label={field.fieldDescription}
                {...formItemProps}
                required={
                  field.componentType !== 'Checkbox' &&
                  toInteger(field.requiredFlag) !== 0 &&
                  !isViewOnly
                } // 当附件只读时,不必输
              >
                {(field.fieldCode === 'mail'
                  ? form.getFieldDecorator(`mail`, {
                      ...otherFormItemOptions,
                      initialValue: getInitialValue({ field, dataSource }),
                      rules: [
                        {
                          required: true,
                          message: intl.get('hzero.common.validation.notNull', {
                            name: intl.get('hzero.common.email').d('邮箱'),
                          }),
                        },
                        {
                          pattern: EMAIL,
                          message: intl.get('hzero.common.validation.email').d('邮箱格式不正确'),
                        },
                        {
                          max: 60,
                          message: intl.get('hzero.common.validation.max', {
                            max: 60,
                          }),
                        },
                      ],
                    })
                  : form.getFieldDecorator(field.fieldCode, {
                      ...otherFormItemOptions,
                      initialValue: getInitialValue({ field, dataSource }),
                      rules: [
                        {
                          required: toInteger(field.requiredFlag) !== 0 && !isViewOnly,
                          message: intl.get('hzero.common.validation.notNull', {
                            name: field.fieldDescription,
                          }),
                        },
                      ],
                    }))(
                  React.createElement(
                    ComponentType,
                    { ...componentProps, ...otherComponentProps } // otherComponentProps 比 componentProps 优先级高
                  )
                )}
              </FormItem>
            ) : (
              <FormItem label={field.fieldDescription} {...formItemProps}>
                {renderDisabledField({
                  field,
                  dataSource,
                  formItemProps,
                  organizationId,
                  disableStyle,
                  componentProps: { ...componentProps, ...otherComponentProps },
                })}
              </FormItem>
            );
          }}
        </ComposeFormContext.Consumer>
      </Col>
    );
    if (isEmpty(leftEmptyCols) && isEmpty(rightEmptyCols)) {
      return composeFormItem;
    }
    return (
      <React.Fragment key={field.fieldCode}>
        {leftEmptyCols}
        {composeFormItem}
        {rightEmptyCols}
      </React.Fragment>
    );
  }
Example #27
Source File: Timeline.js    From hivemind with Apache License 2.0 4 votes vote down vote up
Timeline = ({ data, timestamp, jump }) => {
  const timelineRef = useRef()
  const timeline = useRef()
  const {
    cyWrapper: { cy, viewApi },
  } = useContext(GlobalContext)
  const [modal, setModal] = useState(false)
  const [target, setTarget] = useState('timeline')
  const [node, setNode] = useState(<Spinner />)
  const [showJump, setShowJump] = useState('d-block')
  const [showFind, setShowFind] = useState('d-none')
  const [items, setItems] = useState([])

  const toggle = () => setModal(!modal)
  const jumpTo = async (lctime) => {
    if (lctime !== timestamp) {
      jump(lctime)
    }

    setModal(false)
  }
  const locate = (item) => {
    if (item && item.event !== 'deleted') {
      const node = cy.$id(item.nid)

      viewApi.zoomToSelected(node)
      viewApi.removeHighlights(cy.elements())
      viewApi.highlight(node)
    }

    setModal(false)
  }

  useEffect(() => {
    if (get(data, 'ok')) {
      setItems(
        data.data.map((event, idx) => ({
          id: idx,
          className: event.lctime === timestamp ? 'pinned' : '',
          title: event.event,
          content: '',
          start: event.lctime * 1000,
          style: `background-color: ${bgColors[event.event]};`,
          lctime: event.lctime,
          nid: event.nids[0] || event.mid,
          event: event.event,
        }))
      )
    }
  }, [data, timestamp])

  useEffect(() => {
    if (items.length) {
      if (timeline.current) {
        timeline.current.setItems(items)
      } else {
        const container = timelineRef.current
        if (container.firstChild) {
          container.removeChild(container.firstChild)
        }

        const margin = (items[items.length - 1].start - items[0].start) * 0.05
        const options = {
          width: '100%',
          height: '120px',
          type: 'box',
          stack: false,
          horizontalScroll: false,
          verticalScroll: false,
          cluster: {
            titleTemplate: '{count}',
            maxItems: 1,
            showStipes: true,
            fitOnDoubleClick: true,
          },
          max: items[items.length - 1].start + margin,
          min: items[0].start - margin,
          selectable: false,
          dataAttributes: ['id'],
          zoomMin: 60000,
        }

        timeline.current = new VisTimeline(container, items, options)
      }

      timeline.current.on('click', (properties) => {
        const { what, isCluster, item } = properties

        if (what === 'item' && !isCluster) {
          setNode(<Spinner />)
          setTarget(item)
          setModal(true)

          if (items[item].className === 'pinned') {
            setShowJump('d-none')

            if (items[item].event !== 'deleted') {
              setShowFind('d-block')
            }
          } else {
            setShowJump('d-block')
            setShowFind('d-none')
          }
        } else {
          setModal(false)
          setTarget('timeline')
        }
      })
      timeline.current.on('doubleClick', (properties) => {
        const { what, item, isCluster } = properties

        switch (what) {
          case 'background':
            timeline.current.fit()

            break
          case 'item':
            if (!isCluster) {
              timeline.current.focus(item)
            }

            break
        }
      })

      defer(() => { // To ensure focus/fit on first load.
        if (timestamp) {
          const idx = findIndex(items, { lctime: timestamp })
          timeline.current.focus(idx)
        } else {
          timeline.current.fit()
        }
      })
    }
  }, [items, timestamp])

  useEffect(
    () => () => {
      timeline.current.destroy()
      timeline.current = null
    },
    []
  )

  return (
    <div className={'border border-secondary rounded'}>
      <div id={'timeline'} ref={timelineRef} className={'m-1'} >
        <Spinner/>
      </div>
      <Modal
        isOpen={modal}
        toggle={toggle}
        fade={false}
        centered={true}
        size={'lg'}
        scrollable={true}
      >
        <ModalHeader toggle={toggle}>
          <b>{node}</b> | {get(items, [target, 'event'], 'NA')}{' '}
          {new Date(get(items, [target, 'start'], Date.now())).toLocaleString()}
        </ModalHeader>
        <ModalBody>
          {data && data.data[target] ? (
            <EventDetail event={data.data[target]} setNode={setNode} />
          ) : null}
        </ModalBody>
        <ModalFooter>
          <Button
            className={`ml-1 ${showJump}`}
            outline
            color="secondary"
            id="jump"
            onClick={() => jumpTo(items[target].lctime)}
          >
            <MapPin size={16} /> Jump
          </Button>
          &nbsp;
          <Button
            className={`ml-1 ${showFind}`}
            outline
            color="secondary"
            id="find"
            onClick={() => locate(items[target])}
          >
            <Search size={16} /> Find
          </Button>
          &nbsp;
          <ToolTippedButton
            className="ml-1"
            outline
            color="secondary"
            id="tag"
            disabled={true}
            tooltip={'Coming Soon'}
          >
            <Tag size={16} /> Tag
          </ToolTippedButton>
        </ModalFooter>
      </Modal>
    </div>
  )
}
Example #28
Source File: Canvas.js    From hivemind with Apache License 2.0 4 votes vote down vote up
Canvas = ({ data, timestamp, events }) => {
  const { cyWrapper, poppers } = useContext(GlobalContext)
  const [output, setOutput] = useState(null)
  const [els, setEls] = useState([])
  const prevEls = usePrevious(els)

  useEffect(() => {
    if (cyWrapper.cy && prevEls !== els) {
      const commonEls = intersectionBy(prevEls, els, 'data.id')
      const celMap = zipObject(map(commonEls, 'data.id'), commonEls)

      cyWrapper.cy
        .elements()
        .filter((el) => celMap[el.id()])
        .forEach((el) => {
          el.removeData('summary content audio lastUpdatedBy')
          el.data(celMap[el.id()].data)
        })
    }
  }, [cyWrapper.cy, els, prevEls])

  useEffect(() => {
    if (get(data, 'ok') && typeof window !== 'undefined') {
      setEls(CytoscapeComponent.normalizeElements(data.data.elements))
    }
  }, [data])

  useEffect(() => {
    function initCy(cyInternal) {
      cyWrapper.cy = cyInternal

      cyInternal.nodes().forEach((node) => {
        node.scratch('style', node.style())
      })
    }

    const nodes = els.filter((el) => !el.data.id.startsWith('links'))
    const fit = shouldFit(nodes)
    const options = getOptions(fit)

    setOutput(
      <CytoscapeComponent
        cy={initCy}
        style={{ width: '100%', height: '100%' }}
        stylesheet={style}
        layout={options}
        elements={els}
      />
    )
  }, [cyWrapper, els])

  useEffect(() => {
    function configurePlugins(access) {
      function buildMenu() {
        const { viewApi } = cyWrapper

        return function (node) {
          const menu = []

          view(menu, poppers)
          if (!node.data('isRoot')) {
            hide(menu, viewApi)
          }
          if (node.scratch('showReveal')) {
            reveal(menu, viewApi)
          }

          if (access && ['admin', 'write'].includes(access.access)) {
            add(menu, poppers)
            if (!node.data('isRoot')) {
              del(menu, poppers)
            }
            edit(menu, poppers)
          }

          return menu
        }
      }

      const { cy } = cyWrapper
      const minRadius = Math.min(cy.width(), cy.height()) / 8

      const viewOpts = {
        highlightStyles: [
          {
            node: { 'border-color': '#0b9bcd', 'border-width': 3 },
            edge: {
              'line-color': '#0b9bcd',
              'source-arrow-color': '#0b9bcd',
              'target-arrow-color': '#0b9bcd',
              width: 3,
            },
          },
          {
            node: { 'border-color': '#04f06a', 'border-width': 3 },
            edge: {
              'line-color': '#04f06a',
              'source-arrow-color': '#04f06a',
              'target-arrow-color': '#04f06a',
              width: 3,
            },
          },
        ],
        selectStyles: {
          node: {
            'border-color': 'white',
            'border-width': 3,
            'background-color': 'lightgrey',
          },
          edge: {
            'line-color': 'white',
            'source-arrow-color': 'white',
            'target-arrow-color': 'white',
            width: 3,
          },
        },
        setVisibilityOnHide: false, // whether to set visibility on hide/show
        setDisplayOnHide: true, // whether to set display on hide/show
        zoomAnimationDuration: 500, //default duration for zoom animation speed
        neighbor: function (node) {
          return node.successors()
        },
        neighborSelectTime: 500,
      }
      cyWrapper.viewApi = cy.viewUtilities(viewOpts)

      const cxtMenu = {
        menuRadius: minRadius + 50, // the radius of the circular menu in pixels
        selector: 'node', // elements matching this Cytoscape.js selector will trigger cxtmenus
        commands: buildMenu(), // function( ele ){ return [
        // /*...*/ ] }, // a function
        // that returns
        // commands or a promise of commands
        fillColor: 'rgba(0, 0, 0, 0.75)', // the background colour of the menu
        activeFillColor: 'rgba(100, 100, 100, 0.5)', // the colour used to indicate the selected
        // command
        activePadding: 10, // additional size in pixels for the active command
        indicatorSize: 16, // the size in pixels of the pointer to the active command
        separatorWidth: 3, // the empty spacing in pixels between successive commands
        spotlightPadding: 4, // extra spacing in pixels between the element and the spotlight
        minSpotlightRadius: minRadius - 40, // the minimum radius in pixels of the spotlight
        maxSpotlightRadius: minRadius - 20, // the maximum radius in pixels of the spotlight
        openMenuEvents: 'tap', // space-separated cytoscape events that will open the menu; only
        // `cxttapstart` and/or `taphold` work here
        itemColor: 'white', // the colour of text in the command's content
        itemTextShadowColor: 'transparent', // the text shadow colour of the command's content
        // zIndex: 9999, // the z-index of the ui div
        atMouse: false, // draw menu at mouse position
      }
      cyWrapper.menu = cy.cxtmenu(cxtMenu)
    }

    function setHandlers() {
      const { viewApi, cy } = cyWrapper

      cy.on(
        'boxend',
        throttle(() => defer(() => viewApi.zoomToSelected(cy.$(':selected')))),
        1000
      )

      cy.on('mouseover', 'node', () => {
        document.getElementById('cy').style.cursor = 'pointer'
      })

      cy.on('mouseout', 'node', () => {
        document.getElementById('cy').style.cursor = 'default'
      })

      cy.on('mouseover', 'edge', (e) => {
        e.target.style({
          width: 4,
          'line-color': '#007bff',
          'target-arrow-color': '#007bff',
        })
      })

      cy.on('unselect mouseout', 'edge', (e) => {
        const edge = e.target

        if (!edge.selected()) {
          edge.style({
            width: 2,
            'line-color': '#ccc',
            'target-arrow-color': '#ccc',
          })
        }
      })

      cy.on('add', 'node', (e) => {
        const node = e.target

        node.scratch('style', node.style())
      })

      cy.on(
        'add data remove',
        'node',
        throttle(() => {
          if (timestamp) {
            const idx = findIndex(events.data, { lctime: timestamp })
            const event = events.data[idx]
            const { viewApi } = cyWrapper

            viewApi.removeHighlights(cy.elements())
            if (event && event.event !== 'deleted') {
              const nid = event.nids[0]
              const node = cy.$id(nid)

              viewApi.highlight(node)
            }
          }
        }, 100)
      )

      cy.on('mouseover', 'node', (e) => {
        e.target.style('background-color', '#007bff')
      })

      cy.on('unselect mouseout', 'node', (e) => {
        const node = e.target

        viewApi.removeHighlights(node)

        if (!node.selected()) {
          node.style(
            'background-color',
            node.scratch('style')['background-color']
          )
        }
      })
    }

    if (cyWrapper.cy && get(data, 'ok') && get(events, 'ok')) {
      configurePlugins(data.data.access)
      setHandlers()
    }

    return () => {
      if (cyWrapper.menu) {
        cyWrapper.menu.destroy()
      }
    }
  }, [data, events, cyWrapper.menu, cyWrapper, timestamp, poppers, els])

  return (
    <div
      className={`border border-${
        timestamp ? 'secondary' : 'danger'
      } rounded w-100`}
      id="cy-container"
    >
      <div className="m-1" id="cy">
        {output}
      </div>
    </div>
  )
}
Example #29
Source File: index.js    From strapi-molecules with MIT License 4 votes vote down vote up
function SelectWrapper({
  componentUid,
  description,
  editable,
  label,
  isCreatingEntry,
  isFieldAllowed,
  isFieldReadable,
  mainField,
  name,
  relationType,
  slug,
  targetModel,
  placeholder,
  valueToSet,
}) {
  // Disable the input in case of a polymorphic relation
  const isMorph = relationType.toLowerCase().includes("morph");
  const {
    addRelation,
    modifiedData,
    moveRelation,
    onChange,
    onRemoveRelation,
    initialData,
  } = useDataManager();
  const { isDraggingComponent } = useEditView();

  const value =
    valueToSet && valueToSet !== "current"
      ? valueToSet
      : get(modifiedData, name, null);
  const initialValue = get(initialData, name, null);

  // This is needed for making requests when used in a component
  const fieldName = useMemo(() => {
    const fieldNameArray = getFieldName(name);
    return fieldNameArray[fieldNameArray.length - 1];
  }, [name]);

  const { pathname } = useLocation();

  const [state, setState] = useState({
    _contains: "",
    _limit: 20,
    _start: 0,
  });
  const [options, setOptions] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const abortController = new AbortController();
  const { signal } = abortController;
  const ref = useRef();
  const startRef = useRef();

  const filteredOptions = useMemo(() => {
    return options.filter((option) => {
      if (!isEmpty(value)) {
        // SelectMany
        if (Array.isArray(value)) {
          return findIndex(value, (o) => o.id === option.value.id) === -1;
        }

        // SelectOne
        return get(value, "id", "") !== option.value.id;
      }

      return true;
    });
  }, [options, value]);

  startRef.current = state._start;

  ref.current = async () => {
    if (isMorph) {
      setIsLoading(false);

      return;
    }

    if (!isDraggingComponent) {
      try {
        const requestUrl = `/${pluginId}/explorer/${slug}/relation-list/${fieldName}`;

        const containsKey = `${mainField}_contains`;
        const { _contains, ...restState } = cloneDeep(state);
        const params = isEmpty(state._contains)
          ? restState
          : { [containsKey]: _contains, ...restState };

        if (componentUid) {
          set(params, "_component", componentUid);
        }

        const data = await request(requestUrl, {
          method: "GET",
          params,
          signal,
        });

        const formattedData = data.map((obj) => {
          return { value: obj, label: obj[mainField] };
        });

        setOptions((prevState) =>
          prevState.concat(formattedData).filter((obj, index) => {
            const objIndex = prevState.findIndex(
              (el) => el.value.id === obj.value.id,
            );

            if (objIndex === -1) {
              return true;
            }

            return (
              prevState.findIndex((el) => el.value.id === obj.value.id) ===
              index
            );
          }),
        );
        setIsLoading(false);
      } catch (err) {
        if (err.code !== 20) {
          strapi.notification.error("notification.error");
        }
      }
    }
  };

  useEffect(() => {
    if (state._contains !== "") {
      let timer = setTimeout(() => {
        ref.current();
      }, 300);

      return () => clearTimeout(timer);
    }

    if (isFieldAllowed) {
      ref.current();
    }

    return () => {
      abortController.abort();
    };
  }, [state._contains, isFieldAllowed]);

  useEffect(() => {
    if (state._start !== 0) {
      ref.current();
    }

    return () => {
      abortController.abort();
    };
  }, [state._start]);

  const onInputChange = (inputValue, { action }) => {
    if (action === "input-change") {
      setState((prevState) => {
        if (prevState._contains === inputValue) {
          return prevState;
        }

        return { ...prevState, _contains: inputValue, _start: 0 };
      });
    }

    return inputValue;
  };

  const onMenuScrollToBottom = () => {
    setState((prevState) => ({ ...prevState, _start: prevState._start + 20 }));
  };

  const isSingle = [
    "oneWay",
    "oneToOne",
    "manyToOne",
    "oneToManyMorph",
    "oneToOneMorph",
  ].includes(relationType);

  const changeRelationValueForCurrentVersion = () => {
    if (valueToSet && startRef.current != 0) {
      valueToSet !== "current"
        ? onChange({ target: { name, value: valueToSet } })
        : onChange({ target: { name, value: initialValue } });
    }
  };

  useEffect(() => {
    changeRelationValueForCurrentVersion();
  }, [valueToSet]);

  const to = `/plugins/${pluginId}/collectionType/${targetModel}/${
    value ? value.id : null
  }`;
  const link =
    value === null ||
    value === undefined ||
    [
      "plugins::users-permissions.role",
      "plugins::users-permissions.permission",
    ].includes(targetModel) ? null : (
      <Link to={{ pathname: to, state: { from: pathname } }}>
        <FormattedMessage id="content-manager.containers.Edit.seeDetails" />
      </Link>
    );
  const Component = isSingle ? SelectOne : SelectMany;
  const associationsLength = isArray(value) ? value.length : 0;

  const customStyles = {
    option: (provided) => {
      return {
        ...provided,
        maxWidth: "100% !important",
        overflow: "hidden",
        textOverflow: "ellipsis",
        whiteSpace: "nowrap",
      };
    },
  };

  const isDisabled = useMemo(() => {
    if (isMorph) {
      return true;
    }

    if (!isCreatingEntry) {
      return !isFieldAllowed && isFieldReadable;
    }

    return !editable;
  });

  if (!isFieldAllowed && isCreatingEntry) {
    return <NotAllowedInput label={label} />;
  }

  if (!isCreatingEntry && !isFieldAllowed && !isFieldReadable) {
    return <NotAllowedInput label={label} />;
  }

  return (
    <Wrapper className="form-group">
      <Nav>
        <div>
          <label htmlFor={name}>
            {label}
            {!isSingle && (
              <span style={{ fontWeight: 400, fontSize: 12 }}>
                &nbsp;({associationsLength})
              </span>
            )}
          </label>
          {isSingle && link}
        </div>
        {!isEmpty(description) && <p className="description">{description}</p>}
      </Nav>
      <Component
        addRelation={(value) => {
          addRelation({ target: { name, value } });
        }}
        id={name}
        isDisabled={isDisabled}
        isLoading={isLoading}
        isClearable
        mainField={mainField}
        move={moveRelation}
        name={name}
        options={filteredOptions}
        onChange={(value) => {
          onChange({ target: { name, value: value ? value.value : value } });
        }}
        onInputChange={onInputChange}
        onMenuClose={() => {
          setState((prevState) => ({ ...prevState, _contains: "" }));
        }}
        onMenuScrollToBottom={onMenuScrollToBottom}
        onRemove={onRemoveRelation}
        placeholder={
          isEmpty(placeholder) ? (
            <FormattedMessage id={`${pluginId}.containers.Edit.addAnItem`} />
          ) : (
            placeholder
          )
        }
        styles={customStyles}
        targetModel={targetModel}
        value={value}
      />
      <div style={{ marginBottom: 18 }} />
    </Wrapper>
  );
}