...
This commit is contained in:
parent
1e22d28754
commit
6b926d3afe
126 changed files with 128562 additions and 104297 deletions
|
|
@ -0,0 +1,3 @@
|
|||
#godot-editor
|
||||
Right upper corner in Menu selector window
|
||||
![[Pasted image 20240204164006.png]]
|
||||
34408
MyObsidianVault/Knowledgebase/Godot/2D/Godot Custom Ressources.md
Normal file
34408
MyObsidianVault/Knowledgebase/Godot/2D/Godot Custom Ressources.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,16 @@
|
|||
Sprite2D node
|
||||
|
||||
F => focus view on object
|
||||
|
||||
To get type information in code auto-completion, go to _Editor -> Editor Settings_, and in the _Text Editor -> Completion_ section, turn on the checkbox named _Add Type Hints_. You can also search for "type hints" in the search bar at the top of the window to find it quickly.
|
||||
|
||||
|
||||
`Vector2` values have a function named `angle()` that calculates and returns the vector's angle in radians. If we assign that to the ship's rotation, it will turn to face its move direction.
|
||||
|
||||
```gdscript
|
||||
var velocity = Vector2(480, -480)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
position += velocity * delta
|
||||
rotation = velocity.angle()
|
||||
````
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/to_space_and_beyond/taking_control
|
||||
|
||||
```gdscript
|
||||
extends Sprite2D
|
||||
var direction := Vector2(0,0)
|
||||
var max_speed := 600.0
|
||||
var velocity = Vector2(0, 0)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
||||
direction.x = Input.get_axis("move_left", "move_right")
|
||||
direction.y = Input.get_axis("move_up", "move_down")
|
||||
|
||||
if direction.length() > 1.0:
|
||||
direction = direction.normalized()
|
||||
|
||||
velocity = direction * max_speed
|
||||
position += velocity * delta
|
||||
if direction.length() > 0.0:
|
||||
rotation = velocity.angle()
|
||||
```
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
```python
|
||||
func _process(delta: float) -> void:
|
||||
var direction := Vector2(0, 0)
|
||||
direction.x = Input.get_axis("move_left", "move_right")
|
||||
direction.y = Input.get_axis("move_up", "move_down")
|
||||
|
||||
if direction.length() > 1.0:
|
||||
direction = direction.normalized()
|
||||
|
||||
if Input.is_action_just_pressed("boost"):
|
||||
max_speed = boost_speed
|
||||
get_node("Timer").start()
|
||||
|
||||
velocity = direction * max_speed
|
||||
position += velocity * delta
|
||||
|
||||
if direction.length() > 0.0:
|
||||
rotation = velocity.angle()
|
||||
```
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
```python
|
||||
extends Sprite2D
|
||||
var direction := Vector2(0,0)
|
||||
var max_speed := 300.0
|
||||
var velocity = Vector2(0, 0)
|
||||
var boost_speed := 1500.0
|
||||
var normal_speed := 300.0
|
||||
var desired_velocity := Vector2()
|
||||
var steering_factor := 10
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var direction := Vector2(0, 0)
|
||||
direction.x = Input.get_axis("move_left", "move_right")
|
||||
direction.y = Input.get_axis("move_up", "move_down")
|
||||
|
||||
if direction.length() > 1.0:
|
||||
direction = direction.normalized()
|
||||
|
||||
if Input.is_action_just_pressed("boost"):
|
||||
max_speed = boost_speed
|
||||
get_node("Timer").start()
|
||||
|
||||
desired_velocity = max_speed * direction
|
||||
var steering = desired_velocity - velocity
|
||||
|
||||
velocity += steering * steering_factor * delta
|
||||
position += velocity * delta
|
||||
|
||||
if direction.length() > 0.0:
|
||||
rotation = velocity.angle()
|
||||
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
max_speed = normal_speed
|
||||
```
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
```python
|
||||
extends Area2D
|
||||
|
||||
func _ready() -> void:
|
||||
area_entered.connect(_on_area_entered)
|
||||
|
||||
func _on_area_entered(area_that_entred: Area2D) -> void:
|
||||
queue_free()
|
||||
```
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
````python
|
||||
extends Area2D
|
||||
|
||||
var max_speed := 1200.0
|
||||
var velocity := Vector2(0, 0)
|
||||
var steering_factor := 3.0
|
||||
|
||||
var health := 10
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
area_entered.connect(_on_area_entered)
|
||||
# This call updates the health bar to match the health variable when the
|
||||
# game starts.
|
||||
set_health(health)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var direction := Vector2(0, 0)
|
||||
direction.x = Input.get_axis("move_left", "move_right")
|
||||
direction.y = Input.get_axis("move_up", "move_down")
|
||||
|
||||
if direction.length() > 1.0:
|
||||
direction = direction.normalized()
|
||||
|
||||
var desired_velocity := max_speed * direction
|
||||
var steering := desired_velocity - velocity
|
||||
velocity += steering * steering_factor * delta
|
||||
position += velocity * delta
|
||||
|
||||
if velocity.length() > 0.0:
|
||||
get_node("Sprite2D").rotation = velocity.angle()
|
||||
|
||||
|
||||
func set_health(new_health: int) -> void:
|
||||
health = new_health
|
||||
get_node("UI/HealthBar").value = health
|
||||
|
||||
|
||||
func _on_area_entered(area_that_entered: Area2D) -> void:
|
||||
set_health(health + 10)
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/telling_a_story/overview
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
```python
|
||||
var inventory: Array[Dictionary] = [
|
||||
{
|
||||
"name": "sword",
|
||||
"amount": 1,
|
||||
},
|
||||
{
|
||||
"name": "potion",
|
||||
"amount": 5,
|
||||
},
|
||||
{
|
||||
"name": "potion",
|
||||
"amount": 3,
|
||||
},
|
||||
{
|
||||
"name": "coins",
|
||||
"amount": 65,
|
||||
},
|
||||
]
|
||||
```
|
||||
[]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```python
|
||||
var expressions := {
|
||||
"happy" : preload("res://assets/emotion_happy.png"),
|
||||
"regular" : preload("res://assets/emotion_regular.png"),
|
||||
"sad" : preload("res://assets/emotion_sad.png"),
|
||||
}
|
||||
|
||||
var dialogue_items: Array[Dictionary]= [
|
||||
{
|
||||
"expression": expressions["regular"],
|
||||
"text": "I'm learning about Arrays..."
|
||||
},
|
||||
{
|
||||
"expression": expressions["sad"],
|
||||
"text": "... and it is a little bit complicated."
|
||||
},
|
||||
{
|
||||
"expression": expressions["happy"],
|
||||
"text": "Let's see if I got it right: an array is a list of values!",
|
||||
},
|
||||
{
|
||||
"expression": expressions["regular"],
|
||||
"text": "Did I get it right? Did I?",
|
||||
},
|
||||
{
|
||||
"expression": expressions["happy"],
|
||||
"text": "Hehe! Bye bye~!",
|
||||
},
|
||||
]
|
||||
|
||||
func show_text() -> void:
|
||||
var current_item := dialogue_items[current_item_index]
|
||||
rich_text_label.text = current_item["text"]
|
||||
expression.texture = current_item["expression"]
|
||||
```
|
||||
|
|
@ -0,0 +1 @@
|
|||
A UI nodes' opacity is modified via the modulate property which controls the tinting of the node.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
Tweens are created using a nodes' create_tween() method
|
||||
|
||||
|
||||
```python
|
||||
func slide_in() -> void:
|
||||
var tween := create_tween()
|
||||
tween.set_trans(Tween.TRANS_QUART)
|
||||
tween.set_ease(Tween.EASE_OUT)
|
||||
````
|
||||
|
||||
Running tweens in parallel via parallel method.
|
||||
Modulate controls tinting, subproperty a is for alpha, meaning a target visibilty of 100%
|
||||
```python
|
||||
tween.parallel().tween_property(body, "modulate:a", 1.0, 0.2)
|
||||
```
|
||||
|
||||
|
||||
Full tween method:
|
||||
```python
|
||||
func slide_in() -> void:
|
||||
var tween = create_tween()
|
||||
tween.set_trans(Tween.TRANS_QUART)
|
||||
tween.set_ease(Tween.EASE_OUT)
|
||||
body.position.x = 200.0
|
||||
tween.tween_property(body, "position:x", 0.0, 1.0) # Move x from 200 to 0
|
||||
body.modulate.a = 0.0 # UI node visibility = 0
|
||||
tween.parallel().tween_property(body, "modulate:a", 1.0, 1.0) # Animate visibility towards 100% (a=alpha)
|
||||
```
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
Nothing new here, just replacement of TextureRect images
|
||||
|
||||
```python
|
||||
extends Control
|
||||
|
||||
|
||||
var expressions := {
|
||||
"happy": preload ("res://assets/emotion_happy.png"),
|
||||
"regular": preload ("res://assets/emotion_regular.png"),
|
||||
"sad": preload ("res://assets/emotion_sad.png"),
|
||||
}
|
||||
|
||||
var bodies := {
|
||||
"sophia": preload ("res://assets/sophia.png"),
|
||||
"pink": preload ("res://assets/pink.png")
|
||||
}
|
||||
|
||||
|
||||
|
||||
## Data to display in the dialogue. Each dictionary in this array has three properties:
|
||||
##
|
||||
## - expression: a [code]Texture[/code] containing an expression
|
||||
## - text: a [code]String[/code] containing the text the character says
|
||||
## - character: a [code]Texture[/code] representing the character
|
||||
|
||||
var dialogue_items: Array[Dictionary] = [
|
||||
{
|
||||
"expression": expressions["regular"],
|
||||
"text": "I've been studying arrays and dictionaries lately.",
|
||||
"character": bodies["sophia"],
|
||||
},
|
||||
{
|
||||
"expression": expressions["regular"],
|
||||
"text": "Oh, nice. How has it been going?",
|
||||
"character": bodies["pink"],
|
||||
},
|
||||
{
|
||||
"expression": expressions["sad"],
|
||||
"text": "Well... it's a little complicated!",
|
||||
"character": bodies["sophia"],
|
||||
},
|
||||
{
|
||||
"expression": expressions["sad"],
|
||||
"text": "Oh!",
|
||||
"character": bodies["pink"],
|
||||
},
|
||||
{
|
||||
"expression": expressions["regular"],
|
||||
"text": "It sure takes time to click at first.",
|
||||
"character": bodies["pink"],
|
||||
},
|
||||
{
|
||||
"expression": expressions["happy"],
|
||||
"text": "If you keep at it, eventually, you'll get the hang of it!",
|
||||
"character": bodies["pink"],
|
||||
},
|
||||
{
|
||||
"expression": expressions["regular"],
|
||||
"text": "Mhhh... I see. I'll keep at it, then.",
|
||||
"character": bodies["sophia"],
|
||||
},
|
||||
{
|
||||
"expression": expressions["happy"],
|
||||
"text": "Thanks for the encouragement. Time to LEARN!!!",
|
||||
"character": bodies["sophia"],
|
||||
},
|
||||
]
|
||||
|
||||
var current_item_index := 0
|
||||
|
||||
@onready var rich_text_label: RichTextLabel = %RichTextLabel
|
||||
@onready var next_button: Button = %NextButton
|
||||
@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer
|
||||
@onready var body: TextureRect = %Body
|
||||
@onready var expression_texture_rect: TextureRect = %Expression
|
||||
|
||||
func _ready() -> void:
|
||||
show_text()
|
||||
next_button.pressed.connect(advance)
|
||||
|
||||
|
||||
func show_text() -> void:
|
||||
# We first extract data for the current dialogue item and apply it to the UI
|
||||
# elements.
|
||||
var current_item := dialogue_items[current_item_index]
|
||||
rich_text_label.text = current_item["text"]
|
||||
expression_texture_rect.texture = current_item["expression"]
|
||||
body.texture = current_item["character"]
|
||||
|
||||
# We then hide the text and animate it appearing.
|
||||
rich_text_label.visible_ratio = 0.0
|
||||
var tween := create_tween()
|
||||
var text_appearing_duration: float = current_item["text"].length() / 30.0
|
||||
tween.tween_property(rich_text_label, "visible_ratio", 1.0, text_appearing_duration)
|
||||
|
||||
# We randomize the audio playback's start time to make it sound different
|
||||
# every time and play the sound until the text finishes displaying.
|
||||
var sound_max_length := audio_stream_player.stream.get_length() - text_appearing_duration
|
||||
var sound_start_position := randf() * sound_max_length
|
||||
audio_stream_player.play(sound_start_position)
|
||||
tween.finished.connect(audio_stream_player.stop)
|
||||
|
||||
# We animate the character sliding in.
|
||||
slide_in()
|
||||
|
||||
|
||||
## Advances the dialogue to the next item or quits the game if there are no more
|
||||
## items.
|
||||
func advance() -> void:
|
||||
current_item_index += 1
|
||||
if current_item_index == dialogue_items.size():
|
||||
get_tree().quit()
|
||||
else:
|
||||
show_text()
|
||||
|
||||
|
||||
|
||||
|
||||
## Animates the character sliding in.
|
||||
func slide_in() -> void:
|
||||
var slide_tween := create_tween()
|
||||
slide_tween.set_ease(Tween.EASE_OUT)
|
||||
body.position.x = get_viewport_rect().size.x / 7
|
||||
slide_tween.tween_property(body, "position:x", 0, 0.3)
|
||||
body.modulate.a = 0
|
||||
slide_tween.parallel().tween_property(body, "modulate:a", 1, 0.2
|
||||
```
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
ProjectProject Settings...Input Map
|
||||
Define action + button there
|
||||
![[Pasted image 20240713073756.png]]
|
||||
|
||||
Next, select a button, and open Shortcut section
|
||||
![[Pasted image 20240713073933.png]]
|
||||
|
||||
Click the <empty> Shortcut field, then select New Shortcut
|
||||
|
||||
|
||||
The `Shortcut` resource takes an array of inputs as its data because in case you'd want to trigger the same button with multiple different inputs or input actions.
|
||||
sadfs
|
||||
![[Pasted image 20240713074212.png]]
|
||||
|
||||
|
||||
|
||||
![[Pasted image 20240713074345.png]]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Click on the created InputEventAction to expand it.
|
||||
![[Pasted image 20240713080012.png]]
|
||||
Set action to advance_text ( The action we created in the very first screenshot of this page)
|
||||
|
||||
![[Pasted image 20240713080125.png]]
|
||||
|
||||
Now all input defined for advance_text action will trigger the button.
|
||||
|
||||
|
||||
|
||||
Disabling button until tween finished
|
||||
```python
|
||||
func show_text() -> void:
|
||||
# We first extract data for the current dialogue item and apply it to the UI
|
||||
# elements.
|
||||
# ...
|
||||
next_button.disabled = true
|
||||
tween.finished.connect(func() -> void:
|
||||
next_button.disabled = false
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Note to self: It seems to be possible to connect an event to more than 1 function. Here the full function from the tutorial. Note the tween.connect re-use
|
||||
|
||||
```python
|
||||
func show_text() -> void:
|
||||
# We first extract data for the current dialogue item and apply it to the UI
|
||||
# elements.
|
||||
var current_item := dialogue_items[current_item_index]
|
||||
rich_text_label.text = current_item["text"]
|
||||
expression_texture_rect.texture = current_item["expression"]
|
||||
body.texture = current_item["character"]
|
||||
|
||||
# We then hide the text and animate it appearing.
|
||||
rich_text_label.visible_ratio = 0.0
|
||||
var tween := create_tween()
|
||||
var text_appearing_duration: float = current_item["text"].length() / 30.0
|
||||
tween.tween_property(rich_text_label, "visible_ratio", 1.0, text_appearing_duration)
|
||||
|
||||
# We randomize the audio playback's start time to make it sound different
|
||||
# every time and play the sound until the text finishes displaying.
|
||||
var sound_max_length := audio_stream_player.stream.get_length() - text_appearing_duration
|
||||
var sound_start_position := randf() * sound_max_length
|
||||
audio_stream_player.play(sound_start_position)
|
||||
tween.finished.connect(audio_stream_player.stop)
|
||||
|
||||
# We animate the character sliding in.
|
||||
slide_in()
|
||||
|
||||
# Finally, we disable the next button until the text finishes displaying.
|
||||
next_button.disabled = true
|
||||
tween.finished.connect(func() -> void:
|
||||
next_button.disabled = false
|
||||
)
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
```bbcode
|
||||
BBCode is a simple [b]markup language[/b] that is [i]supposed[/i] to be simpler than [b]HTML[/b].
|
||||
```
|
||||
|
||||
Examples
|
||||
```bbcode
|
||||
|
||||
[b]bold[/b]
|
||||
[i]italics[/i]
|
||||
[u]underline[/u]
|
||||
[s]strikethrough[/s]
|
||||
[url=https://godotengine.org]link[/url]
|
||||
|
||||
[shake]This text shakes[/shake] makes the text shake.
|
||||
[wave]This text is wavy[/wave] makes the text animate in a wave pattern.
|
||||
[tornado freq=1.0]This text is even more wavy[/tornado] makes the text rotate like leaves. The freq parameter controls the speed of the rotation.
|
||||
[rainbow val=0.8]This text is colored[/rainbow] tints the text and animates it using rainbow colors. The val parameter controls the saturation, that is to say, how intense the colors are.
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Works only with the `RichTextLabel` node, and needs to be activated for the node (performance reasons)
|
||||
|
||||
![[Pasted image 20240719083713.png]]
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
`HBoxContainer`
|
||||
`VBoxContainer`
|
||||
`GridContainer`
|
||||
|
||||
`Control`
|
||||
Anchors define corner position of a control node
|
||||
|
||||
TextureRect => Like Sprite2D, but for UI
|
||||
You can drag an asset file on top of the TextureRect
|
||||
![[Pasted image 20240615074811.png]]
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/telling_a_story/first_ui_exploration
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
RichtextLabel
|
||||
TextureRect
|
||||
Button
|
||||
ColorRect
|
||||
AudioStreamPlayer
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
![[Pasted image 20240623095140.png]]![[Pasted image 20240623095146.png]]
|
||||
![[Pasted image 20240623095156.png]]
|
||||
.tres is text-based, recommnended and git-friendly
|
||||
|
||||
Theme Editor Overview: https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/telling_a_story/all_theme_editor_areas
|
||||
|
||||
Beware of not working copy/paste in theme styles. Workaround is Make unique and saving / loding in new component
|
||||
|
||||
1.) Create Theme Resource
|
||||
2.) Modifiy
|
||||
3.) Assign to uppermost UI Control Node
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/telling_a_story/your_first_words
|
||||
|
||||
Scene Unique Nodes
|
||||
|
||||
`To mark a node as unique, right-click it in the _Scene_ dock and select _Access As Unique Name_ in the dropdown menu.`
|
||||
|
||||
![[Pasted image 20240625081421.png]]
|
||||
|
||||
Restriction: They can only be addressed from the scene their uniqueness is defined in.
|
||||
|
||||
`There's also an important limitation to this feature: nodes with a scene-unique name only exist in the scene they're marked in. For example, if we mark our `RichTextLabel` in the scene dialogue.tscn as unique, we can't reference it from another scene.
|
||||
|
||||
You also can't mark nodes instantiated at runtime as unique. That's why we learned the more general way of getting nodes first with `get_node()`.`
|
||||
|
||||
|
||||
Script shortcut:
|
||||
1.) Mark unique nodes
|
||||
2.) Drag and hold over script editor
|
||||
3.) Press and hold Ctrl
|
||||
4.) Release and see created initialization code
|
||||
|
||||
![[Pasted image 20240625081825.png]]
|
||||
|
||||
|
||||
|
||||
Quitting the game
|
||||
![[Pasted image 20240625083616.png]]
|
||||
`get_tree().quit()`
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
![[Pasted image 20240626083938.png]]
|
||||
|
||||
`Do you remember how to find the name of the property in code? You can hover over the property in the editor, and a tooltip will show you the code name.`
|
||||
|
||||
````
|
||||
## Draws the current text to the rich text element
|
||||
func show_text() -> void:
|
||||
var current_item := dialogue_items[current_item_index]
|
||||
rich_text_label.text = current_item
|
||||
rich_text_label.visible_ratio = 0.0 # <--rich
|
||||
````
|
||||
|
||||
````
|
||||
func show_text() -> void:
|
||||
# ...
|
||||
var tween := create_tween()
|
||||
var text_appearing_duration := 1.2
|
||||
tween.tween_property(rich_text_label, "visible_ratio", 1.0, text_appearing_duration)
|
||||
````
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
Generic:
|
||||
AudioStream
|
||||
Positional:
|
||||
AudioStream2D
|
||||
AudioStream3D
|
||||
|
||||
`
|
||||
```
|
||||
var text_appearing_duration = 1.2 # value is in seconds
|
||||
var sound_max_length := audio_stream_player.stream.get_length() - text_appearing_duration
|
||||
var sound_start_position := randf() * sound_max_length
|
||||
audio_stream_player.play(sound_start_position)
|
||||
tween.finished.connect(audio_stream_player.stop)
|
||||
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
```
|
||||
extends Control
|
||||
|
||||
## An array of strings. Each string contains the text we want the character to
|
||||
## say.
|
||||
var dialogue_items: Array[String] = [
|
||||
"I'm learning about Arrays...",
|
||||
"...and it is a little bit complicated.",
|
||||
"Let's see if I got it right: an array is a list of values!",
|
||||
"Did I get it right? Did I?",
|
||||
"Hehe! Bye bye~!",
|
||||
]
|
||||
## Holds the index of the currently displayed text
|
||||
var current_item_index := 0
|
||||
|
||||
@onready var rich_text_label: RichTextLabel = %RichTextLabel
|
||||
@onready var next_button: Button = %NextButton
|
||||
@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
show_text()
|
||||
next_button.pressed.connect(advance)
|
||||
|
||||
## Draws the current text to the rich text element
|
||||
func show_text() -> void:
|
||||
var current_item := dialogue_items[current_item_index]
|
||||
rich_text_label.text = current_item
|
||||
# We animate the text appearing letter by letter.
|
||||
rich_text_label.visible_ratio = 0.0
|
||||
var tween := create_tween()
|
||||
var text_appearing_duration := 1.2
|
||||
tween.tween_property(rich_text_label, "visible_ratio", 1.0, text_appearing_duration)
|
||||
# This is where we play the audio. We randomize the audio playback's start
|
||||
# time to make it sound different every time.
|
||||
var sound_max_length := audio_stream_player.stream.get_length() - text_appearing_duration
|
||||
var sound_start_position := randf() * sound_max_length
|
||||
audio_stream_player.play(sound_start_position)
|
||||
# We stop the audio when the text finishes appearing.
|
||||
tween.finished.connect(audio_stream_player.stop)
|
||||
|
||||
func advance() -> void:
|
||||
current_item_index += 1
|
||||
# If we reached the end of the dialogue, we quit the game. Otherwise, we
|
||||
# show the next dialogue line.
|
||||
if current_item_index == dialogue_items.size():
|
||||
get_tree().quit()
|
||||
else:
|
||||
show_text()
|
||||
```
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
1. Arrange UI nodes in rows and columns using `Container` nodes.
|
||||
2. Change the texture of a `TextureRect` node programmatically.
|
||||
3. Map different image textures to different strings representing the expressions using [dictionaries](https://school.gdquest.com/glossary/dictionary).
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
```python
|
||||
button_sophia.pressed.connect(func() -> void:
|
||||
body.texture = bodies["sophia"]
|
||||
)
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
In this lesson, you will write the code to change the expressions of the characters when clicking the buttons.
|
||||
|
||||
Loading image at compile time (advantage of seeing error if file doesnt exist)
|
||||
```python
|
||||
var image := preload("res:://image.png")
|
||||
```
|
||||
|
||||
|
||||
Loading at runtime on demand:
|
||||
```python
|
||||
load("res:://image.png")
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Preloadingloading into a dictionary:
|
||||
```python
|
||||
var bodies := {
|
||||
"sophia": preload("res://assets/sophia.png"),
|
||||
"pink": preload("res://assets/pink.png")
|
||||
}
|
||||
```
|
||||
|
||||
Dont forget to multiselect + drag&drop assets into code editor, then multi-cursor correct using
|
||||
either Alt+Ctrl+Mouseclicks, or Alt+Shift+Cursor Up/Down
|
||||
|
||||
|
||||
*Setting TextureRect texture*
|
||||
```python
|
||||
func _ready() -> void:
|
||||
body.texture = bodies["pink"]
|
||||
expression.texture = expressions["happy"]
|
||||
```
|
||||
|
||||
|
||||
It's not mandatory to use square brackets to access dictionary values. You can also use the `.` operator. For example, writing `bodies["sophia"]` is equivalent to `bodies.sophia`.
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
```python
|
||||
func create_button_pink() -> void:
|
||||
var button := Button.new()
|
||||
row_bodies.add_child(button)
|
||||
```
|
||||
|
||||
|
||||
```python
|
||||
func create_buttons() -> void:
|
||||
for current_body: String in bodies:
|
||||
var button := Button.new()
|
||||
row_bodies.add_child(button)
|
||||
button.text = current_body.capitalize()
|
||||
button.pressed.connect(func()-> void:
|
||||
body.texture = bodies[current_body]
|
||||
)
|
||||
```
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
**Topics**:
|
||||
Control Nodes
|
||||
Label Node
|
||||
AudioStreamPlayer Node
|
||||
Arrays
|
||||
Dictionaries
|
||||
Dictionaries inside Arrays
|
||||
Theming
|
||||
Iterative Design
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
![[Pasted image 20240720081419.png]]
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
The goal: Going from linear dialogue display to integrating player choice
|
||||
|
||||
https://school.gdquest.com/courses/learn_2d_gamedev_godot_4/start_a_dialogue/refactoring_the_dialogue#preparing-to-update-the-dialogue-scene
|
||||
|
||||
Designing the interface to include choice buttons
|
||||
![[Pasted image 20240720075840.png]]
|
||||
|
||||
![[Pasted image 20240720080105.png]]
|
||||
|
||||
|
||||
Sizing examples:
|
||||
![[Pasted image 20240720081104.png]]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
```python
|
||||
extends Control
|
||||
|
||||
@onready var http_request: HTTPRequest = $btn_login/HTTPRequest
|
||||
@onready var te_result: TextEdit = $te_result
|
||||
|
||||
var url = "https://meowfacts.herokuapp.com/"
|
||||
|
||||
func _on_btn_login_button_up() -> void:
|
||||
var headers = ["Content-Type: application/json"]
|
||||
http_request.request(url, headers, HTTPClient.Method.METHOD_GET)
|
||||
|
||||
func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
var data = JSON.parse_string(body.get_string_from_utf8())
|
||||
te_result.text = data.data[0]
|
||||
```
|
||||
|
||||
![[Pasted image 20241222121529.png]]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
```
|
||||
# Find out printer URI
|
||||
torsten@Demogorgon:~$ lpinfo --include-schemes dnssd -v
|
||||
network dnssd://Brother%20HL-L8260CDW%20series._ipp._tcp.local/?uuid=e3248000-80ce-11db-8000-b42200d990c3
|
||||
|
||||
# Publicize
|
||||
lpadmin -p BrotherManualEntry -v dnssd://Brother%20HL-L8260CDW%20series._ipp._tcp.local/?uuid=e3248000-80ce-11db-8000-b42200d990c3
|
||||
|
||||
```
|
||||
|
||||
Find cups using localhost address http://localhost:631/
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
```
|
||||
vim ~/.vimrc
|
||||
```
|
||||
|
||||
```
|
||||
set nocompatible
|
||||
```
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
|
||||
|
||||
Source:
|
||||
|
||||
[Setup CodeIgniter — Devilbox 1.0 documentation](https://devilbox.readthedocs.io/en/latest/examples/setup-codeigniter.html?highlight=CodeIgniter)
|
||||
|
||||
|
||||
Prerequisites: Have correct docker compose installed:
|
||||
```bash
|
||||
sudo apt install docker-compose-v2
|
||||
```
|
||||
|
||||
Download Devilbox
|
||||
|
||||
Remove unwanted hosts from docker-compose.yml file
|
||||
|
||||
copy env file to .env
|
||||
|
||||
Set TLD_SUFFIX=local
|
||||
|
||||
|
||||
In /etc/hosts, add 127.0.01 with application URL:
|
||||
|
||||
127.0.0.1 verleihapp.local
|
||||
|
||||
|
||||
Start docker container: sudo docker compose up -d
|
||||
|
||||
Enter Container: ./shell.sh
|
||||
|
||||
|
||||
Install CodeIgniter4 and create htdocs link:
|
||||
```bash
|
||||
mkdir myproject
|
||||
cd myproject
|
||||
composer create-project codeigniter4/appstarter myprojectapp
|
||||
ln -s myprojectapp/public htdocs
|
||||
```
|
||||
|
||||
.env settings
|
||||
```php
|
||||
|
||||
CI_ENVIRONMENT = development (instead of production)
|
||||
|
||||
app.baseURL = 'http://myproject.local'
|
||||
|
||||
database.default.hostname = myproject.local
|
||||
database.default.database = myproject
|
||||
database.default.username = root
|
||||
database.default.password =
|
||||
database.default.DBDriver = MySQLi
|
||||
database.default.DBPrefix =
|
||||
database.default.port = 3306
|
||||
```
|
||||
|
||||
|
||||
Access mysql from inside container
|
||||
```bash
|
||||
./shell.sh
|
||||
|
||||
mysql -h 127.0.0.1 -u root
|
||||
|
||||
CREATE DATABASE godotweb;
|
||||
```
|
||||
|
||||
|
||||
|
||||
https://shield.codeigniter.com/getting_started/install/
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
docker-compose up -d
|
||||
./shell.sh
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
```bash
|
||||
$ grep -R CI_VERSION|grep const
|
||||
framework/system/CodeIgniter.php: public const CI_VERSION = '4.5.5';
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
Inside Devilbox container:
|
||||
```bash
|
||||
devilbox@php-8.1.14 in /shared/httpd $ pwd
|
||||
/shared/httpd
|
||||
|
||||
|
||||
mkdir myproject
|
||||
cd myproject
|
||||
composer create-project codeigniter4/appstarter myprojectapp
|
||||
ln -s myprojectapp/public htdocs
|
||||
```
|
||||
|
||||
|
||||
Setup db settings in .env:
|
||||
```txt
|
||||
CI_ENVIRONMENT = development
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# APP
|
||||
#--------------------------------------------------------------------
|
||||
app.baseURL = 'http://godotweb.local'
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# DATABASE
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
database.default.hostname = godotweb.local
|
||||
database.default.database = godotweb
|
||||
database.default.username = root
|
||||
database.default.password =
|
||||
database.default.DBDriver = MySQLi
|
||||
database.default.DBPrefix =
|
||||
database.default.port = 3306
|
||||
```
|
||||
|
||||
Add shield:
|
||||
```bash
|
||||
composer require codeigniter4/shield
|
||||
```
|
||||
|
||||
In case of issues, see installation.
|
||||
```bash
|
||||
composer config minimum-stability dev
|
||||
composer config prefer-stable true
|
||||
```
|
||||
|
||||
```bash
|
||||
php spark shield:setup
|
||||
```
|
||||
|
||||
####
|
||||
|
||||
SET DEFAULT LOCALE
|
||||
|
||||
|
||||
Config/App.php
|
||||
```php
|
||||
|
||||
public bool $negotiateLocale = true;
|
||||
public string $defaultLocale = 'en';
|
||||
public array $supportedLocales = ['en', 'de'];
|
||||
```
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
```bash
|
||||
php spark make:controller Api/Auth --suffix --restful
|
||||
```
|
||||
|
||||
|
||||
Routes.php
|
||||
```php
|
||||
//Get
|
||||
$routes->get("logout", "AuthController::logout", ["filter" => "apiauth"]);
|
||||
```
|
||||
|
||||
|
||||
Api/AuthController.php
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\RESTful\ResourceController;
|
||||
use CodeIgniter\Shield\Models\UserModel;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
|
||||
class AuthController extends ResourceController
|
||||
{
|
||||
//Post
|
||||
public function register()
|
||||
{
|
||||
$rules = [
|
||||
"username" => "required|is_unique[users.username]",
|
||||
"email" => "required|valid_email|is_unique[auth_identities.secret]", // gets written to secret field in user table
|
||||
"password" => "required" // gets written to secret 2 field in user table
|
||||
];
|
||||
|
||||
if (!$this->validate($rules))
|
||||
{
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => $this->validator->getErrors(),
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
$um = new UserModel(); // coming from Shield
|
||||
$userEntity = new User([
|
||||
"username" => $this->request->getVar("username"),
|
||||
"email" => $this->request->getVar("email"),
|
||||
"password" => $this->request->getVar("password")
|
||||
]);
|
||||
|
||||
$um->save($userEntity);
|
||||
|
||||
return $this->respondCreated( [
|
||||
"status" => true,
|
||||
"message" => "User saved successfully",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// Post
|
||||
public function login()
|
||||
{
|
||||
if(auth()->loggedIn()) {
|
||||
auth()->logout();
|
||||
}
|
||||
|
||||
$rules = [
|
||||
"email" => "required|valid_email",
|
||||
"password" => "required"
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => $this->validator->getErrors(),
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
// success
|
||||
$credentials = [
|
||||
"email" => $this->request->getVar("email"),
|
||||
"password" => $this->request->getVar("password")
|
||||
];
|
||||
|
||||
$loginAttempt = auth()->attempt($credentials);
|
||||
|
||||
if (!$loginAttempt->isOK()) {
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => "Invalid login details",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
// We have a valid data set
|
||||
|
||||
$um = new UserModel();
|
||||
$userData = $um->findById(auth()->id());
|
||||
$token = $userData->generateAccessToken("thisismysecretkey");
|
||||
$auth_token = $token->raw_token; // Only available after generation
|
||||
|
||||
return $this->respondCreated( [
|
||||
"status" => true,
|
||||
"message" => "User logged in successfully",
|
||||
"data" => [
|
||||
"token" => $auth_token
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
|
||||
public function logout()
|
||||
{
|
||||
auth()->logout();
|
||||
auth()->user()->revokeAllAccessTokens();
|
||||
return $this->respondCreated([
|
||||
"status" => true,
|
||||
"message" => "User logged out successfully",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
//Get
|
||||
public function profile()
|
||||
{
|
||||
$userId = auth()->id();
|
||||
return $this->respondCreated([
|
||||
"status" => true,
|
||||
"message" => "Profile api called",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
public function accessDenied()
|
||||
{
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => "Invalid access",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
https://www.youtube.com/watch?v=_ZRHt-fWJpc&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=29
|
||||
|
||||
app/Config/Routes.php
|
||||
|
||||
Remember: Shield added =>
|
||||
```php
|
||||
service('auth')->routes($routes)
|
||||
```
|
||||
|
||||
```php
|
||||
// API routes (put below service('auth'))
|
||||
$routes->group("api", ["namespace" => "App\Controller\Api"], function($routes) {
|
||||
$routes->post("register", "AuthController::register");
|
||||
$routes->post("login", "AuthController::login");
|
||||
$routes->get("profile", "AuthController::profile");
|
||||
$routes->get("logout", "AuthController::logout", ["filter"=>"apiauth"]);
|
||||
|
||||
//$routes->post("add-project", "ProjectController::addProject");
|
||||
//$routes->get("list-projects", "ProjectController::listProjects");
|
||||
//$routes->delete("delete-project/(:num)", "ProjectController::deleteProject/$1");
|
||||
});
|
||||
```
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
https://www.youtube.com/watch?v=USqOc-GCA4s&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=32
|
||||
|
||||
Create AuthFilter.php
|
||||
```bash
|
||||
php spark make:filter Auth --suffix
|
||||
```
|
||||
|
||||
Define before method in AuthFilter.php
|
||||
```php
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
helper("auth");
|
||||
|
||||
if (!auth("tokens")->loggedIn()) {
|
||||
return redirect()->to(base_url("api/ invalid-access")); // need to create invalid-access route
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Routes.php
|
||||
```php
|
||||
// API ROUTES
|
||||
$routes->group("api",["namespace]=> "App\Controllers\Api"], function($routes) {
|
||||
...
|
||||
$routes->get("invalid-access", "AuthController::accessDenied");
|
||||
§routes->get('demo-method", "AuthController::demoMethod",["filter" => "apiauth"]); // Filter Name is defined in aliases array in Filters.php below.
|
||||
...
|
||||
```
|
||||
|
||||
Add filter to app/Config/Filters.php
|
||||
```php
|
||||
use App\Filters\AuthFilter;
|
||||
|
||||
|
||||
public array $aliases = [
|
||||
'csrf' => CSRF::class,
|
||||
'toolbar' => DebugToolbar::class,
|
||||
'honeypot' => Honeypot::class,
|
||||
'invalidchars' => InvalidChars::class,
|
||||
'secureheaders' => SecureHeaders::class,
|
||||
'apiauth' => AuthFilter::class
|
||||
]
|
||||
```
|
||||
|
||||
Authorize request by adding field "Authorization" with value "Bearer xxxxxx" in Request Header:
|
||||
![[Pasted image 20241127140801.png]]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
![[Pasted image 20241214094809.png]]
|
||||
|
||||
![[Pasted image 20241214094906.png]]
|
||||
600933326ac42a7f1669bfcaceb4bf1f973c8f64f54dccff9d6c424a9c2c0e5d
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
```bash
|
||||
php spark make:model Board --suffix
|
||||
php spark make:controller Api/Board --suffix --restful
|
||||
```
|
||||
|
||||
|
||||
Add allowed fields to model:
|
||||
(BoardModel.php)
|
||||
```php
|
||||
$allowedFields = ["name"];
|
||||
```
|
||||
|
||||
|
||||
|
||||
```php
|
||||
php spark make:migration create_boards
|
||||
```
|
||||
|
||||
Migration file:
|
||||
|
||||
```php
|
||||
//up:
|
||||
$this->forge->addField([
|
||||
"id" => [ "type"=>"INT", "constraint"=>5, "auto increment" => true, "unsigned" => true ],
|
||||
"name" => [ "type"=>"VARCHAR", "constraint" => 120, "null" => false ]
|
||||
]);
|
||||
$this->forge->addPrimaryKey("id);
|
||||
$this->forge->createTable("boards");
|
||||
|
||||
//down:
|
||||
$this->forge->dropTable("boards");
|
||||
```
|
||||
|
||||
|
||||
```bash
|
||||
php spark migrate:status
|
||||
|
||||
php spark migrate
|
||||
```
|
||||
|
||||
Create seeder ?
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
Inside Devilbox container:
|
||||
```bash
|
||||
devilbox@php-8.1.14 in /shared/httpd $ pwd
|
||||
/shared/httpd
|
||||
|
||||
|
||||
mkdir myproject
|
||||
cd myproject
|
||||
composer create-project codeigniter4/appstarter myprojectapp
|
||||
ln -s myprojectapp/public htdocs
|
||||
```
|
||||
|
||||
|
||||
Add shield:
|
||||
```bash
|
||||
composer require codeigniter4/shield
|
||||
```
|
||||
|
||||
In case of issues, see installation.
|
||||
```bash
|
||||
composer config minimum-stability dev
|
||||
composer config prefer-stable true
|
||||
```
|
||||
|
||||
```bash
|
||||
php spark shield:setup
|
||||
```
|
||||
|
||||
####
|
||||
|
||||
SET DEFAULT LOCALE
|
||||
|
||||
|
||||
Config/App.php
|
||||
```php
|
||||
|
||||
public bool $negotiateLocale = true;
|
||||
public string $defaultLocale = 'en';
|
||||
public array $supportedLocales = ['en', 'de'];
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
```bash
|
||||
php spark make:scaffold Product
|
||||
```
|
||||
Creates Controller, Model, Migration, Seeder
|
||||
|
||||
```php
|
||||
//up:
|
||||
$this->forge->addField((["id"=>
|
||||
["type"=>"INT",
|
||||
"constraint"=>5,
|
||||
"auto increment" => true,
|
||||
"unsigned" => true
|
||||
],
|
||||
"name" => ["type"=>"VARCHAR".
|
||||
"constraint" => 120,
|
||||
"null" => false
|
||||
]
|
||||
]
|
||||
));
|
||||
$this->forge->addPrimaryKey("id);
|
||||
$this->forge->createTable("students");
|
||||
|
||||
//down:
|
||||
$this->forge->dropTable("students");
|
||||
```
|
||||
|
||||
|
||||
```bash
|
||||
php spark migrate:status
|
||||
|
||||
php spark migrate
|
||||
```
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
https://www.youtube.com/watch?v=QaNGT-hj0so&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=8
|
||||
|
||||
![[Pasted image 20241112083749.png]]
|
||||
![[Pasted image 20241112084116.png]]
|
||||
|
||||
Add route to Controller+method in Config/Routes.php:
|
||||
|
||||
![[Pasted image 20241112084252.png]]
|
||||
|
||||
Example for using where clause ( second parameter in update method ):
|
||||
![[Pasted image 20241112084920.png]]
|
||||
|
||||
Delete:
|
||||
|
||||
![[Pasted image 20241112085347.png]]
|
||||
Select all:
|
||||
![[Pasted image 20241112090019.png]]
|
||||
|
||||
|
||||
![[Pasted image 20241112090048.png]]
|
||||
|
||||
Select with where clause
|
||||
|
||||
![[Pasted image 20241112090400.png]]
|
||||
|
||||
For single row result, we directly load into Array ( instead of Array of Array)
|
||||
![[Pasted image 20241112090526.png]]
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
https://www.youtube.com/watch?v=4_MdtVqfH1o&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=9
|
||||
|
||||
```bash
|
||||
php spark make:model Student --suffix
|
||||
```
|
||||
Creates StudentModel.php in app/Models
|
||||
|
||||
|
||||
Add allowedFields
|
||||
![[Pasted image 20241113082554.png]]
|
||||
|
||||
Add Model to Controller
|
||||
![[Pasted image 20241113082703.png]]
|
||||
![[Pasted image 20241113083139.png]]
|
||||
|
||||
|
||||
add to routes
|
||||
```php
|
||||
$routes->get("add-student", "StudentController::insertStudent");
|
||||
```
|
||||
|
||||
|
||||
update example (condition, data):
|
||||
|
||||
![[Pasted image 20241113083603.png]]
|
||||
|
||||
delete:
|
||||
|
||||
![[Pasted image 20241113083823.png]]
|
||||
get all :
|
||||
![[Pasted image 20241113084056.png]]
|
||||
|
||||
get single with generic condition:
|
||||
|
||||
![[Pasted image 20241113084645.png]]
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
For creating dummy data
|
||||
|
||||
Seeder files go into app/Database/Seeds
|
||||
|
||||
```bash
|
||||
php spark make:seeder Student --suffix
|
||||
```
|
||||
|
||||
fill run method
|
||||
|
||||
![[Pasted image 20241113085432.png]]
|
||||
|
||||
Run seeder file
|
||||
|
||||
```bash
|
||||
php spark db:seed StudentSeeder
|
||||
```
|
||||
|
||||
Mass dummy data using Factory / Faker
|
||||
|
||||
![[Pasted image 20241113085948.png]]
|
||||
|
||||
![[Pasted image 20241113090300.png]]
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
https://www.youtube.com/watch?v=WGtj1Ihlf10&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=11
|
||||
get, post, put/patch, delete
|
||||
|
||||
![[Pasted image 20241114084652.png]]
|
||||
![[Pasted image 20241114084843.png]]
|
||||
|
||||
|
||||
|
||||
![[Pasted image 20241114084916.png]]
|
||||
|
||||
|
||||
|
||||
![[Pasted image 20241114084945.png]]
|
||||
|
||||
|
||||
|
||||
|
||||
Routes.php
|
||||
|
||||
![[Pasted image 20241114085118.png]]
|
||||
|
||||
Test using postman
|
||||
![[Pasted image 20241114085726.png]]
|
||||
|
||||
![[Pasted image 20241114085814.png]]
|
||||
![[Pasted image 20241114085826.png]]
|
||||
Remove debugging noise in payload by switching to production environment in .env
|
||||
|
||||
Method spoofing ( changing POST into DELETE request )
|
||||
![[Pasted image 20241114090303.png]]
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
https://www.youtube.com/watch?v=4neOZhzn-qI&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=12
|
||||
![[Pasted image 20241115082725.png]]
|
||||
|
||||
|
||||
app/Config/Routes.php
|
||||
![[Pasted image 20241115082852.png]]
|
||||
|
||||
http://godotweb.local/product/add
|
||||
http://godotweb.local/product/list
|
||||
http://godotweb.local/category/add
|
||||
http://godotweb.local/category/list
|
||||
|
||||
|
||||
**ROUTE NAMESPACE**
|
||||
Create subfolder in Controllers directory
|
||||
```bash
|
||||
php spark make:controller Admin/Admin --suffix
|
||||
```
|
||||
|
||||
![[Pasted image 20241115084552.png]]
|
||||
|
||||
```bash
|
||||
php spark make:controller Seller/Seller --suffix
|
||||
```
|
||||
|
||||
![[Pasted image 20241115084636.png]]
|
||||
|
||||
Access Controller in subfolder using namespace parameter in routes group method:
|
||||
![[Pasted image 20241115085338.png]]
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
https://www.youtube.com/watch?v=rlIKHZ5Nq2U&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=14
|
||||
|
||||
app/Filters
|
||||
app/Config/Filters.php
|
||||
|
||||
![[Pasted image 20241116111944.png]]
|
||||
|
||||
```bash
|
||||
php spark make:filter Auth --suffix
|
||||
```
|
||||
![[Pasted image 20241116113439.png]]
|
||||
![[Pasted image 20241116113532.png]]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
```bash
|
||||
php spark make:controller Api/Api --suffix --restful
|
||||
```
|
||||
|
||||
#17/38
|
||||
|
||||
== > Created controller ApiController (in Controlles subfolder Api) will have 9 methods.
|
||||
( WHich the tutorial doesnt use, basically only difference to standard Controllers is usage of ResourceController)
|
||||
|
||||
![[Pasted image 20241117112106.png]]
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
Routes.php
|
||||
![[Pasted image 20241117113037.png]]
|
||||
|
||||
Route parameter
|
||||
![[Pasted image 20241117113342.png]]
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
![[Pasted image 20241117113802.png]]
|
||||
|
||||
```php
|
||||
$studentObject = new StudentModel();
|
||||
|
||||
$imageFile = $this->request->getFile("profile_image");
|
||||
|
||||
// only go through image storing logic if image actually exists in payload
|
||||
if (isset($imageFile) && !empty($imageFile)) { // with image
|
||||
|
||||
$imageName = $imageFile->getName();
|
||||
$tempArray = explode(".", $imageName);
|
||||
$newImageName = round(microtime(true)) . "" . end($tempArray);
|
||||
|
||||
if($imageFile->move("images/" . $newImageName)) { // move automatically creates images folder if not exists
|
||||
$data = [
|
||||
"name" => $this->request->getVar("name"),
|
||||
"email" => $this->request->getVar("email"),
|
||||
"profile_image" => "images/" . $newImageName
|
||||
];
|
||||
}
|
||||
else {
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "Image Upload failed",
|
||||
"data" => []
|
||||
];
|
||||
}
|
||||
}
|
||||
else { // without image
|
||||
$data = [
|
||||
"name" => $this->request->getVar("name"),
|
||||
"email" => $this->request->getVar("email"),
|
||||
];
|
||||
}
|
||||
|
||||
// store student
|
||||
if ($studentObject->insert($data)) {
|
||||
$response = [
|
||||
"status" => true,
|
||||
"message" => "Student saved successfully",
|
||||
"data" => []
|
||||
];
|
||||
} else {
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "Failed to save student",
|
||||
"data" => []
|
||||
];
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
|
||||
![[Pasted image 20241118085649.png]]
|
||||
|
||||
**![[Pasted image 20241118090014.png]]
|
||||
|
||||
![[Pasted image 20241118090047.png]]
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://www.youtube.com/watch?v=0aBmb1aaj_Q&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=22
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
https://www.youtube.com/watch?v=hX612xp_Gtw&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=23
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
```bash
|
||||
php spark make:controller Api/Auth --suffix --restful
|
||||
php spark make:controller Api/Project --suffix --restful
|
||||
```
|
||||
|
||||
![[Pasted image 20241123100630.png]]
|
||||
![[Pasted image 20241123100706.png]]
|
||||
![[Pasted image 20241123100734.png]]
|
||||
![[Pasted image 20241123100756.png]]
|
||||
|
||||
|
||||
--- PROJECT CONTROLLER
|
||||
![[Pasted image 20241123100834.png]]
|
||||
![[Pasted image 20241123100851.png]]
|
||||
![[Pasted image 20241123100913.png]]
|
||||
ADD SHIELD USERMODEL
|
||||
|
||||
![[Pasted image 20241123101019.png]]
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
php spark make:model Project --suffix
|
||||
```
|
||||
|
||||
|
||||
extend allowedFields, e.g.
|
||||
```php
|
||||
$allowedFields = ["user_id","name"]
|
||||
```
|
||||
|
||||
Add Project Model to ProjectController
|
||||
```php
|
||||
use App\Models\ProjectModel;
|
||||
```
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
https://www.youtube.com/watch?v=_ZRHt-fWJpc&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=29
|
||||
|
||||
app/Config/Routes.php
|
||||
|
||||
Remember: Shield added =>
|
||||
```php
|
||||
service('auth')->routes($routes)
|
||||
```
|
||||
|
||||
```php
|
||||
// API routes (put below service('auth'))
|
||||
$routes->group("api", ["namespace" => "App\Controller\Api"], function($routes) {
|
||||
$routes->post("register", "AuthController::register");
|
||||
$routes->post("login", "AuthController::login");
|
||||
$routes->get("profile", "AuthController::profile");
|
||||
$routes->get("logout", "AuthController::logout");
|
||||
|
||||
$routes->post("add-project", "ProjectController::addProject");
|
||||
$routes->get("list-projects", "ProjectController::listProjects");
|
||||
$routes->delete("delete-project/(:num)", "ProjectController::deleteProject/$1");
|
||||
});
|
||||
```
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
AuthController:
|
||||
|
||||
```php
|
||||
|
||||
use CodeIgniter\Restful\ResourceController;
|
||||
use CodeIgniter\Shield\Models\UserModel;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
|
||||
class AuthController extends ResourceController
|
||||
{
|
||||
//Post
|
||||
public function register()
|
||||
{
|
||||
$rules = [
|
||||
"username" => "required|is_unique[users.username]",
|
||||
"email" => "required|valid_email|is_unique[auth_identities.secret]", // gets written to secret field in user table
|
||||
"password" => "required" // gets written to secret 2 field in user table
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => $this->validator->getErrors(),
|
||||
"data" => []
|
||||
];
|
||||
} else {
|
||||
$userObject = new UserModel(); // coming from Shield
|
||||
$userEntityObject = new User([
|
||||
"username" => $this->request->getVar("username"),
|
||||
"email" => $this->request->getVar("email"),
|
||||
"password" => $this->request->getVar("password")
|
||||
]);
|
||||
$userObject->save($userEntityObject);
|
||||
$response = [
|
||||
"status" => true,
|
||||
"message" => "User saved successfully",
|
||||
"data" => []
|
||||
]
|
||||
}
|
||||
return $this->respondCreated($response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: shield stores users in auth_identies. Email address should be stored in field secret.
|
||||
|
||||
https://onlinewebtutorblog.com/codeigniter-4-restful-api-using-shield-authentication/
|
||||
|
||||
|
||||
|
||||
My version:
|
||||
```php
|
||||
use CodeIgniter\Restful\ResourceController;
|
||||
use CodeIgniter\Shield\Models\UserModel;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
|
||||
class AuthController extends ResourceController
|
||||
{
|
||||
//Post
|
||||
public function register()
|
||||
{
|
||||
$rules = [
|
||||
"username" => "required|is_unique[users.username]",
|
||||
"email" => "required|valid_email|is_unique[auth_identities.secret]", // gets written to secret field in user table
|
||||
"password" => "required" // gets written to secret 2 field in user table
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => $this->validator->getErrors(),
|
||||
"data" => []
|
||||
]
|
||||
);
|
||||
|
||||
$um = new UserModel(); // coming from Shield
|
||||
$userEntity = new User([
|
||||
"username" => $this->request->getVar("username"),
|
||||
"email" => $this->request->getVar("email"),
|
||||
"password" => $this->request->getVar("password")
|
||||
]);
|
||||
$um->save($userEntity);
|
||||
|
||||
return $this->respondCreated( [
|
||||
"status" => true,
|
||||
"message" => "User saved successfully",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
https://www.youtube.com/watch?v=XJHFtfQQmRs&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=31
|
||||
|
||||
https://onlinewebtutorblog.com/codeigniter-4-restful-api-using-shield-authentication/
|
||||
|
||||
In AuthController::login:
|
||||
|
||||
|
||||
|
||||
|
||||
```php
|
||||
// Post
|
||||
public function login()
|
||||
{
|
||||
|
||||
if(auth()->loggedIn()){
|
||||
auth()->logout();
|
||||
}
|
||||
|
||||
$rules = [
|
||||
"email" => "required|valid_email",
|
||||
"password" => "required"
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => $this->validator->getErrors(),
|
||||
"data" => []
|
||||
];
|
||||
} else {
|
||||
|
||||
// success
|
||||
$credentials = [
|
||||
"email" => $this->request->getVar("email"),
|
||||
"password" => $this->request->getVar("password")
|
||||
];
|
||||
|
||||
$loginAttempt = auth()->attempt($credentials);
|
||||
|
||||
if (!$loginAttempt->isOK()) {
|
||||
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "Invalid login details",
|
||||
"data" => []
|
||||
];
|
||||
} else {
|
||||
|
||||
// We have a valid data set
|
||||
$userObject = new UserModel();
|
||||
|
||||
$userData = $userObject->findById(auth()->id());
|
||||
|
||||
$token = $userData->generateAccessToken("thisismysecretkey");
|
||||
|
||||
$auth_token = $token->raw_token; // Only available after generation
|
||||
|
||||
$response = [
|
||||
"status" => true,
|
||||
"message" => "User logged in successfully",
|
||||
"data" => [
|
||||
"token" => $auth_token
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->respondCreated($response);
|
||||
}
|
||||
```
|
||||
https://shield.codeigniter.com/references/authentication/tokens/
|
||||
|
||||
|
||||
|
||||
My version:
|
||||
```php
|
||||
// Post
|
||||
public function login()
|
||||
{
|
||||
if(auth()->loggedIn()){
|
||||
auth()->logout();
|
||||
}
|
||||
|
||||
$rules = [
|
||||
"email" => "required|valid_email",
|
||||
"password" => "required"
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => $this->validator->getErrors(),
|
||||
"data" => []
|
||||
]);
|
||||
|
||||
// success
|
||||
$credentials = [
|
||||
"email" => $this->request->getVar("email"),
|
||||
"password" => $this->request->getVar("password")
|
||||
];
|
||||
|
||||
$loginAttempt = auth()->attempt($credentials);
|
||||
if (!$loginAttempt->isOK()) {
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => "Invalid login details",
|
||||
"data" => []
|
||||
]);
|
||||
|
||||
// We have a valid data set
|
||||
$um = new UserModel();
|
||||
$userData = $um->findById(auth()->id());
|
||||
$token = $userData->generateAccessToken("thisismysecretkey");
|
||||
$auth_token = $token->raw_token; // Only available after generation
|
||||
|
||||
return $this->respondCreated( [
|
||||
"status" => true,
|
||||
"message" => "User logged in successfully",
|
||||
"data" => [
|
||||
"token" => $auth_token
|
||||
] );
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
https://www.youtube.com/watch?v=USqOc-GCA4s&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=32
|
||||
|
||||
Create AuthFilter.php
|
||||
```bash
|
||||
php spark make:filter Auth --suffix
|
||||
```
|
||||
|
||||
Define before method in AuthFilter.php
|
||||
```php
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
helper("auth");
|
||||
|
||||
if (!auth("tokens")->loggedIn()) {
|
||||
return redirect()->to(base_url("api/ invalid-access")); // need to create invalid-access route
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Routes.php
|
||||
```php
|
||||
// API ROUTES
|
||||
$routes->group("api",["namespace]=> "App\Controllers\Api"], function($routes) {
|
||||
...
|
||||
$routes->get("invalid-access", "AuthController::accessDenied");
|
||||
§routes->get('demo-method", "AuthController::demoMethod",["filter" => "apiauth"]); // Filter Name is defined in aliases array in Filters.php below.
|
||||
...
|
||||
```
|
||||
|
||||
Define accessDenied method in app/Controllers/Api/AuthController.php
|
||||
```php
|
||||
...
|
||||
public function accessDenied()
|
||||
{
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => "Invalid access",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Add filter to app/Config/Filters.php
|
||||
```php
|
||||
use App\Filters\AuthFilter;
|
||||
|
||||
|
||||
public array $aliases = [
|
||||
'csrf' => CSRF::class,
|
||||
'toolbar' => DebugToolbar::class,
|
||||
'honeypot' => Honeypot::class,
|
||||
'invalidchars' => InvalidChars::class,
|
||||
'secureheaders' => SecureHeaders::class,
|
||||
'apiauth' => AuthFilter::class
|
||||
]
|
||||
```
|
||||
|
||||
Authorize request by adding field "Authorization" with value "Bearer xxxxxx" in Request Header:
|
||||
![[Pasted image 20241127140801.png]]
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
https://www.youtube.com/watch?v=f0NifEgwh30&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=33
|
||||
|
||||
|
||||
```php
|
||||
auth()->user()
|
||||
```
|
||||
|
||||
|
||||
```php
|
||||
$userId = auth()->id();
|
||||
```
|
||||
|
||||
![[Pasted image 20241129092837.png]]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AuthController
|
||||
```php
|
||||
//...
|
||||
|
||||
// Post
|
||||
public function login()
|
||||
{
|
||||
|
||||
if(auth()->loggedIn()){
|
||||
auth()->logout();
|
||||
}
|
||||
|
||||
$rules = [
|
||||
"email" => "required|valid_email",
|
||||
"password" => "required"
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => $this->validator->getErrors(),
|
||||
"data" => []
|
||||
];
|
||||
} else {
|
||||
|
||||
// success
|
||||
$credentials = [
|
||||
"email" => $this->request->getVar("email"),
|
||||
"password" => $this->request->getVar("password")
|
||||
];
|
||||
|
||||
$loginAttempt = auth()->attempt($credentials);
|
||||
|
||||
if (!$loginAttempt->isOK()) {
|
||||
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "Invalid login details",
|
||||
"data" => []
|
||||
];
|
||||
} else {
|
||||
|
||||
// to test: directly getting $userObject via auth()->user()
|
||||
|
||||
// We have a valid data set
|
||||
$userObject = new UserModel();
|
||||
|
||||
$userData = $userObject->findById(auth()->id());
|
||||
|
||||
$token = $userData->generateAccessToken("thisismysecretkey");
|
||||
|
||||
$auth_token = $token->raw_token;
|
||||
|
||||
$response = [
|
||||
"status" => true,
|
||||
"message" => "User logged in successfully",
|
||||
"data" => [
|
||||
"token" => $auth_token
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondCreated($response);
|
||||
}
|
||||
|
||||
//...
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
https://www.youtube.com/watch?v=H1KiXEsmsCs&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=34
|
||||
|
||||
Routes.php:
|
||||
```php
|
||||
//Get
|
||||
$routes->get("logout", "AuthController::logout", ["filter" => "apiauth"]);
|
||||
```
|
||||
|
||||
AuthController.php
|
||||
```php
|
||||
public function logout() {
|
||||
auth()->logout();
|
||||
auth()->user()->revokeAllAccessTokens();
|
||||
|
||||
return $this->respondCreated([
|
||||
"status" => true,
|
||||
"message" => "User logged out successfully",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
https://www.youtube.com/watch?v=104Q76g7DWA&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=36
|
||||
|
||||
ProjectController
|
||||
```php
|
||||
// Post
|
||||
public function addProject()
|
||||
{
|
||||
$rules = [
|
||||
"name" => "required",
|
||||
"budget" => "required"
|
||||
];
|
||||
|
||||
if(!$this->validate($rules)) {
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => $this->validator->getErrors(),
|
||||
"data" => []
|
||||
];
|
||||
}else {
|
||||
$user_id = auth()-id();
|
||||
$name = $this->request->getVar("name");
|
||||
$budget = $this->request->getVar("budget");
|
||||
$pm = new ProjectModel();
|
||||
$data= [
|
||||
"user_id" => $user_id,
|
||||
"name" => name,
|
||||
"budget" => budget
|
||||
];
|
||||
if($pm->insert($data)) {
|
||||
$response = [
|
||||
"status" => true,
|
||||
"message" => "project saved",
|
||||
"data" => []
|
||||
];
|
||||
}else{
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "project save failed",
|
||||
"data" => []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$return $this->respondCreated($response);
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://www.youtube.com/watch?v=B5GafCSXj9U&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=37
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
https://www.youtube.com/watch?v=-NIoOj-FtHE&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=36
|
||||
|
||||
ProjectController.php
|
||||
```php
|
||||
public function listProjects()
|
||||
{
|
||||
$user_id = auth()->id();
|
||||
$pm = new ProjectModel();
|
||||
|
||||
$projectData = $pm->where([
|
||||
"user_id" => $user_id
|
||||
])->findAll();
|
||||
|
||||
if (count($projectData)>0) {
|
||||
$response = [
|
||||
"status" => true,
|
||||
"message" => "Project data",
|
||||
"data" => $projectData
|
||||
];
|
||||
}else {
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "No Project data found",
|
||||
"data" => []
|
||||
];
|
||||
}
|
||||
|
||||
return $this->respondCreated($response);
|
||||
}
|
||||
```
|
||||
|
||||
Routes.php
|
||||
```php
|
||||
$routes->get("list-projects", "ProjectController::listProjects", ["filter" => "apiauth"]);
|
||||
```
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
https://www.youtube.com/watch?v=B5GafCSXj9U&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=37
|
||||
|
||||
ProjectController.php
|
||||
```php
|
||||
public function deleteProject($project_id)
|
||||
{
|
||||
$user_id = auth()->id();
|
||||
if(!empty($project_id)) {
|
||||
$pm = new ProjectModel();
|
||||
$projectData = $pm->where([
|
||||
"user_id" => $user_id,
|
||||
"id" => $project_id
|
||||
])->first();
|
||||
|
||||
if (!empty($projectData)) {
|
||||
if(pm->delete($project_id)) {
|
||||
$response = [
|
||||
"status" => true,
|
||||
"message" => "project deleted",
|
||||
"data" => []
|
||||
];
|
||||
}else {
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "failed to delete project",
|
||||
"data" => []
|
||||
];
|
||||
}
|
||||
}else{
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "project not found",
|
||||
"data" => []
|
||||
];
|
||||
}
|
||||
|
||||
}else{
|
||||
$response = [
|
||||
"status" => false,
|
||||
"message" => "Project Id required",
|
||||
"data" => []
|
||||
];
|
||||
}
|
||||
|
||||
return $this->respondCreated($response);
|
||||
}
|
||||
```
|
||||
|
||||
Routes.php
|
||||
```php
|
||||
$routes->delete("delete-project/(:num)", "ProjectController::deleteProject/$1",["filter"=>"apiauth"]);
|
||||
```
|
||||
|
||||
|
||||
|
||||
Alternative version:
|
||||
```php
|
||||
public function deleteProject($project_id)
|
||||
{
|
||||
$user_id = auth()->id();
|
||||
|
||||
if(empty($project_id)) {
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => "project id required",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
$pm = new ProjectModel();
|
||||
$projectData = $pm->where([
|
||||
"user_id" => $user_id,
|
||||
"id" => $project_id
|
||||
])->first();
|
||||
|
||||
if(empty($projectData)) {
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => "project not found",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
if(!$pm->delete($project_id)) {
|
||||
return $this->respondCreated([
|
||||
"status" => false,
|
||||
"message" => "failed to delete project",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->respondCreated([
|
||||
"status" => true,
|
||||
"message" => "project deleted",
|
||||
"data" => []
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
https://www.youtube.com/watch?v=8GhfHZlEaEo&list=PLT9miexWCpPW6Z_OXNv2d78cqCrZaPrVG&index=41
|
||||
|
||||
|
||||
In case Web Server throws CORS errors when trying to access API.
|
||||
We add a filter that adds header with CORS info to define which kind of HTTP are allowed from foreign source.
|
||||
|
||||
|
||||
Create CorsFilter.php
|
||||
```bash
|
||||
php spark make:filter Cors --suffix
|
||||
```
|
||||
|
||||
Add to before method:
|
||||
```php
|
||||
public function before(RequestInterface $request, $arguments=null)
|
||||
{
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Access-Control-Allow-Headers: Authorization, Content-Type, Accept, API-KEY");
|
||||
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH");
|
||||
}
|
||||
```
|
||||
|
||||
Add in app/Config/Filters.php:
|
||||
![[Pasted image 20241202084231.png]]
|
||||
![[Pasted image 20241202084303.png]]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
```bash
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
```php
|
||||
<?php
|
||||
|
||||
$DBUSER="user";
|
||||
$DBPASSWD="password";
|
||||
$DATABASE="user_db";
|
||||
|
||||
$filename = "backup-" . date("d-m-Y") . ".sql.gz";
|
||||
$mime = "application/x-gzip";
|
||||
|
||||
header( "Content-Type: " . $mime );
|
||||
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
|
||||
|
||||
$cmd = "mysqldump -u $DBUSER --password=$DBPASSWD $DATABASE | gzip --best";
|
||||
|
||||
passthru( $cmd );
|
||||
|
||||
exit(0);
|
||||
?>
|
||||
```
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
developer.tomtom.com
|
||||
tschneider
|
||||
Supertomtom1!
|
||||
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
```php
|
||||
<!-- test1.php -->
|
||||
|
||||
<?php
|
||||
|
||||
//The URL with parameters / query string.
|
||||
// Escape: $str = preg_replace('/[^a-z\d ]/i', '', $str);
|
||||
|
||||
$url='https://api.tomtom.com/search/2/search/Am%20Kapellchen%2021%2C%2041470%20Neuss.json?lat=37.337&lon=-121.89&minFuzzyLevel=1&maxFuzzyLevel=2&view=Unified&relatedPois=off&key=xcA6NjLwDPmayP6y56AoVoloz1iohbUV';
|
||||
|
||||
//we use file_get_contents to GET the URL in question.
|
||||
$contents = file_get_contents($url);
|
||||
|
||||
$result = json_decode($contents);
|
||||
$lat = $result->results[0]->position->lat;
|
||||
$lon = $result->results[0]->position->lon;
|
||||
|
||||
echo "Lat=" . $lat . ", Lon=" . $lon;
|
||||
|
||||
```
|
||||
|
||||
````php
|
||||
<?php
|
||||
|
||||
//The URL with parameters / query string.
|
||||
|
||||
$url='https://api.tomtom.com/search/2/search/Am%20Kapellchen%2021%2C%2041470%20Neuss.json?lat=37.337&lon=-121.89&minFuzzyLevel=1&maxFuzzyLevel=2&view=Unified&relatedPois=off&key=xcA6NjLwDPmayP6y56AoVoloz1iohbUV';
|
||||
|
||||
//we use file_get_contents to GET the URL in question.
|
||||
$contents = file_get_contents($url);
|
||||
|
||||
//If $contents is not a boolean FALSE value.
|
||||
if($contents !== false){
|
||||
//Print out the contents.
|
||||
echo $contents;
|
||||
}
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
````php
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class='use-all-space'>
|
||||
<head>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=Edge' />
|
||||
<meta charset='UTF-8'>
|
||||
<title>Maps SDK for Web - Static route</title>
|
||||
|
||||
<meta name='viewport'
|
||||
content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'/>
|
||||
|
||||
<link rel='stylesheet' type='text/css' href='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.20.0/maps/maps.css'>
|
||||
|
||||
<link rel='stylesheet' type='text/css' href='../test/assets/ui-library/icons-css/routing.css'/>
|
||||
|
||||
<link rel='stylesheet' type='text/css' href='../test/assets/ui-library/index.css'/>
|
||||
|
||||
</head>
|
||||
|
||||
<style>
|
||||
.icon {
|
||||
background-size: cover;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.route-marker {
|
||||
align-items: center;
|
||||
background-color: #4a90e2;
|
||||
border: solid 3px #2faaff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
transition: width .1s, height .1s;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div><h1>TEST KARTE</<h1></div>
|
||||
<div style="height: 90%" id='map' class='map'></div>
|
||||
<script src='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.20.0/maps/maps-web.min.js'></script>
|
||||
|
||||
<script src='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.20.0/services/services-web.min.js'></script>
|
||||
|
||||
<script type='text/javascript' src='../test/assets/js/mobile-or-tablet.js'></script>
|
||||
|
||||
<script>
|
||||
var map = tt.map({
|
||||
key: 'xcA6NjLwDPmayP6y56AoVoloz1iohbUV',
|
||||
container: 'map',
|
||||
//dragPan: !isMobileOrTablet()
|
||||
dragPan: false
|
||||
});
|
||||
|
||||
map.scrollZoom.disable()
|
||||
//map.addControl(new tt.FullscreenControl());
|
||||
//map.addControl(new tt.NavigationControl());
|
||||
|
||||
|
||||
|
||||
function createMarkerElement(type) {
|
||||
var element = document.createElement('div');
|
||||
var innerElement = document.createElement('div');
|
||||
|
||||
|
||||
|
||||
element.className = 'route-marker';
|
||||
innerElement.className = 'icon tt-icon -white -' + type;
|
||||
element.appendChild(innerElement);
|
||||
return element;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function addMarkers(feature) {
|
||||
|
||||
var startPoint, endPoint;
|
||||
if (feature.geometry.type === 'MultiLineString') {
|
||||
startPoint = feature.geometry.coordinates[0][0]; //get first point from first line
|
||||
endPoint = feature.geometry.coordinates.slice(-1)[0].slice(-1)[0]; //get last point from last line
|
||||
} else {
|
||||
startPoint = feature.geometry.coordinates[0];
|
||||
endPoint = feature.geometry.coordinates.slice(-1)[0];
|
||||
}
|
||||
|
||||
|
||||
new tt.Marker({ element: createMarkerElement('start') }).setLngLat(startPoint).addTo(map);
|
||||
|
||||
new tt.Marker({ element: createMarkerElement('finish') }).setLngLat(endPoint).addTo(map);
|
||||
|
||||
}
|
||||
|
||||
function findFirstBuildingLayerId() {
|
||||
var layers = map.getStyle().layers;
|
||||
for (var index in layers) {
|
||||
if (layers[index].type === 'fill-extrusion') {
|
||||
return layers[index].id;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Map style does not contain any layer with fill-extrusion type.');
|
||||
}
|
||||
|
||||
map.once('load', function() {
|
||||
tt.services.calculateRoute({
|
||||
key: 'xcA6NjLwDPmayP6y56AoVoloz1iohbUV',
|
||||
traffic: false,
|
||||
locations: '4.8786,52.3679:4.8798,52.3679'
|
||||
})
|
||||
.then(function(response) {
|
||||
var geojson = response.toGeoJson();
|
||||
map.addLayer({
|
||||
'id': 'route',
|
||||
'type': 'line',
|
||||
'source': {
|
||||
'type': 'geojson',
|
||||
'data': geojson
|
||||
},
|
||||
|
||||
'paint': {
|
||||
'line-color': '#4a90e2',
|
||||
'line-width': 8
|
||||
}
|
||||
}, findFirstBuildingLayerId());
|
||||
|
||||
addMarkers(geojson.features[0]);
|
||||
|
||||
var bounds = new tt.LngLatBounds();
|
||||
geojson.features[0].geometry.coordinates.forEach(function(point) {
|
||||
bounds.extend(tt.LngLat.convert(point));
|
||||
|
||||
});
|
||||
|
||||
map.fitBounds(bounds, { duration: 0, padding: 50 });
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue