Files
vue-cli/packages/@vue/cli-ui/apollo-server/connectors/widgets.js
Guillaume Chau a09407dd5b feat(ui): Redesign, dashboard, local plugins (#2806)
* feat: basic fonctionality, welcome and kill port widgets

* fix: contrast improvements

* feat: plugin/dep/vulnerability widgets design

* fix: widget add/remove animation

* feat: run task widget

* feat: news + wip resizing

* feat: nuxt

* chore: removed widget example

* fix: visual polish for widget transform

* feat(widget): overlap detection

* fix: news default/max size

* feat(dashboard): sidepane transition

* chore: dev api server port

* fix(widget): configure tooltip

* refactor(widget): generic Movable mixin

* refactor(widget): resizable mixin

* feat(widget): resize transition

* feat(widget): resize improvements

* refactor(widget): zoom factor

* refactor(widget): OnGrid mixin

* refactor(widget): resize handler style moved to global

* chore: remove console.log

* refactor: files structure

* feat: improved design and layout

* fix: content background vars

* fix: status bar / view nav z-indexes

* fix: webpack dashboard grid gap

* feat(news feed): handle errors

* fix(card): dimmed box shadow

* fix: view nav & status bar z-index

* fix: remove (wip)

* feat(widget): style tweaks

* feat(widget): details pane (wip)

* feat: news feed widget improvements

* feat(widget): custom header button

* feat(news): item details pane

* feat(widget): custom title

* fix(news): better cache and misc fixes

* feat(widget): resize left and top handles

* feat(widget): transparent widget while moving/resizing

* feat(news): better "big size" style

* fix(news): media sizes in rich content

* feat(plugin): local plugins support

* fix: scrolling issue in Fx

* fix: colors

* fix(nav bar): more item overflowing

* feat(vuln): frontend

* chore: locale update

* fix: image in suggestion dropdown (dev)

* fix(suggestion): missing custom image

* feat(view): user default plugin logo if no provided icon

* feat(view): better loading UX

* feat(view): button background if view is selected

* feat(view): new nav indicator

* feat(widget): use plugin logo as default icon

* feat(widget): better widget modal

* feat(widget): longDescription

* fix(widget): news validate url param

* feat(widget): filter widgets in add pane

* feat(widget): tease upcoming widgets

* chore: fix merge dev

* chore: yarn install

* chore: sync versions

* chore: update apollo

* docs: widget

* fix(progress): graphql error

* fix(deps): localPath

* perf(plugin): faster local plugin refresh

* fix(nav): center active indicator

* feat(task): improved header

* feat(client addon): custom component load timeout message

* feat(suggestion): ping animation to improve discoverability

* chore: update vue-apollo

* feat(api): requestRoute

* fix(suggestion): hide more info link if no link

* fix(style): ul padding

* test(e2e): fix plugin path

* chore: change test scripts

* chore(deps): upgrade

* fix: build error

* fix(widget): removed moving scale transform

* fix(widget): resize handles style

* chore(deps): unpin apollo-utilities

* chore(deps): lock fix

* test(e2e): fix server

* fix: issue with writeQuery

See: https://github.com/apollographql/apollo-client/issues/4031#issuecomment-433668473

* test(e2e): fix tests

* test(e2e): missing widgets build

* fix: missing widgets dep
2018-10-28 04:10:34 +01:00

335 lines
7.5 KiB
JavaScript

const shortid = require('shortid')
// Connectors
const cwd = require('./cwd')
const prompts = require('./prompts')
// Utils
const { log } = require('../util/logger')
function getDefaultWidgets () {
return [
{
id: shortid(),
definitionId: 'org.vue.widgets.welcome',
x: 0,
y: 0,
width: 3,
height: 4,
configured: true,
config: null
},
{
id: shortid(),
definitionId: 'org.vue.widgets.kill-port',
x: 3,
y: 0,
width: 2,
height: 1,
configured: true,
config: null
}
]
}
let widgetDefs = new Map()
let widgetCount = new Map()
let widgets = []
let currentWidget
let loadPromise, loadResolve
function reset (context) {
widgetDefs = new Map()
widgetCount = new Map()
widgets = []
loadPromise = new Promise((resolve) => {
loadResolve = () => {
loadPromise = null
resolve()
log('Load Promise resolved')
}
})
}
async function registerDefinition ({ definition, project }, context) {
definition.hasConfigPrompts = !!definition.onConfigOpen
// Default icon
if (!definition.icon) {
const plugins = require('./plugins')
const plugin = plugins.findOne({ id: definition.pluginId, file: cwd.get() }, context)
const logo = await plugins.getLogo(plugin, context)
if (logo) {
definition.icon = `${logo}?project=${project.id}`
}
}
widgetDefs.set(definition.id, definition)
}
function listDefinitions (context) {
return widgetDefs.values()
}
function findDefinition ({ definitionId }, context) {
const def = widgetDefs.get(definitionId)
if (!def) {
throw new Error(`Widget definition ${definitionId} not found`)
}
return def
}
async function list (context) {
log('loadPromise', loadPromise)
if (loadPromise) {
await loadPromise
}
log('widgets', widgets)
return widgets
}
function load (context) {
const projects = require('./projects')
const id = projects.getCurrent(context).id
const project = context.db.get('projects').find({ id }).value()
widgets = project.widgets
if (!widgets) {
widgets = getDefaultWidgets()
}
widgets.forEach(widget => {
updateCount(widget.definitionId, 1)
})
log('Widgets loaded', widgets.length)
loadResolve()
}
function save (context) {
const projects = require('./projects')
const id = projects.getCurrent(context).id
context.db.get('projects').find({ id }).assign({
widgets
}).write()
}
function canAddMore (definition, context) {
if (definition.maxCount) {
return getCount(definition.id) < definition.maxCount
}
return true
}
function add ({ definitionId }, context) {
const definition = findDefinition({ definitionId }, context)
const { x, y, width, height } = findValidPosition(definition)
const widget = {
id: shortid(),
definitionId,
x,
y,
width,
height,
config: null,
configured: !definition.needsUserConfig
}
// Default config
if (definition.defaultConfig) {
widget.config = definition.defaultConfig({
definition
})
}
updateCount(definitionId, 1)
widgets.push(widget)
save(context)
if (definition.onAdded) {
definition.onAdded({ widget, definition })
}
return widget
}
function getCount (definitionId) {
if (widgetCount.has(definitionId)) {
return widgetCount.get(definitionId)
} else {
return 0
}
}
function updateCount (definitionId, mod) {
widgetCount.set(definitionId, getCount(definitionId) + mod)
}
function findValidPosition (definition, currentWidget = null) {
// Find next available space
const width = (currentWidget && currentWidget.width) || definition.defaultWidth || definition.minWidth
const height = (currentWidget && currentWidget.height) || definition.defaultHeight || definition.minHeight
// Mark occupied positions on the grid
const grid = new Map()
for (const widget of widgets) {
if (widget !== currentWidget) {
for (let x = widget.x; x < widget.x + widget.width; x++) {
for (let y = widget.y; y < widget.y + widget.height; y++) {
grid.set(`${x}:${y}`, true)
}
}
}
}
// Go through the possible positions
let x = 0
let y = 0
while (true) {
// Virtual "line brak"
if (x !== 0 && x + width >= 7) {
x = 0
y++
}
const { result, testX } = hasEnoughSpace(grid, x, y, width, height)
if (!result) {
x = testX + 1
continue
}
// Found! :)
break
}
return {
x,
y,
width,
height
}
}
function hasEnoughSpace (grid, x, y, width, height) {
// Test if enough horizontal available space
for (let testX = x; testX < x + width; testX++) {
// Test if enough vertical available space
for (let testY = y; testY < y + height; testY++) {
if (grid.get(`${testX}:${testY}`)) {
return { result: false, testX }
}
}
}
return { result: true }
}
function findById ({ id }, context) {
return widgets.find(w => w.id === id)
}
function remove ({ id }, context) {
const index = widgets.findIndex(w => w.id === id)
if (index !== -1) {
const widget = widgets[index]
updateCount(widget.definitionId, -1)
widgets.splice(index, 1)
save(context)
const definition = findDefinition(widget, context)
if (definition.onAdded) {
definition.onAdded({ widget, definition })
}
return widget
}
}
function move (input, context) {
const widget = findById(input, context)
if (widget) {
widget.x = input.x
widget.y = input.y
const definition = findDefinition(widget, context)
widget.width = input.width
widget.height = input.height
if (widget.width < definition.minWidth) widget.width = definition.minWidth
if (widget.width > definition.maxWidth) widget.width = definition.maxWidth
if (widget.height < definition.minHeight) widget.height = definition.minHeight
if (widget.height > definition.maxHeight) widget.height = definition.maxHeight
for (const otherWidget of widgets) {
if (otherWidget !== widget) {
if (areOverlapping(otherWidget, widget)) {
const otherDefinition = findDefinition(otherWidget, context)
Object.assign(otherWidget, findValidPosition(otherDefinition, otherWidget))
}
}
}
save(context)
}
return widgets
}
function areOverlapping (widgetA, widgetB) {
return (
widgetA.x + widgetA.width - 1 >= widgetB.x &&
widgetA.x <= widgetB.x + widgetB.width - 1 &&
widgetA.y + widgetA.height - 1 >= widgetB.y &&
widgetA.y <= widgetB.y + widgetB.height - 1
)
}
async function openConfig ({ id }, context) {
const widget = findById({ id }, context)
const definition = findDefinition(widget, context)
if (definition.onConfigOpen) {
const result = await definition.onConfigOpen({
widget,
definition,
context
})
await prompts.reset(widget.config || {})
result.prompts.forEach(prompts.add)
await prompts.start()
currentWidget = widget
}
return widget
}
function getConfigPrompts ({ id }, context) {
return currentWidget && currentWidget.id === id ? prompts.list() : []
}
function saveConfig ({ id }, context) {
const widget = findById({ id }, context)
widget.config = prompts.getAnswers()
widget.configured = true
save(context)
currentWidget = null
return widget
}
function resetConfig ({ id }, context) {
// const widget = findById({ id }, context)
// TODO
save(context)
}
module.exports = {
reset,
registerDefinition,
listDefinitions,
findDefinition,
list,
load,
save,
canAddMore,
getCount,
add,
remove,
move,
openConfig,
getConfigPrompts,
saveConfig,
resetConfig
}