npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

kps-component

v2.0.0

Published

- [KPS-Component Api/Components Document](#kps-component-apicomponents-document) - [1. Reference](#1-reference) - [1/1 Shotgun Api Document](#11-shotgun-api-document) - [1/2 Introduction](#12-introduction) - [2. Read/Create/Update/Delete](

Downloads

18

Readme

KPS-Component Api/Components Document

1. Reference

1/1 Shotgun Api Document

https://developer.shotgunsoftware.com/rest-api/?javascript

1/2 Introduction

在./src/index.js 中必需配置服务器信息

//./src/.index.js
import Api, { CorsImage, CorsVideo, ImageViewer } from 'kps-components'

Api.config = {
  //全部必填实际信息
  grant_type: '',
  client_id: '',
  client_secret: '',
  host: '',
  hostApi: '',
}

const dev = process.env.NODE_ENV === 'development'
if (!dev) {
  Api.trackClient() // 注册追踪页面接口,开发环境下不追踪
}

2. Read/Create/Update/Delete

entityName 必须属于类似下面 entity type 中的类型,即 shotgun entity 在后台的名字

'Asset',
'Attachment',
'Department',
'EventLogEntry',
'Group',
'HumanUser',
'Note',
'Page',
'Phase',
'PhysicalAsset',
'Project',
'Reply',
'Software',
'Status',
'Step',
'Tag',
'Task',
'Version',
'CustomNonProjectEntity12',
'PermissionRuleSet'
......

2/1 Read

id 查询一个条目示例

Api.getEntityFromId({
  entityName: 'Task', //必填
  id: 23844, //必填
  fields: ['id', 'content', 'created_at'], //必填 填入要查询的字段列表
  linkEntities: [
    //选填
    {
      fieldName: 'sg_title_type', //在上面fields中必须写入
      fieldType: 'CustomNonProjectEntity12',
      fields: ['id', 'code', 'sg_title_level'],
      multiple: false, //field 类型是entity or multi-entity
    },
    {
      fieldName: 'tags',
      fieldType: 'Tag',
      fields: ['id', 'name', 'sg_tag_type'],
      multiple: true,
    },
  ], //查询关联entity的详细Field,默认查询只有entity的id和name, [{fieldName:'some_field',fieldType: 'someEntityType', multiple: false/true, fields: ['id', 'code', ...]}], 注意linkEntities次级查询总条目数上限5000
}).then((res) => {
  console.log('res', res)
})

查询符合条件的一组数据示例

Api.getEntityList({
  entityName: 'HumanUser', //必填
  fields: ['id', 'name', 'sg_title_type', 'department', 'tags'], //选填 填入要查询的字段列表
  filters: [//必填,可以是array或hash形式,hash形式示例见下个示例
      ['sg_status_list', 'is', 'act'],
      ['name', 'contains', 'li'],
    ],
  options: { sort: '-created_at', number: 1, size: 500 }, //选填 使用方式见上方kpsapi.getEntityFromId()
  linkEntities: [] //选填 使用方式见上方kpsapi.getEntityFromId()
  totalCount = true, //选填 是否返回符合条件的总条目数,分页时使用
}).then((res) => {
  console.log('res', res)
})

筛选 hash 形式示例

Api.getEntityList({
  entityName: 'HumanUser',
  fields: ['id', 'name', 'sg_title_type', 'department', 'tags'],
  filters: {
    logical_operator: 'and',
    conditions: [
      ['sg_status_list', 'is', 'act'],
      ['name', 'contains', 'li'],
      //可继续添加复杂条件嵌套
      //{
      //  logical_operator: 'and',
      //  conditions: [
      //    ['name', 'is', 'Demo Project'],
      //    ['sg_status_list', 'is', 'res'],
      //  ],
      //},
    ],
  },
  options: { size: 500 },
}).then((res) => {
  console.log('res', res)
})

获取统计信息示例

Api.getSummarize({
  entityName: 'HumanUser',
  filters: [['sg_status_list', 'is', 'act']],
  fields: [{ field: 'id', type: 'count' }],
  grouping: [{ field: 'sg_asset_type', type: 'exact', direction: 'asc' }],
  //or 不分组grouping: null
}).then((res) => {
  console.log('res', res)
})

//统计信息 type 类型详见文档
//https://developer.shotgunsoftware.com/rest-api/?javascript#summarize-field-data
//常用:record_count, count, sum, maximum, minimum, average, earliest, latest...

2/2 Create

创建一个条目

Api.createEntity({
  entityName: 'Task', //必填
  fields: ['content', 'project', 'id'], //选填 返回的相关字段信息
  variables: {
    //必填 有些entity类型必填project字段
    content: 'task create demo',
    project: { id: 94, type: 'Project' },
    //other_field:value
  },
  linkEntities: [], //选填 返回的关联entity信息
}).then((res) => {
  console.log('res', res)
})

批量创建条目

Api.batchCreate({
  entityName: 'Task', //必填
  fields: ['content', 'project', 'id'], //选填
  variables: [ //必填 array形式
    {
      content: 'task create demo 0',
      project: { id: 94, type: 'Project' },
      //other_field:value
    },
    {
      content: 'task create demo 1',
      project: { id: 94, type: 'Project' },
      //other_field:value
    },
  ],
  linkEntities:[] //选填 返回的关联entity信息
  options: null //选填
  totalCount: false //选填
}).then((res) => {
  console.log('res', res)
})

2/3 Update

更新一个条目

Api.updateEntity({
  entityName: 'Task', //必填
  id: 24347, //必填
  variables: {
    //必填
    content: 'Cool task update',
    project: { id: 94, type: 'Project' },
    //other_field:value
  },
  fields: ['id', 'start_date'],
  linkEntities: [], //选填 返回的关联entity信息
}).then((res) => {
  console.log('res', res)
})

批量更新条目

Api.batchUpdate({
  entityName: 'Task', //必填
  variables: [ //必填 array形式
    {
      id: 123,
      content: 'Cool task create update 1',
      project: { id: 94, type: 'Project' },
      //other_field:value
    },
    {
      id: 124,
      content: 'Cool task create update 2',
      project: { id: 97, type: 'Project' },
      //other_field:value
    },
  ],
  fields: ['id', 'start_date'],
  linkEntities:[] //选填 返回的关联entity信息
  options: null //选填
  totalCount: false //选填
}).then((res) => {
  console.log('res', res)
})

2/4 Delete

删除一个条目

Api.deleteEntity({
  entityName: 'Version',
  id: 43691,
}).then((res) => {
  console.log('res', res)
})

批量删除条目

Api.batchDelete({
  entityName: 'Version',
  ids: [121, 122, 123, 124],
}).then((res) => {
  console.log('res', res)
})

2/5 Track

1. normal track

建议在每个应用的./src/App.js 中定义全局配置,保证重复信息只输入一次

window.track = (e) => {
  e.channel = 'Application Center' //当前频道名
  e.code = 'Digital Center' //当前应用名
  if (window.userInfo) {
    //在查询到当前用户信息后赋值
    e.userInfo = { type: 'HumanUser', id: parseInt(window.userInfo.id) }
  }
  Api.track(e)
}

在需要的节点传入相应值,调用 window.track(e),某个 track 示例:

window.track({
  page: 'issue management', //以下根据需要选填
  module: 'issue card',
  component: 'add button',
  actionType: 'click',
  actionResult: 'add',
  desc: '',
  projectId: 123,
})

2. client track

在 App.js 的初始 useEffect()注册

// ./App.js
// function App(){
useEffect(() => {
  Api.trackClient()
}, [])
// }

2/6 Tools

使用时直接调用 Api.isImage(...)

const specialCode =
  /[(\~)(\!)(\@)(\#)(\$)(\%)(\^)(\&)(\()(\))(\+)(\=)(\[)(\])(\{)(\})(\;)(\')(\,)(\,)]+/g

const fileTypeIcon = {
  ppt: 'https://z3.ax1x.com/2021/08/11/fNDrtK.png',
  pptx: 'https://z3.ax1x.com/2021/08/11/fNDrtK.png',
  doc: 'https://z3.ax1x.com/2021/08/11/fNDOns.png',
  docx: 'https://z3.ax1x.com/2021/08/11/fNDOns.png',
  xls: 'https://z3.ax1x.com/2021/08/11/fNDMmn.png',
  xlsx: 'https://z3.ax1x.com/2021/08/11/fNDMmn.png',
  wire: 'https://z3.ax1x.com/2021/08/11/fNDItf.png',
  igs: 'https://z3.ax1x.com/2021/08/11/fNDUX9.png',
  iges: 'https://z3.ax1x.com/2021/08/11/fNDUX9.png',
  other: 'https://z3.ax1x.com/2021/08/11/fNDw01.png',
  psd: 'https://z3.ax1x.com/2021/08/11/fNDc1e.png',
  ps: 'https://z3.ax1x.com/2021/08/11/fNDc1e.png',
  sketch: 'https://z3.ax1x.com/2021/08/11/fNDWnA.png',
  ai: 'https://z3.ax1x.com/2021/08/11/fNDuOs.png',
  img: 'https://z3.ax1x.com/2021/08/11/fNDdmR.png',
  zip: 'https://z3.ax1x.com/2021/08/11/fNDXBn.png',
  rar: 'https://z3.ax1x.com/2021/08/11/fNDXBn.png',
  video: 'https://z3.ax1x.com/2021/08/11/fNDh7t.png',
  txt: 'https://z3.ax1x.com/2021/08/11/fNDf0I.png',
  stp: 'https://z3.ax1x.com/2021/08/11/fND5AP.png',
  step: 'https://z3.ax1x.com/2021/08/11/fND5AP.png',
  pdf: 'https://z3.ax1x.com/2021/08/11/fND0Tx.png',
  eps: 'https://z3.ax1x.com/2021/08/11/fNDnyj.png',
  '3dm': 'https://z3.ax1x.com/2021/08/11/fNDlT0.png',
  gh: 'https://z3.ax1x.com/2021/08/11/fNDQwq.png',
}

const haveSpecialCode = (name) => {
  return specialCode.test(name)
}

const replaceSpecialCode = (name, toCode = '_') => {
  return name.replace(specialCode, toCode)
}

const isImage = (origUrl) => {
  return /\.(png|tif|tiff|jpg|gif|jpeg|webp)$/i.test(origUrl)
}

const isVideo = (origUrl) => {
  return /\.(mp4|ogg|webm|m4v)$/i.test(origUrl)
}

const isVersionImage = (item) => {
  return (
    item.sg_uploaded_movie &&
    /\.(png|tif|tiff|jpg|gif|jpeg|webp)$/i.test(item.sg_uploaded_movie.name)
  )
}

const isVersionVideo = (item) => {
  return (
    item.sg_uploaded_movie &&
    /\.(mp4|ogg|webm|m4v)$/i.test(item.sg_uploaded_movie.name)
  )
}

const getReplaceThumbnailIcon = (name) => {
  const strList = name.split('.')
  const sub = strList[strList.length - 1].toLowerCase()
  if (strList.length === 1 || Object.keys(fileTypeIcon).indexOf(sub) < 0) {
    return fileTypeIcon.other
  } else {
    return fileTypeIcon[sub]
  }
}

const getVersionThumb = (item) => {
  // sg_thumb_image, sg_trans_image, image, sg_uploaded_movie
  let thumbUrl
  if (!item.sg_uploaded_movie) {
    thumbUrl = fileTypeIcon.other
  } else if (item.sg_thumb_image) {
    thumbUrl = item.sg_thumb_image.url
  } else if (
    item.image &&
    item.image.indexOf('/transient/thumbnail_pending.png') < 0
  ) {
    thumbUrl = item.image
  } else {
    if (isImage(item.sg_uploaded_movie.name)) {
      thumbUrl = fileTypeIcon.img
    } else if (isVideo(item.sg_uploaded_movie.name)) {
      thumbUrl = fileTypeIcon.video
    } else {
      thumbUrl = getReplaceThumbnailIcon(item.sg_uploaded_movie.name)
    }
  }
  return thumbUrl
}

const getVersionThumbType = (item) => {
  let type = 'original'
  if (!item.sg_uploaded_movie) {
    type = 'original'
  } else if (item.sg_thumb_image) {
    type = 'original'
  } else if (
    item.image &&
    item.image.indexOf('/transient/thumbnail_pending.png') < 0
  ) {
    type = 'original'
  } else {
    type = 'icon'
  }
  return type
}

const getVersionShow = (item) => {
  let url
  if (!item.sg_uploaded_movie) {
    url = fileTypeIcon.other
  } else if (item.sg_trans_image) {
    url = item.sg_trans_image.url
  } else {
    url = item.sg_uploaded_movie.url
  }
  return url
}

const isGroupOverlap = (a1, a2, targetKey = 'id') => {
  /*
    a1 = [{id:1, name:1},{id:2, name:2}]
    a2 = [{id:1, name:1},{id:3, name:3}]
    return true
  */
  let isIn = false
  a1.some((e1) => {
    if (a2.findIndex((e2) => e2[targetKey] === e1[targetKey]) > -1) {
      isIn = true
      return true
    }
    return false
  })
  return isIn
}

const isAppPermissioned = (userInfo, appInfo) => {
  // 判断用户是否拥有用户权限,参数是用户信息(含有'userInfo.groups' Fields)和app信息(含有四个权限Fields)
  if (
    appInfo.sg_excluding_people.data.length > 0 &&
    appInfo.sg_excluding_people.data.findIndex((e) => e.id === userInfo.id) >= 0
  ) {
    return false
  }
  if (
    appInfo.sg_including_people.data.length > 0 &&
    appInfo.sg_including_people.data.findIndex((e) => e.id === userInfo.id) >= 0
  ) {
    return true
  }
  if (
    appInfo.sg_excluding_groups.data.length > 0 &&
    isGroupOverlap(userInfo.groups.data, appInfo.sg_excluding_groups.data)
  ) {
    return false
  }
  if (appInfo.sg_including_groups.data.length > 0) {
    return isGroupOverlap(
      userInfo.groups.data,
      appInfo.sg_including_groups.data
    )
  } else {
    return true
  }
}

export const tools = {
  isAppPermissioned,
  isGroupOverlap,
  haveSpecialCode,
  replaceSpecialCode,
  isImage,
  isVideo,
  isVersionImage,
  isVersionVideo,
  getVersionThumb,
  getVersionShow,
  getVersionThumbType,
  getReplaceThumbnailIcon,
}

2/7 Download

// 单个文件下载
Api.downloadFile({
  entityName: 'Version',
  field: 'sg_uploaded_movie' //entity文件类型的字段
  fileList: {
      name: '1.jpg', // 带有实际文件名后缀
      id: 123, // entity的id,不是file的id
    },
}).then(res=>{
  // 成功返回 'success'
}).catch(err=>{
  // 失败返回 'fail'
})

// 多个文件下载
Api.downloadFileList({
  entityName: 'Version',
  field: 'sg_uploaded_movie' //entity文件类型的字段
  fileList: [
    {
      name: '1.jpg', //带有实际文件名后缀
      id: 123, //entity的id,不是file的id
    },
    {
      name: '2.jpg',
      id: 124,
    },
  ],
}) //没有返回值

2/8 获取 SG 文件或图片信息

// 获取单个文件内容
Api.getFileContent({
  entityName: 'Version', //必填
  fileInfo: {
    id: 123, // entity id
    name: '123.jpg', // file name
  },
  field: 'sg_uploaded_movie', // 文件所在field
}).then((res) => {
  console.log('res', res)
})

## 3. Other Options

### 3/1. Filter / Search

使用复杂的条件 query

> "filters": [[field, relation, value(s)]]

Array Style

```js
//dmeo1:
"filters": [["name", "contains", "template"]]

//demo2:
"filters": [
    ["name", "contains", "template"],
    ["is_demo", "is", true]
  ]

Hash Style 多组及嵌套

"filters": {
  "logical_operator": "or", //'and'
  "conditions": [
    ["is_demo", "is", true],
    ["sg_status", "in", ["Hold", "Lost"]]
  ]
}

"filters":{
  "logical_operator": "or",
  "conditions": [
    {
      "logical_operator": "and",
      "conditions": [
        ["name", "is", "Demo Project"]
        ["sg_status_list", "is" "res"]
      ]
    },
    ["name", "is", "Game Demo"]
  ]
}

3/2. Operators and Arguments

操作符和对应的参数列表

| Operator | Arguments | Notes | | ------------------- | --------------------------------------------------------------------- | ----- | | 'is' | [field_value] / None | | 'is_not' | [field_value] / None | | 'less_than' | [field_value] | None | | 'greater_than' | [field_value] / None | | 'contains' | [field_value] / None | | 'not_contains' | [field_value] / None | | 'starts_with' | [string] | | 'ends_with' | [string] | | 'between' | [[field_value] / None, [field_value]/ None] | | 'not_between' | [[field_value] / None, [field_value] / None] | | 'in_last' | [[int], 'HOUR' / 'DAY' / 'WEEK'/ 'MONTH' / 'YEAR'] | | 'in_next' | [[int], 'HOUR'/ 'DAY' / 'WEEK' / 'MONTH' / 'YEAR'] | | 'in' | [[field_value] / None, ...] # Array of field values | | 'type_is' | [string] / None # Shotgun entity type | | 'type_is_not' | [string] / None # Shotgun entity type | | 'in_calendar_day' | [int] # Offset (e.g. 0 = today, 1 = tomorrow, -1 = yesterday) | | 'in_calendar_week' | [int] # Offset (e.g. 0 = this week, 1 = next week, -1 = last week) | | 'in_calendar_month' | [int] # Offset (e.g. 0 = this month, 1 = next month, -1 = last month) | | 'name_contains' | [string] | | 'name_not_contains' | [string] | | 'name_starts_with' | [string] | | 'name_ends_with' | [string] |

3/3. Valid Operators By Data Type

不同类型 Field 识别的操作符

| Field type | Operators | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | addressing | 'is'/'is_not'/'contains'/'not_contains'/'in'/'type_is' /'type_is_not'/'name_contains' / 'name_not_contains'/'name_starts_with'/'name_ends_with' | | checkbox | 'is'/'is_not' | | currency | 'is'/'is_not'/'less_than'/'greater_than'/'between'/'not_between'/'in'/'not_in'/date/ 'is'/'is_not'/'greater_than'/'less_than'/'in_last'/'not_in_last'/'in_next'/'not_in_next' /'in_calendar_day'/'in_calendar_week'/'in_calendar_month' /'in_calendar_year' /'between'/'in'/'not_in' | | date_time | 'is'/'is_not'/'greater_than'/'less_than'/'in_last'/'not_in_last'/'in_next'/'not_in_next' / 'in_calendar_day'/'in_calendar_week'/'in_calendar_month'/ 'in_calendar_year'/'between'/'in'/'not_in' | | duration | 'is'/'is_not'/'greater_than'/'less_than'/'between'/'in'/'not_in' | | entity | 'is'/'is_not'/'type_is'/'type_is_not'/'name_contains'/'name_not_contains'/'name_is'/'in'/'not_in' | | float | 'is'/'is_not'/'greater_than'/'less_than'/'between'/'in'/'not_in' | | image | 'is'/'is_not' ** Note: For both 'is' and 'is_not', the only supported value is None, ** which supports detecting the presence or lack of a thumbnail. | | list | 'is'/'is_not'/'in'/'not_in' | | multi_entity | 'is' ** Note: when used on multi_entity, this functions as you would expect 'contains' to function/ 'is_not'/'type_is'/'type_is_not'/ 'name_contains'/'name_not_contains'/'in'/'not_in' | | number | 'is'/'is_not'/'less_than'/'greater_than'/'between'/'not_between'/'in'/'not_in' | | password | ** Filtering by this data type field not supported | | percent | 'is'/'is_not'/'greater_than'/'less_than'/'between'/'in'/'not_in' | | serializable | ** Filtering by this data type field not supported | | status_list | 'is'/'is_not'/'in'/'not_in' | | summary | ** Filtering by this data type field not supported | | text | 'is'/ is_not'/'contains'/'not_contains'/'starts_with'/'ends_with'/'in'/'not_in' | | timecode | 'is'/'is_not'/'greater_than'/'less_than'/'between'/'in'/'not_in' | | url | ** Filtering by this data type field is not supported |

3/4. Data Types

写入不同类型 Field 的数据类型

| Name | Type | Example/Format | Notes | | --------------------- | ------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | addressing | array | [{'type': "HumanUser" or "Group", "id": }, ...] | | | checkbox | bool | true / false | | | color | string | 255,0,0 / pipeline_step | pipeline_step indicates the Task color inherits from the Pipeline Step color. | | currency | float / null | | Range: -9999999999999.99 to 9999999999999.99 | | date | string / null | YYYY-MM-DD | Year must be >= 1970 | | date_time | string / null | YYYY-MM-DDThh:mm:ssZ | Datetimes must be in the ISO8601 format. | | duration | int / null | | Range: -2147483648 to 2147483647. Length of time, in minutes | | entity | object / null | {'type': , "id": } | | | float | float / null | | Range: -9999999999999.99 to 9999999999999.99 | | footage | string / null | FF-ff | Frames must be < Preferences value for “Advanced > Number of frames per foot of film”> Format matches Preference value for “Formatting > Display of footage fields”. Example above is default.F=Feet f=Frames. | | image | string / null | | | | list | string / null | | | | multi_entity | array | [{'type': , "id": }, ...] | | | password | string / null | | | | number | int / null | | | | serializable | object / null | | | | status_list | string / null | | | | text | string / null | | | | timecode | int / null | | Range: -2147483648 to 2147483647. Length of time, in milliseconds (1000 = 1 second) | | url (file/link field) | object / null |

3/5. Updating a multi-entity field

更新某条目的某个属性是 multi entity 的字段,如 task 的 task_owners,version 的 tags

Simple assignment example (setting users to [436, 536])

{
  "users": [
      {
          "type": "HumanUser",
          "id": 436
      },
      {
          "type": "HumanUser",
          "id": 536
      }
  ]
}

Add multi-entity update mode example (adding users [574, 538] to the field)

{
  "users": {
    "multi_entity_update_mode": "add",
    "value": [
      {
          "type": "HumanUser",
          "id": 574
      },
      {
          "type": "HumanUser",
          "id": 538
      }
    ]
  }
}

Remove multi-entity update mode example (removing users [103, 203] from the field)

{
  "users": {
    "multi_entity_update_mode": "remove",
    "value": [
      {
          "type": "HumanUser",
          "id": 103
      },
      {
          "type": "HumanUser",
          "id": 203
      }
    ]
  }
}

Set multi-entity update mode example (setting users to [436, 536])

{
  "users": {
    "multi_entity_update_mode": "set",
    "value": [
      {
          "type": "HumanUser",
          "id": 436
      },
      {
          "type": "HumanUser",
          "id": 536
      }
    ]
  }
}

4. Upload Files

4/1 Upload Api

上传并返回生成的 version 的 id

Api.createUploadVersion({
  file: fileToUpload, //必填
  variables: {
    //必填 version的其它信息
    code: 'upload version',
    project: { type: 'Project', id: 97 },
    //other_filed: value
  },
})

用于直接上传到各 entity 内的 file 类型 field,如已知 version 更新文件

Api.uploadFileToField({
  entityName: 'Version', //必填
  id: 1234, //必填
  field: 'sg_uploaded_movie', //必填 要上传到的字段
  file: fileToUpload, //必填
})

4/2 React Upload Demo

import React, { useState, useEffect } from 'react'
import { Upload, Progress, Badge, Button, message } from 'antd'
import { InboxOutlined } from '@ant-design/icons'

const { Dragger } = Upload

export default function UploadModuleReact(props) {
  const [fileList, setFileList] = useState([])
  const [progressMock, setProgressMock] = useState(0)

  const uploadProps = {
    name: 'file',
    multiple: true,
    showUploadList: false,
    fileList,
    beforeUpload: (file, newAddFileList) => {
      // 如果需要立即上传,在此处拦截
      setFileList([...fileList, ...newAddFileList])
      return false
    },
  }

  const handleUpload = async () => {
    console.log('fileList', fileList)
    let createList = [],
      pMock = 1,
      sizeAll = 0
    fileList.forEach((f) => {
      sizeAll = sizeAll + f.size / 750000
      createList.push(
        Api.createUploadVersion({
          file: f,
          variables: {
            //TODO: your version variables here
            project: {
              type: 'Project',
              id: 0,
            },
            code: f.name,
            sg_task: {
              type: 'Task',
              id: 0,
            },
          },
        })
      )
    })
    let mock = setInterval(() => {
      if (pMock <= 99) {
        pMock = parseFloat((pMock + 99 / sizeAll).toFixed(1))
        pMock = Math.min(99, pMock)
        setProgressMock(pMock)
      }
    }, 500)
    let versionRes = await Promise.all(createList)
    console.log('created versions: ', versionRes)
    clearInterval(mock)
    setProgressMock(100)
    setFileList([])
    setTimeout(() => {
      setProgressMock(0)
      message.success('Upload successfully')
    }, 1000)

    // write your finish functions here
    // for images, use 'sg_thumb_image' to show image immediately
    // use 'sg_trans_image' to show image at web page
  }

  return (
    <div
      style={{
        width: '400px',
      }}>
      <Dragger {...uploadProps}>
        <div
          style={{
            height: '68px',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}>
          {progressMock > 0 ? (
            <Progress
              percent={progressMock}
              type='circle'
              status='active'
              width={72}
            />
          ) : (
            <Badge count={fileList.length}>
              <InboxOutlined style={{ fontSize: '46px', color: '#40a9ff' }} />
            </Badge>
          )}
        </div>
        <p
          style={{
            margin: ' 0 0 4px',
            color: 'rgba(0, 0, 0, 0.85)',
            fontSize: '16px',
          }}>
          {progressMock === 0
            ? 'Click or drag file here'
            : progressMock === 99
            ? 'Finishing...'
            : 'Click or drag file here'}
        </p>
      </Dragger>
      <Button
        onClick={handleUpload}
        disabled={fileList.length === 0 || progressMock > 0}>
        Start
      </Button>
    </div>
  )
}

COMPONENTS

基于 React 和 Antd

// index.js
// 配置见 文档开头配置处

Image

 跨域图片获取,必包含 info 内的内容

versions.map((v) => (
  <CorsImage
    key={v.id}
    info={{
      id: v.id,
      type: 'Version',
      name: v.sg_uploaded_movie.name, //此键值也可以是code, content, 分别对应version,task等,选一即可
      field: 'sg_thumb_image',
      className: 'hello-image',
    }}
    style={{
      width: '300px',
      height: '200px',
      padding: '10px',
      objectFit: 'auto', // 不填或'auto',则自动判断contain或cover
    }}
  />
))

Video

 跨域图片获取,必包含 info 内的内容

versions.map((v) => (
  <CorsVideo
    key={v.id}
    info={{
      id: v.id,
      type: 'Version',
      name: v.sg_uploaded_movie.name, //此键值也可以是code, content, 分别对应version,task等,选一即可
      field: 'sg_thumb_image',
      className: 'hello-image'

      autoply:'autoplay',
      loop:'loop',
      controls: 'controls'
    }}
    style={{
      width: '300px',
      height: '200px',
    }}
  />
))

ImageViewer

基本的图片列表和看图组件,包含右键下载和删除功能

TODO Update:

  1. 滚轮放大和缩小,左键拖拽
  2. 与父级框选操作冲突问题
import { ImageViewer } from 'kps-component'
...
const versionList = [........]
...
// Basic Demo
versionList.map(v => <ImageViewer
  key={v.id}
  item={v} //必填
  itemList={versionList} //必填
  onDelete={()=>{}} //默认undefined,必填,此处需要更新传入的VersionList

  style={{}} //img wrapper style, 选填
  showName={true} //默认false, 选填
  onCloseViewModal={()=>{}} //默认undefined, 选填
  onClickLeft={()=>{}} //默认undefined, 选填
  onClickRight={()=>{}} //默认undefined, 选填
  onDownload={()=>{}} //默认undefined, 选填
/>)

KpsIcons

kps 2.0 新图标svg icon,使用方式与antd icon相同

图标与与图片对应关系见...shotgun.../page/57145