mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 23:58:02 -05:00
Removed Wizards from hero levels. Fixed issues with GameMenuModal width and swapping of hero config. No need to click start with ?dev=true. Hero is always selected in hero levels. GameMenuModal shows up while loading if no heroConfig is detected.
This commit is contained in:
parent
949f4594af
commit
600e985259
14 changed files with 82 additions and 48 deletions
|
@ -63,7 +63,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
loadSession: ->
|
||||
return if @headless
|
||||
if @sessionID
|
||||
url = "/db/level_session/#{@sessionID}"
|
||||
url = "/db/level.session/#{@sessionID}"
|
||||
else
|
||||
url = "/db/level/#{@levelID}/session"
|
||||
url += "?team=#{@team}" if @team
|
||||
|
@ -72,14 +72,15 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@sessionResource = @supermodel.loadModel(session, 'level_session', {cache: false})
|
||||
@session = @sessionResource.model
|
||||
if @session.loaded
|
||||
@session.setURL '/db/level.session/' + @session.id
|
||||
@loadDependenciesForSession @session
|
||||
else
|
||||
@listenToOnce @session, 'sync', ->
|
||||
@session.url = -> '/db/level.session/' + @id
|
||||
@session.setURL '/db/level.session/' + @session.id
|
||||
@loadDependenciesForSession @session
|
||||
|
||||
if @opponentSessionID
|
||||
opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}"
|
||||
opponentSession = new LevelSession().setURL "/db/level.session/#{@opponentSessionID}"
|
||||
@opponentSessionResource = @supermodel.loadModel(opponentSession, 'opponent_session')
|
||||
@opponentSession = @opponentSessionResource.model
|
||||
if @opponentSession.loaded
|
||||
|
@ -100,6 +101,9 @@ module.exports = class LevelLoader extends CocoClass
|
|||
url = "/db/thang.type/#{itemThangType}/version?project=name,components,original"
|
||||
@worldNecessities.push @maybeLoadURL(url, ThangType, 'thang')
|
||||
|
||||
if session is @session
|
||||
Backbone.Mediator.publish 'level:session-loaded', level: @level, session: @session
|
||||
|
||||
# Grabbing the rest of the required data for the level
|
||||
|
||||
populateLevel: ->
|
||||
|
|
|
@ -81,6 +81,10 @@ module.exports =
|
|||
level: {type: 'object'}
|
||||
team: {type: ['string', 'null', 'undefined']}
|
||||
|
||||
'level:session-loaded': c.object {required: ['level', 'session']},
|
||||
level: {type: 'object'}
|
||||
session: {type: 'object'}
|
||||
|
||||
'level:loading-view-unveiling': c.object {}
|
||||
|
||||
'level:loading-view-unveiled': c.object {required: ['view']},
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
|
||||
.modal-dialog
|
||||
margin-top: 0
|
||||
|
||||
width: 963px
|
||||
|
||||
.nav-tabs
|
||||
h2
|
||||
margin: 0
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
left: 50%
|
||||
$WIDTH: 1000px
|
||||
width: $WIDTH
|
||||
min-height: 60px
|
||||
margin-left: (-$WIDTH / 2)
|
||||
z-index: 100
|
||||
background-color: rgba(220, 255, 230, 0.6)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
.replace-me(data-item-id=equipment[slot].get('original'))
|
||||
|
||||
.item-slot-column.pull-left
|
||||
for slot in ['torso', 'gloves', 'left-hand', 'minion']
|
||||
for slot in ['minion', 'torso', 'gloves', 'left-hand']
|
||||
.item-slot(data-slot=slot)
|
||||
.placeholder
|
||||
.item-container
|
||||
|
@ -26,7 +26,7 @@
|
|||
span.glyphicon.glyphicon-transfer
|
||||
|
||||
.item-slot-column.pull-right
|
||||
for slot in ['waist', 'feet', 'right-hand', 'pet']
|
||||
for slot in ['pet', 'waist', 'feet', 'right-hand']
|
||||
.item-slot(data-slot=slot)
|
||||
.placeholder
|
||||
.item-container
|
||||
|
|
|
@ -23,7 +23,6 @@ module.exports = class ChooseHeroView extends CocoView
|
|||
constructor: (options) ->
|
||||
super options
|
||||
@heroes = new CocoCollection([], {model: ThangType})
|
||||
@equipment = options.equipment or @options.session?.get('heroConfig')?.inventory or {}
|
||||
@heroes.url = '/db/thang.type?view=heroes&project=original,name,slug,soundTriggers'
|
||||
@supermodel.loadCollection(@heroes, 'heroes')
|
||||
@stages = {}
|
||||
|
|
|
@ -11,7 +11,6 @@ submenuViews = [
|
|||
|
||||
module.exports = class GameMenuModal extends ModalView
|
||||
template: template
|
||||
modalWidthPercent: 95
|
||||
id: 'game-menu-modal'
|
||||
instant: true
|
||||
|
||||
|
@ -52,7 +51,7 @@ module.exports = class GameMenuModal extends ModalView
|
|||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-close', volume: 1
|
||||
|
||||
updateConfig: ->
|
||||
sessionHeroConfig = @options.session.get('heroConfig') ? {}
|
||||
sessionHeroConfig = $.extend {}, true, (@options.session.get('heroConfig') ? {})
|
||||
lastHeroConfig = me.get('heroConfig') ? {}
|
||||
thangType = @subviews.choose_hero_view.selectedHero.get 'original'
|
||||
inventory = @subviews.inventory_view.getCurrentEquipmentConfig()
|
||||
|
@ -72,8 +71,11 @@ module.exports = class GameMenuModal extends ModalView
|
|||
me.set 'aceConfig', aceConfig
|
||||
if patchSession
|
||||
@options.session.set 'heroConfig', sessionHeroConfig
|
||||
@options.session.patch success: ->
|
||||
success = ->
|
||||
_.defer -> Backbone.Mediator.publish 'level:hero-config-changed', {}
|
||||
error = (model, res) ->
|
||||
console.error 'error patching session', model, res, res.responseJSON, res.status, res.statusText
|
||||
@options.session.patch success: success, error: error
|
||||
if patchMe
|
||||
me.set 'heroConfig', lastHeroConfig
|
||||
me.patch()
|
||||
|
|
|
@ -29,6 +29,7 @@ module.exports = class InventoryView extends CocoView
|
|||
super(arguments...)
|
||||
@items = new CocoCollection([], {model: ThangType})
|
||||
@equipment = options.equipment or @options.session?.get('heroConfig')?.inventory or me.get('heroConfig')?.inventory or {}
|
||||
@equipment = $.extend true, {}, @equipment
|
||||
@assignLevelEquipment()
|
||||
@items.url = '/db/thang.type?view=items&project=name,components,original,rasterIcon'
|
||||
@supermodel.loadCollection(@items, 'items')
|
||||
|
@ -88,6 +89,7 @@ module.exports = class InventoryView extends CocoView
|
|||
cursorAt: {left: 35.5, top: 35.5}
|
||||
helper: -> dragHelper
|
||||
revertDuration: 200
|
||||
distance: 10
|
||||
scroll: false
|
||||
zIndex: 100
|
||||
itemView.$el.on 'dragstart', =>
|
||||
|
@ -137,6 +139,8 @@ module.exports = class InventoryView extends CocoView
|
|||
@onSelectionChanged()
|
||||
|
||||
onAvailableItemDoubleClick: (e) ->
|
||||
@selectAvailableItem $(e.target).closest('.list-group-item')
|
||||
@onSelectionChanged()
|
||||
slot = @getSelectedSlot()
|
||||
slot = @$el.find('.item-slot:not(.disabled):first') if not slot.length
|
||||
@unequipItemFromSlot(slot)
|
||||
|
|
|
@ -24,7 +24,6 @@ module.exports = class MultiplayerView extends CocoView
|
|||
super(options)
|
||||
@level = options.level
|
||||
@session = options.session
|
||||
@playableTeams = options.playableTeams
|
||||
@listenTo @session, 'change:multiplayer', @updateLinkSection
|
||||
@initMultiplayerSessions()
|
||||
|
||||
|
@ -40,7 +39,6 @@ module.exports = class MultiplayerView extends CocoView
|
|||
c.multiplayer = @session.get 'multiplayer'
|
||||
c.team = @session.get 'team'
|
||||
c.levelSlug = @level?.get 'slug'
|
||||
c.playableTeams = @playableTeams
|
||||
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
|
||||
if @level?.get('type') is 'ladder'
|
||||
c.ladderGame = true
|
||||
|
|
|
@ -98,9 +98,9 @@ module.exports = class SpectateLevelView extends RootView
|
|||
$('body').addClass('is-playing')
|
||||
|
||||
onLoaded: ->
|
||||
_.defer => @onLevelLoaded()
|
||||
_.defer => @onLevelLoaderLoaded()
|
||||
|
||||
onLevelLoaded: ->
|
||||
onLevelLoaderLoaded: ->
|
||||
@grabLevelLoaderData()
|
||||
#at this point, all requisite data is loaded, and sessions are not denormalized
|
||||
team = @world.teamForPlayer(0)
|
||||
|
@ -119,18 +119,19 @@ module.exports = class SpectateLevelView extends RootView
|
|||
@register()
|
||||
@controlBar.setBus(@bus)
|
||||
@surface.showLevel()
|
||||
if me.id isnt @session.get 'creator'
|
||||
@surface.createOpponentWizard
|
||||
id: @session.get('creator')
|
||||
name: @session.get('creatorName')
|
||||
team: @session.get('team')
|
||||
levelSlug: @level.get('slug')
|
||||
if @level.get('type', true) isnt 'hero'
|
||||
if me.id isnt @session.get 'creator'
|
||||
@surface.createOpponentWizard
|
||||
id: @session.get('creator')
|
||||
name: @session.get('creatorName')
|
||||
team: @session.get('team')
|
||||
levelSlug: @level.get('slug')
|
||||
|
||||
@surface.createOpponentWizard
|
||||
id: @otherSession.get('creator')
|
||||
name: @otherSession.get('creatorName')
|
||||
team: @otherSession.get('team')
|
||||
levelSlug: @level.get('slug')
|
||||
@surface.createOpponentWizard
|
||||
id: @otherSession.get('creator')
|
||||
name: @otherSession.get('creatorName')
|
||||
team: @otherSession.get('team')
|
||||
levelSlug: @level.get('slug')
|
||||
|
||||
grabLevelLoaderData: ->
|
||||
@session = @levelLoader.session
|
||||
|
@ -186,7 +187,7 @@ module.exports = class SpectateLevelView extends RootView
|
|||
ctx.fillText("Loaded #{@modelsLoaded} thingies",50,50)
|
||||
|
||||
insertSubviews: ->
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel, spectateView: true, spectateOpponentCodeLanguage: @otherSession?.get('submittedCodeLanguage')
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel, spectateView: true, spectateOpponentCodeLanguage: @otherSession?.get('submittedCodeLanguage'), level: @level
|
||||
@insertSubView new PlaybackView {}
|
||||
|
||||
@insertSubView new GoldView {}
|
||||
|
@ -205,7 +206,7 @@ module.exports = class SpectateLevelView extends RootView
|
|||
|
||||
initSurface: ->
|
||||
surfaceCanvas = $('canvas#surface', @$el)
|
||||
@surface = new Surface(@world, surfaceCanvas, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview, spectateGame: true)
|
||||
@surface = new Surface(@world, surfaceCanvas, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview, spectateGame: true, wizards: @level.get('type', true) isnt 'hero')
|
||||
worldBounds = @world.getBounds()
|
||||
bounds = [{x:worldBounds.left, y:worldBounds.top}, {x:worldBounds.right, y:worldBounds.bottom}]
|
||||
@surface.camera.setBounds(bounds)
|
||||
|
|
|
@ -32,7 +32,6 @@ module.exports = class ControlBarView extends CocoView
|
|||
@worldName = options.worldName
|
||||
@session = options.session
|
||||
@level = options.level
|
||||
@playableTeams = options.playableTeams
|
||||
@spectateGame = options.spectateGame ? false
|
||||
super options
|
||||
|
||||
|
@ -83,7 +82,7 @@ module.exports = class ControlBarView extends CocoView
|
|||
@guideHighlightInterval = null
|
||||
|
||||
showGameMenuModal: ->
|
||||
@openModalView new GameMenuModal level: @level, session: @session, playableTeams: @playableTeams
|
||||
@openModalView new GameMenuModal level: @level, session: @session
|
||||
|
||||
onJoinedRealTimeMultiplayerGame: (e) ->
|
||||
@multiplayerSession = e.session
|
||||
|
|
|
@ -6,11 +6,11 @@ module.exports = class LevelLoadingView extends CocoView
|
|||
template: template
|
||||
|
||||
events:
|
||||
'mousedown .start-level-button': 'startUnveiling' # split into two for animation smoothness
|
||||
'mousedown .start-level-button': 'startUnveiling' # Split into two for animation smoothness.
|
||||
'click .start-level-button': 'onClickStartLevel'
|
||||
|
||||
subscriptions:
|
||||
'level:loaded': 'onLevelLoaded'
|
||||
'level:loaded': 'onLevelLoaded' # If Level loads after level loading view.
|
||||
|
||||
afterRender: ->
|
||||
@$el.find('.tip.rare').remove() if _.random(1, 10) < 9
|
||||
|
@ -18,13 +18,13 @@ module.exports = class LevelLoadingView extends CocoView
|
|||
tip = _.sample(tips)
|
||||
$(tip).removeClass('to-remove')
|
||||
@$el.find('.to-remove').remove()
|
||||
@onLevelLoaded level: @options.level if @options.level?.get('goals') # If Level was already loaded.
|
||||
|
||||
onLevelLoaded: (e) ->
|
||||
@level = e.level
|
||||
goalList = @$el.find('.level-loading-goals').removeClass('secret').find('ul')
|
||||
for goalID, goal of @level.get('goals') when not goal.team or goal.team is e.team
|
||||
for goalID, goal of @level.get('goals') when (not goal.team or goal.team is e.team) and not goal.hiddenGoal
|
||||
goalList.append $('<li class="list-group-item header-font">' + goal.name + '</li>')
|
||||
console.log 'got goals', @level.get('goals'), 'team', e.team
|
||||
|
||||
showReady: ->
|
||||
return if @shownReady
|
||||
|
@ -32,7 +32,11 @@ module.exports = class LevelLoadingView extends CocoView
|
|||
ready = $.i18n.t('play_level.loading_ready', defaultValue: 'Ready!')
|
||||
@$el.find('#tip-wrapper .tip').addClass('ready').text ready
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'level_loaded', volume: 0.75 # old: loading_ready
|
||||
@$el.find('.start-level-button').removeClass 'secret'
|
||||
if @options.autoUnveil
|
||||
@startUnveiling()
|
||||
@unveil()
|
||||
else
|
||||
@$el.find('.start-level-button').removeClass 'secret'
|
||||
|
||||
startUnveiling: (e) ->
|
||||
Backbone.Mediator.publish 'level:loading-view-unveiling', {}
|
||||
|
|
|
@ -33,6 +33,7 @@ LevelFlagsView = require './LevelFlagsView'
|
|||
GoldView = require './LevelGoldView'
|
||||
VictoryModal = require './modal/VictoryModal'
|
||||
InfiniteLoopModal = require './modal/InfiniteLoopModal'
|
||||
GameMenuModal = require 'views/game-menu/GameMenuModal'
|
||||
|
||||
PROFILE_ME = false
|
||||
|
||||
|
@ -64,6 +65,8 @@ module.exports = class PlayLevelView extends RootView
|
|||
'level:started': 'onLevelStarted'
|
||||
'level:loading-view-unveiling': 'onLoadingViewUnveiling'
|
||||
'level:loading-view-unveiled': 'onLoadingViewUnveiled'
|
||||
'level:loaded': 'onLevelLoaded'
|
||||
'level:session-loaded': 'onSessionLoaded'
|
||||
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
|
||||
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||
|
@ -171,14 +174,13 @@ module.exports = class PlayLevelView extends RootView
|
|||
afterRender: ->
|
||||
super()
|
||||
window.onPlayLevelViewLoaded? @ # still a hack
|
||||
@insertSubView @loadingView = new LevelLoadingView {}
|
||||
@insertSubView @loadingView = new LevelLoadingView autoUnveil: @options.autoUnveil, level: @level # May not have @level loaded yet
|
||||
@$el.find('#level-done-button').hide()
|
||||
$('body').addClass('is-playing')
|
||||
$('body').bind('touchmove', false) if @isIPadApp()
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
@showWizardSettingsModal() if not me.get('name') and not @isIPadApp()
|
||||
|
||||
# Partially Loaded Setup ####################################################
|
||||
|
||||
|
@ -252,7 +254,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
@god.setGoalManager @goalManager
|
||||
|
||||
insertSubviews: ->
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, level: @level
|
||||
@insertSubView new LevelPlaybackView session: @session
|
||||
@insertSubView new GoalsView {}
|
||||
@insertSubView new LevelFlagsView world: @world
|
||||
|
@ -260,7 +262,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
@insertSubView new HUDView {}
|
||||
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
|
||||
worldName = utils.i18n @level.attributes, 'name'
|
||||
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel, playableTeams: @world.playableTeams}
|
||||
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel}
|
||||
Backbone.Mediator.publish('level:set-debug', debug: true) if @isIPadApp() # if me.displayName() is 'Nick'
|
||||
|
||||
initVolume: ->
|
||||
|
@ -280,10 +282,19 @@ module.exports = class PlayLevelView extends RootView
|
|||
|
||||
# Load Completed Setup ######################################################
|
||||
|
||||
onLoaded: ->
|
||||
_.defer => @onLevelLoaded()
|
||||
onLevelLoaded: (e) ->
|
||||
# Just the level has been loaded by the level loader
|
||||
@showWizardSettingsModal() if not me.get('name') and not @isIPadApp() and e.level.get('type', true) isnt 'hero'
|
||||
|
||||
onLevelLoaded: ->
|
||||
onSessionLoaded: (e) ->
|
||||
# Just the level and session have been loaded by the level loader
|
||||
if e.level.get('type', true) is 'hero' and not _.size e.session.get('heroConfig')?.inventory ? {}
|
||||
@openModalView new GameMenuModal level: e.level, session: e.session
|
||||
|
||||
onLoaded: ->
|
||||
_.defer => @onLevelLoaderLoaded()
|
||||
|
||||
onLevelLoaderLoaded: ->
|
||||
# Everything is now loaded
|
||||
return unless @levelLoader.progress() is 1 # double check, since closing the guide may trigger this early
|
||||
|
||||
|
@ -306,7 +317,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
|
||||
initSurface: ->
|
||||
surfaceCanvas = $('canvas#surface', @$el)
|
||||
@surface = new Surface(@world, surfaceCanvas, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview)
|
||||
@surface = new Surface(@world, surfaceCanvas, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview, wizards: @level.get('type', true) isnt 'hero')
|
||||
worldBounds = @world.getBounds()
|
||||
bounds = [{x: worldBounds.left, y: worldBounds.top}, {x: worldBounds.right, y: worldBounds.bottom}]
|
||||
@surface.camera.setBounds(bounds)
|
||||
|
@ -320,9 +331,12 @@ module.exports = class PlayLevelView extends RootView
|
|||
if window.currentModal and not window.currentModal.destroyed
|
||||
return Backbone.Mediator.subscribeOnce 'modal:closed', @onLevelStarted, @
|
||||
@surface.showLevel()
|
||||
if @otherSession
|
||||
if @otherSession and @level.get('type', true) isnt 'hero'
|
||||
# TODO: colorize name and cloud by team, colorize wizard by user's color config
|
||||
@surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team'), levelSlug: @level.get('slug'), codeLanguage: @otherSession.get('submittedCodeLanguage')
|
||||
if @isEditorPreview
|
||||
@loadingView.startUnveiling()
|
||||
@loadingView.unveil()
|
||||
|
||||
onLoadingViewUnveiling: (e) ->
|
||||
@restoreSessionState()
|
||||
|
@ -342,13 +356,13 @@ module.exports = class PlayLevelView extends RootView
|
|||
return if @alreadyLoadedState
|
||||
@alreadyLoadedState = true
|
||||
state = @originalSessionState
|
||||
if state.frame and @level.get('type') isnt 'ladder' # https://github.com/codecombat/codecombat/issues/714
|
||||
if state.frame and @level.get('type', true) isnt 'ladder' # https://github.com/codecombat/codecombat/issues/714
|
||||
Backbone.Mediator.publish 'level:set-time', time: 0, frameOffset: state.frame
|
||||
if state.selected
|
||||
if @level.get('type', true) is 'hero'
|
||||
Backbone.Mediator.publish 'tome:select-primary-sprite', {}
|
||||
else if state.selected
|
||||
# TODO: Should also restore selected spell here by saving spellName
|
||||
Backbone.Mediator.publish 'level:select-sprite', thangID: state.selected, spellName: null
|
||||
else if @isIPadApp()
|
||||
Backbone.Mediator.publish 'tome:select-primary-sprite', {}
|
||||
if state.playing?
|
||||
Backbone.Mediator.publish 'level:set-playing', playing: state.playing
|
||||
|
||||
|
@ -378,10 +392,12 @@ module.exports = class PlayLevelView extends RootView
|
|||
#@setLevel @level, @supermodel
|
||||
#Backbone.Mediator.publish 'tome:cast-spell', {}
|
||||
# We'll just make a new PlayLevelView instead
|
||||
console.log 'Hero config changed; reload the level.'
|
||||
Backbone.Mediator.publish 'router:navigate', {
|
||||
route: window.location.pathname,
|
||||
viewClass: PlayLevelView,
|
||||
viewArgs: [{supermodel: @supermodel}, @levelID]}
|
||||
viewArgs: [{supermodel: @supermodel, autoUnveil: true}, @levelID]
|
||||
}
|
||||
|
||||
onWindowResize: (s...) ->
|
||||
$('#pointer').css('opacity', 0.0)
|
||||
|
|
|
@ -183,6 +183,7 @@ module.exports = class TomeView extends CocoView
|
|||
@thangList?.$el.show()
|
||||
|
||||
onSpriteSelected: (e) ->
|
||||
return if @spellView and @options.level.get('type', true) is 'hero' # Never deselect the hero in the Tome.
|
||||
thang = e.thang
|
||||
spellName = e.spellName
|
||||
@spellList?.$el.hide()
|
||||
|
|
Loading…
Reference in a new issue