Gatsby: How Do I Make a List of All Nodes? - graphql

I want to make a list of all unique node values and render them on my page. This is what I have:
import { graphql, Link } from "gatsby"
import React from "react"
import Layout from "../layouts/Layout"
const _ = require("lodash")
export default props => {
const majorNodes = props.item.edges.node
let majors = []
let major = []
majorNodes.map(majorNode => majors.push(majorNode.major))
majors = majors.concat(major)
majors = _.uniq(majors)
return (
<Layout>
<div>{majors}</div>
</Layout>
)
}
export const query = graphql`
query majors {
item: allContentfulPortfolio {
edges {
node {
major
}
}
}
}
`
However, this gives me the following error:
TypeError: Cannot read property 'edges' of undefined
_default
D:/Gatsby/archives/src/pages/majors.js:7
4 | const _ = require("lodash")
5 |
6 | export default props => {
> 7 | const majorNodes = props.item.edges.node
8 | let majors = []
9 | let major = []
10 | majorNodes.map(majorNode => majors.push(majorNode.major))
How do I fix this? Additionally, is this how I should pass the array majors for rendering? I think that is where I am going wrong.

Your nodes are stored inside props.data so:
const majorNodes = props.item.edges.node
Should become:
const majorNodes = props.data.item.edges.node
Additionally, is this how I should pass the array majors for
rendering? I think that is where I am going wrong.
majors is an array so printing it directly as:
<div>{majors}</div>
Won't work.
You should loop through each element with something like:
<div>{majors.map(major => <p> Major element is {major}</p> )}</div>

Related

Ckeditor5 error with undoing multiline paste operation

I have a plugin for my ckeditor build which should convert pasted content with formulas,
separated by '(' ')', '$$' etc. into math-formulas from ckeditor5-math (https://github.com/isaul32/ckeditor5-math). I changed the AutoMath Plugin so that it supports text with the separators.
I have run into a problem where undoing (ctrl-z) the operation works fine for single-line content, but not for multiline content.
To reproduce the issue, I have built a similar plugin which does not require the math plugin. This plugin converts text enclosed by '&' to bold text.
To reproduce this issue with an editor instance it is required to have the cursor inside a word (not after or before the end of the text, I don't know why that doesn't work, if you know why, help is appreciated^^) and paste it from the clipboard. The content will inside the '&' will be marked bold, however if you undo this operation twice, an model-position-path-incorrect-format error will be thrown.
example to paste:
aa &bb& cc
dd
ee &ff& gg
Undoing the operation twice results in this error:
Uncaught CKEditorError: model-position-path-incorrect-format {"path":[]}
Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-model-position-path-incorrect-form
Unfortunately, I haven't found a way to fix this issue, and have not found a similar issue.
I know it has to do with the batches that are operated, and that maybe the position parent has to do something with it, that I should cache the position of the parent. However, I do not know how.
Below my code for an example to reproduce:
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import Undo from '#ckeditor/ckeditor5-undo/src/undo';
import LiveRange from '#ckeditor/ckeditor5-engine/src/model/liverange';
import LivePosition from '#ckeditor/ckeditor5-engine/src/model/liveposition';
import global from '#ckeditor/ckeditor5-utils/src/dom/global';
export default class Test extends Plugin {
static get requires() {
return [Undo];
}
static get pluginName() {
return 'Test';
}
constructor(editor) {
super(editor);
this._timeoutId = null;
this._positionToInsert = null;
}
init() {
const editor = this.editor;
const modelDocument = editor.model.document;
const view = editor.editing.view;
//change < Clipboard > to < 'ClipboardPipeline' > because in version upgrade from 26 to 27
//the usage of this call changed
this.listenTo(editor.plugins.get('ClipboardPipeline'), 'inputTransformation', (evt, data) => {
const firstRange = modelDocument.selection.getFirstRange();
const leftLivePosition = LivePosition.fromPosition(firstRange.start);
leftLivePosition.stickiness = 'toPrevious';
const rightLivePosition = LivePosition.fromPosition(firstRange.end);
rightLivePosition.stickiness = 'toNext';
modelDocument.once('change:data', () => {
this._boldBetweenPositions(leftLivePosition, rightLivePosition);
leftLivePosition.detach();
rightLivePosition.detach();
}, {priority: 'high'});
});
editor.commands.get('undo').on('execute', () => {
if (this._timeoutId) {
global.window.clearTimeout(this._timeoutId);
this._timeoutId = null;
}
}, {priority: 'high'});
}
_boldBetweenPositions(leftPosition, rightPosition) {
const editor = this.editor;
const equationRange = new LiveRange(leftPosition, rightPosition);
// With timeout user can undo conversation if wants to use plain text
this._timeoutId = global.window.setTimeout(() => {
this._timeoutId = null;
let walker = equationRange.getWalker({ignoreElementEnd: true});
let nodeArray = [];
for (const node of walker) { // remember nodes, because when they are changed model-textproxy-wrong-length error occurs
nodeArray.push(node);
}
editor.model.change(writer => {
for (let node of nodeArray) {
let text = node.item.data;
if (node.item.is('$textProxy') && text !== undefined && text.match(/&/g)) {
let finishedFormulas = this._split(text);
const realRange = writer.createRange(node.previousPosition, node.nextPosition);
writer.remove(realRange);
for (let i = finishedFormulas.length - 1; i >= 0; i--) {
if (i % 2 === 0) {
writer.insertText(finishedFormulas[i], node.previousPosition);
} else {
writer.insertText(finishedFormulas[i], {bold: true}, node.previousPosition);
}
}
}
}
});
}, 100);
}
_split(text) {
let mathFormsAndText = text.split(/(&)/g);
let mathTextArray = [];
for (let i = 0; i < mathFormsAndText.length; i++) {
if (i % 4 === 0) {
mathTextArray.push(mathFormsAndText[i]);
} else if (i % 2 === 0) {
mathTextArray.push(mathFormsAndText[i]);
}
}
return mathTextArray;
}
}
Let me know if I can clarify anything.

Bookmarklet - Multiple lines of input

I am trying to create a simple bookmarklet that creates a little textbox that can be created, written in, and then closed.
Thank you ahead of time.
See a live example here
(() => {
const d = document.createElement('div')
d.style = "position:fixed;top:0px;left:0px;padding:5px;background-color:red;"
const e = document.createElement('textarea')
const b = document.createElement('button')
b.innerText = "Close"
b.onclick = () => {
document.body.removeChild(d)
}
d.appendChild(e)
d.appendChild(b)
document.body.appendChild(d)
})();

Types for observable

I would like to have an type for my observable (just for learning purposes). How can I do it for the follwoing lines of code? I get compiler errors and don't understand them.
type Color = "white" | "green" | "red" | "blue";
type Logo = "fish" | "dog" | "bird" | "cow";
const color$ = new Subject<Color>();
const logo$ = new Subject<Logo>();
const observabl1:<Subject<Color>, Subject<Logo>> = combineLatest([color$, logo$]);
observabl1.subscribe(([color, logo]) =>
console.log(`${color} ${logo}`)
);
and
const observable2$:Observable<string> = from(["white", "green", "red", "blue"]);
Update: I found the error. I imported import { Observable } from 'rx'; Changing it to import { Observable } from 'rxjs'; fixed the problem
I get compiler errors and don't understand them
Kindly provide the errors you want to discuss.
const observabl1:<Subject, Subject> = combineLatest([color$, logo$]);
combineLatest provides a stream with an array of the data.
const observabl1: Observable<[Color, logo]> = combineLatest([color$, logo$]);

Set alternative colors when using the propertyFields on series.columns.template

Im having a problem to set an alternative color for a label on a series column template using the propertyField.
For example. I use this:
let columnTemplate = series.columns.template;
columnTemplate.propertyFields.fill = 'color';
Where 'color' is comming from DataItem, as a data from backend system.
And later on I want to add a label on the column. And I want a contrast so I want to use the alternative color: https://www.amcharts.com/docs/v4/concepts/colors/#Getting_contrasting_color
I make up my label as:
let label = columnTemplate.createChild(am4core.Label);
Then I will set the alternative color from the series.columns.template.
But I dont get it work. I tried for example:
label.fill = series.columns.template.fill.alternative;
Dosent work.
This:
label.fill = am4core.color(series.columns.template.propertyFields.fill).alternative;
Dont work.
This:
label.adapter.add('fill', (value, target, key) => {
const dataContext: { [key: string]: string } | undefined = target.dataItem?.dataContext as {
[key: string]: string;
};
return am4core.color(dataContext?.color).alternative;
});
Throws an error...
Is there anyone who know a solution for this? Please help.
I,m not sure this is a good solution but it works:
export function setContrastColor(value: any, target: any) {
const dataContext = (target.dataItem?.dataContext ?? {}) as Record<string, string | undefined>;
if (dataContext.color) {
return am4core.color(`${dataContext?.color}`).alternative;
} else {
return am4core.color('#FFF');
}
}
And calling it:
label.adapter.add('fill', setContrastColor);

Resetting/Restart Rxjs Stream on form submit

I have an input field which when submitted makes a http call and then plots a graph. When I click on any node of graph, same http call is made and results are appended to the previous results and graph is updated. It is working fine till here. I am using scan operator to update my resultset. Now, what I want is to reset the resultset (ie - return new original response) whenever I am submitting the input form and append to resultset when graph node is clicked. Any ideas on how this can be achieved? Mainly how can I reset this stream on form submit? Or how can I show new data on form submit and updated data on node click
Here linkingDetailsByAccount$ makes the http call and gets the data from the server.
this.linkingDetailsByAccountSubject.next(account);
Same code is called on node click as well as on form submit which then activates my stream.
graph$ = this.linkingDetailsByAccount$.pipe(
pluck('graph'),
scan((linkedDetails, adjacency) => {
const { nodes: linkedNodes = [], edges: linkedEdges = [] } = linkedDetails;
const { nodes: newNodes = [], edges: newEdges = [] } = adjacency;
const updatedNodes = differenceBy(newNodes, linkedNodes, 'id');
const updatedEdges = differenceWith(
newEdges,
linkedEdges,
(newEdge: VisEdge, existingEdge: VisEdge) => newEdge.from === existingEdge.to
);
const allNodes = [...linkedNodes, ...updatedNodes];
const allEdges = [...linkedEdges, ...updatedEdges];
return {
nodes: allNodes,
edges: allEdges
};
}, {} as NodesEdges)
);
Appreciate any inputs on this.
Thanks,
Vatsal
Edit: Updated answer when I received more details from OP.
How I would do it is turn it into a mini Redux like state manager.
So the scan operator should take in functions or event objects.
First you want to store the first initial state from the initial HTTP call you make. You will use this object to reset your state on form submission.
Then create a graphEvents subject.
interface UpdateGraphEvent {
type: 'Update';
account: any;
}
interface ResetGraphEvent {
type: 'Reset';
account: any;
}
type GraphEvent = UpdateGraphEvent | ResetGraphEvent;
this.graphEvents$ = new Subject<GraphEvent>();
Then you can use your new graphEvents$ subject to replace uses of linkingDetailsByAccountSubject.
// When you want to update with new data.
this.graphEvent$.next({type: 'Update', account: account});
// when you want to reset with initial data.
this.graphEvent$.next({type: 'Reset', account: this.initialAccount});
Then use it in your stream.
graph$ = this.graphEvent$.pipe(
pluck('graph'),
scan((linkedDetails, event: GraphEvent) => {
if (event.type === 'Reset') {
return {
nodes: event.account.nodes,
edges: event.account.edges,
}
}
const { nodes: linkedNodes = [], edges: linkedEdges = [] } = linkedDetails;
const { nodes: newNodes = [], edges: newEdges = [] } = event.account;
const updatedNodes = differenceBy(newNodes, linkedNodes, 'id');
const updatedEdges = differenceWith(
newEdges,
linkedEdges,
(newEdge: VisEdge, existingEdge: VisEdge) => newEdge.from === existingEdge.to
);
const allNodes = [...linkedNodes, ...updatedNodes];
const allEdges = [...linkedEdges, ...updatedEdges];
return {
nodes: allNodes,
edges: allEdges
};
}, {} as NodesEdges)
);
The graphEvent$ will be a Subject that emits those events (GraphEvent).

Resources